diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json
index 43a11a8709..40866db9ef 100644
--- a/application/src/main/data/json/demo/dashboards/thermostats.json
+++ b/application/src/main/data/json/demo/dashboards/thermostats.json
@@ -17,7 +17,10 @@
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
- "quickInterval": "CURRENT_DAY"
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideQuickInterval": false
},
"history": {
"historyType": 0,
@@ -27,7 +30,11 @@
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
- "quickInterval": "CURRENT_DAY"
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideFixedInterval": false,
+ "hideQuickInterval": false
},
"aggregation": {
"type": "NONE",
@@ -407,7 +414,10 @@
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideQuickInterval": false
},
"history": {
"historyType": 0,
@@ -417,7 +427,11 @@
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
- "quickInterval": "CURRENT_DAY"
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideFixedInterval": false,
+ "hideQuickInterval": false
},
"aggregation": {
"type": "AVG",
@@ -460,595 +474,132 @@
"id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f",
"typeFullFqn": "system.input_widgets.update_multiple_attributes"
},
- "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
- "type": "latest",
- "sizeX": 13,
- "sizeY": 6,
+ "eda8a397-0959-690c-405c-11e2c9b2bc7e": {
+ "typeFullFqn": "system.time_series_chart",
+ "type": "timeseries",
+ "sizeX": 8,
+ "sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
+ "name": "",
+ "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
"dataKeys": [
{
"name": "temperature",
"type": "timeseries",
- "label": "temperature",
- "color": "#2196f3",
- "settings": {},
- "_hash": 0.1371919646686739,
+ "label": "Temperature",
+ "color": "#EF5350",
+ "settings": {
+ "yAxisId": "default",
+ "showInLegend": true,
+ "dataHiddenByDefault": false,
+ "type": "line",
+ "lineSettings": {
+ "showLine": true,
+ "step": false,
+ "stepType": "start",
+ "smooth": true,
+ "lineType": "solid",
+ "lineWidth": 2.5,
+ "showPoints": false,
+ "showPointLabel": false,
+ "pointLabelPosition": "top",
+ "pointLabelFont": {
+ "family": "Roboto",
+ "size": 11,
+ "sizeUnit": "px",
+ "style": "normal",
+ "weight": "400",
+ "lineHeight": "1"
+ },
+ "pointLabelColor": "rgba(0, 0, 0, 0.76)",
+ "pointShape": "circle",
+ "pointSize": 14,
+ "fillAreaSettings": {
+ "type": "gradient",
+ "opacity": 0.4,
+ "gradient": {
+ "start": 60,
+ "end": 10
+ }
+ }
+ },
+ "barSettings": {
+ "showBorder": false,
+ "borderWidth": 2,
+ "borderRadius": 0,
+ "showLabel": false,
+ "labelPosition": "top",
+ "labelFont": {
+ "family": "Roboto",
+ "size": 11,
+ "sizeUnit": "px",
+ "style": "normal",
+ "weight": "400",
+ "lineHeight": "1"
+ },
+ "labelColor": "rgba(0, 0, 0, 0.76)",
+ "backgroundSettings": {
+ "type": "none",
+ "opacity": 0.4,
+ "gradient": {
+ "start": 100,
+ "end": 0
+ }
+ }
+ }
+ },
+ "_hash": 0.5973804076994531,
+ "units": "°C",
"decimals": 1,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "humidity",
- "type": "timeseries",
- "label": "humidity",
- "color": "#4caf50",
- "settings": {},
- "_hash": 0.043177186765847475,
- "decimals": 0,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "longitude",
- "type": "attribute",
- "label": "longitude",
- "color": "#f44336",
- "settings": {},
- "_hash": 0.5548964320315584
- },
- {
- "name": "latitude",
- "type": "attribute",
- "label": "latitude",
- "color": "#ffc107",
- "settings": {},
- "_hash": 0.1803778014971602
- },
- {
- "name": "active",
- "type": "attribute",
- "label": "active",
- "color": "#607d8b",
- "settings": {},
- "_hash": 0.30926987994082844
+ "aggregationType": null,
+ "funcBody": null,
+ "usePostProcessing": null,
+ "postFuncBody": null
}
],
- "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
- }
- ],
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085177686,
- "endTimeMs": 1694171577686
+ "alarmFilterConfig": {
+ "statusList": [
+ "ACTIVE"
+ ]
},
- "quickInterval": "CURRENT_DAY"
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": false,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "fitMapBounds": true,
- "latKeyName": "latitude",
- "lngKeyName": "longitude",
- "showLabel": true,
- "label": "${entityName}",
- "tooltipPattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Thermostat details
",
- "markerImageSize": 48,
- "useColorFunction": false,
- "markerImages": [
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_0.svg",
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_1.svg"
- ],
- "useMarkerImageFunction": true,
- "colorFunction": "\n",
- "color": "#fe7569",
- "mapProvider": "OpenStreetMap.HOT",
- "showTooltip": true,
- "autocloseTooltip": true,
- "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
- "defaultCenterPosition": [
- 0,
- 0
- ],
- "showTooltipAction": "click",
- "polygonKeyName": "coordinates",
- "polygonOpacity": 0.5,
- "polygonStrokeOpacity": 1,
- "polygonStrokeWeight": 1,
- "zoomOnClick": true,
- "showCoverageOnHover": true,
- "animate": true,
- "maxClusterRadius": 80,
- "removeOutsideVisibleBounds": true,
- "useLabelFunction": true,
- "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''",
- "defaultZoomLevel": 14,
- "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;"
- },
- "title": "Thermostat maps",
- "dropShadow": true,
- "enableFullscreen": false,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {
- "headerButton": [],
- "tooltipAction": [
- {
- "id": "bef25673-b37a-8821-bc0f-5d6dd3680f24",
- "name": "navigate_to_details",
- "icon": "more_horiz",
- "type": "openDashboardState",
- "targetDashboardStateId": "chart",
- "setEntityId": true
- }
- ]
- },
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true
- },
- "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb",
- "typeFullFqn": "system.maps_v2.openstreetmap"
- },
- "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
- "type": "latest",
- "sizeX": 24,
- "sizeY": 12,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "longitude",
- "type": "attribute",
- "label": "longitude",
- "color": "#2196f3",
- "settings": {},
- "_hash": 0.3640193654284214
- },
+ "latestDataKeys": [
{
- "name": "latitude",
+ "name": "temperatureAlarmThreshold",
"type": "attribute",
- "label": "latitude",
+ "label": "temperatureAlarmThreshold",
"color": "#4caf50",
- "settings": {},
- "_hash": 0.49020393887695923
- },
- {
- "name": "temperature",
- "type": "timeseries",
- "label": "temperature",
- "color": "#f44336",
- "settings": {},
- "_hash": 0.5885892766009955,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "humidity",
- "type": "timeseries",
- "label": "humidity",
- "color": "#ffc107",
- "settings": {},
- "_hash": 0.21077893588180707,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "active",
- "type": "attribute",
- "label": "active",
- "color": "#607d8b",
- "settings": {},
- "_hash": 0.34722983638504346
+ "settings": {
+ "__thresholdKey": true
+ },
+ "_hash": 0.7120450032526351
}
- ],
- "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
+ ]
}
],
"timewindow": {
- "displayValue": "",
+ "hideAggregation": false,
+ "hideAggInterval": false,
+ "hideTimezone": false,
"selectedTab": 0,
"realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085177686,
- "endTimeMs": 1694171577686
- },
- "quickInterval": "CURRENT_DAY"
+ "realtimeType": 0,
+ "timewindowMs": 3600000,
+ "quickInterval": "CURRENT_DAY",
+ "interval": 30000
},
"aggregation": {
"type": "AVG",
"limit": 25000
- }
+ },
+ "timezone": null
},
- "showTitle": false,
- "backgroundColor": "#fff",
+ "showTitle": true,
+ "backgroundColor": "rgba(0, 0, 0, 0)",
"color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "fitMapBounds": true,
- "latKeyName": "latitude",
- "lngKeyName": "longitude",
- "showLabel": true,
- "label": "${entityName}",
- "tooltipPattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Delete",
- "markerImageSize": 34,
- "useColorFunction": false,
- "markerImages": [
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_0.svg",
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_1.svg"
- ],
- "useMarkerImageFunction": true,
- "color": "#fe7569",
- "mapProvider": "OpenStreetMap.HOT",
- "showTooltip": true,
- "autocloseTooltip": true,
- "defaultCenterPosition": "0,0",
- "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
- "showTooltipAction": "click",
- "polygonKeyName": "coordinates",
- "polygonOpacity": 0.5,
- "polygonStrokeOpacity": 1,
- "polygonStrokeWeight": 1,
- "zoomOnClick": true,
- "showCoverageOnHover": true,
- "animate": true,
- "maxClusterRadius": 80,
- "removeOutsideVisibleBounds": true,
- "defaultZoomLevel": 12,
- "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''",
- "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;",
- "useLabelFunction": true,
- "provider": "openstreet-map",
- "draggableMarker": true
- },
- "title": "New Markers Placement - OpenStreetMap",
- "dropShadow": true,
- "enableFullscreen": false,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {
- "tooltipAction": [
- {
- "name": "delete",
- "icon": "more_horiz",
- "type": "custom",
- "customFunction": "var entityDatasource = widgetContext.mapInstance.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.mapInstance.saveMarkerLocation(entityDatasource[0], null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
- "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66"
- }
- ]
- },
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true
- },
- "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06",
- "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
- },
- "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
- "type": "latest",
- "sizeX": 6,
- "sizeY": 6,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "dataKeys": [
- {
- "name": "longitude",
- "type": "attribute",
- "label": "longitude",
- "color": "#2196f3",
- "settings": {},
- "_hash": 0.3640193654284214
- },
- {
- "name": "latitude",
- "type": "attribute",
- "label": "latitude",
- "color": "#4caf50",
- "settings": {},
- "_hash": 0.49020393887695923
- },
- {
- "name": "temperature",
- "type": "timeseries",
- "label": "temperature",
- "color": "#f44336",
- "settings": {},
- "_hash": 0.5885892766009955,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "humidity",
- "type": "timeseries",
- "label": "humidity",
- "color": "#ffc107",
- "settings": {},
- "_hash": 0.21077893588180707,
- "postFuncBody": "return value || \"\";"
- },
- {
- "name": "active",
- "type": "attribute",
- "label": "active",
- "color": "#607d8b",
- "settings": {},
- "_hash": 0.34722983638504346
- }
- ],
- "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
- }
- ],
- "timewindow": {
- "displayValue": "",
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 1,
- "interval": 1000,
- "timewindowMs": 60000,
- "quickInterval": "CURRENT_DAY"
- },
- "history": {
- "historyType": 0,
- "interval": 1000,
- "timewindowMs": 60000,
- "fixedTimewindow": {
- "startTimeMs": 1694085177686,
- "endTimeMs": 1694171577686
- },
- "quickInterval": "CURRENT_DAY"
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- }
- },
- "showTitle": false,
- "backgroundColor": "#fff",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "8px",
- "settings": {
- "fitMapBounds": true,
- "latKeyName": "latitude",
- "lngKeyName": "longitude",
- "showLabel": true,
- "label": "${entityName}",
- "tooltipPattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Delete",
- "markerImageSize": 34,
- "useColorFunction": false,
- "markerImages": [
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_0.svg",
- "tb-image;/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_1.svg"
- ],
- "useMarkerImageFunction": true,
- "color": "#fe7569",
- "mapProvider": "OpenStreetMap.HOT",
- "showTooltip": true,
- "autocloseTooltip": true,
- "defaultCenterPosition": "0,0",
- "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
- "showTooltipAction": "click",
- "polygonKeyName": "coordinates",
- "polygonOpacity": 0.5,
- "polygonStrokeOpacity": 1,
- "polygonStrokeWeight": 1,
- "zoomOnClick": true,
- "showCoverageOnHover": true,
- "animate": true,
- "maxClusterRadius": 80,
- "removeOutsideVisibleBounds": true,
- "defaultZoomLevel": 5,
- "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''",
- "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;",
- "useLabelFunction": true,
- "provider": "openstreet-map",
- "draggableMarker": true,
- "editablePolygon": true
- },
- "title": "New Markers Placement - OpenStreetMap",
- "dropShadow": true,
- "enableFullscreen": false,
- "titleStyle": {
- "fontSize": "16px",
- "fontWeight": 400
- },
- "useDashboardTimewindow": true,
- "showLegend": false,
- "widgetStyle": {},
- "actions": {
- "tooltipAction": [
- {
- "name": "delete",
- "icon": "more_horiz",
- "type": "custom",
- "customFunction": "var entityDatasource = widgetContext.mapInstance.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.mapInstance.saveMarkerLocation(entityDatasource[0], null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
- "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66"
- }
- ]
- },
- "showTitleIcon": false,
- "titleIcon": null,
- "iconColor": "rgba(0, 0, 0, 0.87)",
- "iconSize": "24px",
- "titleTooltip": "",
- "displayTimewindow": true
- },
- "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf",
- "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
- },
- "eda8a397-0959-690c-405c-11e2c9b2bc7e": {
- "typeFullFqn": "system.time_series_chart",
- "type": "timeseries",
- "sizeX": 8,
- "sizeY": 5,
- "config": {
- "datasources": [
- {
- "type": "entity",
- "name": "",
- "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
- "dataKeys": [
- {
- "name": "temperature",
- "type": "timeseries",
- "label": "Temperature",
- "color": "#EF5350",
- "settings": {
- "yAxisId": "default",
- "showInLegend": true,
- "dataHiddenByDefault": false,
- "type": "line",
- "lineSettings": {
- "showLine": true,
- "step": false,
- "stepType": "start",
- "smooth": true,
- "lineType": "solid",
- "lineWidth": 2.5,
- "showPoints": false,
- "showPointLabel": false,
- "pointLabelPosition": "top",
- "pointLabelFont": {
- "family": "Roboto",
- "size": 11,
- "sizeUnit": "px",
- "style": "normal",
- "weight": "400",
- "lineHeight": "1"
- },
- "pointLabelColor": "rgba(0, 0, 0, 0.76)",
- "pointShape": "circle",
- "pointSize": 14,
- "fillAreaSettings": {
- "type": "gradient",
- "opacity": 0.4,
- "gradient": {
- "start": 60,
- "end": 10
- }
- }
- },
- "barSettings": {
- "showBorder": false,
- "borderWidth": 2,
- "borderRadius": 0,
- "showLabel": false,
- "labelPosition": "top",
- "labelFont": {
- "family": "Roboto",
- "size": 11,
- "sizeUnit": "px",
- "style": "normal",
- "weight": "400",
- "lineHeight": "1"
- },
- "labelColor": "rgba(0, 0, 0, 0.76)",
- "backgroundSettings": {
- "type": "none",
- "opacity": 0.4,
- "gradient": {
- "start": 100,
- "end": 0
- }
- }
- }
- },
- "_hash": 0.5973804076994531,
- "units": "°C",
- "decimals": 1,
- "aggregationType": null,
- "funcBody": null,
- "usePostProcessing": null,
- "postFuncBody": null
- }
- ],
- "alarmFilterConfig": {
- "statusList": [
- "ACTIVE"
- ]
- },
- "latestDataKeys": [
- {
- "name": "temperatureAlarmThreshold",
- "type": "attribute",
- "label": "temperatureAlarmThreshold",
- "color": "#4caf50",
- "settings": {
- "__thresholdKey": true
- },
- "_hash": 0.7120450032526351
- }
- ]
- }
- ],
- "timewindow": {
- "hideInterval": false,
- "hideLastInterval": false,
- "hideQuickInterval": false,
- "hideAggregation": false,
- "hideAggInterval": false,
- "hideTimezone": false,
- "selectedTab": 0,
- "realtime": {
- "realtimeType": 0,
- "timewindowMs": 3600000,
- "quickInterval": "CURRENT_DAY",
- "interval": 30000
- },
- "aggregation": {
- "type": "AVG",
- "limit": 25000
- },
- "timezone": null
- },
- "showTitle": true,
- "backgroundColor": "rgba(0, 0, 0, 0)",
- "color": "rgba(0, 0, 0, 0.87)",
- "padding": "0px",
+ "padding": "0px",
"settings": {
"showLegend": true,
"legendConfig": {
@@ -1419,9 +970,6 @@
}
],
"timewindow": {
- "hideInterval": false,
- "hideLastInterval": false,
- "hideQuickInterval": false,
"hideAggregation": false,
"hideAggInterval": false,
"hideTimezone": false,
@@ -1705,17 +1253,793 @@
"row": 0,
"col": 0,
"id": "ac90f089-197f-b767-82c3-2668844265a2"
- }
- },
- "states": {
- "default": {
- "name": "Thermostats",
- "root": true,
- "layouts": {
- "main": {
- "widgets": {
- "f33c746c-0dfc-c212-395b-b448c8a17209": {
- "sizeX": 11,
+ },
+ "5186d1e3-f076-e062-5129-e4dd8e4adfb0": {
+ "typeFullFqn": "system.map",
+ "type": "latest",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "config": {
+ "datasources": [],
+ "timewindow": {
+ "displayValue": "",
+ "selectedTab": 0,
+ "realtime": {
+ "realtimeType": 1,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideQuickInterval": false
+ },
+ "history": {
+ "historyType": 0,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "fixedTimewindow": {
+ "startTimeMs": 1741884755143,
+ "endTimeMs": 1741971155143
+ },
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideFixedInterval": false,
+ "hideQuickInterval": false
+ },
+ "aggregation": {
+ "type": "AVG",
+ "limit": 25000
+ }
+ },
+ "showTitle": false,
+ "backgroundColor": "rgba(0, 0, 0, 0)",
+ "color": "rgba(0, 0, 0, 0.87)",
+ "padding": "0px",
+ "settings": {
+ "mapType": "geoMap",
+ "layers": [
+ {
+ "label": "{i18n:widgets.maps.layer.roadmap}",
+ "provider": "openstreet",
+ "layerType": "OpenStreetMap.Mapnik"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.satellite}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.hybrid}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery",
+ "referenceLayer": "openstreetmap_hybrid"
+ }
+ ],
+ "imageSource": null,
+ "markers": [
+ {
+ "dsType": "entity",
+ "dsLabel": "",
+ "dsDeviceId": null,
+ "dsEntityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e",
+ "dsFilterId": null,
+ "additionalDataKeys": [
+ {
+ "name": "temperature",
+ "type": "timeseries",
+ "label": "temperature",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.570889787682481,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "humidity",
+ "type": "timeseries",
+ "label": "humidity",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.13597394595782442,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "active",
+ "type": "attribute",
+ "label": "active",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.21080919932756603
+ }
+ ],
+ "label": {
+ "show": true,
+ "type": "function",
+ "pattern": "${entityName}",
+ "patternFunction": "var color;\nif(data.active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''"
+ },
+ "tooltip": {
+ "show": true,
+ "trigger": "click",
+ "autoclose": true,
+ "type": "pattern",
+ "pattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Thermostat details
",
+ "offsetX": 0,
+ "offsetY": -1,
+ "patternFunction": null,
+ "tagActions": [
+ {
+ "name": "navigate_to_details",
+ "type": "openDashboardState",
+ "targetDashboardStateId": "chart",
+ "setEntityId": true,
+ "stateEntityParamName": null,
+ "openRightLayout": false,
+ "openInSeparateDialog": false,
+ "openInPopover": false
+ }
+ ]
+ },
+ "click": {
+ "type": "doNothing"
+ },
+ "groups": null,
+ "edit": {
+ "enabledActions": [],
+ "attributeScope": "SERVER_SCOPE",
+ "snappable": false
+ },
+ "xKey": {
+ "name": "latitude",
+ "label": "latitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "yKey": {
+ "name": "longitude",
+ "label": "longitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "markerType": "icon",
+ "markerShape": {
+ "shape": "markerShape1",
+ "size": 34,
+ "color": {
+ "type": "constant",
+ "color": "#307FE5"
+ }
+ },
+ "markerIcon": {
+ "size": 48,
+ "color": {
+ "type": "function",
+ "color": "rgb(255, 0, 0)",
+ "range": null,
+ "colorFunction": "if (data.active !== \"true\"){\n return 'rgb(255, 0, 0)';\n} else {\n return 'rgb(39, 134, 34)';\n}"
+ },
+ "iconContainer": "iconContainer1",
+ "icon": "thermostat"
+ },
+ "markerImage": {
+ "type": "function",
+ "imageFunction": "return {};",
+ "images": []
+ },
+ "markerOffsetX": 0.5,
+ "markerOffsetY": 1,
+ "markerClustering": {
+ "enable": false,
+ "zoomOnClick": true,
+ "maxZoom": null,
+ "maxClusterRadius": 80,
+ "zoomAnimation": true,
+ "showCoverageOnHover": true,
+ "spiderfyOnMaxZoom": false,
+ "chunkedLoad": false,
+ "lazyLoad": true,
+ "useClusterMarkerColorFunction": false,
+ "clusterMarkerColorFunction": null
+ }
+ }
+ ],
+ "polygons": [],
+ "circles": [],
+ "additionalDataSources": [],
+ "controlsPosition": "topleft",
+ "zoomActions": [
+ "scroll",
+ "doubleClick",
+ "controlButtons"
+ ],
+ "scales": [],
+ "dragModeButton": false,
+ "fitMapBounds": true,
+ "useDefaultCenterPosition": false,
+ "defaultCenterPosition": "0,0",
+ "defaultZoomLevel": 14,
+ "mapPageSize": 16384,
+ "mapActionButtons": [],
+ "background": {
+ "type": "color",
+ "color": "#fff",
+ "overlay": {
+ "enabled": false,
+ "color": "rgba(255,255,255,0.72)",
+ "blur": 3
+ }
+ },
+ "padding": "8px"
+ },
+ "title": "Map",
+ "useDashboardTimewindow": true,
+ "displayTimewindow": true,
+ "showTitleIcon": false,
+ "titleTooltip": "",
+ "dropShadow": true,
+ "enableFullscreen": false,
+ "widgetStyle": {},
+ "widgetCss": "",
+ "titleStyle": {
+ "fontSize": "16px",
+ "fontWeight": 400
+ },
+ "pageSize": 1024,
+ "noDataDisplayMessage": "",
+ "configMode": "advanced",
+ "titleFont": null,
+ "titleColor": null,
+ "margin": "0px",
+ "borderRadius": "0px",
+ "iconSize": "24px",
+ "titleIcon": "map",
+ "iconColor": "#1F6BDD",
+ "actions": {}
+ },
+ "row": 0,
+ "col": 0,
+ "id": "5186d1e3-f076-e062-5129-e4dd8e4adfb0"
+ },
+ "1bbd4b8d-db42-ed7c-70c2-3d32c771843d": {
+ "typeFullFqn": "system.map",
+ "type": "latest",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "config": {
+ "datasources": [],
+ "timewindow": {
+ "displayValue": "",
+ "selectedTab": 0,
+ "realtime": {
+ "realtimeType": 1,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideQuickInterval": false
+ },
+ "history": {
+ "historyType": 0,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "fixedTimewindow": {
+ "startTimeMs": 1741884755143,
+ "endTimeMs": 1741971155143
+ },
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideFixedInterval": false,
+ "hideQuickInterval": false
+ },
+ "aggregation": {
+ "type": "AVG",
+ "limit": 25000
+ }
+ },
+ "showTitle": false,
+ "backgroundColor": "rgba(0, 0, 0, 0)",
+ "color": "rgba(0, 0, 0, 0.87)",
+ "padding": "0px",
+ "settings": {
+ "mapType": "geoMap",
+ "layers": [
+ {
+ "label": "{i18n:widgets.maps.layer.roadmap}",
+ "provider": "openstreet",
+ "layerType": "OpenStreetMap.Mapnik"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.satellite}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.hybrid}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery",
+ "referenceLayer": "openstreetmap_hybrid"
+ }
+ ],
+ "imageSource": null,
+ "markers": [
+ {
+ "dsType": "entity",
+ "dsLabel": "",
+ "dsDeviceId": null,
+ "dsEntityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e",
+ "dsFilterId": null,
+ "additionalDataKeys": [
+ {
+ "name": "temperature",
+ "type": "timeseries",
+ "label": "temperature",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.570889787682481,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "humidity",
+ "type": "timeseries",
+ "label": "humidity",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.13597394595782442,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "active",
+ "type": "attribute",
+ "label": "active",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.21080919932756603
+ }
+ ],
+ "label": {
+ "show": true,
+ "type": "function",
+ "pattern": "${entityName}",
+ "patternFunction": "var color;\nif(data.active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''"
+ },
+ "tooltip": {
+ "show": true,
+ "trigger": "click",
+ "autoclose": true,
+ "type": "pattern",
+ "pattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Delete",
+ "offsetX": 0,
+ "offsetY": -1,
+ "patternFunction": null,
+ "tagActions": [
+ {
+ "name": "delete",
+ "type": "custom",
+ "customFunction": "widgetContext.mapInstance.saveMarkerLocation(additionalParams, null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
+ "openInSeparateDialog": false,
+ "openInPopover": false
+ }
+ ]
+ },
+ "click": {
+ "type": "doNothing"
+ },
+ "groups": null,
+ "edit": {
+ "enabledActions": [
+ "add",
+ "move",
+ "remove"
+ ],
+ "attributeScope": "SERVER_SCOPE",
+ "snappable": false
+ },
+ "xKey": {
+ "name": "latitude",
+ "label": "latitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "yKey": {
+ "name": "longitude",
+ "label": "longitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "markerType": "icon",
+ "markerShape": {
+ "shape": "markerShape1",
+ "size": 34,
+ "color": {
+ "type": "constant",
+ "color": "#307FE5"
+ }
+ },
+ "markerIcon": {
+ "size": 48,
+ "color": {
+ "type": "function",
+ "color": "rgb(255, 0, 0)",
+ "range": null,
+ "colorFunction": "if (data.active !== \"true\"){\n return 'rgb(255, 0, 0)';\n} else {\n return 'rgb(39, 134, 34)';\n}"
+ },
+ "iconContainer": "iconContainer1",
+ "icon": "thermostat"
+ },
+ "markerImage": {
+ "type": "function",
+ "imageFunction": "return {};",
+ "images": []
+ },
+ "markerOffsetX": 0.5,
+ "markerOffsetY": 1,
+ "markerClustering": {
+ "enable": false,
+ "zoomOnClick": true,
+ "maxZoom": null,
+ "maxClusterRadius": 80,
+ "zoomAnimation": true,
+ "showCoverageOnHover": true,
+ "spiderfyOnMaxZoom": false,
+ "chunkedLoad": false,
+ "lazyLoad": true,
+ "useClusterMarkerColorFunction": false,
+ "clusterMarkerColorFunction": null
+ }
+ }
+ ],
+ "polygons": [],
+ "circles": [],
+ "additionalDataSources": [],
+ "controlsPosition": "topleft",
+ "zoomActions": [
+ "scroll",
+ "doubleClick",
+ "controlButtons"
+ ],
+ "scales": [],
+ "dragModeButton": false,
+ "fitMapBounds": true,
+ "useDefaultCenterPosition": false,
+ "defaultCenterPosition": "0,0",
+ "defaultZoomLevel": null,
+ "mapPageSize": 16384,
+ "mapActionButtons": [],
+ "background": {
+ "type": "color",
+ "color": "#fff",
+ "overlay": {
+ "enabled": false,
+ "color": "rgba(255,255,255,0.72)",
+ "blur": 3
+ }
+ },
+ "padding": "8px"
+ },
+ "title": "Map",
+ "useDashboardTimewindow": true,
+ "displayTimewindow": true,
+ "showTitleIcon": false,
+ "titleTooltip": "",
+ "dropShadow": true,
+ "enableFullscreen": false,
+ "widgetStyle": {},
+ "widgetCss": "",
+ "titleStyle": {
+ "fontSize": "16px",
+ "fontWeight": 400
+ },
+ "pageSize": 1024,
+ "noDataDisplayMessage": "",
+ "configMode": "advanced",
+ "titleFont": null,
+ "titleColor": null,
+ "margin": "0px",
+ "borderRadius": "0px",
+ "iconSize": "24px",
+ "titleIcon": "map",
+ "iconColor": "#1F6BDD",
+ "actions": {}
+ },
+ "row": 0,
+ "col": 0,
+ "id": "1bbd4b8d-db42-ed7c-70c2-3d32c771843d"
+ },
+ "c9da2d6b-4ba9-1ac8-63b8-68ab49b13c81": {
+ "typeFullFqn": "system.map",
+ "type": "latest",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "config": {
+ "datasources": [],
+ "timewindow": {
+ "displayValue": "",
+ "selectedTab": 0,
+ "realtime": {
+ "realtimeType": 1,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideQuickInterval": false
+ },
+ "history": {
+ "historyType": 0,
+ "interval": 1000,
+ "timewindowMs": 60000,
+ "fixedTimewindow": {
+ "startTimeMs": 1741884755143,
+ "endTimeMs": 1741971155143
+ },
+ "quickInterval": "CURRENT_DAY",
+ "hideInterval": false,
+ "hideLastInterval": false,
+ "hideFixedInterval": false,
+ "hideQuickInterval": false
+ },
+ "aggregation": {
+ "type": "AVG",
+ "limit": 25000
+ }
+ },
+ "showTitle": false,
+ "backgroundColor": "rgba(0, 0, 0, 0)",
+ "color": "rgba(0, 0, 0, 0.87)",
+ "padding": "0px",
+ "settings": {
+ "mapType": "geoMap",
+ "layers": [
+ {
+ "label": "{i18n:widgets.maps.layer.roadmap}",
+ "provider": "openstreet",
+ "layerType": "OpenStreetMap.Mapnik"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.satellite}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery"
+ },
+ {
+ "label": "{i18n:widgets.maps.layer.hybrid}",
+ "provider": "openstreet",
+ "layerType": "Esri.WorldImagery",
+ "referenceLayer": "openstreetmap_hybrid"
+ }
+ ],
+ "imageSource": null,
+ "markers": [
+ {
+ "dsType": "entity",
+ "dsLabel": "",
+ "dsDeviceId": null,
+ "dsEntityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
+ "dsFilterId": null,
+ "additionalDataKeys": [
+ {
+ "name": "temperature",
+ "type": "timeseries",
+ "label": "temperature",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.570889787682481,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "humidity",
+ "type": "timeseries",
+ "label": "humidity",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.13597394595782442,
+ "aggregationType": "NONE",
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": true,
+ "postFuncBody": "return value || \"\";"
+ },
+ {
+ "name": "active",
+ "type": "attribute",
+ "label": "active",
+ "color": "#2196f3",
+ "settings": {},
+ "_hash": 0.21080919932756603
+ }
+ ],
+ "label": {
+ "show": true,
+ "type": "function",
+ "pattern": "${entityName}",
+ "patternFunction": "var color;\nif(data.active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '' + \n '${entityLabel}' + \n ''"
+ },
+ "tooltip": {
+ "show": true,
+ "trigger": "click",
+ "autoclose": true,
+ "type": "pattern",
+ "pattern": "${entityName}
Temperature: ${temperature:1} °C
Humidity: ${humidity:0} %
Delete",
+ "offsetX": 0,
+ "offsetY": -1,
+ "patternFunction": null,
+ "tagActions": [
+ {
+ "name": "delete",
+ "type": "custom",
+ "customFunction": "widgetContext.mapInstance.saveMarkerLocation(additionalParams, null, null).subscribe(function success() {\n widgetContext.updateAliases();\n});",
+ "openInSeparateDialog": false,
+ "openInPopover": false
+ }
+ ]
+ },
+ "click": {
+ "type": "doNothing"
+ },
+ "groups": null,
+ "edit": {
+ "enabledActions": [
+ "add",
+ "move",
+ "remove"
+ ],
+ "attributeScope": "SERVER_SCOPE",
+ "snappable": false
+ },
+ "xKey": {
+ "name": "latitude",
+ "label": "latitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "yKey": {
+ "name": "longitude",
+ "label": "longitude",
+ "type": "attribute",
+ "settings": {},
+ "color": "#2196f3"
+ },
+ "markerType": "icon",
+ "markerShape": {
+ "shape": "markerShape1",
+ "size": 34,
+ "color": {
+ "type": "constant",
+ "color": "#307FE5"
+ }
+ },
+ "markerIcon": {
+ "size": 48,
+ "color": {
+ "type": "function",
+ "color": "rgb(255, 0, 0)",
+ "range": null,
+ "colorFunction": "if (data.active !== \"true\"){\n return 'rgb(255, 0, 0)';\n} else {\n return 'rgb(39, 134, 34)';\n}"
+ },
+ "iconContainer": "iconContainer1",
+ "icon": "thermostat"
+ },
+ "markerImage": {
+ "type": "function",
+ "imageFunction": "return {};",
+ "images": []
+ },
+ "markerOffsetX": 0.5,
+ "markerOffsetY": 1,
+ "markerClustering": {
+ "enable": false,
+ "zoomOnClick": true,
+ "maxZoom": null,
+ "maxClusterRadius": 80,
+ "zoomAnimation": true,
+ "showCoverageOnHover": true,
+ "spiderfyOnMaxZoom": false,
+ "chunkedLoad": false,
+ "lazyLoad": true,
+ "useClusterMarkerColorFunction": false,
+ "clusterMarkerColorFunction": null
+ }
+ }
+ ],
+ "polygons": [],
+ "circles": [],
+ "additionalDataSources": [],
+ "controlsPosition": "topleft",
+ "zoomActions": [
+ "scroll",
+ "doubleClick",
+ "controlButtons"
+ ],
+ "scales": [],
+ "dragModeButton": false,
+ "fitMapBounds": true,
+ "useDefaultCenterPosition": false,
+ "defaultCenterPosition": "0,0",
+ "defaultZoomLevel": 5,
+ "mapPageSize": 16384,
+ "mapActionButtons": [],
+ "background": {
+ "type": "color",
+ "color": "#fff",
+ "overlay": {
+ "enabled": false,
+ "color": "rgba(255,255,255,0.72)",
+ "blur": 3
+ }
+ },
+ "padding": "8px"
+ },
+ "title": "Map",
+ "useDashboardTimewindow": true,
+ "displayTimewindow": true,
+ "showTitleIcon": false,
+ "titleTooltip": "",
+ "dropShadow": true,
+ "enableFullscreen": false,
+ "widgetStyle": {},
+ "widgetCss": "",
+ "titleStyle": {
+ "fontSize": "16px",
+ "fontWeight": 400
+ },
+ "pageSize": 1024,
+ "noDataDisplayMessage": "",
+ "configMode": "advanced",
+ "titleFont": null,
+ "titleColor": null,
+ "margin": "0px",
+ "borderRadius": "0px",
+ "iconSize": "24px",
+ "titleIcon": "map",
+ "iconColor": "#1F6BDD",
+ "actions": {}
+ },
+ "row": 0,
+ "col": 0,
+ "id": "c9da2d6b-4ba9-1ac8-63b8-68ab49b13c81"
+ }
+ },
+ "states": {
+ "default": {
+ "name": "Thermostats",
+ "root": true,
+ "layouts": {
+ "main": {
+ "widgets": {
+ "f33c746c-0dfc-c212-395b-b448c8a17209": {
+ "sizeX": 11,
"sizeY": 11,
"row": 0,
"col": 0,
@@ -1730,11 +2054,12 @@
"mobileOrder": 2,
"mobileHeight": 5
},
- "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
+ "5186d1e3-f076-e062-5129-e4dd8e4adfb0": {
"sizeX": 13,
"sizeY": 6,
"row": 5,
"col": 11,
+ "resizable": true,
"mobileOrder": 3,
"mobileHeight": 5
}
@@ -1748,7 +2073,8 @@
"mobileAutoFillHeight": false,
"mobileRowHeight": 70,
"margin": 10,
- "outerMargin": true
+ "outerMargin": true,
+ "layoutType": "default"
}
}
}
@@ -1759,9 +2085,10 @@
"layouts": {
"main": {
"widgets": {
- "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
+ "1bbd4b8d-db42-ed7c-70c2-3d32c771843d": {
"sizeX": 24,
- "sizeY": 12,
+ "sizeY": 11,
+ "resizable": true,
"row": 0,
"col": 0
}
@@ -1775,7 +2102,8 @@
"mobileAutoFillHeight": false,
"mobileRowHeight": 70,
"margin": 10,
- "outerMargin": true
+ "outerMargin": true,
+ "layoutType": "default"
}
}
}
@@ -1794,14 +2122,6 @@
"mobileOrder": 3,
"mobileHeight": 5
},
- "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
- "sizeX": 6,
- "sizeY": 6,
- "row": 6,
- "col": 0,
- "mobileOrder": 4,
- "mobileHeight": 6
- },
"eda8a397-0959-690c-405c-11e2c9b2bc7e": {
"sizeX": 18,
"sizeY": 6,
@@ -1817,6 +2137,13 @@
"col": 6,
"mobileOrder": 2,
"mobileHeight": 6
+ },
+ "c9da2d6b-4ba9-1ac8-63b8-68ab49b13c81": {
+ "sizeX": 6,
+ "sizeY": 6,
+ "resizable": true,
+ "row": 6,
+ "col": 0
}
},
"gridSettings": {
@@ -1828,7 +2155,8 @@
"mobileAutoFillHeight": false,
"mobileRowHeight": 70,
"margin": 10,
- "outerMargin": true
+ "outerMargin": true,
+ "layoutType": "default"
}
}
}
@@ -1861,7 +2189,6 @@
"timewindow": {
"displayValue": "",
"selectedTab": 0,
- "hideInterval": false,
"hideAggregation": false,
"hideAggInterval": false,
"realtime": {
@@ -1889,33 +2216,17 @@
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
- "toolbarAlwaysOpen": true
+ "toolbarAlwaysOpen": true,
+ "titleColor": "rgba(0,0,0,0.870588)",
+ "showDashboardLogo": false,
+ "dashboardLogoUrl": null,
+ "hideToolbar": false,
+ "showFilters": true,
+ "showUpdateDashboardImage": true,
+ "dashboardCss": ""
},
"filters": {}
},
"name": "Thermostats",
- "resources": [
- {
- "link": "/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_0.svg",
- "title": "\"Thermostats\" dashboard widget \"Thermostat maps\" marker image 0",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "thermostats_dashboard_widget_thermostat_maps_marker_image_0.svg",
- "publicResourceKey": "DXvJsh8m6v4NOcO6AHXXm9kQzfrVgisT",
- "mediaType": "image/svg+xml",
- "data": "PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
- "public": true
- },
- {
- "link": "/api/images/system/thermostats_dashboard_widget_thermostat_maps_marker_image_1.svg",
- "title": "\"Thermostats\" dashboard widget \"Thermostat maps\" marker image 1",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "thermostats_dashboard_widget_thermostat_maps_marker_image_1.svg",
- "publicResourceKey": "HtfNoQ7FAZKdeH4m3WGodwajcsscKrTR",
- "mediaType": "image/svg+xml",
- "data": "PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==",
- "public": true
- }
- ]
+ "resources": []
}
\ No newline at end of file
diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
index 6701b59e0e..81f9e6a14d 100644
--- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
+++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
@@ -50,8 +50,11 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
- "configurationVersion": 2,
+ "configurationVersion": 3,
"configuration": {
+ "processingSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ },
"scope": "CLIENT_SCOPE",
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
@@ -119,7 +122,7 @@
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"configuration": {
- "scope": "SERVER_SCOPE"
+ "scope": "CLIENT_SCOPE"
},
"externalId": null
},
diff --git a/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-connector-hp.svg
index 4e41fd77c6..37b3a199c6 100644
--- a/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Bottom right elbow connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimation,\n animationDirection: flowDirection,\n flowAnimationSpeed\n} = ctx.values;\nconst {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\nconst line = ctx.tags.line[0].attr('d');\nconst animation = ctx.tags.animationGroup[0];\nconst offset = Date.now() % 1000;\nconst duration = 1 / flowAnimationSpeed;\n\nconst prevFlowAnimation = animation.remember('flowAnimation');\nconst prevFlowDirection = animation.remember('flowDirection');\nconst prevFlowDuration = animation.remember('flowDuration');\n\nif (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n} else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n}\n\nfunction animateFlow(offset, flowDirection) {\n animation.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `-${dashWidth + (dashGap || dashWidth)}` : `${dashWidth + (dashGap || dashWidth)}`;\n\n animation.add(``);\n}\n",
"tags": [
{
"tag": "line",
@@ -10,23 +11,132 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "flowAnimation",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "animationDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "flowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -37,12 +147,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -51,32 +160,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/bottom-tee-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/bottom-tee-connector-hp.svg
index 33942432cb..2feb95e9ff 100644
--- a/application/src/main/data/json/system/scada_symbols/bottom-tee-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/bottom-tee-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Bottom tee connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\n\nconst leftLine = \"M0 100H100\";\nconst rightLine = \"M100 100H200\";\nconst bottomLine = \"M 100,200 V 103\";\n\nprepareFlowAnimation('left', leftLine);\nprepareFlowAnimation('right', rightLine);\nprepareFlowAnimation('bottom', bottomLine);\n\nfunction prepareFlowAnimation(prefix, line) {\n const flowAnimation = ctx.values[prefix + 'Flow'];\n const flowDirection = ctx.values[prefix + 'FlowDirection'];\n const flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n const animation = ctx.tags[prefix + 'Line'][0];\n const offset = Date.now() % 1000;\n const duration = 1 / flowAnimationSpeed;\n \n const prevFlowAnimation = animation.remember('flowAnimation');\n const prevFlowDirection = animation.remember('flowDirection');\n const prevFlowDuration = animation.remember('flowDuration');\n \n if (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n } else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n }\n}\n\nfunction animateFlow(group, offset, flowDirection, duration, line) {\n group.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n group.add(``);\n}",
"tags": [
{
"tag": "line",
@@ -15,23 +16,364 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "leftFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.fluid-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "leftFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "leftFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlowDirection",
+ "name": "{i18n:scada.symbol.flow-direction}",
+ "hint": "{i18n:scada.symbol.flow-direction-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "rightFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "bottomFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -42,12 +384,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -56,32 +397,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/cross-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/cross-connector-hp.svg
index df708d47d3..ab6798b48b 100644
--- a/application/src/main/data/json/system/scada_symbols/cross-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/cross-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Cross connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\n\nconst leftLine = \"M0 100H100\";\nconst topLine = \"M100 97L100 0\";\nconst rightLine = \"M100 100H200\";\nconst bottomLine = \"M 100,200 V 103\";\n\nprepareFlowAnimation('left', leftLine);\nprepareFlowAnimation('top', topLine);\nprepareFlowAnimation('right', rightLine);\nprepareFlowAnimation('bottom', bottomLine);\n\nfunction prepareFlowAnimation(prefix, line) {\n const flowAnimation = ctx.values[prefix + 'Flow'];\n const flowDirection = ctx.values[prefix + 'FlowDirection'];\n const flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n const animation = ctx.tags[prefix + 'Line'][0];\n const offset = Date.now() % 1000;\n const duration = 1 / flowAnimationSpeed;\n \n const prevFlowAnimation = animation.remember('flowAnimation');\n const prevFlowDirection = animation.remember('flowDirection');\n const prevFlowDuration = animation.remember('flowDuration');\n \n if (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n } else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n }\n}\n\nfunction animateFlow(group, offset, flowDirection, duration, line) {\n group.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n group.add(``);\n}",
"tags": [
{
"tag": "line",
@@ -15,23 +16,480 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "leftFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "leftFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "leftFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.fluid-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "topFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlowDirection",
+ "name": "{i18n:scada.symbol.flow-direction}",
+ "hint": "{i18n:scada.symbol.flow-direction-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "rightFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "bottomFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -42,12 +500,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -56,32 +513,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
index 513809cca5..acb895cd5a 100644
--- a/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
@@ -33,7 +33,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": true,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": true,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -364,80 +327,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": true,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": true,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -450,128 +411,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/elevated-tank.svg b/application/src/main/data/json/system/scada_symbols/elevated-tank.svg
index 9db211457e..48668e9851 100644
--- a/application/src/main/data/json/system/scada_symbols/elevated-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/elevated-tank.svg
@@ -34,7 +34,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -274,80 +274,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -360,80 +323,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "transparent",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -446,128 +407,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg
index 33b6c0a222..74d048e884 100644
--- a/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Horizontal connector with an optional directional arrow to visually indicate flow.",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimation,\n arrowDirection: flowDirection,\n flowAnimationSpeed\n} = ctx.values;\nconst {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\nconst line = ctx.tags.line[0].attr('d');\nconst animation = ctx.tags.animationGroup[0];\nconst offset = Date.now() % 1000;\nconst duration = 1 / flowAnimationSpeed;\n\nconst prevFlowAnimation = animation.remember('flowAnimation');\nconst prevFlowDirection = animation.remember('flowDirection');\nconst prevFlowDuration = animation.remember('flowDuration');\n\nif (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n} else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n}\n\nfunction animateFlow(offset, flowDirection) {\n animation.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n animation.add(``);\n}\n",
"tags": [
{
"tag": "arrow",
@@ -85,6 +86,83 @@
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
+ },
+ {
+ "id": "flowAnimation",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "flowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
}
],
"properties": [
@@ -93,16 +171,8 @@
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -113,12 +183,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -127,48 +196,93 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
- "id": "arrowColor",
- "name": "{i18n:scada.symbol.arrow-color}",
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
"type": "color",
- "default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "default": "#C8DFF7"
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
index cff6a97eb9..18f194684b 100644
--- a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
@@ -38,7 +38,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 592 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 198, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 198, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 198, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 592 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 198, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 198, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 198, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -307,64 +307,67 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#EBEBEB",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#C8DFF7",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": false,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -377,96 +380,57 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "color",
"default": "#00000061",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "color",
"default": "#0000001F",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
index 7875b5dde0..b6b6eede7b 100644
--- a/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
@@ -33,7 +33,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -364,80 +327,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -450,128 +411,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
index 772e34f0f7..69360fbe9c 100644
--- a/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
@@ -33,7 +33,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -364,80 +327,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -450,128 +411,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
index dc9cb47268..11bd47916f 100644
--- a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
@@ -34,7 +34,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -365,80 +328,76 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ]
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -451,128 +410,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
index b7cb67f2a0..d9c05bde40 100644
--- a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
@@ -34,7 +34,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -365,80 +328,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -451,128 +412,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
index 3b9420e04d..cc168915ad 100644
--- a/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
@@ -33,7 +33,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -364,80 +327,78 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -450,128 +411,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-connector-hp.svg
index bfc97fe928..fd14834d12 100644
--- a/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Left bottom elbow connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimation,\n animationDirection: flowDirection,\n flowAnimationSpeed\n} = ctx.values;\nconst {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\nconst line = ctx.tags.line[0].attr('d');\nconst animation = ctx.tags.animationGroup[0];\nconst offset = Date.now() % 1000;\nconst duration = 1 / flowAnimationSpeed;\n\nconst prevFlowAnimation = animation.remember('flowAnimation');\nconst prevFlowDirection = animation.remember('flowDirection');\nconst prevFlowDuration = animation.remember('flowDuration');\n\nif (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n} else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n}\n\nfunction animateFlow(offset, flowDirection) {\n animation.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n animation.add(``);\n}\n",
"tags": [
{
"tag": "line",
@@ -10,23 +11,132 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "flowAnimation",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "animationDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "flowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -37,12 +147,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -51,32 +160,93 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7"
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/left-tee-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/left-tee-connector-hp.svg
index eac3869a1a..af83a4abb3 100644
--- a/application/src/main/data/json/system/scada_symbols/left-tee-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/left-tee-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Left tee connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\n\nconst leftLine = \"M0 100H97\";\nconst topLine = \"M100 100L100 0\";\nconst bottomLine = \"M 100,200 V 100\";\n\nprepareFlowAnimation('left', leftLine);\nprepareFlowAnimation('top', topLine);\nprepareFlowAnimation('bottom', bottomLine);\n\nfunction prepareFlowAnimation(prefix, line) {\n const flowAnimation = ctx.values[prefix + 'Flow'];\n const flowDirection = ctx.values[prefix + 'FlowDirection'];\n const flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n const animation = ctx.tags[prefix + 'Line'][0];\n const offset = Date.now() % 1000;\n const duration = 1 / flowAnimationSpeed;\n \n const prevFlowAnimation = animation.remember('flowAnimation');\n const prevFlowDirection = animation.remember('flowDirection');\n const prevFlowDuration = animation.remember('flowDuration');\n \n if (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n } else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n }\n}\n\nfunction animateFlow(group, offset, flowDirection, duration, line) {\n group.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n group.add(``);\n}",
"tags": [
{
"tag": "line",
@@ -15,23 +16,364 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "leftFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "leftFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "leftFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.fluid-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "topFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlowDirection",
+ "name": "{i18n:scada.symbol.flow-direction}",
+ "hint": "{i18n:scada.symbol.flow-direction-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "bottomFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -42,12 +384,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -56,32 +397,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/left-top-elbow-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/left-top-elbow-connector-hp.svg
index 6dfbc9e265..5b6d30ab65 100644
--- a/application/src/main/data/json/system/scada_symbols/left-top-elbow-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/left-top-elbow-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Left top elbow connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimation,\n animationDirection: flowDirection,\n flowAnimationSpeed\n} = ctx.values;\nconst {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\nconst line = ctx.tags.line[0].attr('d');\nconst animation = ctx.tags.animationGroup[0];\nconst offset = Date.now() % 1000;\nconst duration = 1 / flowAnimationSpeed;\n\nconst prevFlowAnimation = animation.remember('flowAnimation');\nconst prevFlowDirection = animation.remember('flowDirection');\nconst prevFlowDuration = animation.remember('flowDuration');\n\nif (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n} else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n}\n\nfunction animateFlow(offset, flowDirection) {\n animation.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n animation.add(``);\n}\n",
"tags": [
{
"tag": "line",
@@ -10,23 +11,132 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "flowAnimation",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "animationDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "flowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -37,12 +147,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -51,32 +160,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg b/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg
index 689851529d..ed6eb25bf0 100644
--- a/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg
+++ b/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg
@@ -1,7 +1,7 @@
+
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/long-top-filter.svg b/application/src/main/data/json/system/scada_symbols/long-top-filter.svg
index 9b287cbe46..d23059de37 100644
--- a/application/src/main/data/json/system/scada_symbols/long-top-filter.svg
+++ b/application/src/main/data/json/system/scada_symbols/long-top-filter.svg
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/pool-hp.svg b/application/src/main/data/json/system/scada_symbols/pool-hp.svg
index 357563a687..925ea8906a 100644
--- a/application/src/main/data/json/system/scada_symbols/pool-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/pool-hp.svg
@@ -38,7 +38,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 792 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 288, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 288, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 288, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 792 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 288, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 288, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 288, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -307,64 +307,57 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#EBEBEB",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#C8DFF7",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": false,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -377,96 +370,67 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "color",
"default": "#00000061",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "color",
"default": "#0000001F",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/right-tee-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/right-tee-connector-hp.svg
index 17d8bf4e0c..62aecb065d 100644
--- a/application/src/main/data/json/system/scada_symbols/right-tee-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/right-tee-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Right tee connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\n\nconst topLine = \"M100 100L100 0\";\nconst rightLine = \"M103 100H200\";\nconst bottomLine = \"M 100,200 V 100\";\n\nprepareFlowAnimation('top', topLine);\nprepareFlowAnimation('right', rightLine);\nprepareFlowAnimation('bottom', bottomLine);\n\nfunction prepareFlowAnimation(prefix, line) {\n const flowAnimation = ctx.values[prefix + 'Flow'];\n const flowDirection = ctx.values[prefix + 'FlowDirection'];\n const flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n const animation = ctx.tags[prefix + 'Line'][0];\n const offset = Date.now() % 1000;\n const duration = 1 / flowAnimationSpeed;\n \n const prevFlowAnimation = animation.remember('flowAnimation');\n const prevFlowDirection = animation.remember('flowDirection');\n const prevFlowDuration = animation.remember('flowDuration');\n \n if (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n } else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n }\n}\n\nfunction animateFlow(group, offset, flowDirection, duration, line) {\n group.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n group.add(``);\n}",
"tags": [
{
"tag": "line",
@@ -15,23 +16,364 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "topFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.fluid-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "topFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlowDirection",
+ "name": "{i18n:scada.symbol.flow-direction}",
+ "hint": "{i18n:scada.symbol.flow-direction-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "rightFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "bottomFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "bottomFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.bottom-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -42,12 +384,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -56,32 +397,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/short-top-filter.svg b/application/src/main/data/json/system/scada_symbols/short-top-filter.svg
index 0a77c78f00..0cb6da475c 100644
--- a/application/src/main/data/json/system/scada_symbols/short-top-filter.svg
+++ b/application/src/main/data/json/system/scada_symbols/short-top-filter.svg
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/top-tee-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/top-tee-connector-hp.svg
index 004096aaa7..e4561a8347 100644
--- a/application/src/main/data/json/system/scada_symbols/top-tee-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/top-tee-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Top tee connector",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\n\nconst leftLine = \"M0 100H100\";\nconst topLine = \"M100 97L100 0\";\nconst rightLine = \"M100 100H200\";\n\nprepareFlowAnimation('left', leftLine);\nprepareFlowAnimation('top', topLine);\nprepareFlowAnimation('right', rightLine);\n\nfunction prepareFlowAnimation(prefix, line) {\n const flowAnimation = ctx.values[prefix + 'Flow'];\n const flowDirection = ctx.values[prefix + 'FlowDirection'];\n const flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n const animation = ctx.tags[prefix + 'Line'][0];\n const offset = Date.now() % 1000;\n const duration = 1 / flowAnimationSpeed;\n \n const prevFlowAnimation = animation.remember('flowAnimation');\n const prevFlowDirection = animation.remember('flowDirection');\n const prevFlowDuration = animation.remember('flowDuration');\n \n if (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(animation, offset, flowDirection, duration, line);\n } else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n } else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n }\n}\n\nfunction animateFlow(group, offset, flowDirection, duration, line) {\n group.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n group.add(``);\n}",
"tags": [
{
"tag": "line",
@@ -15,23 +16,364 @@
"actions": null
}
],
- "behavior": [],
+ "behavior": [
+ {
+ "id": "leftFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "leftFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "leftFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.left-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.fluid-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "topFlowDirection",
+ "name": "{i18n:scada.symbol.animation-direction}",
+ "hint": "{i18n:scada.symbol.animation-direction-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "topFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.top-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlow",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "rightFlowDirection",
+ "name": "{i18n:scada.symbol.flow-direction}",
+ "hint": "{i18n:scada.symbol.flow-direction-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.forward}",
+ "falseLabel": "{i18n:scada.symbol.reverse}",
+ "stateLabel": "{i18n:scada.symbol.forward}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": true,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "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": "rightFlowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": "{i18n:scada.symbol.right-connector}",
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ }
+ ],
"properties": [
{
"id": "mainLine",
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -42,12 +384,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -56,32 +397,95 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "color",
+ "default": "#C8DFF7",
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg
index 6ce9336fee..cfaf6793ea 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg
@@ -3,6 +3,7 @@
"description": "Vertical connector with an optional directional arrow to visually indicate flow.",
"widgetSizeX": 1,
"widgetSizeY": 1,
+ "stateRenderFunction": "const {\n flowAnimation,\n arrowDirection: flowDirection,\n flowAnimationSpeed\n} = ctx.values;\nconst {\n flowAnimationWidth: lineWidth,\n flowAnimationColor: lineColor,\n flowStyleDash: dashWidth,\n flowStyleGap: dashGap,\n flowDashCap: dashCap\n} = ctx.properties;\nconst line = ctx.tags.line[0].attr('d');\nconst animation = ctx.tags.animationGroup[0];\nconst offset = Date.now() % 1000;\nconst duration = 1 / flowAnimationSpeed;\n\nconst prevFlowAnimation = animation.remember('flowAnimation');\nconst prevFlowDirection = animation.remember('flowDirection');\nconst prevFlowDuration = animation.remember('flowDuration');\n\nif (flowAnimation && flowAnimation !== prevFlowAnimation) {\n animation.remember('flowAnimation', flowAnimation);\n animation.remember('flowDuration', duration);\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && flowDirection !== prevFlowDirection) {\n animation.remember('flowDirection', flowDirection);\n animateFlow(offset, flowDirection);\n} else if (flowAnimation && duration !== prevFlowDuration) {\n animation.remember('flowDuration', duration);\n animation.findOne('animate').attr('dur', `${duration}s`) ;\n} else if (!flowAnimation && prevFlowAnimation) {\n animation.remember('flowAnimation', null);\n animation.clear();\n}\n\nfunction animateFlow(offset, flowDirection) {\n animation.clear();\n const dashArray = `${dashWidth}${dashGap ? ` ${dashGap}` : ''}`;\n const value = flowDirection ? `${dashWidth + (dashGap || dashWidth)}` : `-${dashWidth + (dashGap || dashWidth)}`;\n\n animation.add(``);\n}\n",
"tags": [
{
"tag": "arrow",
@@ -85,6 +86,83 @@
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
+ },
+ {
+ "id": "flowAnimation",
+ "name": "{i18n:scada.symbol.flow-animation}",
+ "hint": "{i18n:scada.symbol.flow-animation-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "BOOLEAN",
+ "trueLabel": "{i18n:scada.symbol.present}",
+ "falseLabel": "{i18n:scada.symbol.absent}",
+ "stateLabel": "{i18n:scada.symbol.flow-present}",
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": false,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "key": "state",
+ "scope": null
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "compareToValue": true,
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
+ },
+ {
+ "id": "flowAnimationSpeed",
+ "name": "{i18n:scada.symbol.flow-animation-speed}",
+ "hint": "{i18n:scada.symbol.flow-animation-speed-hint}",
+ "group": null,
+ "type": "value",
+ "valueType": "DOUBLE",
+ "trueLabel": null,
+ "falseLabel": null,
+ "stateLabel": null,
+ "defaultGetValueSettings": {
+ "action": "DO_NOTHING",
+ "defaultValue": 1,
+ "executeRpc": {
+ "method": "getState",
+ "requestTimeout": 5000,
+ "requestPersistent": false,
+ "persistentPollingInterval": 1000
+ },
+ "getAttribute": {
+ "scope": null,
+ "key": "state"
+ },
+ "getTimeSeries": {
+ "key": "state"
+ },
+ "getAlarmStatus": {
+ "severityList": null,
+ "typeList": null
+ },
+ "dataToValue": {
+ "type": "NONE",
+ "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
+ }
+ },
+ "defaultSetValueSettings": null,
+ "defaultWidgetActionSettings": null
}
],
"properties": [
@@ -93,16 +171,8 @@
"name": "{i18n:scada.symbol.main-line}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "mainLineSize",
@@ -113,12 +183,11 @@
"subLabel": "Main",
"divider": true,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "secondaryLineSize",
@@ -127,48 +196,93 @@
"default": 2,
"required": true,
"subLabel": "Secondary",
- "divider": null,
"fieldSuffix": "px",
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
"min": 0,
"max": 99,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
},
{
"id": "lineColor",
"name": "{i18n:scada.symbol.line-color}",
"type": "color",
"default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
- "id": "arrowColor",
- "name": "{i18n:scada.symbol.arrow-color}",
+ "id": "flowAnimationWidth",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 4,
+ "subLabel": "Width",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowAnimationColor",
+ "name": "{i18n:scada.symbol.flow}",
+ "group": "{i18n:scada.symbol.animation}",
"type": "color",
- "default": "#1A1A1A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "default": "#C8DFF7"
+ },
+ {
+ "id": "flowStyleDash",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "required": true,
+ "subLabel": "{i18n:scada.symbol.dash}",
+ "divider": true,
+ "fieldSuffix": "px",
+ "min": 0,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowStyleGap",
+ "name": "{i18n:scada.symbol.flow-style}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "number",
+ "default": 10,
+ "subLabel": "{i18n:scada.symbol.gap}",
+ "fieldSuffix": "px",
+ "min": 1,
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "flowDashCap",
+ "name": "{i18n:scada.symbol.flow-dash-cap}",
+ "group": "{i18n:scada.symbol.animation}",
+ "type": "select",
+ "default": "butt",
+ "items": [
+ {
+ "value": "butt",
+ "label": "{i18n:scada.symbol.dash-cap-butt}"
+ },
+ {
+ "value": "round",
+ "label": "{i18n:scada.symbol.dash-cap-round}"
+ },
+ {
+ "value": "square",
+ "label": "{i18n:scada.symbol.dash-cap-square}"
+ }
+ ],
+ "disabled": false,
+ "visible": true
}
]
}]]>
-
+
\ No newline at end of file
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg b/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
index d4358e8fa7..57a79d72d7 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
@@ -34,7 +34,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -365,80 +328,79 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "required": false,
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -451,128 +413,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
index 3e29a4e657..491fb4477e 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
@@ -38,7 +38,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\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(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -307,64 +307,67 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#EBEBEB",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#C8DFF7",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": false,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -377,96 +380,57 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "color",
"default": "#00000061",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "color",
"default": "#0000001F",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
},
{
"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
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank.svg
index de7f907e76..99fa9b649c 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-tank.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-tank.svg
@@ -33,7 +33,7 @@
},
{
"tag": "scale",
- "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
+ "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, 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 \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (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\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(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}",
"type": "color",
"default": "#E5E5E5",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}",
"type": "color",
"default": "#1EC1F480",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color",
"default": "#F3F3F3",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "gal",
- "required": null,
"subLabel": "{i18n:scada.symbol.units}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextFont",
@@ -364,80 +327,79 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color",
"default": "#0000008A",
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "valueBox",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "scale",
"name": "{i18n:scada.symbol.scale}",
"type": "switch",
"default": true,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
- "disableOnProperty": null,
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "scaleDisplayFormat",
+ "name": "{i18n:scada.symbol.scale}",
+ "type": "select",
+ "default": true,
+ "subLabel": "{i18n:scada.symbol.display-format}",
+ "divider": false,
+ "disableOnProperty": "scale",
+ "items": [
+ {
+ "value": true,
+ "label": "Percentage"
+ },
+ {
+ "value": false,
+ "label": "Absolute"
+ }
+ ],
+ "disabled": false,
+ "visible": true
},
{
"id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch",
"default": false,
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": 1
+ "step": 1,
+ "disabled": false,
+ "visible": true
+ },
+ {
+ "id": "majorUnits",
+ "name": "{i18n:scada.symbol.major-ticks}",
+ "type": "units",
+ "subLabel": "{i18n:scada.symbol.units}",
+ "divider": true,
+ "disableOnProperty": "scale",
+ "disabled": false,
+ "visible": true
},
{
"id": "majorFont",
@@ -450,128 +412,84 @@
"weight": "500",
"style": "normal"
},
- "required": null,
- "subLabel": null,
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#00000061",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 5,
- "required": null,
"subLabel": "{i18n:scada.symbol.intervals}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
"min": 1,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#0000001F",
- "required": null,
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
- "required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
- "required": null,
"subLabel": "{i18n:scada.symbol.critical}",
- "divider": null,
- "fieldSuffix": null,
"disableOnProperty": "scale",
- "rowClass": "",
- "fieldClass": "",
- "min": null,
- "max": null,
- "step": null
+ "disabled": false,
+ "visible": true
}
]
}]]>
diff --git a/application/src/main/data/json/system/widget_bundles/maps.json b/application/src/main/data/json/system/widget_bundles/maps.json
index 86754f0bd4..74b4870de1 100644
--- a/application/src/main/data/json/system/widget_bundles/maps.json
+++ b/application/src/main/data/json/system/widget_bundles/maps.json
@@ -2,13 +2,17 @@
"widgetsBundle": {
"alias": "maps_v2",
"title": "Maps",
- "image": "data:image/png;base64,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",
- "description": "Visualize the latest location or trip of the devices or other entities on the indoor or outdoor maps.",
+ "image": "tb-image:bWFwcy13aWRnZXQtYnVuZGxlLnBuZw==:Ik1hcHMiIHN5c3RlbSBidW5kbGUgaW1hZ2U=:SU1BR0U=;data:image/png;base64,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",
+ "scada": false,
+ "description": "Visualize the latest location or trip of devices and entities on indoor and outdoor maps using markers, polygons, and circles for enhanced spatial representation.",
"order": 6000,
- "externalId": null,
"name": "Maps"
},
"widgetTypeFqns": [
+ "map",
+ "image_map",
+ "trip_map",
+ "route_map",
"maps_v2.openstreetmap",
"maps_v2.google_maps",
"maps_v2.image_map",
diff --git a/application/src/main/data/json/system/widget_types/google_map.json b/application/src/main/data/json/system/widget_types/google_map.json
index 9c7696d8db..cd69b304e9 100644
--- a/application/src/main/data/json/system/widget_types/google_map.json
+++ b/application/src/main/data/json/system/widget_types/google_map.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.google_maps",
"name": "Google Map",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/google_map_system_widget_image.png",
"description": "Displays the location of the entities on Google Maps. Requires the Google map key to work properly. Highly customizable via custom markers, marker tooltips, and widget actions.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('google-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"google-map\",\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"gmDefaultMapType\":\"roadmap\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See advanced settings for details\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7568\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"perimeter\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.2,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":3,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false,\"useClusterMarkers\":false,\"zoomOnClick\":true,\"maxClusterRadius\":80,\"animate\":true,\"spiderfyOnMaxZoom\":false,\"showCoverageOnHover\":true,\"chunkedLoading\":false,\"removeOutsideVisibleBounds\":true,\"useIconCreateFunction\":false},\"title\":\"Google Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/here_map.json b/application/src/main/data/json/system/widget_types/here_map.json
index 9f5d7e9812..b49e1dcf41 100644
--- a/application/src/main/data/json/system/widget_types/here_map.json
+++ b/application/src/main/data/json/system/widget_types/here_map.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.here_map",
"name": "HERE Map",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/here_map_system_widget_image.png",
"description": "Displays the location of the entities on HERE Maps. Requires the HERE map key to work properly. Highly customizable via custom markers, marker tooltips, and widget actions.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('here', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"here\",\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"gmDefaultMapType\":\"roadmap\",\"mapProvider\":\"HERE.normalDay\",\"useCustomProvider\":false,\"customProviderTileUrl\":\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\"mapProviderHere\":\"HERE.normalDay\",\"credentials\":{\"useV3\":true,\"apiKey\":\"kVXykxAfZ6LS4EbCTO02soFVfjA7HoBzNVVH9u7nzoE\"},\"mapImageUrl\":\"tb-image;/api/images/system/here_map_system_widget_map_image.svg\",\"tmApiKey\":\"84d6d83e0e51e481e50454ccbe8986b\",\"tmDefaultMapType\":\"roadmap\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See advanced settings for details\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7569\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"coordinates\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.5,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false,\"useClusterMarkers\":false,\"zoomOnClick\":true,\"maxClusterRadius\":80,\"animate\":true,\"spiderfyOnMaxZoom\":false,\"showCoverageOnHover\":true,\"chunkedLoading\":false,\"removeOutsideVisibleBounds\":true,\"useIconCreateFunction\":false},\"title\":\"HERE Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json b/application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json
index 6e7d86b847..441a5a520c 100644
--- a/application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json
+++ b/application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json
@@ -17,7 +17,7 @@
"settingsDirective": "tb-liquid-level-card-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-liquid-level-card-basic-config",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.floor(Math.random() * 101);\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"tankSelectionType\":\"Static\",\"selectedShape\":\"Horizontal Ellipse\",\"shapeAttributeName\":\"tankShape\",\"tankColor\":{\"type\":\"range\",\"color\":\"#242770\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E73535DE\"},{\"from\":20,\"to\":null,\"color\":\"#242770\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E73535DE';\\n }\\n}\\nreturn '#242770';\"},\"datasourceUnits\":\"%\",\"layout\":\"percentage\",\"volumeSource\":\"static\",\"volumeConstant\":500,\"volumeAttributeName\":\"volume\",\"volumeUnits\":\"L\",\"volumeFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"volumeColor\":\"rgba(0, 0, 0, 0.18)\",\"units\":\"%\",\"widgetUnitsSource\":\"static\",\"widgetUnitsAttributeName\":\"units\",\"liquidColor\":{\"type\":\"range\",\"color\":\"#7A8BFF\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E27C7CDE\"},{\"from\":20,\"to\":null,\"color\":\"#7A8BFF\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"valueColor\":{\"type\":\"range\",\"color\":\"#000000DE\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#FF0000DE\"},{\"from\":20,\"to\":null,\"color\":\"rgba(0,0,0,0.87)\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FF0000DE';\\n }\\n}\\nreturn '#000000DE';\"},\"showBackgroundOverlay\":true,\"backgroundOverlayColor\":{\"type\":\"range\",\"color\":\"#FFFFFFC2\",\"rangeList\":[{\"from\":0,\"to\":20,\"color\":\"#FFEFEFDE\"},{\"from\":20,\"to\":null,\"color\":\"#FFFFFFC2\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FFEFEFDE';\\n }\\n}\\nreturn '#FFFFFFC2';\"},\"showTooltip\":true,\"showTooltipLevel\":true,\"tooltipUnits\":\"%\",\"tooltipLevelDecimals\":0,\"tooltipLevelFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipLevelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.76)\",\"rangeList\":[],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Liquid level\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"configMode\":\"basic\",\"titleFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1.5\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"showTitleIcon\":false,\"titleIcon\":\"water_drop\",\"iconColor\":\"#5469FF\",\"decimals\":0,\"enableDataExport\":false,\"enableFullscreen\":false,\"borderRadius\":\"0px\",\"actions\":{},\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"margin\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}"
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.floor(Math.random() * 101);\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"tankSelectionType\":\"static\",\"selectedShape\":\"Horizontal Ellipse\",\"shapeAttributeName\":\"tankShape\",\"tankColor\":{\"type\":\"range\",\"color\":\"#242770\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E73535DE\"},{\"from\":20,\"to\":null,\"color\":\"#242770\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E73535DE';\\n }\\n}\\nreturn '#242770';\"},\"datasourceUnits\":\"%\",\"layout\":\"percentage\",\"volumeSource\":\"static\",\"volumeConstant\":500,\"volumeAttributeName\":\"volume\",\"volumeUnits\":\"L\",\"volumeFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"volumeColor\":\"rgba(0, 0, 0, 0.18)\",\"units\":\"%\",\"widgetUnitsSource\":\"static\",\"widgetUnitsAttributeName\":\"units\",\"liquidColor\":{\"type\":\"range\",\"color\":\"#7A8BFF\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E27C7CDE\"},{\"from\":20,\"to\":null,\"color\":\"#7A8BFF\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"valueColor\":{\"type\":\"range\",\"color\":\"#000000DE\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#FF0000DE\"},{\"from\":20,\"to\":null,\"color\":\"rgba(0,0,0,0.87)\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FF0000DE';\\n }\\n}\\nreturn '#000000DE';\"},\"showBackgroundOverlay\":true,\"backgroundOverlayColor\":{\"type\":\"range\",\"color\":\"#FFFFFFC2\",\"rangeList\":[{\"from\":0,\"to\":20,\"color\":\"#FFEFEFDE\"},{\"from\":20,\"to\":null,\"color\":\"#FFFFFFC2\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FFEFEFDE';\\n }\\n}\\nreturn '#FFFFFFC2';\"},\"showTooltip\":true,\"showTooltipLevel\":true,\"tooltipUnits\":\"%\",\"tooltipLevelDecimals\":0,\"tooltipLevelFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipLevelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.76)\",\"rangeList\":[],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Liquid level\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"configMode\":\"basic\",\"titleFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1.5\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"showTitleIcon\":false,\"titleIcon\":\"water_drop\",\"iconColor\":\"#5469FF\",\"decimals\":0,\"enableDataExport\":false,\"enableFullscreen\":false,\"borderRadius\":\"0px\",\"actions\":{},\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"margin\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}"
},
"tags": [
"reservoir",
diff --git a/application/src/main/data/json/system/widget_types/image_map.json b/application/src/main/data/json/system/widget_types/image_map.json
index 02554e82f6..2a4fe71696 100644
--- a/application/src/main/data/json/system/widget_types/image_map.json
+++ b/application/src/main/data/json/system/widget_types/image_map.json
@@ -1,46 +1,34 @@
{
- "fqn": "maps_v2.image_map",
+ "fqn": "image_map",
"name": "Image Map",
"deprecated": false,
- "image": "tb-image;/api/images/system/image_map_system_widget_image.png",
- "description": "Displays the indoor or relative location of the entities on the image map. Useful to display floor maps, smart parking, etc. Entity coordinates are expected to be in the range from 0 to 1. Highly customizable via custom markers, marker tooltips, and widget actions. ",
+ "image": "tb-image;/api/images/system/image-map-widget.png",
+ "description": "Displays the indoor or relative location of entities on an image map, making it ideal for floor plans, smart parking, and more. Entity coordinates are expected to range from 0 to 1. Supports markers, marker tooltips, widget actions, polygons, and circles for enhanced spatial representation.",
"descriptor": {
"type": "latest",
"sizeX": 8.5,
- "sizeY": 6.5,
+ "sizeY": 6,
"resources": [],
- "templateHtml": "",
- "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n",
- "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
- "settingsSchema": "",
- "dataKeySettingsSchema": "",
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
+ "settingsForm": [],
+ "dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"image-map\",\"mapImageUrl\":\"tb-image;/api/images/system/image_map_system_widget_map_image.svg\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See advanced settings for details\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7569\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"perimeter\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.2,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":3,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false},\"title\":\"Image Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-map-basic-config",
+ "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"mapType\":\"image\",\"imageSource\":{\"sourceType\":\"image\",\"url\":\"tb-image;/api/images/system/image_map_system_widget_map_image.svg\",\"entityAliasId\":null,\"entityKey\":null},\"markers\":[{\"dsType\":\"function\",\"dsLabel\":\"First point\",\"dsDeviceId\":null,\"dsEntityAliasId\":null,\"dsFilterId\":null,\"additionalDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8239425680406081,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details\",\"patternFunction\":null,\"trigger\":\"click\",\"autoclose\":true,\"offsetX\":0,\"offsetY\":-1},\"groups\":null,\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"markerType\":\"shape\",\"markerShape\":{\"shape\":\"markerShape1\",\"size\":34,\"color\":{\"type\":\"function\",\"color\":\"#307FE5\",\"colorFunction\":\"var temperature = data.temperature;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\\n\"}},\"markerIcon\":{\"icon\":\"mdi:lightbulb-on\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerImage\":{\"type\":\"image\",\"image\":\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xOTEuMzUgLTM1MS4xOCAxMDgzLjU4IDE3MzAuNDYiPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjZmU3NTY5IiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMzciIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgZD0iTTM1MS44MzMgMTM2MC43OGMtMzguNzY2LTE5MC4zLTEwNy4xMTYtMzQ4LjY2NS0xODkuOTAzLTQ5NS40NEMxMDAuNTIzIDc1Ni40NjkgMjkuMzg2IDY1NS45NzgtMzYuNDM0IDU1MC40MDRjLTIxLjk3Mi0zNS4yNDQtNDAuOTM0LTcyLjQ3Ny02Mi4wNDctMTA5LjA1NC00Mi4yMTYtNzMuMTM3LTc2LjQ0NC0xNTcuOTM1LTc0LjI2OS0yNjcuOTMyIDIuMTI1LTEwNy40NzMgMzMuMjA4LTE5My42ODUgNzguMDMtMjY0LjE3M0MtMjEtMjA2LjY5IDEwMi40ODEtMzAxLjc0NSAyNjguMTY0LTMyNi43MjRjMTM1LjQ2Ni0yMC40MjUgMjYyLjQ3NSAxNC4wODIgMzUyLjU0MyA2Ni43NDcgNzMuNiA0My4wMzggMTMwLjU5NiAxMDAuNTI4IDE3My45MiAxNjguMjggNDUuMjIgNzAuNzE2IDc2LjM2IDE1NC4yNiA3OC45NzEgMjYzLjIzMyAxLjMzNyA1NS44My03LjgwNSAxMDcuNTMyLTIwLjY4NCAxNTAuNDE3LTEzLjAzNCA0My40MS0zMy45OTYgNzkuNjk1LTUyLjY0NiAxMTguNDU1LTM2LjQwNiA3NS42NTktODIuMDQ5IDE0NC45ODEtMTI3Ljg1NSAyMTQuMzQ1LTEzNi40MzcgMjA2LjYwNi0yNjQuNDk2IDQxNy4zMS0zMjAuNTggNzA2LjAyOHoiLz48Y2lyY2xlIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBjeD0iMzUyLjg5MSIgY3k9IjIyNS43NzkiIHI9IjE4My4zMzIiLz48L3N2Zz4=\",\"imageSize\":34},\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"positionFunction\":\"return {x: origXPos, y: origYPos};\",\"markerClustering\":{\"enable\":false,\"zoomOnClick\":true,\"maxZoom\":null,\"maxClusterRadius\":80,\"zoomAnimation\":true,\"showCoverageOnHover\":true,\"spiderfyOnMaxZoom\":false,\"chunkedLoad\":false,\"lazyLoad\":true,\"useClusterMarkerColorFunction\":false,\"clusterMarkerColorFunction\":null}},{\"dsType\":\"function\",\"dsLabel\":\"Second point\",\"dsDeviceId\":null,\"dsEntityAliasId\":null,\"dsFilterId\":null,\"additionalDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7826299113906372,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details\",\"offsetX\":0,\"offsetY\":-1,\"patternFunction\":null},\"click\":{\"type\":\"doNothing\"},\"groups\":null,\"edit\":{\"enabledActions\":[],\"snappable\":false},\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"markerType\":\"icon\",\"markerShape\":{\"shape\":\"markerShape1\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerIcon\":{\"size\":40,\"color\":{\"type\":\"function\",\"color\":\"#307FE5\",\"colorFunction\":\"var colors = ['#488bc7','#549c5d','#ed7546','#be2b29'];\\nvar temperature = data.temperature;\\nvar res = colors[0];\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120;\\n var index = Math.min(3, Math.floor(4 * percent));\\n res = colors[index];\\n}\\nreturn res;\"},\"icon\":\"thermostat\"},\"markerImage\":{\"type\":\"function\",\"image\":\"/assets/markers/shape1.svg\",\"imageSize\":34,\"imageFunction\":\" \",\"images\":[]},\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"positionFunction\":\"return {x: origXPos, y: origYPos};\",\"markerClustering\":{\"enable\":false,\"zoomOnClick\":true,\"maxZoom\":null,\"maxClusterRadius\":80,\"zoomAnimation\":true,\"showCoverageOnHover\":true,\"spiderfyOnMaxZoom\":false,\"chunkedLoad\":false,\"lazyLoad\":true,\"useClusterMarkerColorFunction\":false,\"clusterMarkerColorFunction\":null}}],\"polygons\":[],\"circles\":[],\"additionalDataSources\":[]},\"title\":\"Image Map\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\",\"titleFont\":null,\"titleColor\":null,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\",\"titleIcon\":\"map\",\"iconColor\":\"#1F6BDD\",\"actions\":{}}"
},
- "tags": [
- "building",
- "interior",
- "venue",
- "inside",
- "room",
- "office",
- "manufacturing",
- "floor",
- "plant",
- "storage",
- "warehouse",
- "depot"
- ],
"resources": [
{
- "link": "/api/images/system/image_map_system_widget_image.png",
+ "link": "/api/images/system/image-map-widget.png",
"title": "\"Image Map\" system widget image",
"type": "IMAGE",
"subType": "IMAGE",
- "fileName": "image_map_system_widget_image.png",
- "publicResourceKey": "hDdSISQr6elribOYD6T3uePXZI5WvNtM",
+ "fileName": "image-map-widget.png",
+ "publicResourceKey": "UJKMEwPHCOL8rQk6qr9pRRHNnD90yeTA",
"mediaType": "image/png",
- "data": "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",
+ "data": "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",
"public": true
},
{
@@ -49,54 +37,39 @@
"type": "IMAGE",
"subType": "IMAGE",
"fileName": "image_map_system_widget_map_image.svg",
- "publicResourceKey": "QKRIYhDeBGwjaeIS601VvNLSsvZ25DRj",
+ "publicResourceKey": "P4hZLRjmo2P1RjGPZ4CrCMxh6xOQFQr5",
"mediaType": "image/svg+xml",
"data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTEzNC41IiBoZWlnaHQ9Ijc2Mi43OCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI3LjA3MSAtMzA3LjkpIj4KICA8ZyBmaWxsPSJub25lIj4KICAgPHBhdGggZD0ibTkwNi4wMyA3MDYuMTMgMy40MjkyIDE3Ljc5Nm0tODgwLjg5IDQxLjEyMWMxNTAuNDQgNi44MzM0IDE0Ni4zOS0yNi4zMzQgMTY2LjQzLTI5LjMyIDM2LjE0NC01LjM4NDggMTE0LjI5LTYuNTI1NCAxNDguMzMtOC42MjM1IDQzLjM3OC0yLjY3MzggMTQxLjc2LTExLjIzMSAxODguODYtMTkuODM0IDM5LjgxMS03LjI3MjggMjIxLjM3LTAuODYyMzUgMzE5LjA3LTAuODYyMzUgNzAuODI3IDAgMTQ2LjkyLTEuNzI0NyAyMTguMTgtMS43MjQ3LTMxLjYyIDAgMTE3Ljg2LTIuNTg3MSA4Ni4yMzYtMi41ODcxbS0yNS4wOTEtNjguMTI2Yy01Mi44IDM0Ljc4NS02NS44OTUgNTEuNzQ5LTk1LjYzOSA4MS40OTMtMjQuOTMxIDI0LjkzMS0xNDAuNC0xOS4xMzktMTc4Ljk0IDM2LjY1LTEyLjI4MSAxNy43NzctNDcuMDAzIDQ2LjU0Ny02NS4xMDggNTkuMDcxLTIwLjEwNSAxMy45MDgtNTYuMDM3IDQ0Ljk1Ny02Ny43NjkgNzMuMDc4LTQuODAxNSAxMS41MDktMTMuMzggMzUuOTkzLTIzLjQ0OSA0Ni4wNjItMTAuNDk3IDEwLjQ5Ny0zOC4zNzcgNi4zODU3LTQ0LjAyMyAxNy42NDgtMTkuMDA1IDM3LjkwOC0yNS40NjUgMTAwLjkyLTY3LjYxOCAxMDIuMDVtMTkuMjgyLTYyNC4wMWMzNC42NTktMS44NzM4IDg0LjAyNyA3LjM5MTMgMTA5LjktNC4yODU0IDEzLjI4Mi01Ljk5NDEgNDEuNDA3LTIuNDYxNCA2Ni44MjktMi4zMjA1IDM1LjMyMiAwLjE5NTc4IDY0LjM4MiAwLjYzNDc3IDEwMS45MiA1LjAyMzIgMjUuMDMgMi45MjY1IDQ0LjY2MyAzNC4yODcgNTguNTI3IDUwLjY0NCAxNy4wOTkgMjAuMTczIDYyLjc2NC0xLjcxNDcgNjYuMzA2IDMyLjEzNCA1LjEwMjcgNDguNzY2LTYuMzI4NCA3OC42MzcgNi4xNDExIDk3LjM0MiAxOS45NjkgMjkuOTU0IDUwLjQ4NiAxNy44NTYgNDQuNjE5IDgzLjk3MW0tNDcyLjQ1LTM3OC43OWM0LjY0MzUgMjMuNzI5IDE1LjA2OSA3Mi43NzYgMTkuMDYxIDEzMC42NCAwLjg3MjA2IDEyLjY0IDUuNDQ3MiAyNC45OTMgNC4yMjIzIDQ1LjI3OC0yLjUxNzIgNDEuNjg4LTE1LjcxNyA0My42NzctMTUuMDkxIDYwLjM2NSAxLjQzMiAzOC4xODIgMzAuNjE0IDkzLjgzNyAzMC42MTQgMTM5LjcgMCAyNC4xODEtMi42Njk2IDExNS4zOSA3LjMzIDEzNS4zOSAwLjE1OTExIDAuMzE4MjEgMTAuMDY1IDM1Ljg4MyAxMC43NzkgNDkuMTU0IDAuOTQzNzggMTcuNTI1LTI0LjQ3OCAzOS40Ny0yOC4wMjcgNDYuNTY3LTUuNDc3NyAxMC45NTUtMzYuOTczIDEwLjg4Mi00MC4xIDI0LjE0Ni0zLjg2ODggMTYuNDE1LTMuODY2MyA0My43OTcgNC4wNDY1IDU5LjQ0MW05Ny4zMzctNjkxLjAxYy01LjAxMzMgMzUuNTE2LTQzLjY1OSAxMS4zMTctNTguNTM5IDIzLjc4MS0yMS4zMyAxNy44NjktNjIuNSAzMS40MzItNzAuMTI0IDM1LjM2Ny0zNS4wODggMTguMTA4LTExMC40Ny0xNS4xNDItMTI1LjYxIDQuMjY4NC0xNS45NTEgMjAuNDQ3LTAuMDczNSA2MS40NjYtOS4xNDY3IDg0LjE0OS02LjAzNTcgMTUuMDg5LTE4Ljg3NyAyMy4wMTctMjcuNDQgMzIuOTI4LTE5Ljc0OCAyMi44NTYtNjkuOTc0IDY5LjgyNC04NC43NTkgMTAwLTcuNDk3NCAxNS4zMDQtMy4yODQzIDQ0LjQyLTMuNDcwNSA2My4zNDMtMC4xMjc5MyAxMi45OTQtMC44MTAxNSAyMy4xMDQgMi40MDM0IDI4LjI3NiA0Ljk2MTYgNy45ODU4IDIzLjcyIDI4LjExMiAyNC4yMzkgNTAuNjExIDAuMjk0MTEgMTIuNzcxIDAuMDEzMyA3OC41OTEgMy4wNDg5IDg3LjY1NSAyLjMxMjYgNi45MDU1IDQuMjIgMjYuNTY1IDEwLjIxNCAzNi41ODcgMTEuMzU0IDE4Ljk4NCA0LjM4NzQgNDAuMTU3IDI3Ljg5NyA1My41MDggMTkuMDUgMTAuODE5IDQ2Ljg3OCAxMi4yMTkgODEuOTI2IDE0LjQ2MSAzMy43MDMgMi4xNTU5IDYxLjUxMi0xLjQzMDQgNzYuOTIxIDYuMTQxMSAxMS41ODUgNS42OTI3IDguNTgxNSAxNy45MzMgMTQuMjk1IDI5LjM2MSA1LjY0MDQgMTEuMjgxIDMxLjUwMyAxMS4xNTYgNDEuODA0IDQzLjQ1NSA3LjYwNTkgMjMuODQ3IDMuMDg1OSA0NC4xNTcgNi43MDc2IDY1Ljg4NyIgc3Ryb2tlPSIjMzY0ZTU5IiBzdHJva2Utd2lkdGg9IjMiLz4KICAgPHBhdGggZD0ibTQzLjI3OCA1MTcuOTVzMjMwLjg1LTMuNjM4IDI1MC4wMS0zLjY1ODdjNy40ODIyLThlLTMgOC42MTk1IDUuMTUxOSAxNC4wMjEgMTEuNDU5IDI0LjU5NiAyOC43MTkgOTMuOTEgMTEyLjk0IDkzLjkxIDExMi45NCIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogICA8cGF0aCBkPSJtMzUuOTYxIDU3Ny43czE2NS41Mi0xLjY4NDUgMjQ4Ljc4LTEuNjg0NWM0Ljk0NzUgMCA3LjcyOTktMi44ODMzIDEwLjUzOC01LjcyOTggOS42NjExLTkuNzk0MiAyNS42MzItMjguNTkgMjUuNjMyLTI4LjU5IiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPC9nPgogIDxwYXRoIGQ9Im0zOC40IDY0MS43MyAzOTMuMzEtNC4yNjg0IiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0iIzMzNiIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDxwYXRoIGQ9Im0zOS4wMDkgNzA0LjU0IDQ4NC4xNi02LjcwNzYiIGNvbG9yPSIjMDAwMDAwIiBmaWxsPSIjMzM2IiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2Ij4KICAgPGcgc3Ryb2tlLXdpZHRoPSIxcHgiPgogICAgPHBhdGggZD0ibTMwMy45NiA2ODIuNTkgMTQ2LjggMS44MjkzYzEwLjUzNCAwLjEzMTI3IDE0LjM0NC0yLjYzNzQgMjUuNDg3LTYuMzcyOCAxMC40MTItMy40OTAzIDMxLjQyNC0yLjY5OSA0MS4zODUtMi43NzM4bDQwNS41Ni0zLjA0ODkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDI2LjIyIDMxNC44OWMyLjA2NzUgOS4wNTI3IDEuODQxOCA1MS43MjggNi41MDc5IDc0LjgzNSAxLjY3NDggOC4yOTM0IDguNjc1MSAxNC4wNjYgMTAuMDU1IDE0Ljg1OSA0LjkwMTUgMi44MTQ2IDEwLjgxNSA4LjE0OTggMTMuMDQ2IDE2LjA4OCA2Ljc1NzggMjQuMDQ2IDAuODc5NzIgNjguNDUyIDAuODc5NzIgMTEwLjY5IDAgNi4wOTc4IDEuNjYwMSAzMC4xNDctMi4xNTU5IDMzLjk2My0yLjU0MDggMi41NDA4LTAuMjgxNjMgMTIuOTkxLTMuNDM2OCAxNi4xNDRsLTkuODQ5NCA5Ljg0MzFjLTEwLjM2NyAxMC4zNi0xMS41OSA2LjUyNjEtMTcuNzM4IDE4LjgyMy0zLjU2NzcgNy4xMzU0IDUuNDAyNCAyMC42NzIgNy4zNTQzIDI0LjU3NiAxLjkzMjEgMy44NjQzLTEuODQyMiA0Ljc3NzctMS43OTI0IDcuNDQ2MyAwLjI1Mjg2IDEzLjU0NSAyLjI5NzUgMzczLjkzIDIuMjk3NSAzNzMuOTMiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMzY1LjI0IDUxOS43OCA0LjExNiA1MDIuMTUiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMTE2LjUzIDUwNC4xOSAzLjg4MDYgMzEwLjk2IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTMxNy42OCA1NzYuNDkgMTMwLjE5IDEuNTI0NGM0LjUxMDggMy4yNDE3IDIwLjM0NSA3Ljk2ODUgMjcuNzQ1IDQuMjY4NCAzLjE1NTUtMS41Nzc3IDkuNDE5LTUuMzg4MiAxNC4wMjUtMy45NjM2IDQuMjY3IDEuMzE5OCA2LjAxNjkgMy4xMTYzIDEwLjM2NiAzLjA0ODkgMTAuMzA0LTAuMTU5NzUgMjAuMjEyIDAuMzg3NDEgMzAuNDg5IDAuMzA0ODkgMTc3Ljg5LTEuNDI4MyAzNTYuNTktMi4xMzI1IDUzNC43Ny0zLjA0ODkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDc1LjMxIDU4Mi44OWMtMy40NDQyIDExLjM1MS0yLjEwMzQgMTIuNDM0IDMuNjU4NiAyMS4wMzcgMy43OTQ0IDUuNjY1NiA1MC44NjMgMTMuMDM4IDQxLjQ2NSAyNy4xMzUtMTAuNTM3IDE1LjgwNS0yMi44OTctNS40Nzc3LTMzLjg0My0xLjgyOTMtNS40NTI0IDEuODE3NC03LjM0OSA1LjQ1NjMtMy42NTg3IDkuMTQ2NiAyLjgwNjggMi44MDY4IDQuMDQ4IDEuODA0IDYuNTIwMyA1LjEwMDQiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDMyLjAxIDYzNi44NWM4LjMxOSAxMy4xMSAxOC44NDYgMTQuNjM1IDM1LjY3MiAxNC42MzUgMi45Mzg2IDAgNy44Ny0wLjkzMzcxIDEwLjY3MSAwIDExLjM1OSAzLjc4NjQgMjcuMTk0IDEwLjI3NiAzNi4yMDIgMjEuMTI5IDguMjggOS45NzY2IDEwLjI1MyAyMy44ODMgNy43MDIgMzcuMTA0LTYuMTY5OSAzMS45OC0xNi43MTQgNTYuOTg5LTE5LjA0NCA4Ni41NjktMS4zNDggMTcuMTE5IDQuNTA5NiAyMi41MzUgMTEuMDcxIDMzLjkyOSAxMC42NyAxOC41MjcgOC43MjQ1IDE0LjIgOC41NzE0IDM0LjI4Ni0wLjEzOTYzIDE4LjMxOSAwIDYwLjI2NCAwIDgwLjcxNCIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MjguNTEgNjU4Ljk2Yy0xMC42ODEgMC45MDQ1NC03LjEwOC01LjYwMjYtMTAuODI0LTguMDc5Ni00Ljc4NDUtMy4xODk3LTEyLjIyNy0xLjI1MS0xNi43NjktNS43OTI5LTAuNjY2MTItMC42NjYxMi04LjgwOTctNC4xMDg4LTEwLjE3NC0yLjc0NC04LjM2NDYgOC4zNjQ2LTMuMDQ4OSAyMC41NTItMy4wNDg5IDMzLjUzOGwzLjAyMiAzMzkuNyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MTcuOTkgNjUxLjAzYy0wLjIyMTcxLTIuNzAxOCAxLjkwMzUtNS41NjIxIDMuMzUzOC03LjAxMjQgMS43OTk0LTEuNzk5NCA2LjkyMjkgMS4wMDQyIDguODQxOC0wLjkxNDY2IDAuMjg3NjUtMC4yODc2NiAwLjg0MzI5LTExLjE2NCAwLjIyODY2LTEzLjU2OC0yLjA2NDgtOC4wNzQyLTIuMDU4LTI4LjY1Ny0yLjA1OC0zOC43MjF2LTczLjE3MyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MjguNjYgNjc1LjQyLTAuNDU3MzMtMzEuNTU2IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTc2Ni4zMiA1NzkuNjQgMC40MzExOCAxMy43OThjMy4xMzY0IDQuNjY5MiAzLjAxODIgOS42MDA3IDMuMDE4MiAxNi4zODV2MTU3LjM4IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTExMjIuOSA3NjUuOTFjLTIwMi4zMSA0LjY5MDUtNDAzLjc0LTEuMTEzOC02MDUuOTUgMy4zNTM5LTEwLjg2NCAwLjI0MDAyLTMuMzYxNS04LjU4NjMtMjguNTM3LTguNTg2MyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im04NjAuMDEgNzM3LjA3cy05Ny40NDggMC44NTgwNi0xNDcuNTcgMC44NTgwNmMtNS4yNjg2IDAtNC41MTU1LTguMzI5OS03LjMwMDktOC4zMjk5LTMuOTc0NCAwLTguNjI5MiAwLjAyMDEtMTAuNTA5IDAuMDM1OS0yLjMzNDggMC4wMTk3LTEuODEwOSA4LjM2Ni00LjE0NTggOC4zNjY5LTQ2LjE2OSAwLjAxODgtMTY3LjQxLTEuMzA4LTE3NS4wNS0xLjMwOC00LjQyOTYgMC04LjU3NjMtNi40Mzk3LTEzLjEzMi02LjQzOTdoLTE0LjM5NSIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im02NzUuMDEgODMxLjE3LTAuNjA5NzgtNTIxLjc3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTc5OS40IDMxMy4wNiAxLjIxOTYgNDk1Ljg3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTczNi41OSAzMTIuNDUtMS4yMTk2IDcxNi40OSIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MzAuMDMgNjQzLjQ2IDM5Mi4zNy0zLjAxODIiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtODU5LjQ1IDMxNC45IDEuMjkzNSA1MDcuOTgiIGNvbG9yPSIjMDAwMDAwIi8+CiAgIDwvZz4KICAgPHBhdGggZD0ibTkyMS41NCAzMTAuNTkgMS43MjQ3IDUzMS43NSIgY29sb3I9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgIDxnIHN0cm9rZS13aWR0aD0iMXB4Ij4KICAgIDxwYXRoIGQ9Im03MzYuMjkgNDUzLjMxIDE4NS42OC0wLjMwNDg5IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTEwNjAuOCA1MTQuOTdzLTM2My4yOC01LjYyNjItNTQ0LjY1IDIuNTIxOGMtNC4xNzc4IDAuMTg3NjktMTIuNSAxLjA2NzEtMTIuNSAxLjA2NzEtMS41NzEgMC4xMzQxLTIuMDAwOS0yLjMyNS0yLjU5MTYtMy41MDYyLTAuMDk2Ny0wLjE5MzQzLTcuMDYwOC0xLjkzMzQtNy42MjIyLTEuMzcyLTIuODkzMSAyLjg5MzEtNy42MzE3IDQuMjQ4Ny0xMi4xOTYgNC4xMTZsLTExMi4wNS0zLjI1NzgiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMzk5LjgyIDQ3OS42MSAxMS42NDIgNS42MDUzYzIuOTg0MSAxLjQzNjggNi41Mjg4LTAuNDc3MTIgOS45MTcxLTAuNDMxMThsMTI3LjIgMS43MjQ3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTUxOS4yNSA1MTcuMTItMC40MzExOS0yMDguNjkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDMyLjkzIDM4OS43MWMxMS4wNDUgMCAzNS41MzMgMC42MTkyNyA0Mi41OC0xLjAwNCA4LjQwNTItMS45MzYyIDcuMDY2LTYuOTUzOCAxNC4xOTctNi45NTM4IDcuODA5NSAwIDYuNTQyOSA4LjA2MjQgMjAuMTQyIDguMDYyNCAxMy45OTEgMCA0NC45NzcgMC4zNzg4NiA2My45NCAwLjM3ODg2IDEyLjA4NCAwIDgyLjAwMyAwLjMwNDg5IDkzLjYwMSAwLjMwNDg5IDguNzYwNSAwIDEzLjE2LTIuMjg4MyAyMS4zNDItNy4wMTI0IDcuMTk1Mi00LjE1NDEgMi4wNTQ2LTkuNDkxNCAyMC40MjgtOC44NDE4IDIzLjE0NSAwLjgxODMzIDEyLjY0MyAxNC4wMjUgMzIuMzE4IDE0LjAyNWgxNTAuOTJjMTQuMzMyIDAtNC4xMTkxLTEzLjExIDI5LjI2OS0xMy40MTUiIGNvbG9yPSIjMDAwMDAwIi8+CiAgIDwvZz4KICA8L2c+CiAgPGcgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IlZlcmRhbmEiIGxldHRlci1zcGFjaW5nPSIwcHgiIHdvcmQtc3BhY2luZz0iMHB4Ij4KICAgPHRleHQgeD0iNTg4LjY3OTU3IiB5PSI3MzUuODA0NjMiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU4OC42Nzk1NyIgeT0iNzM1LjgwNDYzIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TGluY29sbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI2ODYuMzk4NSIgeT0iNzY1LjYyODQyIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI2ODYuMzk4NSIgeT0iNzY1LjYyODQyIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+SGFycnk8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI3MDkuODcxODMiIHk9Ii04MDIuMzc3MzgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjcwOS44NzE4MyIgeT0iLTgwMi4zNzczOCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldvb2RsYXduPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNTYyLjExOTI2IiB5PSItNzcxLjk2ODE0IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1NjIuMTE5MjYiIHk9Ii03NzEuOTY4MTQiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5FZGdlbW9vcjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU5OC4zMDQ4NyIgeT0iLTczOC4zNjY0NiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTk4LjMwNDg3IiB5PSItNzM4LjM2NjQ2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+T2xpdmVyPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNTkyLjEyMjg2IiB5PSItNjc3LjIwMzk4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1OTIuMTIyODYiIHk9Ii02NzcuMjAzOTgiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5IaWxsc2lkZTwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU5Ny4zMjcwOSIgeT0iLTg2Mi42MTQwNyIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTk3LjMyNzA5IiB5PSItODYyLjYxNDA3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Um9jazwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU4Ny4zNzAxOCIgeT0iLTkyNi4xMzY2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1ODcuMzcwMTgiIHk9Ii05MjYuMTM2NiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldlYmI8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iODcxLjE2MTAxIiB5PSI2MzcuNTc1MiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iODcxLjE2MTAxIiB5PSI2MzcuNTc1MiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPkNlbnRyYWw8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iODczLjgzMjI4IiB5PSI1NzcuMDMyNDciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijg3My44MzIyOCIgeT0iNTc3LjAzMjQ3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI4NzUuOTY2NDkiIHk9IjUxMC4yNjE4MSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iODc1Ljk2NjQ5IiB5PSI1MTAuMjYxODEiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij4yMXN0PC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9Ijg4MS4zMTY1OSIgeT0iNDUwLjE5ODc2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI4ODEuMzE2NTkiIHk9IjQ1MC4xOTg3NiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjI5dGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iNjE1Ljc5MjQ4IiB5PSIzODcuNzQ3MTYiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjYxNS43OTI0OCIgeT0iMzg3Ljc0NzE2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Mzd0aDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI0ODQuNjkwMzciIHk9IjQ4MS42NTI4NiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNDg0LjY5MDM3IiB5PSI0ODEuNjUyODYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij4yNXRoPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjU2My4wNDY3NSIgeT0iNTEzLjM2MTMzIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1NjMuMDQ2NzUiIHk9IjUxMy4zNjEzMyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjIxc3Q8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iNTY1Ljk3MTUiIHk9IjU3Ny44OTQ4NCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTY1Ljk3MTUiIHk9IjU3Ny44OTQ4NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI0MzMuNTgwNzUiIHk9Ii00NjAuNzMzMTIiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjQzMy41ODA3NSIgeT0iLTQ2MC43MzMxMiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPkFtaWRvbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjQwNS41MzA5OCIgeT0iLTUyMy41NDAxNiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNDA1LjUzMDk4IiB5PSItNTIzLjU0MDE2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+QXJrYW5zYXM8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI3NDUuNDg0NjIiIHk9Ii0zNzIuNTg1OTQiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijc0NS40ODQ2MiIgeT0iLTM3Mi41ODU5NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldlc3Q8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1OTYuNzI4MzMiIHk9Ii01MzEuMjU5MjgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU5Ni43MjgzMyIgeT0iLTUzMS4yNTkyOCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldhY288L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1OTUuNDM0ODEiIHk9Ii0xMjIuNTAyOTUiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU5NS40MzQ4MSIgeT0iLTEyMi41MDI5NSIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPk1hemllPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDQ1KSIgeD0iNjk1Ljc3Mjk1IiB5PSIxNjIuMDY4NzciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjY5NS43NzI5NSIgeT0iMTYyLjA2ODc3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Wm9vPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjI0MC41ODk5NyIgeT0iNTc0LjQ0NTQzIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIyNDAuNTg5OTciIHk9IjU3NC40NDU0MyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iMjA2LjAzMTc1IiB5PSI1MTEuNjM2NjMiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIwNi4wMzE3NSIgeT0iNTExLjYzNjYzIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+MjFzdDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjYyMC40NDMxMiIgeT0iLTUwNi42ODIxOSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNjIwLjQ0MzEyIiB5PSItNTA2LjY4MjE5IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TmltczwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSIzNzAuMjE2ODYiIHk9IjY5OC44NDAwOSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iMzcwLjIxNjg2IiB5PSI2OTguODQwMDkiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5NYXBsZTwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSIzODQuMDg0MiIgeT0iNjgwLjg1MTM4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIzODQuMDg0MiIgeT0iNjgwLjg1MTM4IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogIDwvZz4KICA8cGF0aCBkPSJtMzY3LjkxIDEwMTBoMjYzLjAyIiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDxnIGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBsZXR0ZXItc3BhY2luZz0iMHB4IiB3b3JkLXNwYWNpbmc9IjBweCI+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNzM2LjI2NzQ2IiB5PSItNDMzLjEzNzc2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI3MzYuMjY3NDYiIHk9Ii00MzMuMTM3NzYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5NZXJpZGlhbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI1NzIuODMyMTUiIHk9IjY0MC4yMDUyNiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTcyLjgzMjE1IiB5PSI2NDAuMjA1MjYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjU3NS4wODk2NiIgeT0iNjcwLjkwMzUiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU3NS4wODk2NiIgeT0iNjcwLjkwMzUiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5Eb3VnbGFzPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjQ5OS40ODk2MiIgeT0iMTAwOC42MDY5IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI0OTkuNDg5NjIiIHk9IjEwMDguNjA2OSIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjQ3dGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iMjE2LjY0NTQzIiB5PSI3MjUuOTgyOTciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIxNi42NDU0MyIgeT0iNzI1Ljk4Mjk3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+S2VsbG9nZzwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9Ijc3NC44NzU2MSIgeT0iLTUwOC4xODk3MyIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNzc0Ljg3NTYxIiB5PSItNTA4LjE4OTczIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TWNDbGVhbjwvdHNwYW4+PC90ZXh0PgogIDwvZz4KICA8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDI4Ny4zNikiIGQ9Im0zNjQuMTYgNjU4LjQzIDI5OS41MS0xLjAxMDJjNi40OTg3LTAuMDIxOSA2Ljk3NzIgOS4yNTQxIDE2LjU5NiA5LjM5MjUgMTIuMDU0IDAuMTczMzkgMjkuMTExLTAuNTM1NzIgNTQuMTE0LTAuMzAxMSIgY29sb3I9IiMwMDAwMDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzMzNiIgc3Ryb2tlLXdpZHRoPSIxcHgiLz4KICA8dGV4dCB4PSIzNzMuOTkzMDQiIHk9Ijk0NC4zNTc1NCIgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IlZlcmRhbmEiIGxldHRlci1zcGFjaW5nPSIwcHgiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIzNzMuOTkzMDQiIHk9Ijk0NC4zNTc1NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPk1hY0FydGh1cjwvdHNwYW4+PC90ZXh0PgogIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNzgwLjg0NjA3IiB5PSItNDkwLjI0NTk3IiBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0iVmVyZGFuYSIgbGV0dGVyLXNwYWNpbmc9IjBweCIgd29yZC1zcGFjaW5nPSIwcHgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijc4MC44NDYwNyIgeT0iLTQ5MC4yNDU5NyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPlNlbmVjYTwvdHNwYW4+PC90ZXh0PgogIDxwYXRoIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMjg3LjM2KSIgZD0ibTM2Ny43IDUzNy4yMSAxNDEuMjgtMS4wMTAyYzYuNDktMC4wNDY0IDEyLjc4MSA3LjIzNTQgMTkuMTkzIDcuMzIzNiA1NS45MjQgMC43Njg5IDE1OC42OS0wLjE3MzMzIDIzNi41MS0xLjAxMDIgNy44Mzk2LTAuMDg0MyAyMi42MzEtMTkuODU0IDMwLjMwNS0yMC40NTYgMjIuMjY2LTEuMzUxOCA0NS4xNzktMC41MDUwNyA2Ny42OC0wLjUwNTA3IDE2LjE0Ny0wLjYzMjQxIDMuNjEwMiAyMC43MDggMjYuNzY5IDIwLjcwOGwyNDMuNDUtMS4wMTAyIiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDx0ZXh0IHg9IjY4NS4yMDgxMyIgeT0iODI3LjUzMDgyIiBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0iVmVyZGFuYSIgbGV0dGVyLXNwYWNpbmc9IjBweCIgd29yZC1zcGFjaW5nPSIwcHgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjY4NS4yMDgxMyIgeT0iODI3LjUzMDgyIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+UGF3bmVlPC90c3Bhbj48L3RleHQ+CiAgPHBhdGggdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAyODcuMzYpIiBkPSJtNTU0LjI5IDcyMS40My00LjI4NTctMTc4LjIxLTIuODU3MS00NDAuNzEtMC4zNTcxNC03OS4yODYiIGNvbG9yPSIjMDAwMDAwIiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1MjkuNjI1MzEiIHk9Ii01NTAuODQ3NzgiIGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBsZXR0ZXItc3BhY2luZz0iMHB4IiB3b3JkLXNwYWNpbmc9IjBweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTI5LjYyNTMxIiB5PSItNTUwLjg0Nzc4IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+QnJvYWR3YXk8L3RzcGFuPjwvdGV4dD4KIDwvZz4KPC9zdmc+Cg==",
"public": true
- },
- {
- "link": "/api/images/system/map_marker_image_0.png",
- "title": "Map marker image 0",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "map_marker_image_0.png",
- "publicResourceKey": "CdCrVxsjA4EAiFaXK4a7K2MZFMeEuGeD",
- "mediaType": "image/png",
- "data": "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",
- "public": true
- },
- {
- "link": "/api/images/system/map_marker_image_1.png",
- "title": "Map marker image 1",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "map_marker_image_1.png",
- "publicResourceKey": "DF3fuPXua9Vi3o3d9Nz2I1LXDTwEs2Tv",
- "mediaType": "image/png",
- "data": "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",
- "public": true
- },
- {
- "link": "/api/images/system/map_marker_image_2.png",
- "title": "Map marker image 2",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "map_marker_image_2.png",
- "publicResourceKey": "rz5SFAw2Sg5T2EyXNdwLycoDwf4QbMiZ",
- "mediaType": "image/png",
- "data": "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",
- "public": true
- },
- {
- "link": "/api/images/system/map_marker_image_3.png",
- "title": "Map marker image 3",
- "type": "IMAGE",
- "subType": "IMAGE",
- "fileName": "map_marker_image_3.png",
- "publicResourceKey": "KfPfTuvKCeAnmTcKcrvZQHfdU0TPArWY",
- "mediaType": "image/png",
- "data": "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",
- "public": true
}
+ ],
+ "scada": false,
+ "tags": [
+ "plan",
+ "zone",
+ "parking",
+ "location",
+ "coordinates",
+ "indoor",
+ "image",
+ "marker",
+ "geofence",
+ "placement",
+ "polygon",
+ "circle",
+ "layer",
+ "tiles",
+ "building",
+ "interior",
+ "venue",
+ "inside",
+ "room",
+ "office",
+ "manufacturing",
+ "floor",
+ "plant",
+ "storage",
+ "warehouse",
+ "depot"
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/image_map_deprecated.json b/application/src/main/data/json/system/widget_types/image_map_deprecated.json
new file mode 100644
index 0000000000..23cf7f7a14
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/image_map_deprecated.json
@@ -0,0 +1,102 @@
+{
+ "fqn": "maps_v2.image_map",
+ "name": "Image Map",
+ "deprecated": true,
+ "image": "tb-image;/api/images/system/image_map_system_widget_image.png",
+ "description": "Displays the indoor or relative location of the entities on the image map. Useful to display floor maps, smart parking, etc. Entity coordinates are expected to be in the range from 0 to 1. Highly customizable via custom markers, marker tooltips, and widget actions. ",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 8.5,
+ "sizeY": 6.5,
+ "resources": [],
+ "templateHtml": "",
+ "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n",
+ "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-map-widget-settings-legacy",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"image-map\",\"mapImageUrl\":\"tb-image;/api/images/system/image_map_system_widget_map_image.svg\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See advanced settings for details\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7569\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"perimeter\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.2,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":3,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false},\"title\":\"Image Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
+ },
+ "tags": [
+ "building",
+ "interior",
+ "venue",
+ "inside",
+ "room",
+ "office",
+ "manufacturing",
+ "floor",
+ "plant",
+ "storage",
+ "warehouse",
+ "depot"
+ ],
+ "resources": [
+ {
+ "link": "/api/images/system/image_map_system_widget_image.png",
+ "title": "\"Image Map\" system widget image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "image_map_system_widget_image.png",
+ "publicResourceKey": "hDdSISQr6elribOYD6T3uePXZI5WvNtM",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/image_map_system_widget_map_image.svg",
+ "title": "\"Image Map\" system widget map image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "image_map_system_widget_map_image.svg",
+ "publicResourceKey": "QKRIYhDeBGwjaeIS601VvNLSsvZ25DRj",
+ "mediaType": "image/svg+xml",
+ "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTEzNC41IiBoZWlnaHQ9Ijc2Mi43OCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI3LjA3MSAtMzA3LjkpIj4KICA8ZyBmaWxsPSJub25lIj4KICAgPHBhdGggZD0ibTkwNi4wMyA3MDYuMTMgMy40MjkyIDE3Ljc5Nm0tODgwLjg5IDQxLjEyMWMxNTAuNDQgNi44MzM0IDE0Ni4zOS0yNi4zMzQgMTY2LjQzLTI5LjMyIDM2LjE0NC01LjM4NDggMTE0LjI5LTYuNTI1NCAxNDguMzMtOC42MjM1IDQzLjM3OC0yLjY3MzggMTQxLjc2LTExLjIzMSAxODguODYtMTkuODM0IDM5LjgxMS03LjI3MjggMjIxLjM3LTAuODYyMzUgMzE5LjA3LTAuODYyMzUgNzAuODI3IDAgMTQ2LjkyLTEuNzI0NyAyMTguMTgtMS43MjQ3LTMxLjYyIDAgMTE3Ljg2LTIuNTg3MSA4Ni4yMzYtMi41ODcxbS0yNS4wOTEtNjguMTI2Yy01Mi44IDM0Ljc4NS02NS44OTUgNTEuNzQ5LTk1LjYzOSA4MS40OTMtMjQuOTMxIDI0LjkzMS0xNDAuNC0xOS4xMzktMTc4Ljk0IDM2LjY1LTEyLjI4MSAxNy43NzctNDcuMDAzIDQ2LjU0Ny02NS4xMDggNTkuMDcxLTIwLjEwNSAxMy45MDgtNTYuMDM3IDQ0Ljk1Ny02Ny43NjkgNzMuMDc4LTQuODAxNSAxMS41MDktMTMuMzggMzUuOTkzLTIzLjQ0OSA0Ni4wNjItMTAuNDk3IDEwLjQ5Ny0zOC4zNzcgNi4zODU3LTQ0LjAyMyAxNy42NDgtMTkuMDA1IDM3LjkwOC0yNS40NjUgMTAwLjkyLTY3LjYxOCAxMDIuMDVtMTkuMjgyLTYyNC4wMWMzNC42NTktMS44NzM4IDg0LjAyNyA3LjM5MTMgMTA5LjktNC4yODU0IDEzLjI4Mi01Ljk5NDEgNDEuNDA3LTIuNDYxNCA2Ni44MjktMi4zMjA1IDM1LjMyMiAwLjE5NTc4IDY0LjM4MiAwLjYzNDc3IDEwMS45MiA1LjAyMzIgMjUuMDMgMi45MjY1IDQ0LjY2MyAzNC4yODcgNTguNTI3IDUwLjY0NCAxNy4wOTkgMjAuMTczIDYyLjc2NC0xLjcxNDcgNjYuMzA2IDMyLjEzNCA1LjEwMjcgNDguNzY2LTYuMzI4NCA3OC42MzcgNi4xNDExIDk3LjM0MiAxOS45NjkgMjkuOTU0IDUwLjQ4NiAxNy44NTYgNDQuNjE5IDgzLjk3MW0tNDcyLjQ1LTM3OC43OWM0LjY0MzUgMjMuNzI5IDE1LjA2OSA3Mi43NzYgMTkuMDYxIDEzMC42NCAwLjg3MjA2IDEyLjY0IDUuNDQ3MiAyNC45OTMgNC4yMjIzIDQ1LjI3OC0yLjUxNzIgNDEuNjg4LTE1LjcxNyA0My42NzctMTUuMDkxIDYwLjM2NSAxLjQzMiAzOC4xODIgMzAuNjE0IDkzLjgzNyAzMC42MTQgMTM5LjcgMCAyNC4xODEtMi42Njk2IDExNS4zOSA3LjMzIDEzNS4zOSAwLjE1OTExIDAuMzE4MjEgMTAuMDY1IDM1Ljg4MyAxMC43NzkgNDkuMTU0IDAuOTQzNzggMTcuNTI1LTI0LjQ3OCAzOS40Ny0yOC4wMjcgNDYuNTY3LTUuNDc3NyAxMC45NTUtMzYuOTczIDEwLjg4Mi00MC4xIDI0LjE0Ni0zLjg2ODggMTYuNDE1LTMuODY2MyA0My43OTcgNC4wNDY1IDU5LjQ0MW05Ny4zMzctNjkxLjAxYy01LjAxMzMgMzUuNTE2LTQzLjY1OSAxMS4zMTctNTguNTM5IDIzLjc4MS0yMS4zMyAxNy44NjktNjIuNSAzMS40MzItNzAuMTI0IDM1LjM2Ny0zNS4wODggMTguMTA4LTExMC40Ny0xNS4xNDItMTI1LjYxIDQuMjY4NC0xNS45NTEgMjAuNDQ3LTAuMDczNSA2MS40NjYtOS4xNDY3IDg0LjE0OS02LjAzNTcgMTUuMDg5LTE4Ljg3NyAyMy4wMTctMjcuNDQgMzIuOTI4LTE5Ljc0OCAyMi44NTYtNjkuOTc0IDY5LjgyNC04NC43NTkgMTAwLTcuNDk3NCAxNS4zMDQtMy4yODQzIDQ0LjQyLTMuNDcwNSA2My4zNDMtMC4xMjc5MyAxMi45OTQtMC44MTAxNSAyMy4xMDQgMi40MDM0IDI4LjI3NiA0Ljk2MTYgNy45ODU4IDIzLjcyIDI4LjExMiAyNC4yMzkgNTAuNjExIDAuMjk0MTEgMTIuNzcxIDAuMDEzMyA3OC41OTEgMy4wNDg5IDg3LjY1NSAyLjMxMjYgNi45MDU1IDQuMjIgMjYuNTY1IDEwLjIxNCAzNi41ODcgMTEuMzU0IDE4Ljk4NCA0LjM4NzQgNDAuMTU3IDI3Ljg5NyA1My41MDggMTkuMDUgMTAuODE5IDQ2Ljg3OCAxMi4yMTkgODEuOTI2IDE0LjQ2MSAzMy43MDMgMi4xNTU5IDYxLjUxMi0xLjQzMDQgNzYuOTIxIDYuMTQxMSAxMS41ODUgNS42OTI3IDguNTgxNSAxNy45MzMgMTQuMjk1IDI5LjM2MSA1LjY0MDQgMTEuMjgxIDMxLjUwMyAxMS4xNTYgNDEuODA0IDQzLjQ1NSA3LjYwNTkgMjMuODQ3IDMuMDg1OSA0NC4xNTcgNi43MDc2IDY1Ljg4NyIgc3Ryb2tlPSIjMzY0ZTU5IiBzdHJva2Utd2lkdGg9IjMiLz4KICAgPHBhdGggZD0ibTQzLjI3OCA1MTcuOTVzMjMwLjg1LTMuNjM4IDI1MC4wMS0zLjY1ODdjNy40ODIyLThlLTMgOC42MTk1IDUuMTUxOSAxNC4wMjEgMTEuNDU5IDI0LjU5NiAyOC43MTkgOTMuOTEgMTEyLjk0IDkzLjkxIDExMi45NCIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogICA8cGF0aCBkPSJtMzUuOTYxIDU3Ny43czE2NS41Mi0xLjY4NDUgMjQ4Ljc4LTEuNjg0NWM0Ljk0NzUgMCA3LjcyOTktMi44ODMzIDEwLjUzOC01LjcyOTggOS42NjExLTkuNzk0MiAyNS42MzItMjguNTkgMjUuNjMyLTI4LjU5IiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPC9nPgogIDxwYXRoIGQ9Im0zOC40IDY0MS43MyAzOTMuMzEtNC4yNjg0IiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0iIzMzNiIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDxwYXRoIGQ9Im0zOS4wMDkgNzA0LjU0IDQ4NC4xNi02LjcwNzYiIGNvbG9yPSIjMDAwMDAwIiBmaWxsPSIjMzM2IiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2Ij4KICAgPGcgc3Ryb2tlLXdpZHRoPSIxcHgiPgogICAgPHBhdGggZD0ibTMwMy45NiA2ODIuNTkgMTQ2LjggMS44MjkzYzEwLjUzNCAwLjEzMTI3IDE0LjM0NC0yLjYzNzQgMjUuNDg3LTYuMzcyOCAxMC40MTItMy40OTAzIDMxLjQyNC0yLjY5OSA0MS4zODUtMi43NzM4bDQwNS41Ni0zLjA0ODkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDI2LjIyIDMxNC44OWMyLjA2NzUgOS4wNTI3IDEuODQxOCA1MS43MjggNi41MDc5IDc0LjgzNSAxLjY3NDggOC4yOTM0IDguNjc1MSAxNC4wNjYgMTAuMDU1IDE0Ljg1OSA0LjkwMTUgMi44MTQ2IDEwLjgxNSA4LjE0OTggMTMuMDQ2IDE2LjA4OCA2Ljc1NzggMjQuMDQ2IDAuODc5NzIgNjguNDUyIDAuODc5NzIgMTEwLjY5IDAgNi4wOTc4IDEuNjYwMSAzMC4xNDctMi4xNTU5IDMzLjk2My0yLjU0MDggMi41NDA4LTAuMjgxNjMgMTIuOTkxLTMuNDM2OCAxNi4xNDRsLTkuODQ5NCA5Ljg0MzFjLTEwLjM2NyAxMC4zNi0xMS41OSA2LjUyNjEtMTcuNzM4IDE4LjgyMy0zLjU2NzcgNy4xMzU0IDUuNDAyNCAyMC42NzIgNy4zNTQzIDI0LjU3NiAxLjkzMjEgMy44NjQzLTEuODQyMiA0Ljc3NzctMS43OTI0IDcuNDQ2MyAwLjI1Mjg2IDEzLjU0NSAyLjI5NzUgMzczLjkzIDIuMjk3NSAzNzMuOTMiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMzY1LjI0IDUxOS43OCA0LjExNiA1MDIuMTUiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMTE2LjUzIDUwNC4xOSAzLjg4MDYgMzEwLjk2IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTMxNy42OCA1NzYuNDkgMTMwLjE5IDEuNTI0NGM0LjUxMDggMy4yNDE3IDIwLjM0NSA3Ljk2ODUgMjcuNzQ1IDQuMjY4NCAzLjE1NTUtMS41Nzc3IDkuNDE5LTUuMzg4MiAxNC4wMjUtMy45NjM2IDQuMjY3IDEuMzE5OCA2LjAxNjkgMy4xMTYzIDEwLjM2NiAzLjA0ODkgMTAuMzA0LTAuMTU5NzUgMjAuMjEyIDAuMzg3NDEgMzAuNDg5IDAuMzA0ODkgMTc3Ljg5LTEuNDI4MyAzNTYuNTktMi4xMzI1IDUzNC43Ny0zLjA0ODkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDc1LjMxIDU4Mi44OWMtMy40NDQyIDExLjM1MS0yLjEwMzQgMTIuNDM0IDMuNjU4NiAyMS4wMzcgMy43OTQ0IDUuNjY1NiA1MC44NjMgMTMuMDM4IDQxLjQ2NSAyNy4xMzUtMTAuNTM3IDE1LjgwNS0yMi44OTctNS40Nzc3LTMzLjg0My0xLjgyOTMtNS40NTI0IDEuODE3NC03LjM0OSA1LjQ1NjMtMy42NTg3IDkuMTQ2NiAyLjgwNjggMi44MDY4IDQuMDQ4IDEuODA0IDYuNTIwMyA1LjEwMDQiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDMyLjAxIDYzNi44NWM4LjMxOSAxMy4xMSAxOC44NDYgMTQuNjM1IDM1LjY3MiAxNC42MzUgMi45Mzg2IDAgNy44Ny0wLjkzMzcxIDEwLjY3MSAwIDExLjM1OSAzLjc4NjQgMjcuMTk0IDEwLjI3NiAzNi4yMDIgMjEuMTI5IDguMjggOS45NzY2IDEwLjI1MyAyMy44ODMgNy43MDIgMzcuMTA0LTYuMTY5OSAzMS45OC0xNi43MTQgNTYuOTg5LTE5LjA0NCA4Ni41NjktMS4zNDggMTcuMTE5IDQuNTA5NiAyMi41MzUgMTEuMDcxIDMzLjkyOSAxMC42NyAxOC41MjcgOC43MjQ1IDE0LjIgOC41NzE0IDM0LjI4Ni0wLjEzOTYzIDE4LjMxOSAwIDYwLjI2NCAwIDgwLjcxNCIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MjguNTEgNjU4Ljk2Yy0xMC42ODEgMC45MDQ1NC03LjEwOC01LjYwMjYtMTAuODI0LTguMDc5Ni00Ljc4NDUtMy4xODk3LTEyLjIyNy0xLjI1MS0xNi43NjktNS43OTI5LTAuNjY2MTItMC42NjYxMi04LjgwOTctNC4xMDg4LTEwLjE3NC0yLjc0NC04LjM2NDYgOC4zNjQ2LTMuMDQ4OSAyMC41NTItMy4wNDg5IDMzLjUzOGwzLjAyMiAzMzkuNyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MTcuOTkgNjUxLjAzYy0wLjIyMTcxLTIuNzAxOCAxLjkwMzUtNS41NjIxIDMuMzUzOC03LjAxMjQgMS43OTk0LTEuNzk5NCA2LjkyMjkgMS4wMDQyIDguODQxOC0wLjkxNDY2IDAuMjg3NjUtMC4yODc2NiAwLjg0MzI5LTExLjE2NCAwLjIyODY2LTEzLjU2OC0yLjA2NDgtOC4wNzQyLTIuMDU4LTI4LjY1Ny0yLjA1OC0zOC43MjF2LTczLjE3MyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MjguNjYgNjc1LjQyLTAuNDU3MzMtMzEuNTU2IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTc2Ni4zMiA1NzkuNjQgMC40MzExOCAxMy43OThjMy4xMzY0IDQuNjY5MiAzLjAxODIgOS42MDA3IDMuMDE4MiAxNi4zODV2MTU3LjM4IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTExMjIuOSA3NjUuOTFjLTIwMi4zMSA0LjY5MDUtNDAzLjc0LTEuMTEzOC02MDUuOTUgMy4zNTM5LTEwLjg2NCAwLjI0MDAyLTMuMzYxNS04LjU4NjMtMjguNTM3LTguNTg2MyIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im04NjAuMDEgNzM3LjA3cy05Ny40NDggMC44NTgwNi0xNDcuNTcgMC44NTgwNmMtNS4yNjg2IDAtNC41MTU1LTguMzI5OS03LjMwMDktOC4zMjk5LTMuOTc0NCAwLTguNjI5MiAwLjAyMDEtMTAuNTA5IDAuMDM1OS0yLjMzNDggMC4wMTk3LTEuODEwOSA4LjM2Ni00LjE0NTggOC4zNjY5LTQ2LjE2OSAwLjAxODgtMTY3LjQxLTEuMzA4LTE3NS4wNS0xLjMwOC00LjQyOTYgMC04LjU3NjMtNi40Mzk3LTEzLjEzMi02LjQzOTdoLTE0LjM5NSIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im02NzUuMDEgODMxLjE3LTAuNjA5NzgtNTIxLjc3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTc5OS40IDMxMy4wNiAxLjIxOTYgNDk1Ljg3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTczNi41OSAzMTIuNDUtMS4yMTk2IDcxNi40OSIgY29sb3I9IiMwMDAwMDAiLz4KICAgIDxwYXRoIGQ9Im01MzAuMDMgNjQzLjQ2IDM5Mi4zNy0zLjAxODIiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtODU5LjQ1IDMxNC45IDEuMjkzNSA1MDcuOTgiIGNvbG9yPSIjMDAwMDAwIi8+CiAgIDwvZz4KICAgPHBhdGggZD0ibTkyMS41NCAzMTAuNTkgMS43MjQ3IDUzMS43NSIgY29sb3I9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgIDxnIHN0cm9rZS13aWR0aD0iMXB4Ij4KICAgIDxwYXRoIGQ9Im03MzYuMjkgNDUzLjMxIDE4NS42OC0wLjMwNDg5IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTEwNjAuOCA1MTQuOTdzLTM2My4yOC01LjYyNjItNTQ0LjY1IDIuNTIxOGMtNC4xNzc4IDAuMTg3NjktMTIuNSAxLjA2NzEtMTIuNSAxLjA2NzEtMS41NzEgMC4xMzQxLTIuMDAwOS0yLjMyNS0yLjU5MTYtMy41MDYyLTAuMDk2Ny0wLjE5MzQzLTcuMDYwOC0xLjkzMzQtNy42MjIyLTEuMzcyLTIuODkzMSAyLjg5MzEtNy42MzE3IDQuMjQ4Ny0xMi4xOTYgNC4xMTZsLTExMi4wNS0zLjI1NzgiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtMzk5LjgyIDQ3OS42MSAxMS42NDIgNS42MDUzYzIuOTg0MSAxLjQzNjggNi41Mjg4LTAuNDc3MTIgOS45MTcxLTAuNDMxMThsMTI3LjIgMS43MjQ3IiBjb2xvcj0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0ibTUxOS4yNSA1MTcuMTItMC40MzExOS0yMDguNjkiIGNvbG9yPSIjMDAwMDAwIi8+CiAgICA8cGF0aCBkPSJtNDMyLjkzIDM4OS43MWMxMS4wNDUgMCAzNS41MzMgMC42MTkyNyA0Mi41OC0xLjAwNCA4LjQwNTItMS45MzYyIDcuMDY2LTYuOTUzOCAxNC4xOTctNi45NTM4IDcuODA5NSAwIDYuNTQyOSA4LjA2MjQgMjAuMTQyIDguMDYyNCAxMy45OTEgMCA0NC45NzcgMC4zNzg4NiA2My45NCAwLjM3ODg2IDEyLjA4NCAwIDgyLjAwMyAwLjMwNDg5IDkzLjYwMSAwLjMwNDg5IDguNzYwNSAwIDEzLjE2LTIuMjg4MyAyMS4zNDItNy4wMTI0IDcuMTk1Mi00LjE1NDEgMi4wNTQ2LTkuNDkxNCAyMC40MjgtOC44NDE4IDIzLjE0NSAwLjgxODMzIDEyLjY0MyAxNC4wMjUgMzIuMzE4IDE0LjAyNWgxNTAuOTJjMTQuMzMyIDAtNC4xMTkxLTEzLjExIDI5LjI2OS0xMy40MTUiIGNvbG9yPSIjMDAwMDAwIi8+CiAgIDwvZz4KICA8L2c+CiAgPGcgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IlZlcmRhbmEiIGxldHRlci1zcGFjaW5nPSIwcHgiIHdvcmQtc3BhY2luZz0iMHB4Ij4KICAgPHRleHQgeD0iNTg4LjY3OTU3IiB5PSI3MzUuODA0NjMiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU4OC42Nzk1NyIgeT0iNzM1LjgwNDYzIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TGluY29sbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI2ODYuMzk4NSIgeT0iNzY1LjYyODQyIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI2ODYuMzk4NSIgeT0iNzY1LjYyODQyIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+SGFycnk8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI3MDkuODcxODMiIHk9Ii04MDIuMzc3MzgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjcwOS44NzE4MyIgeT0iLTgwMi4zNzczOCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldvb2RsYXduPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNTYyLjExOTI2IiB5PSItNzcxLjk2ODE0IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1NjIuMTE5MjYiIHk9Ii03NzEuOTY4MTQiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5FZGdlbW9vcjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU5OC4zMDQ4NyIgeT0iLTczOC4zNjY0NiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTk4LjMwNDg3IiB5PSItNzM4LjM2NjQ2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+T2xpdmVyPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNTkyLjEyMjg2IiB5PSItNjc3LjIwMzk4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1OTIuMTIyODYiIHk9Ii02NzcuMjAzOTgiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5IaWxsc2lkZTwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU5Ny4zMjcwOSIgeT0iLTg2Mi42MTQwNyIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTk3LjMyNzA5IiB5PSItODYyLjYxNDA3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Um9jazwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjU4Ny4zNzAxOCIgeT0iLTkyNi4xMzY2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1ODcuMzcwMTgiIHk9Ii05MjYuMTM2NiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldlYmI8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iODcxLjE2MTAxIiB5PSI2MzcuNTc1MiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iODcxLjE2MTAxIiB5PSI2MzcuNTc1MiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPkNlbnRyYWw8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iODczLjgzMjI4IiB5PSI1NzcuMDMyNDciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijg3My44MzIyOCIgeT0iNTc3LjAzMjQ3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI4NzUuOTY2NDkiIHk9IjUxMC4yNjE4MSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iODc1Ljk2NjQ5IiB5PSI1MTAuMjYxODEiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij4yMXN0PC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9Ijg4MS4zMTY1OSIgeT0iNDUwLjE5ODc2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI4ODEuMzE2NTkiIHk9IjQ1MC4xOTg3NiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjI5dGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iNjE1Ljc5MjQ4IiB5PSIzODcuNzQ3MTYiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjYxNS43OTI0OCIgeT0iMzg3Ljc0NzE2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Mzd0aDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI0ODQuNjkwMzciIHk9IjQ4MS42NTI4NiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNDg0LjY5MDM3IiB5PSI0ODEuNjUyODYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij4yNXRoPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjU2My4wNDY3NSIgeT0iNTEzLjM2MTMzIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI1NjMuMDQ2NzUiIHk9IjUxMy4zNjEzMyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjIxc3Q8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iNTY1Ljk3MTUiIHk9IjU3Ny44OTQ4NCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTY1Ljk3MTUiIHk9IjU3Ny44OTQ4NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI0MzMuNTgwNzUiIHk9Ii00NjAuNzMzMTIiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjQzMy41ODA3NSIgeT0iLTQ2MC43MzMxMiIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPkFtaWRvbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjQwNS41MzA5OCIgeT0iLTUyMy41NDAxNiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNDA1LjUzMDk4IiB5PSItNTIzLjU0MDE2IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+QXJrYW5zYXM8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI3NDUuNDg0NjIiIHk9Ii0zNzIuNTg1OTQiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijc0NS40ODQ2MiIgeT0iLTM3Mi41ODU5NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldlc3Q8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1OTYuNzI4MzMiIHk9Ii01MzEuMjU5MjgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU5Ni43MjgzMyIgeT0iLTUzMS4yNTkyOCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPldhY288L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1OTUuNDM0ODEiIHk9Ii0xMjIuNTAyOTUiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU5NS40MzQ4MSIgeT0iLTEyMi41MDI5NSIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPk1hemllPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDQ1KSIgeD0iNjk1Ljc3Mjk1IiB5PSIxNjIuMDY4NzciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjY5NS43NzI5NSIgeT0iMTYyLjA2ODc3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+Wm9vPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjI0MC41ODk5NyIgeT0iNTc0LjQ0NTQzIiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIyNDAuNTg5OTciIHk9IjU3NC40NDU0MyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iMjA2LjAzMTc1IiB5PSI1MTEuNjM2NjMiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIwNi4wMzE3NSIgeT0iNTExLjYzNjYzIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+MjFzdDwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9IjYyMC40NDMxMiIgeT0iLTUwNi42ODIxOSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNjIwLjQ0MzEyIiB5PSItNTA2LjY4MjE5IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TmltczwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSIzNzAuMjE2ODYiIHk9IjY5OC44NDAwOSIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iMzcwLjIxNjg2IiB5PSI2OTguODQwMDkiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5NYXBsZTwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSIzODQuMDg0MiIgeT0iNjgwLjg1MTM4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIzODQuMDg0MiIgeT0iNjgwLjg1MTM4IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogIDwvZz4KICA8cGF0aCBkPSJtMzY3LjkxIDEwMTBoMjYzLjAyIiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDxnIGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBsZXR0ZXItc3BhY2luZz0iMHB4IiB3b3JkLXNwYWNpbmc9IjBweCI+CiAgIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNzM2LjI2NzQ2IiB5PSItNDMzLjEzNzc2IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI3MzYuMjY3NDYiIHk9Ii00MzMuMTM3NzYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5NZXJpZGlhbjwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB4PSI1NzIuODMyMTUiIHk9IjY0MC4yMDUyNiIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTcyLjgzMjE1IiB5PSI2NDAuMjA1MjYiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjU3NS4wODk2NiIgeT0iNjcwLjkwMzUiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjU3NS4wODk2NiIgeT0iNjcwLjkwMzUiIGZvbnQtc2l6ZT0iOS42NTg0cHgiIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Ij5Eb3VnbGFzPC90c3Bhbj48L3RleHQ+CiAgIDx0ZXh0IHg9IjQ5OS40ODk2MiIgeT0iMTAwOC42MDY5IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSI0OTkuNDg5NjIiIHk9IjEwMDguNjA2OSIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPjQ3dGg8L3RzcGFuPjwvdGV4dD4KICAgPHRleHQgeD0iMjE2LjY0NTQzIiB5PSI3MjUuOTgyOTciIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIxNi42NDU0MyIgeT0iNzI1Ljk4Mjk3IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+S2VsbG9nZzwvdHNwYW4+PC90ZXh0PgogICA8dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSg5MCkiIHg9Ijc3NC44NzU2MSIgeT0iLTUwOC4xODk3MyIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNzc0Ljg3NTYxIiB5PSItNTA4LjE4OTczIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+TWNDbGVhbjwvdHNwYW4+PC90ZXh0PgogIDwvZz4KICA8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDI4Ny4zNikiIGQ9Im0zNjQuMTYgNjU4LjQzIDI5OS41MS0xLjAxMDJjNi40OTg3LTAuMDIxOSA2Ljk3NzIgOS4yNTQxIDE2LjU5NiA5LjM5MjUgMTIuMDU0IDAuMTczMzkgMjkuMTExLTAuNTM1NzIgNTQuMTE0LTAuMzAxMSIgY29sb3I9IiMwMDAwMDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzMzNiIgc3Ryb2tlLXdpZHRoPSIxcHgiLz4KICA8dGV4dCB4PSIzNzMuOTkzMDQiIHk9Ijk0NC4zNTc1NCIgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IlZlcmRhbmEiIGxldHRlci1zcGFjaW5nPSIwcHgiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MCUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIzNzMuOTkzMDQiIHk9Ijk0NC4zNTc1NCIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPk1hY0FydGh1cjwvdHNwYW4+PC90ZXh0PgogIDx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDkwKSIgeD0iNzgwLjg0NjA3IiB5PSItNDkwLjI0NTk3IiBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0iVmVyZGFuYSIgbGV0dGVyLXNwYWNpbmc9IjBweCIgd29yZC1zcGFjaW5nPSIwcHgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9Ijc4MC44NDYwNyIgeT0iLTQ5MC4yNDU5NyIgZm9udC1zaXplPSI5LjY1ODRweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjEuMjUiPlNlbmVjYTwvdHNwYW4+PC90ZXh0PgogIDxwYXRoIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMjg3LjM2KSIgZD0ibTM2Ny43IDUzNy4yMSAxNDEuMjgtMS4wMTAyYzYuNDktMC4wNDY0IDEyLjc4MSA3LjIzNTQgMTkuMTkzIDcuMzIzNiA1NS45MjQgMC43Njg5IDE1OC42OS0wLjE3MzMzIDIzNi41MS0xLjAxMDIgNy44Mzk2LTAuMDg0MyAyMi42MzEtMTkuODU0IDMwLjMwNS0yMC40NTYgMjIuMjY2LTEuMzUxOCA0NS4xNzktMC41MDUwNyA2Ny42OC0wLjUwNTA3IDE2LjE0Ny0wLjYzMjQxIDMuNjEwMiAyMC43MDggMjYuNzY5IDIwLjcwOGwyNDMuNDUtMS4wMTAyIiBjb2xvcj0iIzAwMDAwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzM2IiBzdHJva2Utd2lkdGg9IjFweCIvPgogIDx0ZXh0IHg9IjY4NS4yMDgxMyIgeT0iODI3LjUzMDgyIiBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0iVmVyZGFuYSIgbGV0dGVyLXNwYWNpbmc9IjBweCIgd29yZC1zcGFjaW5nPSIwcHgiIHN0eWxlPSJsaW5lLWhlaWdodDowJSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjY4NS4yMDgxMyIgeT0iODI3LjUzMDgyIiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+UGF3bmVlPC90c3Bhbj48L3RleHQ+CiAgPHBhdGggdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAyODcuMzYpIiBkPSJtNTU0LjI5IDcyMS40My00LjI4NTctMTc4LjIxLTIuODU3MS00NDAuNzEtMC4zNTcxNC03OS4yODYiIGNvbG9yPSIjMDAwMDAwIiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzYiIHN0cm9rZS13aWR0aD0iMXB4Ii8+CiAgPHRleHQgdHJhbnNmb3JtPSJyb3RhdGUoOTApIiB4PSI1MjkuNjI1MzEiIHk9Ii01NTAuODQ3NzgiIGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBsZXR0ZXItc3BhY2luZz0iMHB4IiB3b3JkLXNwYWNpbmc9IjBweCIgc3R5bGU9ImxpbmUtaGVpZ2h0OjAlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4geD0iNTI5LjYyNTMxIiB5PSItNTUwLjg0Nzc4IiBmb250LXNpemU9IjkuNjU4NHB4IiBzdHlsZT0ibGluZS1oZWlnaHQ6MS4yNSI+QnJvYWR3YXk8L3RzcGFuPjwvdGV4dD4KIDwvZz4KPC9zdmc+Cg==",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_0.png",
+ "title": "Map marker image 0",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_0.png",
+ "publicResourceKey": "CdCrVxsjA4EAiFaXK4a7K2MZFMeEuGeD",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_1.png",
+ "title": "Map marker image 1",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_1.png",
+ "publicResourceKey": "DF3fuPXua9Vi3o3d9Nz2I1LXDTwEs2Tv",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_2.png",
+ "title": "Map marker image 2",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_2.png",
+ "publicResourceKey": "rz5SFAw2Sg5T2EyXNdwLycoDwf4QbMiZ",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_3.png",
+ "title": "Map marker image 3",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_3.png",
+ "publicResourceKey": "KfPfTuvKCeAnmTcKcrvZQHfdU0TPArWY",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/map.json b/application/src/main/data/json/system/widget_types/map.json
new file mode 100644
index 0000000000..b9069d8af6
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/map.json
@@ -0,0 +1,56 @@
+{
+ "fqn": "map",
+ "name": "Map",
+ "deprecated": false,
+ "image": "tb-image;/api/images/system/map-widget.png",
+ "description": "Displays the location of the entities on Map. Allows to choose among existing tile providers or configure own server. Supports markers, marker tooltips, widget actions, polygons, and circles for enhanced spatial representation.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
+ "settingsForm": [],
+ "dataKeySettingsForm": [],
+ "settingsDirective": "tb-map-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-map-basic-config",
+ "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"mapType\":\"geoMap\",\"markers\":[{\"dsType\":\"function\",\"dsLabel\":\"First point\",\"dsDeviceId\":null,\"dsEntityAliasId\":null,\"dsFilterId\":null,\"additionalDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8239425680406081,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See tooltip settings for details\",\"offsetX\":0,\"offsetY\":-1},\"groups\":null,\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 500 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\"},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 500 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\"},\"markerType\":\"shape\",\"markerShape\":{\"shape\":\"markerShape1\",\"size\":34,\"color\":{\"type\":\"function\",\"color\":\"#307FE5\",\"colorFunction\":\"var temperature = data.temperature;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\\n\"}},\"markerIcon\":{\"icon\":\"mdi:lightbulb-on\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerImage\":{\"type\":\"image\",\"image\":\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xOTEuMzUgLTM1MS4xOCAxMDgzLjU4IDE3MzAuNDYiPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjZmU3NTY5IiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMzciIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgZD0iTTM1MS44MzMgMTM2MC43OGMtMzguNzY2LTE5MC4zLTEwNy4xMTYtMzQ4LjY2NS0xODkuOTAzLTQ5NS40NEMxMDAuNTIzIDc1Ni40NjkgMjkuMzg2IDY1NS45NzgtMzYuNDM0IDU1MC40MDRjLTIxLjk3Mi0zNS4yNDQtNDAuOTM0LTcyLjQ3Ny02Mi4wNDctMTA5LjA1NC00Mi4yMTYtNzMuMTM3LTc2LjQ0NC0xNTcuOTM1LTc0LjI2OS0yNjcuOTMyIDIuMTI1LTEwNy40NzMgMzMuMjA4LTE5My42ODUgNzguMDMtMjY0LjE3M0MtMjEtMjA2LjY5IDEwMi40ODEtMzAxLjc0NSAyNjguMTY0LTMyNi43MjRjMTM1LjQ2Ni0yMC40MjUgMjYyLjQ3NSAxNC4wODIgMzUyLjU0MyA2Ni43NDcgNzMuNiA0My4wMzggMTMwLjU5NiAxMDAuNTI4IDE3My45MiAxNjguMjggNDUuMjIgNzAuNzE2IDc2LjM2IDE1NC4yNiA3OC45NzEgMjYzLjIzMyAxLjMzNyA1NS44My03LjgwNSAxMDcuNTMyLTIwLjY4NCAxNTAuNDE3LTEzLjAzNCA0My40MS0zMy45OTYgNzkuNjk1LTUyLjY0NiAxMTguNDU1LTM2LjQwNiA3NS42NTktODIuMDQ5IDE0NC45ODEtMTI3Ljg1NSAyMTQuMzQ1LTEzNi40MzcgMjA2LjYwNi0yNjQuNDk2IDQxNy4zMS0zMjAuNTggNzA2LjAyOHoiLz48Y2lyY2xlIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBjeD0iMzUyLjg5MSIgY3k9IjIyNS43NzkiIHI9IjE4My4zMzIiLz48L3N2Zz4=\",\"imageSize\":34},\"markerOffsetX\":0.5,\"markerOffsetY\":1},{\"dsType\":\"function\",\"dsLabel\":\"Second point\",\"dsDeviceId\":null,\"dsEntityAliasId\":null,\"dsFilterId\":null,\"additionalDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7826299113906372,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See tooltip settings for details\",\"offsetX\":0,\"offsetY\":-1},\"click\":{\"type\":\"doNothing\"},\"groups\":null,\"edit\":{\"enabledActions\":[],\"snappable\":false},\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 500 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 500 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"markerType\":\"icon\",\"markerShape\":{\"shape\":\"markerShape1\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerIcon\":{\"size\":40,\"color\":{\"type\":\"function\",\"color\":\"#307FE5\",\"colorFunction\":\"var colors = ['#488bc7','#549c5d','#ed7546','#be2b29'];\\nvar temperature = data.temperature;\\nvar res = colors[0];\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120;\\n var index = Math.min(3, Math.floor(4 * percent));\\n res = colors[index];\\n}\\nreturn res;\"},\"icon\":\"thermostat\"},\"markerImage\":{\"type\":\"function\",\"image\":\"/assets/markers/shape1.svg\",\"imageSize\":34,\"imageFunction\":\"\\n\",\"images\":[]},\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"markerClustering\":{\"enable\":false,\"zoomOnClick\":true,\"maxZoom\":null,\"maxClusterRadius\":80,\"zoomAnimation\":true,\"showCoverageOnHover\":true,\"spiderfyOnMaxZoom\":false,\"chunkedLoad\":false,\"lazyLoad\":true,\"useClusterMarkerColorFunction\":false,\"clusterMarkerColorFunction\":null}}],\"polygons\":[],\"circles\":[],\"additionalDataSources\":[]},\"title\":\"Map\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\",\"titleFont\":null,\"titleColor\":null,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\",\"titleIcon\":\"map\",\"iconColor\":\"#1F6BDD\",\"actions\":{}}"
+ },
+ "resources": [
+ {
+ "link": "/api/images/system/map-widget.png",
+ "title": "\"Map\" system widget image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map-widget.png",
+ "publicResourceKey": "scAsnySDiQSGXiKpt69cZ9jxZh0zl3eL",
+ "mediaType": "image/png",
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAADAFBMVEXy6uYAAADy7+ny0q/r477y8uvy7enyuaHx7ujo5+Ly7Ozy7+nX78Dx893////w7ue/5bbe4bLT37y92rHZ2dnu7eXs6+Xy8t/Ozs7E5rrm5N7W087S4crb29rs79zl6dwJniTe3dzo6t/P38fM3sXq7OLa7sTV4s3Y49Db5dLy8OXp6OPh59fe5tXz3LfU7L/D2rvW6sXJ3MLd3dTW1tPm79XG5r7n5eHr8djp6tri6dr/mAfi4d7i79DN6MPf39ju7uLC27Tq7t7R28nW4cS21arFxcHk5dnZ6snG2r/J58DKycjc78e31LLY29HV5b+3uLOlzpjO3rnn7tvS09Le6tLe7szQ6caw1KO92Leq0aLM6rsoacrX3q3I3be62K6eypHAwLuXmJfi49b0xbGu0Kns7evy7dra39LZ6s6r0Jvz5s6dy5nt16/B1qvh7Nbz4cLQ38Lh6eXX28vP18XPz8Di4rnqmaiWx43n3Mzz6Nbq17rL5Lnqo6/N2aXt1KTG0sAVeSfU6cvFeQ8VcyZLk//e5Mr00sTh1cGvr6uGwIHz2s/orbS5xq70vam70Zemp6XlwKT09fXz7OHz2KzI3r/txK7vt57I3N/y39jstLzNfg+XyJQvcdO43aiOxIp1dXXj5sK406Tz8OvV4+P0yrnmz6+jzp/D1J1ztnI+g+wNjyTq5c64urwQhCXahAvBybXb6Njo3bCfoKCHhobK27DZy672kwjw0tLvx8no6tN+fn5Hj/ray8jevr28savOvqB9u3zqjArd4r41euDn4tnRhRvy5t/tvsLmxpa+29HA4a3ojZwggTJSidvIlJuSj43i1tU/e9XWqK/CpqOtsKBhqGhKkv5pldmxzY7WjynS4Pa11t6UsZF0oeOjvN7PvbZImFUsiD2nwJ6vmpuisJjkrFzbnEK70PCHrOPhm6bMql04kkerf4eElLmCqIE9b8G3hI6olofnuHXFt4/HyYfXs5s2hUXEhilegbxlZWbMjTbFgB1yiajCnEjEjC5vctFzAAAAC3RSTlP6AK8nriccsKmpKBtc33QAADwQSURBVHjanJlbTCNVGIDxkpg4M87Y9MJZlrLchvvWoXQohA4NU9lSaiF22yZWH7q7rh1vBals1mUDNIjFCwEXSzBqXA0kihpEJbrKsslqVo3rekF9MYYlvmh8NPrqf870SqGKX6C30On5+l/OP0PRDTfdcpkugCk4olkd5kb6TIjL4V1K5cNnNWkevPvuuwXeZJKkV+7WYCoccNPGUjmwLuTyUtTWhcwr7LcbbQzhJEuVViBEq7il2REOMNG7svoAhSm6+aYbim4tWphO8AVV+oTREcHmHxlJqqCRIDyqpZKcjadFXgPuJLw2qAEa2bbGxkYdlQvrE5VAOxWP61KGLjS4eYB4GNmuyo4uARYu0IDNN2sC9vBwjz70EEWdOzw6PHxT0Y0U9fri/Pn85ace9JmC3H2326w2Td8Ip9JHm0wcak8uo+iUQ5PPYBXcsNTuRCyBgD0eTz/lOOmagYjoqAoPS3XZiIjbFhBn3fTenF9dnXti7jz8yc1FRRRwZTqx8+8DdJogh0atVoeJTolo+jQHEULvJtf50Yspkwu2HBcHy1aUPbarjRToiserKZUARHnzWxKQdu+BOkvE4qcxPB1Ea5fpggw/QoJgAg/CU/NLu+cXxBWWPjxg8HJpamo0Q0MeEaUWciqeL6KatB38smpXEZeNiT/qS6aahGLKxXUGsEsBmeZ5Eg9CcPZ8YZHUqlMiJL92VQlyAD2XFgmaOMg3jcajFcUKdZELZzVJJjW5OL780rGHiAFEdABL6TgX6lzYYgC91EwUhJrkIk2zcFOYXBHgyolkqQQREEw+hGaFYpx4eSAtgsvHpNEMaT0cKqMIL17U7IbjSJk8uEOENRjq4c7qlFYeRQgFxtpbdC5Pv17/fX2LgdHpzPZoeum8lT+foPcpAkQ08wkhaKIRB6BgEHtwkFgi5HBs7gKWCAZBRG1lmiHPIKew6upOvbWbCDg0Ogb1uRiNdv3Jk4okrjzKwXFf7Xz/aP9UcUd/KNRvDnX3H+2fwYFAsABT0M2fWf0/Ihxamo+baC6JCQ5HRHy4U60i0s5BlCQx7Rl0DA0KZarJhy/6Jid3E6GOTO5ILYbxdnkjksu1stLT01P3zTczM00zb7/98UxH/8z7U93ftEyFPUOyyIEJZ+Ln+X2LAAqH+pbmz5OIZNsQRvuSwbBYGQbaY1gL1IoxvbqZPMNYdhEhEjtFnJYAeEgrH4iIC0RXopUhT3VxqPhKZ3dvcW00NBaJeN09Yz04KJoEvX8RwOcKSG9NJzQcgRjQqQCNjKp1Y2MAEHFqMSio6EiBnXpgFxH7ZEWuip5l7UYGI6OVDxBSP6cLcXUN61Dt9v50Q+FEuaqqZ+3y/kVYVqfT6+1Go/GZ+aUglwLRqQCtTvrcFgvsUoDVZuU9WKQmiCr1ZDP5KD8kDp1et6PU2WqEvyCEmtFzH7hEEYJCkNgrmwwzdheXAQUEfjYq7FeE1enZlt7i4uLeEu8La/NL6fxyulIHfqSJt7EkZ92MleEFklxVMHWxpN4XmN1qJBdFkjiVZvG5D1AM3hdQDy5R1wzG/nB2WgdpPsHLyr5EwKOhtfvkbZiT3U11FYlpjZpbKHYwdfD7hkHBr/ZFhnGrVSLDt0pMFs76NDvxNVbnisCqXQhiEghARDSxCIVN8PEDzvprbU39BzMiSAwGAwmar6yk/51KuSjj0Xtbit7W1mooFTcvQvVnxXo03UIsWKQWTEq12jBCOrKZfDipyaMtT0SU8PBbJqPnTlfbKcBLw8fApHVxveQuCXGSQGSg+cKMt4Q3R/lf00sYpItSHmz3bRm62QY9C6UicTlcTjd13u3G76/tLIOgwCSMQ1KU2UzgG+4ywX3ZSHueCCdJEGoR1wgXU0XomAXK//RW6A4mwEmMRUHJXpOcTypVk3DtXlOKQ0iJ6Dta1Lx6Xs2ulg49i6eWOLSP7JD0Bf1gADid6h0ueScdMDIsOTPBY6UG3wp2qwNxI5qKXBEJdlfINpZhPIHn3sq8LIJJ2+YUJwU46Ioyh9TBKDmf8Di7BEdLzS6xcDjkqCynRRrMycQ6lkwucwP+ks+dmFdbcaYFR5QgnU0t5BZtihngzw1n4w4frB6bCAY7iHAosnPo9SkNLBaRpZW0CAu55RXEX8dwvBimPppqmOp26Ij2RuVKhyDnx8IhCznFrmttKc4RKW5p1anz4PT87Ox8FtPT8JPmxIkTCcIiZvqrE4snEvi1JUGJjeAOJeWaSFLMV6P4FMYQVlbOd6VEFEXxKujXsOJEomBVmpFEyoTs64JcZ7WoQg4+e94VBEEeFOhckQMlWOT5Y8c+PnbseSxSckCXGouzOTf3lt/vTmIzAEdKS0u1TkGpwCdGn3+kLmwynoBwYBEogwz19RJCMaQoAcbaGVm5bK9n1W0Sh5Np3/S0+UX8tmYxxgEufh57QGex0oSaozLEJQqpJFfK0cFKrJEvkolIRiQf9vAwTUjt8Ue0mFpaYYAHTi1QhEh8dm3tzJm1tbX4MxmeemppbW1pbfLC5FunLy5uJJ5KsQg/i4u/93uaa+Qe2Tx2B2ZsLDofjTrgc9SI4GEi3Acv5LN/ESDLxMLYbFD7Zq2Wpl1k8lg4myw6duHMUztRbQaeWd/4+AfzxvrGemg9RF5aWFh4CPh8o6Ghy2iMQES0HdV6il1I8Lkzo1YrDAr/TeT5XJHCJrhp2oiIQJvgCfDh2azBSk+9nkxP9ZelDPU6xvD6+6Fj3R/3vjf1en9JCQVA+TPAlWtFLCSolalCNU2w63uHl+hcarUtBwvs7PauaiJC2EMke44dABOeTFwMfKzNVqotbfLZIgzh8xepvWDrKV/jY7Z6qpvt7Z7S/tDbfTSkT83EmK24BNXjCjSLli4kCa7VM+r+kQ6CoD0qFBDx05Hj0X8R0Xe0spmYrMK0ZTSCB6ETiuSIsYshGJ79kNobn9fuh+NUxw6Utl5yeSkgS2R9BUY7K8fJgiWG98PLS6pIZv8I/4uIaeTRgiIAW67LismqnznSaGNUjFqgzQtFkl3whdGXs3/miRz/HnqVT3SFrWAD8IlkQgkQDFWkpJCI119QJD+5wOQwk4WxXKstb+tiVJ44dYVKoXv4+tXxiYnxq9cfttfb9dkR9v7p2ynS9r0IQ6rT1mkDEURECJmKr2kpJEJZCovk09AwMJBjUgbJBSFRWTiVmqmfHh9/Z/kQsPzO+PjT9VQGtsN7yVHBkocYchTjNRcnKoE+uRFERIT4RN505SlQ7IBvfyL6A+VgUp9jUtZl9ZLah5tnzsLeV0+1b4+rFurt+LbcSTDLcmdpi2ctVOXBT4/KcnnHccLm4xyAmvH5lklwTibCdU46B1koKBLZp4iuoaHhzGEDk0sF/FrcDPDR2XMGQ+3Vd2D974xPXL06MU4eXq1V801y6ZRO3dxzCGBhoHdB+yVsbXGYHmxjcjd1JuqcPJ1DJV1QpMxRWCS/TthS7Zkz0Ley6cIiPENMXqTYbVj88sT1u/Dx7ro+sQwm26lLA2ViJzXwxR0iEr3tZgn5Impant4gIs3qBXi+fL40TOciFxbRHf+PIo+x6RzXauOP8DkmXixCp7aTH8dxDKpSR6zC8Rn/MXX13dVZply+NCi6OCQH8Bmi2ruvoVREQIQ2T3+rpVOYgH8XOTL231KLBZL3rWCyymRjNGIRa3I7IR4wKKQ4CSbL4xVqKqOKDl/AF7g0KMXsnSweEf0kppsX/KqIGBNoZ+1iXCsIKQ/8y0eFwiKFdvZcE70q0qBvgJ57+nBeblmTQXoDRMbfJQd77DFy9y5+5Q01lZFUqVQEfBcuDSoxT5MiQiDw+4zrW8hlNFbhsxK/4HZ+uwjfTAaYfOWoQyYbJO+GMQ/k9hrjgecLieg70mXSWq4tfeSBXBGgliFsLx9650fV4++/VaEfISRqlVT7XM0uJaYEhEtRSZYiihOGdrKTXBNdjNEjSCDlp52lJyIwlPp9ydbl4LENL8PpqZNc79T4/39EIBapx1Amk3M5uQUmxhpSt9aJQ4cmTpKU2p6Y2FYf4dfUw+oUWfHh66axS3fLdRURK2SXAAlp39qSLJHjFif0QKeJNicOSm53RBvOqXQ4n5JlrGByuvNFenNEevcSIaNqRiQ8PABrJxKpkLzrZYAXJg4tXyWHuj4BXCcPry4fmqiiCLqOiMKWhTzhxkvrR4tfN+DsshhYXcXm4xJqYoDWqiDtTHymY/UHQCS39wp81OYnpxD5InDxIcPJFhBh9dSesCwWgSrhRxkgLWKA3uVl4Pae8UPLf5DGO0EgTfiP5UPj9+tYjL5D117W3V/yXqhk49hMf3//1OOByc/OnTt3+teLjuOMAf75oAhf/vLdy/fe+/J3v/ysBsJBp4nyO4udTYrsuByERdjdakRPqp3VkSdwanh+yZzE4zGb5Z6xsSGP2WN+CUSuk8JQRX4kwQGRl8o7SwH4+6Ee89RM99TMNzMb3d9MvTcwOjo6Bz9Lv/51eG5u7vBo99f3fvrmJ08++cmbn9773ZeZziuoVsIOkfrkxYdQ9gW6AyF88UFPlrtzCG5ls55ow/zqQ0yGCJ5ZGeBOENkmi79OVIjUNojclX5zu+tgKFzn6e71DIWKxzwKElFAkoS645sXKeC3e9/86cknf/rkE3z75r2/HEwnVhiLtFTuEPmHdLOPaaOM47j/d/Xx5XqU0mu9AgV2UN24ckAOit1SkGJt5AoxVhNAhYUg2A5IqYSNOIRYtCIqI2nilJBI0ExQM3UbmkDUZbKNiZLowJctAbPNbX9oZmLi73mu1xfopsQvFMqNXe/D7/k9v7dr2s5oO8gdb5nmueV2EJ29FUQkFoFUKuoluQN96gThIQr+XgWOfRCf7cLB3d+Bu1/Azw+Cs38QO1O7p35ff/2+3KzqmaMH0qvr0osgT6wvs5a9PveTSrUSOX7o+HzEG4HPeXgaWYxlXVW7iEn8iSjxBl2Gw40ySBObR25HBr7elEKJrsNR1KZgUg8gJJLgoPE03tL//Ob3n1/4E+/pT+NjCT7W7qmq3wfTz/KZR4YfTk8HkKLq9PJ95dX6n86vRr4/NOVdXMf864veqUPfR1ZbcpJbc/tTgGg4G5AMyQIOG6dBqTGSf8QgY209JbFUpR/aUVY5jkS3rZHf//zz9xF504LlhuLnQag0uz29GizSvK/zABk2VBdBw+h1ff2a9/ih+cgPylr/ITJ/6Lh3jWq5VSb8+OMAEiNxNshybubIz0cJw4ckj8EkA32FHhw6FJCaWGQHF4+LJF/v7o2S1Krs/e21dlVZ2R69paqoaPhoer0+vQgmWc89V2BYmjo0tQT0ikYi+ABFuZQrHyA2ieVh9+oTxgpiIZ+BxReKmAPZK2J1XgIWEhJJBAor0CbarTVm4u24j47zJs8L94FHvBu/lnfJgUvD9+wFN6iXT2KsVXk8Ln2bx1OePnzAWJCTk1v9PNw0sRYBAxB7xGwCJopsgEsqpSIO9I/EQJ5DiYMeTtBhCRyZMyGzNdXqSt7JaIaQQEZfZi2WnST9frlPdBmXIAcv98sXknP5IK5ILi8vDz9qrW2v3olgu8AqbW/xdBwprb8rfVgriHm5ReUDuTmLU4fmV+Hq4aFodf7Q1KJikqqqeJOegLz9bNLoTZHsCfaaWlUqJeFxZHExbQGjEhh3RQdBj4EF4NJf+OW7L7747psXAAsMcsG9fOkjI+y81glfmkqWzWDoq6gHZx920Lby8rq0khLv8e+9IwCytOZdWdlYW4ftdMT7/XEvvFSVPuOkn5cg39JHM+Guu0FdAHJLaW6RN6IkrCiJzRaN7uTsNcXEJHK5DoKqihhkaGj50nALV7l3r9FkokGmgiaXoTIQ0OMt62jndAA6jgGXFxwCr6eVde/IRmR9aQV+ALfxjsErSSd7eylK4qWqqvJTpyC7Jxx9API/hWgRSAaDVB7YgbQeoyRVULHHRar2S0Og4U6/ILS8rhcEspQrm3S6wOQDWI9+/jlcU0fQEYFlhEEW17zrK96VpQ34AZZbZI3CCCH4U1KSRI396BsrehzfhTHYV3KLpbU9FAabpJDaRfZda79MolZ/cTCJAzaxS0OQVR29Z28+ak/vVwxcS6t6nrhL1sNHqz2IXouBrK+urf8AiywOoh2jeqGUEJixMYYVThVV1+tPTVqQKsVUl0fY2bchsroCQaoQ91DTPh0gc1i8h/0cJyEL6/oyBKmGphKjBiqSxKFc82B6epRk2KNSjSkgsKRiWsIgWi11ovsTDVkINC3w1ClQ36dwIOVUV9gaDjW3SYVZCpukr5Cqgn0X2tDFu2SLqGuhuo1zfLN848qc282j5CQUYZAOfWYRqLq8/ECDjaXAsSObQSKwAYxJJ3rPf4nDKeJwhSfy+tyBtmmcSaWe6hKSbYhjKCYQzM2pU1dUqGEsA835Guhuq18HN4k5yIXlC0CSQcctGYtPPXdn5ZFKHx5+p1HEf/31ZJB1sNLSidPdhxGv0dAC+CUr0DY20DbaQ06yeaorG8WBtkmC4NctZqvaivv0BpJqm7HHP0hICMfvFy4tX1g+c1iNA2E1wmIoEckgHUqOgxDclsKszJNt65n1pJU1vzJ+gmF4lmckCiSxC5N9wQUI0AREFBKmunT4rUZOmer+NwJQdK/umSEgO+rsZMBH1lfz00Ai22MOYuHTr093v6jamZaWr8EDcIYRVUgGUStKq5TgBhscxyEaxrWKY70WLl5iYCXzUNGwfFvbIM1yIi2DqEQRRae67wy9hRV+Rpnq/rsdoEqkNbpcuJXUJtKjZWV1+OrLlALI3DfT9go0GaHd+PvZGzeW57DOXHQnyQE11qACYmivZCX+p29XcJK4EefYwEnkSnjuPCh0/vxvvb3db34dYCiOZCEEBGXbeA2CqW4iCJnq/nswBBBsEFTpd7lYpOuZOVWfeDdkVVvX/eZs6My9/82vw7vlxVqx87Xxr5Req6cOOsUtnWp1a4fSQWZYlqcWPn4vMoVLEJxukW0YipOpSPjK9W/PnTgHn/BlwRIANxFVRMQiFftKWYp3AMhIWDv71qw2PHLbLkqKn2jYf+EKkMV8f+IQvK3HAwtNY7p8uVRTmvD/xkNRkrIyK/bu/erpoK0JBCUySMsHgk4gwXXh0urGxuoS1IrAMXvlYjislRheYiSmsC1AwcpkUBzE6EFKO4iHXwyH+Tu3B6IUWAj1zCRy9PWoNLj9ZFdt1ie98XYYIWntMIBYnY7VwYXCBihK70XmvweUedAUPJmPXP3r7OxseFbiz1IUHxi14RdEgg7FQJDS1wKLzGrBKKktogjlpzgotLRQFDidxZzEAZ04nKZaN2ea7xzr7sEEdWYzmUP6O8nSoitMJpav5KEsgD3JueiF1gPRcagV32r867PP/vqrMTz0GQ/ZNo7B+JyxOVpyg46bbZzlbtugy9agUmOq2M7AghVKYiY50gYcIBN2/7ItRjnc/ZJaHWuwGjoH1dZd9WKlWGmTjDgksSJsT1cXoVwHRbyLf89+9iF8gFEa3Z/BICDA8DojSsu2VdphD9wCosSR21nEWGrSpMocNXj85hs1y+boGn1N+Zfsql2e6roKVZLeuaP7E3VcrR/7rfV35VeaBFY6FzoZOo119aqbX9tYXVm5enb275uNy+HwbGOYZx1O99DHnZKWtJ/UTZBx7yQgaDstUxAyIQ0rpkgrBZynLPTJbt4RO2yvhdhibDfXbvIwNB56+amYXv7j46dexTeyvPHmabjhpa+D8w0Gb3qv3bx59e+b1655rzVevHHx7NmL3548eeLEyblvLw65mwywZWCQNHlpaVKDNGh5v4th9uxxMS74zogoXqWwNMWattqFJbXiEQCZaY0frSFknl111gpcdsYr54rxEKwKUHNzc2tHs8PfjPXlAlZGsK9v8kvdNW9U165cuX4FHmfGx0Ogb3u7u3+7ftHtL/l3ELAIdAYQrG9VLX5ZIUaCbIKK5rV7NKmyeaoQvMQ8qkoEIX2oMtpUXBx1lXaPPJo41v2lWg29UdjuWhFy2hCWZmLCN8GzGTZJoJ3Xohyz4fDcjUa3g2dsdljBDst0nfVw6PQZgCmR84HbgeDXqqmxRjccgVU2CBtpE1IMt5kEgzB9NTtmeqJknGlg4BGsOoNdpbHXmsFVkl3+cJqca4EJkZOceGICIqJT69Q6oKJ4Twb5cBZ0owFKqbEJdYlRbAgGiveVZ7UbjoXOXD/j3o9RbguSLMSwHNmhZE9RQfJGx9N8DueAoMGuGku8xeJykTfEeKJ5gcdcm3TaL7uPKSCYREQ+GnGMgCQKPhiIe2uY4+r1oaFweJJnCgAkDab6DZaALfOuh0ohpOZUfNJ95spcZ9MdpuxsqJ7/m7MjCtcAQjxhsSkogMdycmso2DYNeRoHj0oK5PL74U0M+SWyXT/oN5dVJG5evV/h763EqZBjzEfjDUEDFpG0ToanqDXgcJ9xSu5OC8XSPg6qnUoGllZJVfXuD2RnVze/FDp/9I6HsvQPPKyTQUQGNJIaRLlcVkweYNlYObjC9itbZDTIMBwSWaTa+5zLJbOAURRiuhQ8JEHj44iAIN8Eh5w6+WV070BUgmpV4vmNqxJ/mndLJ9og4cWRJ48ydlpaowzqSgNxkfHxO/L0WQX6SgIiNLpBszGQ/yiOEmn84hRR4OsFgWEgPArCXvxeGL9/j9+VdC9ztqcWJeUrCIP4fGAK61GR4wQsSeKdvJaneF6bIf02p+UzLJCUYBDYODqDySBwNEQCYhSE/bARS8ttCwSkgXoN0YLAksaQjoauJQeHwBAxDaAEu5YmJdHHuu9oDUwg+V6SJw88+MCDoAaHsyHc4JB1/iTDLFhYoxFnAVU7zEeD01YAsRMQA+bo+jQJ5C2sxvfY7YKAOBbROkHEILCq8BJKAvH7OZERYzUOzXHx09PnegNRztq6HbBFl5n1LRkUKzVIcDrsKOdCDLVgYQwGAlJQ2xmcLgaQEiiNK8kAdrpLLYPotgGCNIlKmPFCMs9iEAiecJhmFffAGJSsGAkSP8jGNhRZ3cTExOHTgy6KwXLth7zDtccpOSXJ+R72EUkLq+w3XsqYpGwGyM0MHpsJQAyKRQjIkSPEIsbHtgOSvTsnriyU0ICwwcVI0KmjSAaDcM4im0XhgKJFx0FRBxEG2ff5GHKMRZr+we4qqwqrQrmXAgpOyUHRO6GiFVmxV+BgaRmMVpxkorTOYEA0JIJ03X0Eg+Tn0GSqOzLbCHICyJapLp0AkptgHb0+Mz9uKg58PBAUWPkvjxiReI3/OReVJEiTKZEr25GDhyvQBc2564k3ej9FcIIK+w5rFESNJAevMfEsXl+hc8KCxahU9YaGYGtJSdTZS7CLHPlUTUAys6NTXW0DqFGQp7ok133IKM9kjFlZmfhIbmZWIghC+buBJPGIpQeJlIYEdh1CAnVL5e6CCmmMAtkCXSdD5fU444+DpLk6KZ+POAnz1Tg4e1qa2myWs36YWxqK8+wKCIiAmB7SRae6AgMScTtIAXkgc3eOCcPqfVn4yO5sOslUgKhXJWnn6GuIlTMzROILQ6VW1Y5+aowhCnSZx0NyBaAsLUOay8EjjcQAiu1cL2PDAz7oAGJ1WqaLDYYCQzIIKDPXdcDdktigI1NdEK3XZOc9AJ3zglxNlgm0G23ZezOzk481W7AtElDFlCwtZihbLG1EMJUe7X4TP4O5NFGbxTL5+SR8CU7CU8tp+MVCqOlboKa3FTZMDvpbmviSJBBohORl5rfsb7hki7dM3Uo7SJNVCsVtXl5e7oTvgzysLSAoL2tTydjRk1yukD4tx24xyJG+Vk62GweX09z7iRlcXRFeWpVgFgO4QnFJ6HAPtsheeeGBsxtxQCyzAoghCpKZm5mHCh7ek+EeSjXVzS1V7tQaG/MhVUrlGTcvrtTpTbKHmGfg0uzklAREDW92gIUVFwZRS/amCnz717FmBQTKKcfHnX6/Hx7wZf/+gahF/iHk7GPaKOM4vv/P86HqXWWcvXB3pa9Xa1mBdhutzFE6V5UhbNWgzIHgBorbgIFSiE18YczFgHOjZAx8gSVbmDrAuWgmRBxxiTNGky38oTEZifznnDGZUX/Pc3e94wbx66QvW+h9+nt5fs/ze57zsgIbTsQj53av1tV1BSS7+q16WBLd/w9Cdb3CrkayyQgC5iCHjJEKAhqAY1o0fA+UG1+tG4PYHsPJKUSfGMIgr9sim7ABItNvNjXtOdD0ZF5T0/1N5VqMIL+f5WPFvW2rdXUR4kSBPNu/eJw4ySogDsGAB6iotXWNPoqmB17xkRWhMmiVEBBtoy1tBcHXTlwLmnIbSJYNvt4PILZqOqLkYFI0wp5pYIxFVBCXM5FIOFxhdktvb5uxq6uJdXJeHmGS/SyLJwyUWXaXYwUICRP4Dab2QXE+ZCAYQ0Bft8CVWBFfFna4DCD0CeOCV97uJx/bcP+ePAjypsc6On24Ac7bFJAzNjXOQ6EQgFRU1KwL8zBEJEQReWJNvYauri6v5PDj1x98ACgfrBYodgAxabARmWoxHCLFimfuG2wEY1Th3V0fHIfYYwHEqp4RaDTECJQKhcq4V9Z9wqdsPtItooLQbbQNjs9g1xK5RNJLISRw7GqtN9lLxQnI4uJxCHnMY14RFkVk6vm6fW72gzDEsq5ND9xH/JLFm5/LgEJR2fFFYAEQoguwnKoKxhEdZOiCj7a6aQ2kqySogMg8gNBFCgjMrDiBjAhiAB7NkuOcnwM5OPFjsIlnP3tH+wrFneal1Eafe/vOu6LGZKb8zUBLZ4ym7TRIR1kMa0n38hCtqhBihFZBrBeGfDqH7lrRUIxuC2H2dfiaHImEqGZbBQQZ61rZiYgYJuz0gDngTxaEd9qVfy/JjMFCHiDpat3+4B0gVr5zcM8DBMSoquOLVbQtiqx01Kot1lubcLBjECtthWUGn1WXrwu8EkCi5z3lj963u7g8Zl2HbeGVGclBurqOGHEteBngXF6kxoisrqXzMownzH4Pq0a8IHAOmSISOC9j2h/R+glM5jZXrQBBrS2NsFy9AqRxYWk+k05n5pcWCvfsieR3v+0D4ZHdZ9CVHsOLnhZl8P/56ltXn3jrud1f/Ni6zu9FAm+H8cLr0bu6LAdza68kIAIiyoEsCAiBFHuBx3lFFZcXHWi1oz9BI4i7tZXEhhHkZiYzOTOzcePMzGQmc5N2IyscKXcT13JTVlqNDPcVH00erbQ75gyUBrFrWcvdu8/mP7f7SU85v87rkvxQTTmTDVv0EqV4C55QyAE7AnllSiIDIeJlhFQQQgL+iISAaglWkpCAjGHi7hkFm1RVIQ3EPdilhrKN0VZ9r2dmNwIFCB5mM9f3WfEJFOxahW3Ao8naoYJAVih76eWKEgwCz20bntzgwUPPOkFK+p1SQlq/3rgV8CC5UJcUhyAXw5SIf1BQk3lFpG5jZhkCQiGJz0Y8F5dZo3/ZyOHeAjVQBloHB8hXjOM7ShO1zk8CxWQmnYE/k8AyOQ8prQtIrAyAGDT0oWIPokK98IWGagkZEO1CHDZPJOP2crIvqpisBpVzZEhEggth4S1iMngfq7yR7YEGWL1AIe+54uEVfbq8ngLoitoZrNbBfYYzL6qHXQeO2fTSIP7QwaX0LJBchxA60dFlC+WtBKmHH2gFCI8tVWwtUUCScRlPSw+ew3614eKpixuwd53rDXAOEI+09CTykl2xxmg2ql1xQZD8xkrLKXGGRFz82FxP3iMgR+SB/Bbd6UA2YpMbGWIDzROIfTI3ytva2r57tLz8cyPIBQxCOKzu/YVkIZ6AKG9Z3esCkiQi5OndfTfo4sUnLl4kJjmScMqyLGZng4yXU+fnqH0U6UOMF4eNLpYXDEMKQmWf9yiH/ZweH2UUHWUgGDIzwGHcKAckM5lGPMR3DBTqFEBwODu+uK37YdqluRbEh7KIzcbjImKKzz2Kf8+pT+/+9BTZG3+kATO4nLrD6yCWz1HWJgmZMokNGMmqCj75ZDucZnGu90Upg6ws/A8GmUl3EYSWFvLQlZ4BkyjFygHaqMZuHYou1EHcSlKDRo+X1xexz5+6euq8stIY8ZhAxGQWxDKKtHO8YTtllhEEL1K193RW3CVHpnGI6MMsgLivz26cvEEAXrl9+xXy5Mbkxtnr5IvWOqaa/3TAS6sWI8gwOVRByLpIolABKYfloEcNS6auuFMf5vzqFgey0YssSjuGE3aeEQSe+1hmwoxK7Hc6jXSooKDlk5r1mx7zMbag/tueZxGCrz9NketfuLm0QJ6402AkfMkU+mGINpJcvoCNYc5aRDYrhUGQ7E9GAATr6qmr+AFc6yCLY8AleRHSQByMBgIaDSE0zDJfnU6OjDc3h79Kjg9rTUO7w2s0StldNWdaOiMRXwnNGEAQak1vnJ2/Wwchmp/dmG7FINR9UHbpCn3dT4jMIEQEhBH9rqYt5QrI8+efV0B+bXDhVCV7RTxDtGMqPuDCD+iMRVV7X0hq6GuQx+3NSempMICo4iVZy1sIyDdHWaazxTdoi3oMIBTVkwE3UkGWl1UQcLdMD56sRO+jhi4bSDYrrqaDhHhaEwIQxAc4v7zlV9Oh496DMfB2HMoOjmficWKfALZJNNqukbzY90yzX0QSEpsTrmHBMIUJuNSelTonX7+J7ephWVhgZJHCAQ8LALKkgqTTKsgSgCzAnLCm4ACi+rOt35Lg5v4uE0gsvLhIrAECENYF8/LeX1dr9DAuAGF4kUv67cpyXQABhkEkgRnFKQS8f0XlVQyzNQFW7nDsQ81ShoDD7W7JWoT6O53+m8papAUuswCOiFRlSYLbN9MXLphBoPwHDg2EZMyDZpD7kzK2gUzyrsgp2UsIBMCxUhNHx+rmKi2WuRfBvVCCEnFgqAEeD0i8EvIct5KxJoh772TnUDWCQwIwjEBgZ5SklQYpaQuGFhLsZdsPQsVZ1v1ZMFgC9sDz4H4zCH18kXbrIKTBcc4EcqTXSyaH+MvlvEkyqDgkeDhTe612YmTrobrKynufqay0PP5TuLlhPDn8rjgebmgOD4+P82oXW3SYxpiBQTdVBvchKbCrxeA8jgf8cQMYZAA/w3EzTyvBXgPMFR1v3lUQ3KzUjVYzCMgEsj7PDFLIqCCuABP3szhrkd56qHbCMne6rm7rrr6+yrERS12ibzwx3PDxyLC09VDzyMeHmrNVTdhhXibyQYht385oNfyNSS1tfXT7douWtCZvqCAoiDfBd3TeVVOilFuH7wTRXYsUteydIE0MsgsOvxN5/aJTRhiE51kKhWqP1t5Tl6p7+NBI3d7HKy2Vr5+WmocbpIbmZn+g+anxZIOhASGaduDgZaKyIGJtag1PxnHj55Kxfp8KQgEIIaEVfXbZDILlzoI44knPKhZhUVyKw0qRLIkBGWckUQo4UchiqT2awj+OtrdDiMB/IQqttdsjLJvnWl3wNuURgkGylX5pEq77pv6xN4Fsckn9op9E0WANIVHTlbV7NRBrFsQriZ71R/A2p/x8+GX5+XibE5S/jgCSIV8hPKESlbV5R9SClTpaa7FMWUYtRGeoNcW7zGwQ8AgeqgsKsL/sg1nVDEymFAyYZM3A3Gqf8kWjR6ETSu4d8kXH94WgSOQ3sj0ttieWF4P1rEhM5VNBQGysEIPkP3f+7Nmz55/DHas/O2MBiYtzgbgD2gpMABdkDslBRkNilKnTL/4yN9Y+NTY390i1KDui79qdnJikVkiQOGQOeESGEcZe8UKVrWwhjSdT6fkbN2/emE/jSVZ6gQbVlJYWluao98LLbeuufghv8x76rKw6911viN8Q43le5dB2B+F2XyDw6++kq3sR+ugXz2OQL479Oe1GgiNut8O0j/MLgBvnwTqjKsnevsrTdVsrfzq9dayur+50X+VIc99Ig2QyABLNbrevS/mbsoIaK9qZu5TZCJqdBM3iZ5kl9YRTXDLcauvL7qLcFyBIhujgtgrsWmrVZQQRZYo/eOzPIzhGzhLXOqsMiLFLbw/gTww47UlSwTNJnIXOYApQX9/Iob69hw71VUL62vvwob5de18ebrZTJkmixJtBiKIlJXA0NDcH5ogGTV4PkkRQLcVzDMr9sr/otWwCttErpyqIgAgu6us/23rPmUCO7GHD8UsffU15XZB9BJKj/Xg0gZEdjyS1L+6d2lFZOXbPVO3YjqmpuR1TdTvmXn4ZUM2SRZcXGUAaDXcw3bYt59v5SQPH/Lfw5mvBkugBAqCT/PBlbglEe5cOYraIEHBcOpLsNVukrVdASB7749IWlnIIJC85YVgEQd7CaQtwLNdS11KWVK1FV3v7mTtuimuHLwIZQRBSQYgmgCTLMaFe9xbuNdWzioqUd6BYCVZc/ozGMoGU2TEIEjqPCXykzRQjeRFwCRSOj12q//ASaBpRAqdOrMC3LBaMk6pNHZ0AlpVqN9dflMuvv/MG7JOr/5Do2Eeg6Y/eARKVY+GdtXXl7WPvvNefBdmsgxS9pmSt+m/wyG7MWiRGPH6cqezkLFSUevPSm9pA/TmkXwsxCdhiYqIWSMwoo6Mh06RRq4QP1yvJiwiWJe55dXo5hdMuTsM7llM7ax7ahlW6Dd8ysLS0KKudv7257ZEOqwZSCvtTVe2sJiADb5MSxTSOQPXLCYbFZ4qZ/sOJQOBb2BaExEIENcsqGkXIEPIBVlmn//ANxnAQEa41dzn9762c5fTsbHo559a/6eWaAvApXUX60x1XduT8NqCB5ED60lRdjUHe10B0qSBOljKq89K0yIk8ilpS13QrkCRGXpn1OaXLKSiGqDdvfV5I/41v13crk7mVC3cv/Tu9sPnBp40o2/QXbb8VfUmqlOge3PTRVVGKQQ6vBWLnzC3c6WPfMBzHjOKL10Ncf2GSYdnEoTYi682H56CAT+WATp7Et8FOQTEf1G7NC+/Bj6eLiric3I+rX4D3vuw/0K004XWGGg2kvn7NrYACZ86m39QfpngOz63g2lMQI0TgaKuqPWQCAeubQIDkdvqfW1MPPrs1p/YWuNlH6CXlVskn/Zy0i/C8hOwsotiHnobUdRmvpYQihglwxc4SuioMIANrlPEHwK9YMwiFgMT1CDEChPzRiTUMogeKDkJUP2DeLbxpU2r5n7+I/lmeeFfkXga9uyvnJEexT+06eXLrTkatpV+qqSjov3JCSb4lFVh0TQUUMlX4wta650NbIoZrF2fYnErrT6BHUjgurqUmUgRkAgyyJkkUKSCCmnwPUysF++4ST+3a9ewzKz7HSUAQA4obApUtKOgeUqreIDFWCQQQBkGv5a4FkvdfY+cW2zYVxvGCBAKlcFCMXUAeFr7UdnGoiSiwSEC91kBgodiFYXtaYi7ZwIRAoUAY2ah4oBDoAEG4KUqkcAmRQKKCIkDiUvEArEiAuEhI7Anxwgsv8M53juNcmpbyb7Mk25T6t+9yvuNzvrM7RZFGvCagIZu8ShfBBOBcv+IsTLzsP/TJAMgLbw4tWe8a3hWCzLpjdmYA/VXn9OgVH307BgYhJiEQIYh06VkwsguCIC6RMn7XLmDYtauTfjUZxZCAtiCRim9jEc/qhsp/upeiaCAaLUP91hWjCojmOGEYJJmuRlVb358y+0bnY9+etzu6sXJldDAtnjCM8Ar+K9GACCDRgEgjmQcQMa3FBjXx5rFjy++AIeB7B8/quhfDpbSUIH117OU+EDaTRonOIv6whkAQzLKmpp/5bNd1OPVeADxXXEpOC45hAUjkWqREOTMqUSBraQoK5+1DggF6+cCzb0OBcuAAgOykdzorrY/csfx6rCdGYnqfvjOINIr15I+7Hoc9TlOwogqamrpiCg2CkKLxpg++/+Cmd6P0i9hEp3qNDQkde/ShL48++ysU9NEAf2CnmFdeAnNs0s4gDMOEC30SaWe+5k9oR7r9hvjlsLuf5N+btwK54LmnnrugCxJjcKTREi/EhoVeXb7j2B1fRhhH37azxe1tQ256P/DSVov4wsASPRre6CKzLFnSQHfVqf2jdx754U5Fno1DeYN15S0TsQ6I2g9y4a4L+0CQIIVLI6TG2tLDHj1axJF/dOH8MUgk9tFtWWBwfHI5FklKgGiySSE9MBsOY3zrpYoFp75Rn3nto8dvQbulW8JAf/ziCIRjdZFnohiBbXlRjPCeScuqRMfyEpvUNVI9DuvNOw6PnT8xPY3PF8HtIDcXD2yXh+kPH+lespZLpgSReK4IIDsqjfe+GRtnje6vP/naQRqfmnbvdaD5qX1ndEBSGdCdUdaCLeVh1lo5T6gVMl7FdSvraitTqK2u6x3jyyzdX7W8ecexw2fAFqvOouv58bW14ttb0Tz/hSGq0aqRmIppSZncrp1WVUlexVkstrpKJ7YMG4mXJOlm3NxYv/bhGAKUUPBzIxBN49JpANk0jiyel19f10v5fFtot1ruerviyWEJi/TeYNvZq/vSmy+QRUr4ChcwvlxbO9o/cySzl79VWsLRxnEJHa8fKTl8L0BJxAoVs1D10vkCyq+38nlP1/V8RhmmmQKOcn38yFd4fTz64WORayU0+NjhO43nnFdSkac39Eqmsc7XPJ5Zl1WVgKThqV/QmvvQsac7C64TwBKuIj1hFwGmm8sO/A3VCV1NJpO6nuTDME5xKRbSa6NVKXhuRczE2vlGyyt4HiwbycMgcOZ0uV4e/ezjGCEZG+tfyxu5MrVN9ZuhY2rNk2qe3kJyrerVJC0EQWgIZHT8LRgkDwMDaTUawzoDNlVece2cvQbZAKqyt/8WBlwSQERZYXEtJ/KColR1U42JmiYKisyJqptSJQighQVmwCTlDbjz+EyMkJAfMwF+FoLMcjt19KBk5yUvCPBySDQ54OO8r57Gk/Hluw+NhUbZdx1O9PA4fPejbz/74fP3MrJKa/jGpcYQt9dkylWYzgElr1erYhJ1Zlxoeg8UAwwg2k4RrNcb2/fXAeQI3RlPp6enmYOoYxFlBxCQyvKRFYSkKLIyOxgkpK8NIbKIfBiPLstP3wF6CR7H4OTpN5effvLJVxGEQ4ZPAAjtavjqWUVMg2NwuooHiUYhoVSVFA0hDQe5lcssR36cuzA7e3vPB9BeABl/+OvOu4OEogMi89uAMIrkRjejBb7A9zbxSmD+vntVewgI5I+Oxg7dffcLh1544RAI763aPN+lBQGgWJlmdEFB6WRaYNVk22s02rVku9Uo1Sptp+5SuAaTNAR9r7Hd1wCwiPM0XYe25mfgdOQhjShbg/zVaLVdJV9SMrUCX6vp63S/M0mcJjIRGFmE2tfZYxLBoOhtP4iGdBf4NTfFyQSKExBrUpRXKFS5jCfrmdqq0/JLl9apYhCYsooWgqaW2D27G7dUkNW8e4HkyYe/2gJE3dq1ltoFuSTW5EJ7db1QrbHtpDa41ZGVEIgYBJ/3PA8gA5pACEIEW2Qi4tB1mmxhSwmqkuHIh1BcymSpnOeKiWqSl5BEzz/ubGxslGf9ZmAZSF2wZxEim2CqLAmhyfrk3m9jW4H0Z62Dz932/tkE5I9GI9WAhRu9LVUq4i+ZFqPgQawnlQXh0nSqPI/G907jc8YHNEYyMVhkLCozTC7abUeuDcQr6aqRD841qhTlUhIeKBG9v+40y0XfCfwFhP+Tg1meZAARIhVnro3JvQ9vBZLQ+s9pvAx6wW8iIIt6popaeUbnBKVWJQx8MqGAXTaVQFwGIdIoOdFjAAIQLlp6ILSo8jwM0AziXGxNGsRwJmV6hQ2XyuV0M6FxevrmDWfDsXw/qFs+ZQbBWjwuMk/MIriXN4fp6e1AtEQX5OBtt5GDyrfteksluLSeTMqJCEYUxQSjIrwjMjwXnmAAASKZFxsFjkHoJDxWBrGsLrtpQReSac/Juxk3Z2GVSiXPcwJzwdioO3WrfG08GwRF3zKagdNsicVm1m4262T6my2PPvPqFiAMy2qoM7F67vsuyHuLM3C5w2IkSCVKFOk0zYuqipikG62xgQnI0wQGAiIYf1+cCEHoTstbIoHopEuFEpTwueGRJ/zbYAp/7QktHjdKluH5ptP0vWIAXGvNIgKf3oBoHySZnr9iagQyuioqcNzZgEWu/mVmRgbzw2AlIXZ1M04v+SKeT3J8IsFJEt05iTxSRHbf4TA74OkR2j29G9wQIZkCGVRXuRwVychZJR8wDLNkZamcbWc9v2lbfnGu2JxNodTC1L4v/3ytu511/orx7hl0SFtauXUgRt69pNQouOv6etusVX9xh4pRsc9cKRVLkYl7DWvihftgQGDZNJ1SkaY9Af2u8KuWNajtZcGVU1k7ZwKPbRvZuJ11fL+Y9Uu+xiVS7kb9x4872xA2n5x5ztJBnLWeCrPWwRMn1vl2Q2lUCnqFbw9fIRKlHTfI99pJxhAnSxLiq1VD52chExFlDaxsmTIMys5m+0HMUpC1bUDNAg8IQzuNZjyX86mEK3J7jfOOhO3K44MgzAMPDpyceSP/YLvlNeiSqYiVTFuKDUlO0cMgitAV29vBAU0Yn2SrYIndVQMcp2pDAuppbs7GbkQNqgQhgUGoPsM5lE1RfsnKICj/PtlD4oQeHQThF1cGTs5M/7yylESrMVpfFcTYVhvmeTYpKGgTCAsx1ZWIeiZ5Yq0Yn5ycs8OrykKHez8JvLNJlOgGZZgeKChRptNjCF+YFo4jq5SDDzbje5/Ahcp8P4gqqvSFS3ByZl+P1dUri/RODbq8Kw3GjSYOjDEcF33EQ1+Wi3DBcDSZTa6uPDcJXz0Wuxj4ll/y9KV02jNzpmlaraAYB25iETsbWqRhYouY+JT22fLePY8c6ezRIed8jKCxGJOiycmZ+sr10Eh+63uvQG/SkjDU3DIkQe6HRRDDA20nCTaasy78kyuvNeNEk3P7s2WAmLuYvMU4xWYRspNXcihD11eWoG1qafG9399bXADEeBbHiQEouRDEz0JWQXp5z/j4ax91Yx3FRu5/g5mQwpMzf/7rtr/+uu229088d/VNS6Lem5BAy0jUSd9rE6VjiMTB9tIikMSBo+VsYMdBYJbJ0bmOS5GIt4secR1wHKz8zAKYwqxZlOHqS4sP4ncAks2VPOxkdhw3M9HmHDjV7iNob8gxjkEuvP/8Vxga2lOvv/rGP078ceLEiT9+z2cyx3NSF8RrpFYlCT6g0GA5nm2vcihFF/CsV/ovkFRSgKE/yUL5CBvbNyZHJ+fgEe8LkCzlBFbQiWU/zLzUzCULWd8s5TBVPpPP55sBjCUl3wMOcuoCjVh9Eh8b9fXX41cen+yAwOW8wjCvvHH/K5csXf/ujb9j5c0c5WUyyc7u8Tcabr5QqdT02HojU2s33FolX8p7YdfStjaJHFKqCmgCjmuqh+elz4FRoviA+qPYHQkLDkUFVgGyUt71SjmC5bWWLpkRb1hxijiMPKuIG+PRPpQ092OQZz7e99vxyCLdA+FpJmnmQRBq8OE5E6Hjrgw92uBYByuVaquWP77aaHjV9ZquV9bzrBvGCR/bSYiFycmLh8r1+gaxBrGIARHeDGC06w4dlukFBScISpYDoiwTZysnn1m6QZ7R82lMrZtZuygq5VltYf++PczHr02PXzk5CAJCiFZyofC/Bd7N6KZIy4vUcPVWShRpXhbkggsTLZEtKVzoPmmO/h8gYJLzL7aLPZ8CZw88G55JRipPGjkYISwnwvIKPoAQBc7+DSsoLxnNmRtmMiuX3JBZ0ildk5iPjnSH9b0xAOkTUqPUDQbBF4m2uCz5OHNhfxWpiKz03yBJCJR7oY1wLQSJslVg4yAxyvCA5c9utWXYhuUHGMOISBwz57RWWpZpOumrblgS1ZlCPrP4MET6nmkiehAEhBJVCstLke00IQgtqwlZqNKx0JsQRNQAITdcrwxPAA7fM7uWxU5FHhD38QBbZnLDgKQEorK9AjJoWj6k214h6ZQs3yPDITZbYMGoqL73DD0ftQtFR+4MWcXUEc5JnSUYqVVxa5kCW6hkWm4+U2LzOJtk+tv36B1BYhd9MrsWptyQxg5scKw6Hj3OpQZlBp4PRaJvdd6aft03Oy87rqcknJ8+G5wh3rX5IvgFyuNJYZhRQ4tUCm3XK1TSpXa+wq9nCvCdbqUHSsidQQ5dpC3MdXOvbTs541wjKBV8GEaG5VgePCAFWFbg1x0I/QEJnFC9+Kcfv/5o5PUOx8jJ3OM3T6D+hQmEFDLQybIUMqJKqiGyZqnCFtgWqnlsQci0zHysKzaBdgDhRFa46HZVncUcC/AwKMvxLMvLgc9QW8ssmZQD/mVa4EvUgAxT4Zl95eLnP/30XWiX00ZOGZm49y5p+t6uYRJ6VSSvkwKK0EgnMTxohMInOtbfdqiriRQaunRuoMZBwqHH7LgN38QguNgwO+6f24ak4AeWY/rgfD2F5T4AsrH5yXGeRbHPvhsBg5w6ctIpJ48g7fEUF+2pkgxPRQzZaKaTgmtnIZwMmE2FmKKKA04rarPffInzbUhimJ3rB5vgV1kc+oMWsYo2VCamE2wmhfTg5FLQz0Gu7ofvPj391JP+BaOErri/z/2GAAAAAElFTkSuQmCC",
+ "public": true
+ }
+ ],
+ "scada": false,
+ "tags": [
+ "markers",
+ "polygon",
+ "circle",
+ "navigation",
+ "position",
+ "sensor",
+ "geolocation",
+ "satellite",
+ "roadmap",
+ "directions",
+ "placement",
+ "layer",
+ "openstreet",
+ "google",
+ "tiles",
+ "location",
+ "mapping",
+ "gps"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/markers_placement___google_maps.json b/application/src/main/data/json/system/widget_types/markers_placement___google_maps.json
index e9f492b7fc..bf721b05b7 100644
--- a/application/src/main/data/json/system/widget_types/markers_placement___google_maps.json
+++ b/application/src/main/data/json/system/widget_types/markers_placement___google_maps.json
@@ -1,7 +1,7 @@
{
"fqn": "input_widgets.markers_placement_google_maps",
"name": "Markers Placement - Google Maps",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/markers_placement_google_maps_system_widget_image.png",
"description": "Allows configuring the location of the selected entities on Google Maps. By default, store the location using 'latitude' and 'longitude' server-side attributes.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('google-map', false, self.ctx, null, true);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Delete\",\"markerImageSize\":34,\"gmDefaultMapType\":\"roadmap\",\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"colorFunction\":\"\\n\",\"color\":\"#fe7569\",\"showTooltip\":true,\"autocloseTooltip\":true,\"defaultCenterPosition\":\"0,0\",\"showTooltipAction\":\"click\",\"polygonKeyName\":\"coordinates\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"zoomOnClick\":true,\"defaultZoomLevel\":5,\"provider\":\"google-map\",\"showCoverageOnHover\":true,\"animate\":true,\"maxClusterRadius\":80,\"removeOutsideVisibleBounds\":true,\"mapProvider\":\"HERE.normalDay\",\"draggableMarker\":true,\"editablePolygon\":true,\"mapPageSize\":16384,\"showPolygon\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${coordinates|ts:7}
Delete\",\"showPolygonTooltip\":false},\"title\":\"Markers Placement - Google Maps\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id;\\n });\\n\\nwidgetContext.map.setMarkerLocation(entityDatasource[0], null, null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"8d3c0156-0a14-7a6f-0ddd-0ec16b9ffc91\"},{\"name\":\"delete_polygon\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.savePolygonLocation(entityDatasource[0], null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"46bf69cd-8906-234c-a879-e2e4c92f5b67\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/markers_placement___image_map.json b/application/src/main/data/json/system/widget_types/markers_placement___image_map.json
index 5544471bc3..c4bddab86d 100644
--- a/application/src/main/data/json/system/widget_types/markers_placement___image_map.json
+++ b/application/src/main/data/json/system/widget_types/markers_placement___image_map.json
@@ -1,7 +1,7 @@
{
"fqn": "input_widgets.markers_placement_image_map",
"name": "Markers Placement - Image Map",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/markers_placement_image_map_system_widget_image.png",
"description": "Allows configuring the location of the selected entities on the Image map. By default, store the location using 'xPos' and 'yPos' server-side attributes with values of 0.0 to 1.0.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, null, true);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}
X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Delete\",\"markerImageSize\":34,\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"color\":\"#fe7569\",\"mapImageUrl\":\"tb-image;/api/images/system/markers_placement_image_map_system_widget_map_image.svg\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showTooltip\":true,\"autocloseTooltip\":true,\"showTooltipAction\":\"click\",\"defaultCenterPosition\":\"0,0\",\"provider\":\"image-map\",\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"polygonKeyName\":\"coordinates\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"mapProvider\":\"HERE.normalDay\",\"draggableMarker\":true,\"editablePolygon\":true,\"mapPageSize\":16384,\"showPolygon\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${coordinates|ts:7}
Delete\"},\"title\":\"Markers Placement - Image Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id;\\n });\\n\\nwidgetContext.map.setMarkerLocation(entityDatasource[0], null, null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"c39f512a-21c6-6b06-3aa1-715262c6553d\"},{\"name\":\"delete_polygon\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.savePolygonLocation(entityDatasource[0], null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"94bf5ffd-b526-c6c3-ae3b-ab42191217d9\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/markers_placement___openstreetmap.json b/application/src/main/data/json/system/widget_types/markers_placement___openstreetmap.json
index a351a75564..da96abb49e 100644
--- a/application/src/main/data/json/system/widget_types/markers_placement___openstreetmap.json
+++ b/application/src/main/data/json/system/widget_types/markers_placement___openstreetmap.json
@@ -1,7 +1,7 @@
{
"fqn": "input_widgets.markers_placement_openstreetmap",
"name": "Markers Placement - OpenStreetMap",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/markers_placement_openstreetmap_system_widget_image.png",
"description": "Allows configuring the location of the selected entities on OpenStreetMap. By default, store the location using 'latitude' and 'longitude' server-side attributes.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx, null, true);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7867521952070078,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.7040053227577256,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Delete\",\"markerImageSize\":34,\"useColorFunction\":false,\"markerImages\":[],\"useMarkerImageFunction\":false,\"color\":\"#fe7569\",\"mapProvider\":\"OpenStreetMap.Mapnik\",\"showTooltip\":true,\"autocloseTooltip\":true,\"defaultCenterPosition\":\"0,0\",\"customProviderTileUrl\":\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\"showTooltipAction\":\"click\",\"polygonKeyName\":\"coordinates\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"zoomOnClick\":true,\"showCoverageOnHover\":true,\"animate\":true,\"maxClusterRadius\":80,\"removeOutsideVisibleBounds\":true,\"defaultZoomLevel\":5,\"provider\":\"openstreet-map\",\"draggableMarker\":true,\"editablePolygon\":true,\"mapPageSize\":16384,\"showPolygon\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${coordinates|ts:7}
Delete\"},\"title\":\"Markers Placement - OpenStreetMap\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"tooltipAction\":[{\"name\":\"delete\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id;\\n });\\n\\nwidgetContext.map.setMarkerLocation(entityDatasource[0], null, null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"54c293c4-9ca6-e34f-dc6a-0271944c1c66\"},{\"name\":\"delete_polygon\",\"icon\":\"more_horiz\",\"type\":\"custom\",\"customFunction\":\"var entityDatasource = widgetContext.map.map.datasources.filter(\\n function(entity) {\\n return entity.entityId === entityId.id\\n });\\n\\nwidgetContext.map.savePolygonLocation(entityDatasource[0], null).subscribe(() => widgetContext.updateAliases());\",\"id\":\"6beb7bed-dfd8-388d-b60c-82988ab52f06\"}]},\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"displayTimewindow\":true}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/openstreet_map.json b/application/src/main/data/json/system/widget_types/openstreet_map.json
index 15454743f3..b0294e0988 100644
--- a/application/src/main/data/json/system/widget_types/openstreet_map.json
+++ b/application/src/main/data/json/system/widget_types/openstreet_map.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.openstreetmap",
"name": "OpenStreet Map",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/openstreet_map_system_widget_image.png",
"description": "Displays the location of the entities on OpenStreetMap. Allows to choose among existing tile providers or configure own server. Some providers require additional licenses. Highly customizable via custom markers, marker tooltips, and widget actions. ",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"openstreet-map\",\"mapProvider\":\"OpenStreetMap.Mapnik\",\"useCustomProvider\":false,\"customProviderTileUrl\":\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See advanced settings for details\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7569\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"perimeter\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.2,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":3,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false,\"useClusterMarkers\":false,\"zoomOnClick\":true,\"maxClusterRadius\":80,\"animate\":true,\"spiderfyOnMaxZoom\":false,\"showCoverageOnHover\":true,\"chunkedLoading\":false,\"removeOutsideVisibleBounds\":true,\"useIconCreateFunction\":false},\"title\":\"OpenStreet Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/route_map.json b/application/src/main/data/json/system/widget_types/route_map.json
new file mode 100644
index 0000000000..f1d2cd571c
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/route_map.json
@@ -0,0 +1,96 @@
+{
+ "fqn": "route_map",
+ "name": "Route Map",
+ "deprecated": false,
+ "image": "tb-image;/api/images/system/route-map-widget.png",
+ "description": "Displays an entity's trip on various map providers. Supports custom markers, marker tooltips, widget actions, polygons, and circles for enhanced spatial representation.",
+ "descriptor": {
+ "type": "timeseries",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}",
+ "settingsForm": [],
+ "dataKeySettingsForm": [],
+ "latestDataKeySettingsForm": [],
+ "settingsDirective": "tb-map-widget-settings",
+ "dataKeySettingsDirective": "",
+ "latestDataKeySettingsDirective": "",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-map-basic-config",
+ "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"mapType\":\"geoMap\",\"markers\":[],\"polygons\":[],\"circles\":[],\"additionalDataSources\":[],\"trips\":[{\"dsType\":\"function\",\"dsLabel\":\"First route\",\"dsDeviceId\":null,\"dsEntityAliasId\":null,\"dsFilterId\":null,\"additionalDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.17490048149347315,\"funcBody\":\"var value = prevValue;\\nif (!value) {\\n value = 45;\\n}\\nif (time % 500 < 500) {\\n value = value + Math.random() * 40 - 20;\\n if (value < 45) {\\n \\tvalue = 45;\\n } else if (value > 130) {\\n \\tvalue = 130;\\n }\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Speed: ${Speed} MPH\",\"offsetX\":0,\"offsetY\":-1,\"patternFunction\":null,\"tagActions\":null},\"click\":{\"type\":\"doNothing\"},\"groups\":null,\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var lats = [37.7696499,\\n37.7699074,\\n37.7699536,\\n37.7697242,\\n37.7695189,\\n37.7696889,\\n37.7697153,\\n37.7701244,\\n37.7700604,\\n37.7705491,\\n37.7715705,\\n37.771752,\\n37.7707533,\\n37.769866];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lats[i];\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var lons = [-122.4261215,\\n-122.4219157,\\n-122.4199623,\\n-122.4179074,\\n-122.4155876,\\n-122.4155521,\\n-122.4163203,\\n-122.4193876,\\n-122.4210496,\\n-122.422284,\\n-122.4232717,\\n-122.4235138,\\n-122.4247605,\\n-122.4258812];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lons[i];\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"markerType\":\"image\",\"markerShape\":{\"shape\":\"tripMarkerShape1\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerIcon\":{\"icon\":\"arrow_forward\",\"size\":48,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerImage\":{\"type\":\"function\",\"image\":\"/assets/markers/tripShape1.svg\",\"imageSize\":34,\"imageFunction\":\"var speed = data.Speed;\\nvar res = {\\n url: images[0],\\n size: 55\\n};\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n var index = Math.min(2, Math.floor(3 * percent));\\n res.url = images[index];\\n}\\nreturn res;\",\"images\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\"]},\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"rotateMarker\":true,\"offsetAngle\":0,\"showPath\":true,\"pathStrokeWeight\":4,\"pathStrokeColor\":{\"type\":\"function\",\"color\":\"#307FE5\",\"colorFunction\":\"var speed = data.Speed;\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n if (percent < 0.5) {\\n percent *=2*100; \\n return tinycolor.mix('green', 'yellow', percent).setAlpha(0.65).toRgbString();\\n } else {\\n percent = (percent - 0.5)*2*100;\\n return tinycolor.mix('yellow', 'red', percent).setAlpha(0.65).toRgbString();\\n }\\n}\"},\"usePathDecorator\":false,\"pathDecoratorSymbol\":\"arrowHead\",\"pathDecoratorSymbolSize\":10,\"pathDecoratorSymbolColor\":\"#307FE5\",\"pathDecoratorOffset\":20,\"pathEndDecoratorOffset\":20,\"pathDecoratorRepeat\":20,\"showPoints\":false,\"pointSize\":10,\"pointColor\":{\"type\":\"constant\",\"color\":\"#307FE5\"},\"pointTooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}\",\"offsetX\":0,\"offsetY\":-1}}],\"tripTimeline\":{\"showTimelineControl\":false}},\"title\":\"Route Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"assistant_navigation\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"titleFont\":{\"size\":null,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":null},\"titleColor\":null,\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}"
+ },
+ "resources": [
+ {
+ "link": "/api/images/system/map_marker_image_0.png",
+ "title": "Map marker image 0",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_0.png",
+ "publicResourceKey": "LPbcriZ2v053mkWb33T5JdK7Agkt1jGg",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_1.png",
+ "title": "Map marker image 1",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_1.png",
+ "publicResourceKey": "TwKYnwJfaCIgDbJsetgcj3q7AYK4HUSA",
+ "mediaType": "image/png",
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAFAAAABdCAYAAAAyj+FzAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAH3gAAB94BHQKrYQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic3bx5uF1Flff/WVV77zPeIQMJIYQxYRRBpBGcQFEEbVQQUXB6xW5tWx9+Cm07IYIitiJog2P7qu3UCN22aDs0KIIyg0CYyUhCyHiTmzucce+qtd4/zrkhQIIogz6/9Tz1nHP22buG715D1VpVS/gLktnZjg3P2wGz3RC/N9hBCHtjMgOxGjDZv3UAkyZim4EHQO4i2j3UkxXUb9kkcrb+pcYgz3aDNvLjOah7BSZvRnwLX/8D2axILM0Dtx9ODgGGt/P4GNgtqD6A764i3+iJ44dC9CD/jaRXyqzXrHs2x/OsAGhrL9sB8W/B7ARKwz/H7TAE2btAZj89DbAW6X4HHRknnzgW7KdU5fsyeMKmp6X+J6BnDEAzhLWXHYz4c3GlG8l2HgL3frDsmWqzR5KDfpnOykkIL8D4OHPecIcI9oy09kxUaut//CKCfp7qtMuwwb8DnrOd5nOcXIfJCryAOgEpASUwh5HiBMwKEAW6YF1chIghthtqL97uSxG5C8a/TXvsBLx9VGa/8Yane6xPK4C2/kd7EuWblIZ+itU+BMx93E1OFmLchqQpTmaDA0kAlyDSwSwizkAFfN84RAfOQASLCVACDVgAESOGDZjmSDwEtYO20bVVaPMCionjiPoe2eXNy56uMT8tAJp9I2X10GfxyQTJ4GtADn3UDd6NYv5niKtgyQxcYjinkCSYi3gHikeSLhDwXjHzTNlWB4hEYnRAgoUSmIIqxAQsYEFQA/JRNHZw+lqiTn90R/VGYuNKQl5j/cTH5JD3FE917E8ZQFv23b0oJf9GNnQd8PFH1y7rkORKLJ2BTxJcAqQOSQyXGEiC+IB4wZxHXMABeIeZQ3q/MBQRhdiDNGqCaMSiYTFBKIiF64FYKBbAigKLD6PFsYjt+uhOcwHdiUMRe5fMe8uSpzL+pwSgrfje+/CyK1n1dcBeW/7wMoZll4Kfhy95SARXMsSDSxxkIInifIK4gHpH6kAlggjOOwTBJEFV8FIQDcQCGIh6ighOFdMELQKYYF1Bg0KgB2ZuxG4kFqvw+cmoDG7V/cXk7Z9iulx2efvX/1wM/iwA7bLLPIe1v4m4RSTJuWDJI/+675OUB3ClCmSC9yA1cKn1dF3mSFLDRJEsAYk9wJxSTEzQGW3TXlEQC6G7sde/0gzDZ0Zlt5Ty9Arp4GBfnBWCgxghOkIhkBsxGK4QYgs0gHZAQxPtNLH41q2GH4jhNFRfwrzK20ROis84gLbkohLp4C/IuAXso1v+UFmLlK4gLc/Bl4AMfEWQDHzZIAFJhKQMLgWzhMayjTz0kyYbrt2T2Nq3a5WFE3HWqgYzNxeU81ao5ADVpJ2ldLI6G6YN+o3zStI+CF+9n1kvWcYub6hR330mIgEtIHSAYFgOVkDREegYmoO2QfOHCJ3X4thqDirn0rUX0Kr9rex/Uv6MAWhLLiqRDPwPafdBxN79SC3yS1y9S5JVoQpSEnylB5wrgSvTAzCt0Vqzmfs/32By8cs2xZ2vXGJHjo/KnlVz1aEsLZsXKVQkpIlXTHo6T8wFjU5iTELULFpEQmN8Gsub87lq2gy/5pUM7ftb9jtjgNJO07DQQHPBerMeYqsnztaGmBvabhNbVYivemRwfJWitADktbL7OztPO4C25KISvnolWWMN8OZHasj+L5Luih9QfAWSmvVEtwquAi4DyWay6aYHuO/8A1phYOkd9sbVTbdgVlYqJVk5wycpWZaRJY7E+2i44L2Y0Zv8CkgMCMQ0xOCKGAndnCJG8m5ueacba7pkw/Pksp2rvrkH+3/kXqY9fx+cbiA0HbEDdIzYBmvRE+12l7y1GRffsRUj/JBubR6xdbQsOK37tAFol13mOXjzzymNrQL+/pGnS1/FZXvg64IfAKkqSTnBVRRXEaQ8g7HFK7jnU/NHde7tC5N3RpKB4WqpKuVq2cpZSpJlkvrMSqVEvE8ty9JClSJJJIr44BwWY0xjNC9iWZ4HH2OUdrsjzpm1Wi3X6RTW7XZpdNviwsToQfodP92tPpj9z1nG8Pxd0M4mYtugEygaGdpWQgO05aCxFI3/+Mhg5WsUQ3vx0Npj5GVnh6cHwCVfv4x0ZAViH9py0WcXIpUDcPVIOiBIFZIauKrgKg6z2Sw8c0m72XK3uA+MuMq06dVqzSqlTGq1uqVp6iuVsqRpGtIsM++ceO8NEQQi0AGmuKAElAEfYxTAQowUeZBu0U3zTkc7nVzzvGOtVpdGqyHa3rTpUPvSjpVyreDgc/dGZB3aVrRlFE3QlmBtoxhXtPUQlr/nEVTceRQzd5c9/+GUpwygLf7Ke0jHykjrS1suavpV0vqeuDqkdUEGlKTsSQYUqcxmcvky7v38c+6yt/xqvHTwvEp90OqVTKrVitVqNcrVasiyTBLnUhEBGAXW9ssqYD2QA1MT3bQP4g7APGBOv0w3M4kxxm5RxG67nUxOTkq7ndPO29YYb9pQ55a1z3U/Opb9P3wXA7vtgbbXow1HbBk6aYSGoA2hmFgK+ggnxoEPYENR5r/3y382gHb/xQeQFm/Gr/0ImOuD9zXS6p4kg4ofBKkJyYD1gCzty9IfXlVsumuXm7NPrC6Xh6sDA1XJyhWmDQ1ampa0XM689z4FNgP3A0uBdr8vSk/veXpceFj/9y39/6ccAq7/vQLsCewHDMUYQ7fbtWaz6VqtXJutCWt3OtJubBo7rPjMXsmMA5cz/60vJ3TuJzQEm1TiREJsRsKEElqrcEWfE0UJcz4P2Q9kwfvv/ZMBtD98I6XW+QXZkj0Q9uzdLZcgA8OkdYdMF5KakgwK1AWfPof7Lr52vJk27qufVq0PDKTltOoGBqo2OFi3crmszrkB4CHgAXpc5vpgSf/7MFAD9gVe0AcHYAlwK3Af0AQm+gDrVmV2H8i5ZtZpt9s2MdGwRqNJq9WUVqsZ9m1f1BqqxAr7v++laH43sWHEhhAaRhjzMBkJExNgJ/XhWUK+YDVx9FWy/9nbnN4k27oIQK39FUqLFmLhlX1beB+U67jMIRlIGiF1kApen8PCzy0cYZ91Dw2/dadp9ZqrVWtFrVZLa7UyWZaVRGQc+D2wiR73zARKqhqccwqIquKcmzSz14rImUC135uWmZ0nIrf0fw+oqjjnhJ54d4AGPU6dJSL7VqvVoSRJOlmWSKmU+KxSssVjH6zOa/5g8453nn8bzz3tEHzpDrTrECeQKnjBJSmhdQ+izwEWkNx/Oez9ReB924Jpmxxo91+wl0rjjc4vO7d3wQKu8kP84CyyIUHqgh82/ICQVA7k3m9evybstXb98FtnD9Xrrj5Qp5RlWq/Xnfd+AFgILANSVfVAHZjVf4E1EZlhZs8VEWdm3xCRD/QBfqSjIhtU9SIR+XszUxG5T0Q2quokPV25EZjsv4wA7AI838wajUaDZrujk+NjyWSzyZzJ/3pwTnrffPb5u79B2wsJEylxUonjkTAhxPENaOcURBIADfM/6bT+H7L/6Uv/KIBmiN1z/tWS3TqESM81JMlXSQb3QIYgGwQ/CH5QoDaflT+7cXNjYGLl4Lt3qA/W3WC9rtVqxdfr9QxIVPVeYKNzzoCOqjpVLSdJUg0huCRJusC4qp5FT7wPAlYCP1XVkf5zs4DXAbsCtwN7OOfO7nNiRk+EWzHGwntvzrlSn0N3APYzszjZbE50Wu1sfGKcZqNtu05+ffO0aitlj+NeSphcTGyATkCcgDhmhInlWJziutst/E1D9v2nIx/rmH08gPd+7ijSpc/BRnpW1+RWXGUd6WCGH+5xXjJo+MFhRpcu6q6+a2jJzHO65UpdBgeqbmBgwMrlciYiqqqLnXOFqk7vc4WPMRpQTZJkB1U9FEhF5Fwzu9DMbnfO/VBEXqyqrwGmHKW5c+4XZnatqr5dRA7y3p+uqh8DVFVvE5H1QAtwIhLo6dSNzrkKsEeMMQ0h5GNjY9rpFH5iYizutemTrjTvuS0G91iANcYJE544HoljQpyMxOZcsAN7SM3+AHHPhbLvP/9ua7zc47jP7OPEhz+KdcA64MIdSJKBBxFFpPcGigmx1Tc/f9G0syZrtZofHKi5en3AyuVy0gfveufc5qIo6mZWAWaZ2Rzvfdl7/3AI4SozGzCztqqeG0L4ELCPql4QYzzezFRVH1DVB8xMY4zHq+qFwHwzOyOEcF6Msamqg865K2KMK0XE05vaTDOzUgihqqojqnqD9z5kWZYMDAwmpVJGvT4gS6af1baHbzyY0FQwD67nzBVngMNz4xYcbOXHLMRzzEy2CyB3nfdicQ/8FOvMRrsQu78A2xWJYCaY6zGwxf1Y+quwbNrp19QHB6u1Wo16vUalUk5EZGdVXaiqQ3meO+/9BhFZrqqr6OnAE83sH/ttV4HvAB3n3Plmttg5d6Zz7sNm9i0zu69fvqWqH3bOnWlmy83sAhFpiMh/0JtgO+/9e1X1TTHGATNbb2YPOefGVbUEDKvqQmBOqZS5wcE6lXpdqgODlWXTP/Iblv48RePeOOt5wrUvpZrvDt3/7WMxS9zi/+aezx2+NWSPssImeqbY4j23XJDSeszvBAJODFHBWcrIA1c1sr1zN7DHjqVKhUqlQqlUcsA8M3tQVV8MNEVkjqoGEVkLPKCqVwEvA3Iz+yDwG+BMEfmtmV0hIifHGM81e3T8Z+p3jBHgTu/9h4qiOM4591ERuTKEcIb0JCMTkSuAGWa2v3Nujpk5MxsFEhFZ7ZybWyqVHq6pOrQaJuPOcxvNve6sjy2+l+Gdd8EkIAJ9LFHWYFMLokXvN9vzQWCLE2ILgPbA53bS7qrfi3WP7l+6GvxcJPRG4Kwn5LE1l/FVu63e4fM31CsV6pWKZVlm3vshVb0S2BBjTAG890PAAar6ahE5EjjPzOohhLO89xeaWQ6caWZnAS/vA3Wlqv5eRFqPAbEmIkeIyCtCCAeKSEdEPqaqfw/MVdXTReRCEXHAe4B6COEqEbk7y7LNIQRCCEWaprO894eWsmyTxuhDCG7NzH8s77X+jBcyML2AsBIxQ0xwKgTdCcl/h9kRwAJh/fds4blz5aAzVz+aA7vtE527fi7WXz87/wCwO+ZAVIgFuODZuOqmkfrrJirV+k5ZkkiWZWRZtqOZbYgxHglUnXOJiKwMIdyRZdnVIYTXmdl6EXm7qq52zn1BRD6gqv8CnAtc65z7sarua2avF5GjeQyJCH3R/IaZLXLOvSHGeB498f+AmV2oqqu89/8nxjgmIrPSNL0qhDAjhPBiM9sdiCGEpvd+JE3TOTHGDZVyOSpUNtVe/fMZI9cNMn32LliMmBnmIs4ghnshHtHrybU7Iq9/A3DRFgDNEL1l/U6uUhzRN8wbUD+vF8PAepecoHEandburbkvv7mSpZTLFSuXywnQCSFc1xezIefcc83sOOCQoig+18fgy2Z2gZl92cyON7MvAb9wzl2vqqfHGKfW2rmqLnTOPaiqK1XVJUkyD9iD3grlPX0wNwIfU9WXmNmXzGyVmf1cRN7rnDtdVS+MMTpVfTcwA/gVsFBExsyscM4dVy6XnRmxiGqTw6/cYcbqKw9nmo6CbEREURFIDPW7IGETyAwIL9PO+oZZ7516gLOP/budTZfuIXp3FT89Q9yvcaUhpNTzKLuy4TJlcmLZWPqi+2P9wIFqtUq5XDLv/d5mdqeqntLXQaNm9gBwrIisMbN5fZ10sHPuJ8C7gbu9919X1XeZ2dH0ph8Xq+p/A8tFZJr1RObFIvICM9vVzB4Efq6ql5rZXSLyfOBvRWRXVf2EiMxxzh3vnPt2jPEdgJjZcF+kZznnvuGcK6vqwar6ehG5XUSeZ6YbBcOMxIrx20v5is2UXA0NPYesRgfqIf4Bs5l0H/yDWHUZq45eec63/jDpALTg1c7dNouox9Nafh1KgKRnrhWPakKINZrtlzSnv7ZWq5UlTb1kWVYG1hZFsczMFpnZ61X1VBFJrfeKvm1mL+nruLkhhKPN7MNmdngI4Twzu8Y59wHgWjP7sIhcaGbvV9WXqGpZVVv9UlXVl6rqaX0996GpZ/v68jwze4GZfTiE8BpgjpmdZWYvCSF818wwszSE8E4zO9HMVqnqMhFZm2VZ4n0qWVay1ow31Gg0DydaBTMH3qHiEGeoy2kuv4YY3wy3zUL1WOjLq133ritxP3w+2HSgQTrwc8p7DeBqDl/ueVxi1o7Nimza5VNFqVqlkmWSpulBqvrrT3/603bTTTed6pwrPVZ3/TWRqnZf/epX3/ye97znZzHGwVKpNEtEXhVCuL3b7dLpdGzmio9XZKBb4PIKoQXSNEKrS3dJh2LytfRiFqMWTvmDe+m3X5WYne30+kUbnFkvCC38L0U+HdZ2KO9uSMxw0Wh3xyanvXHCe79TImLee8ys1O125998881HOec46aSTfrd06dIlCxYsWHDvvfcu2n///fdutVqNPM8LYNqvf/3rI733d5xwwgnjU4MaGRnZuHHjxk2zZ8/eYfr06dMvvfTS/VV12pve9KbrZWrSvhWJCHfeeWdz8eLFr5kxY8YtL3/5y1t9cOyBBx5YMn/+/N2yLMtGR0c3XXHFFUclSTI8MDBw1THHHON/+ctf2hVXXPGyer1+/ymnnHJkURSrsiwrpWlqeZ6Lc07Gh49bOty4ZJB6UcXFnh+gvcbQfAbGlcDrwaabdcfMznYJt66Yha2+A3hLr4tuBGQIbWe0V7Yp7xlJY5mQPjfUD16YJok453DOzVTVmycnJ1/kvXdFUXROOumkF8cYJ0XkkBNPPPEgM/secBpwu4hc8etf//rI4447bvyEE07YDNwAvA04cqt535L/+Z//WTk5OZmedNJJP6PnsgJIY4xVYHfgqGOPPbZ26qmn8sY3vjE/4ogjqsB/xhhf6r1/N4D3/lxVPfG6667b0O12hy+++OJ9yuXywAknnHD9qaeeuujHP/7x604++eQlIvJKVf2ZiMzy3m/IshJh2iE1Ri/dn8I2o/lGOg8NQLeKCuDWYr04l0tX38rNuoOjU+zpdHHSW2EAKiWE0PO2FTW6K0qE8fWmyXBWqdfTNLM0TUVV91fVDVtzhqp+xjl3HHC+mVXN7FQz+5CZHaSqHwVYt27dGjN7jZmdr6r7hBC+r6qnq+q5ZrYsy7LEOSchhAtCCF/ql/PN7BwzO8HM/tBut7/Sr2uVqh5iZuc7514FnA98N8Z4ppnNz/M8AmRZNg04X0SOPeWUU64WkTkrVqy4sq8bVwP7eO/FewdpdQjxMwgTG2g9tAMxr6BqGBGTdAtO4QFH7ua7qLoXrjHvEQBtGmopph6LghZCc023M/yCr5mZgk2tCBpm1th61aCqJ5vZ9WZ2DvAZoGlm58cY/11V/y/ATjvtNBe4S1VPB74FvBG4EDhTVY9xzomqmpn9j6r+SFV/FGP8jZnd1Tcmx1er1dMAhoaGZqvqN/ov4D5V/ZCqvkNVvxljvDTLMm9mOOcuNLNPm9mNr3zlK1+oqvHKK698oZmpmY2LyIRzzkTEvEinW33ORbTXdqEbUQUjwUiINn0LTtaYi9meiWg8ACkO6GPQQaRCtBwfCyIlEEOHO6jf1bmkSJIkAENmtjaE8KrR0dHvAh/pc+FewMMxxt+IyDkicq2q/peIvK//tlm+fPnyGOMxwIV9Sz1mZpc75xYCRbPZfDO9KcjuZlYDMLMNZrZSRH6rqqsnJiYAzldVAd7br2d1COHMNE33VtW/FxFijIv6n2c6537rnFNVPRxYd9ddd80xs/NDCMc659aratk5twFI1dUXEJM2lnchRlwUMI+TKpCjZEjYT0PsJBJ1fxwHA2ByD6KG8wENAWcZBYKUM4b2ayeJK8eolqZJbma5qh5YrVbX98FLb7jhhi8fcsghpyZJsjbG+Enn3Dn9acyXVRURef+ee+65B70A0b/EGDc5514A/G0I4c0A1Wr1rna7japuvadwd9VHtkEPDAxM9EV4tYicEULY0Xv/9yJyboyRGOMX77vvPpfn+SnOObrd7qdKpdJbzGxP4JMzZsw4ft26dTNijEc65w4ErnbOdfO8GEqSxJLpB25ktFzDaUSDoNERDEQdjvsxDgQ7VFRDglgZo95TZPogJlUsRpCIRkEAqQ672ryuiQQRSVV1DzObEJH/k2XZJ/uK21988cXv74vyHqr6MTMbT9O0VSqVjqHn9OTyyy/fddWqVfckSXJ4lmU2PDxMjPFmVV3TbDZHNm3a9Gag+pOf/OSHSZL4iYmJep7n3YmJCdm8eXPsdDph06ZNOwMvufLKK1/5+9///tA8z2tFUQB0RCTz3n8QwLmesynP83enaTrovf+AmV04f/7862666aY9RWSWiLwjxniqiOyRJH5pURQastkxteowRW6g1tsVZgZiRHsIOBBjEI2VBNXe3kUAtSaQoma4QsEJFoQ0rbq0hjmX9yTKhoHRWq32yRUrVnyn2Wx+qlqt0g9R4pyT/pywBAx1u48E+WOMu91yyy27sR1Kkt7y/PLLL3/L9u6ZIhGZ3e12Z2/93LZo2bJl//2c5zxnLvAl4L4sy+7tdDoHxxg/MzAw8KlOp7PUzIaT3to+NwYMyUpYXiBqRAOHEdWDTiJTLkHFoYVsUYxCCwgQlRiFEAQtDEmHVBLnvS9UNdCLV7SA3efOnfuqkZER1qxZQ6PR2OJ6+muiJEkOFZHXmdmFIvK5I488UkZGRkoDAwOvA3YVkWY/LlOYmSVpDciGevtrQi/Or7FvPGg/YnCDc72NnvRKdEqkidDEaKK2DmMN4hLvk2az2eyISINe7GIDgPd+N4CiKNi4cSMPP/wwo6Oj9EXqr4IWLVp0D/B3RVG8Kc/z7+y9995Lgc7atWt9/5YNwKoYY8PMmnnezXGuRKBFpAk0UBqYTqDoFrxMSTDskTCJ1VCGCQgmZg4vZoZnMoa8UqkMls0siTHuY2YPbauzMUYmJiaYmJggyzJqtRq1Wu0JReyZoKIoaLVaNJtNfvCDH5RGRkZeOmfOnN/ssMMOyxcsWPBGEVl37bXXPnDyySc/L8Y4R0T2EZGFIhLE2ySiE5jMwBCi9QL+Ig7byotvWALxkXi/yRCQRMU5jFhYCXGaxDgeuk2DctL39TXpBcCfkPI8J89zNm/eTJqmU55rSqXS0w5oCIH+epZOp/MoCdiwYcPJ3//+9x/3zNe//vWFJ598MmY2ADRU1RVF4V0+TmahEYxpBBVBAog6ByJWe4ThIglqG3CyCmwepkNmKIZEwUV1DlTF8gZx3GBGGmN0fZ3x+B34j9Bm59xD9BjdqequRVEMbz2oJElI05QkSciyjCRJcM6RJAkissWCAqgqU/NIVSWEQFEU9L3M5Hk+NbnfFq1yzo1OratjjPvTC8azadOmV/bvmWNmDeecxBgTjZMSi9CIQYcR5zykeFUiINQQAZEHzeLaxDTeLUYKzEPcAWLcY6ZmipppjMFI8tEGzeXW9TtnWZYCrFfVA+jtBngUOefuEhEdHh5ePnPmTN2wYYMbHx8HWNV/BmDL4J9BUu/9LWaWzJ49e/nAwIB7+OGHa51OR60XtdsdyNevX39zrVY72Dl3dwghiTGaH1+UabGpFQszEg3OgSgizjnE9u8bkdscdqczC/ejyeL+Mm6WYQ3MopmoBefMJOk21laTxuKKmbrY83aPmlkSQrgeoF6v/wxARO4WkearXvWqHdI0PcE5d2KpVDrhqKOOmiEiLefcfc8kYluT9/7mUqk0/qIXvWh/7/1JaZqeOG3atGOPPfbYrohMAmumTZt23ZIlSxaaWaqqm83MVFVKnQdrne66ajRJXHSiEcQkGjaO0lvOkSxB7QHnE1lCHFyLWc+3H2l5xKtapqYuRpNue6ySFSsXhKCxv05tAHS73dWPEZ3k2GOPnT4yMjL3C1/4AqVSic9+9rOMjIzsfNRRRw3S24X1bJysXGlmpec973kvnT59euUTn/gE8+bN4/TTT+fee+89/MADD1zhnFvfaDRmttvtrK8eGj3VECzLVy0IjfGKRiOqOtRSU0vFaE3hRJi2nsKWJ2xuL9fa7Nc7t7HXtLNGjOwgaoWpYEoSY3emaPeAPG//CkoVEcmdc4QQ5l166aVXNxqNI5MkuQ6we++99yWHHXYYt912G7vvvjs33XQTCxYs4NZbb91XRK53zt1mZjOfcPhPkURk0+zZsx9cuXLlwcPDw6xcuZKhoSHuuusuDj30UFqt1jELFy5cVhTFXO990teteVEUxJi3he4+MXYjgDnUjAg4orWm9nJo2GGmm2bLEnnrzRP6k8MPe8QS41EwkwTFFIsaRIr25qt8vmY8uHlV770lSbIaOOKFL3zh/y5evPhKgOc+97nV733vexx++OEsXbqUiYkJ5s2bx2GHHcbvfvc7jj322MV33nnnI6HUp2nSLfL4PVJHH3307BtuuIHjjz+eK664gvHxcV7xilcwOTnJ9ddfnx1wwAE3NZtNt+uuu74GeKgoCosxGq1VY3lr9CoN7CjO1KI4ExEUxZNN4SSUXiwvu+YT/bCbbRLldoSDMTnSO1seogU1cTEXb2YyvvaOscHB31dG0zdrjFG893cDx+yyyy4bP/OZz5yqqqgqt9xyCz/72c94xzveQQiBgw46iO9973ssWLCA973vfe+cum/Kom5tXacAfeznFEBbfzrnEJFHfe87erdY8F/96lf86Ec/4rTTTuOaa66h0WhwzTXXcPDBB/O2t73tnd1u99uqOlNVfwVYu92VOe3rapMjCyei2lwfxXDOnMTgRQSTl4OB8QczVkJvcyPnvGnuJDI+ihWvAKaJ2kIzqmoiqhBUpNMZr8/ace8j1snzljrnvHMud87tF2Ospmk6bGZOVVmwYAGXXHIJeZ6zdOlS1q9fzx133MEZZ5xBrVZ7FHjbA3Lr348t2+Pex3KhiDBnzhyuuOIKGo0Gq1atYs2aNaxfv54PV+O33AAAECZJREFUfehDJEkSut1uDZihqre3Wm2X5+3unPy3x2548Kpu6sV7QROPpIL3XkYxDu8teev/Kjrjl+dctnpF71W1uFnjzvVHnIV+rSBCRDDBDDWl3GmM/LrUWTbS6XQkxqiqehuwT7fb/Y+pAe68885cfPHFpGmKc45SqcQFF1zAnDlzHgfSE5W+W2pLeTLPPBbsww47jLPOOot2u02e5+y1115cdNFFZFlGv8/7qOqt3W5Xut3cyvmDGzutkSs0UlLFMHEoiIlhbJjCR8PcIWLj1p4o90kvPegaSe/bC2wOcLsZY50CaRdYNzfJI6gbWL3LIe+euyh9+4ZSqSKDg3UnIieq6n3lcnm3vsf4cQP+YwA8lgufSISfSGwfW7z32/s93ul0NojIfFX9z0ajRbPZtP35wZyVt/zbKomTcyspVkqFSoaWUkSEGcCBmKy2uN9id9LCl8NWu7NU9UKsdHEf5YMFNgjgpLcBFpCiPbkTYbKgvXp9nnes3W6rmd0I7Nduty/dlsg9FpjtgbctDvxjL2B79W2v7anS6XQuN7MFwPV5nlur1RLXfXi9FRPtojM5B3D9bXwighNYh3Fgb65culgtfmEKty0A+qHWFZrvNG/LPEddI3WGiLkE8JiKt/TB2340viD9/X7tdtva7bZ18/xhM5swszfGGK/bnu7a3oC2J7pP9P2JxPaJ9KeZEUL4dYzxFBEZy/N89cTEpO902rpP6boDlt98adNjPsE0wSQRIxEDle4ULhp3nu/Xt696HIDy6qVd1Asql/ZMdXy7F5ssiUURvDcRr1i3NT5DWxsmKvnitZONhms1m1oUxdVmVu92u0NmNj4lftsb2JPVgU9WBTwWzG2B2O/ThjzPZ5tZmuf5NZONhmu22jpkS9fE9sho0R4fRhDvRRMxSxLDY2OIvq0nmXIJKm05bWn3cQACOHFna5hzX1+MM8ytS5yId+AEBMErsui671b3Gbjn2G6n02w0GrRarY6ZXQUc0Ol0rrZetOsJOWFbYG5Ld/4xffpk2zGzTp7nd5rZc4HfdDqdvNloWrc12dqrfs+rF1373YokvU2ECeC9+FTEsGQjPbcfxLlLXJJ/+lGYPcr0n3LPerS8D/DbHhfaWxOxsVKCeYdkHhMvRAvDS2/58Y0H1X9fGZ9o0Gw26Xa7m+htAH99nuc/2NoIbP35ZET6sRZ4ayCfTB3barvb7f48xvhK4A+NRmt0YmLSJpsNe/70m+tLbrrsRrUwLe0fB08F6Z2rtzGI7+jpPq6MoTRfTlo6sl0AATq+/U+az76h/1AVlZg6o5xg3uNSj6WefHz9kj1orrCdSsvWTUxMyOjoZtrt9lLgTjN7ewjhB8CfxYl/zpTlCUpeFMUlZnYicHur1Vo+OTnpGo0GOyVLVkvzIR1bt3yPzBNTJ5Y5LHGQelPE5T1JBHTObb7onvFYvB4HYO3kVWuIpRSTb/aXLSd6WJQ6c5nHSg4p9xqS+67+9tCe9QcPk+66NZOTEzI6Okqz1VpsZjer6luLovjZ1jrxyXLlE80Dnwy3TX0C62KMv1PVk4GbGo32srGxMRkd22x0143sOfTQYXf+5t/qpQQyJ5qmWOKQLDHxxiLM3tTXfV/TIknlnSselxXpcQACuEp+Tsx3HERp9Pz/7kWZp1tySEkg8bjUiROscsvln1v7kl2Wvrzb2LhhbGxSxsfGtNlsPqSqV5jZ60IIK1X1uq3r/1PB/BNBm6Lr8zzfqKqvUNX/nZxsPjw2ttmNj08Smps2vnju4iNv+cnn1qVi1VRESg5KhpQTXObIUXdEP/bRiPnsHZzaJ7aJ1bYuykkPt73xbbR+Zu8N2P6ibqycQrmMlZ1ZKYVyIi6VOHzzjz9/99ELlh8dWhvXjI6OsmnTJhsbG5sIIfwXsIOqHhRj/Da9I1mPom0BsD0An+iZrWg8hPDdEMLzgel5nv98fHx8cnRs1MbHN2unuWHkmL0ffMXNl3/+LtEwVErFlRJcmpiVykgpRcXcJrB9eqJbP9OL/5q8c8U2T7E/4WnN8J2df+iTtQKc3If7KyGyf7ONNbqUOoXQbFtoBaJKZc3hJ33igF89sOPVOcNzhoYGQpqWy/V6RUul8qCZHqGqS4CFMcZTVHvO2a0t67asLvC41cTUiuIxn+qc+w/ghc65nUTkd3mej7dardhqtbLJZlMzHXv41c/Z8MqbL/nMIrHG9EqCr1TEVVOjVibUM0g89wFTx14viWHHkLxz9du3h5Hf3h8A57x++i9jrLzV+ZZgzACe6+B3HqYhaIw4J+JMkbwIQyvuvHryZS9//i6tXB9YuSFMC0Xs5W2KoeVElgCY2dFmttjMfk7v8M1g//qWds1sm56XKRAfc20N8J/0gvgvBO4piuKOZrMZx8YmmZycYPPmUXYb3LT+pXtu+ptrf/iJEbHujEpKUstEaplRKxNrKaTCCoR3AB6RJTGfPekle/s5Px3bbuzhjx64bn9tx92yrPigS8b+AcgQNqP8ohuZ28zxrQ7SynHNDtIulE5wxaEn/PP6WJnDT24faJfSSlKtlsvV6oCVKxmlXgCpDOypqrur6m/NbF2Mcb6qvlh7Z+keJbZbr3+dc8E5d4OILPXe7ygiR3rvV5jZgzHGVp7ntNtt2p3cmq2m73ZbjeOfN1nz3TXx1h+fv2PJaVYtOStnSK2C1lJirUxeSlmPcQwwo7e9b+gifPlf5e1rthm+fdIAAoRvzXytSHcv51rn959aCdzcDcxsdPCtHNfuIO0ca+UaWwEGZu+16pDj3vc3Ny1Nfr1oXW1WuVz2pSyxUqki5XK2dSQuU9VhM9vFzKap6gozW2NmDVUt+qcvMxGpi8iOwK5JkkyIyEOqOm5mRVC1kOd0Ojndbod2u0On0427zypWHLlv53X3XfWD69ctv3V+NcPXMmeVBKplQqWEq5eQUsZalBfSOw2PhuqHTct3J+8e+dUfw+bJZ+345vR3kbTrkE8dR1iF8LsisGujQ2xFrNUmtrvUmwXdboHP1RUvPOmfNyYDO02//BbuGu9k8yqlMlk5w7uEUlYy55A0TcQliTkRw0xxHouxd2Cot3PT+kcbxEzEQIIG0SISo1qet6UoAt08aLvb1uGyrj/hMD0gTK7ZeP2PPj+zlGhazoiVBK1lZJUSRb2CVlMk9ayldzJ+DwBi9gG01pV3b3xS2Yz+pLwx8RvDn3RZdwzrgyhsxvhJMPZq52izLVmzwFq5SbdLaEeNndyZlOutw97wwU5anz39ZzeHO9dPMCvxaZp4j08z0iQlSz1Kz/XhxHdxRFR7ESvnPIqPUTPV6LwX63aDKLEXEy6ChVDkc4Z15G8Pyw4qJtZvvOm/v1ixTqNaTpRSySXVFF/NROslpJwY1RKWeBZjnIAw1BtP9gGKclXevfmzTxaTPzlzUfzawBnOhwSXn9dPDpZj9i1DDmp2oR0Iza5qJzhrFfhuR5NOQdENTpPKwNiBx5w6MXOXfY9YtiZcef097e7IhE2XLMlSvDjn8IngncfMgllvQ7JzIoqkGsRUC4kWpCiCodadPuw2vXi/cmXBTslRIyvvvfauX/37YNGdnFbNVDJHUi67WMmIlVSpJs5XMqRWwouzuzB5J5BhqMbkDGflivzD+JMG788CECB8tfZO8cVOzsd/4pF8pz9CGOjm1DoB1+yStgq1dk6RF851g/puTtE10iLSGNpx95EDjjrFTZu928taOXcvebizfMWabvfhTV3f6UI3qqC9o6WC0ywVyiXYeUYp7rZTWpq/c3WPWsYBm9c9ePU9v7lEx9Y/uEOaUMs8oZSQVTJXZF6tZ22dK2eESkYsJf2NU0I/LwKjhPSLEb8i+YfmD/5ULP7s7G321cphEXuv98XeiL2gf3kxwu8N269VENtdF9sFaadQ6/aSDaXdSMgLfKFoEUhiJCcpjw7OnDu+497P1xk77pqV6tNr5drAoE/THUSchLy7odOcnOg2Rpub1q3M1y26zU1sXD1E6ExPElLv0MzjspRQ8iSlhKKSkpRTJ6WUolZSygmZiNyH8VKmMs2Z3BhDtlyRf83e17r1z8HhKaW/m/jywIy65N+XJP4B9JGljsiPMTSiO3cCRTt32im01A3keeF8oap57nxhWsRA0lEwJQQlMcNCz502ldqk109BEwERJHEEcSQlB6knJN6lmdOYlpzLnMZKQpZlrltJ1ZVLpB63CiPF7PgtfTT3KYv+b0TKb5F/HN/852Lw1BMwXobX9dmZmJrL9K3Agv5fOSr/jmdGVJ2bF3Q76nxRqHZy52LUmCsuj06jqu8qgjmiEtTUmEqF0vumgDlx4h2Jd2qJgHcuJk592ROT1PlSopomzpW9xiwl896tQumAvZlH0gcs1sJd4oTAxnCenP3Udko8bTlU7SvMN/NfFW9XAJ9iKmVJL/vkDzEM0V0Ldb5bqObRuagaikBWmLMQCKAuGAFDowKO3gpASbwDBJcICeI08SSJU8kceZKSpGCl1EnqNWJuBYLD7C1bsmBCC+MTMcqrfIjvlQ+y/OkY99ObhNaQ+K8ch+OdPnG3KXwco7xVY/eqyY3iqRk6xyJZUFeEqC4oFOYExaKh9JzaPSMiGOLECw6HpKKWeMyLI/XqxVMIbq1Fmk7scIP9txphxymfiUGfD3zL/3/84ulMifzMpEH+Bmls8yaMU8y535rnNOnP8rdqeA3I1eKsbSolB4NRdFgMb0Y0wcR64mXSSzggggeCw40rTIizrqlUwF5msNOj+gArPVykhR6t8P27q1x2yHt42vcdP6OZzO1sXBjmKODDqvxeEjfN4ANP0JlRRG43GBdljN5OWDCrmWNYYAizgw2mP0EdXzJ0s1NeivDZZDNXP1U990T0rOXSty8wsyuchXMrTLjgmWhDjDNQ3a1kfEr+iY3PRBuPa/PZaGSKDKR9AZc47x6KulUuwqelbrnIqe5c+SdOFJ4+HffHaJse6WeKBKwyyVtDoQca/Eb7qbSfarHItVbovpUB3vxsggfPMoAAcjahW+KNVlhDlZVPGcDIWjFbF5U3yTNgJP7oeJ7tBqdo9F84wMOpivwjsmWS+6dSMLUvpsJ3Bz7CdpMkPpP0FwMQYPyznByUHXFy4Z9VgdrpzjE+7aN8+2nu2pOmvyiAAJvO44tmboNh5/1pT8qnPTpj+se3nRjx2aK/OIBmyKbP8FMzN6bY257MM+LkMjGtzQy89pmc4z0ZetaNyGNJBGtv4k2gczVy+5MwHHcRdVoROOkvDR78FQAIMO+LtIm8zYndr8bodqcrRkuwG73jXTudTeuP1/zM019chLemtWdzpCkvir2EZI8jBx9xCTfNOYvfbev/vwT9VXDgFM05m2sMgiinP477lNMd6F8TePBXxoHQW+6tOYsfFsrDpnyof/GiJGHmzp/mrc/2SuOP0bN7CvpJkICZ4+2rjF8G5RcmDDrHvjt7Xv3XBh78lYnwFMnZhOg5SRxF6hmhyUlyNs/o2dj/X9LKT7D/yo+y31+6H09E/w/wHJVcjfUH5AAAAABJRU5ErkJggg==",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/map_marker_image_2.png",
+ "title": "Map marker image 2",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "map_marker_image_2.png",
+ "publicResourceKey": "FazBQsEp1uSeIsT1XL31o2npLAx5s3zJ",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ },
+ {
+ "link": "/api/images/system/route-map-widget.png",
+ "title": "\"Route Map\" system widget image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "route-map-widget.png",
+ "publicResourceKey": "xHDxUSAefNkVjlpwj2OoNCHgKGGJLfbx",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ }
+ ],
+ "scada": false,
+ "tags": [
+ "trip",
+ "route",
+ "movement",
+ "tracking",
+ "path",
+ "marker",
+ "location",
+ "point",
+ "satellite",
+ "directions",
+ "placement",
+ "polygon",
+ "circle",
+ "layer",
+ "openstreet",
+ "google",
+ "tiles",
+ "roadmap",
+ "mapping",
+ "gps",
+ "navigation",
+ "geolocation"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/route_map___google.json b/application/src/main/data/json/system/widget_types/route_map___google.json
index ac7f511687..6eda23ef00 100644
--- a/application/src/main/data/json/system/widget_types/route_map___google.json
+++ b/application/src/main/data/json/system/widget_types/route_map___google.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.route_map",
"name": "Route Map - Google",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/route_map_google_system_widget_image.png",
"description": "Visualize the entity trip on Google Maps. Allows to visualize location history. Use the Trip Animation widget for advanced features.",
"descriptor": {
diff --git a/application/src/main/data/json/system/widget_types/route_map___openstreet.json b/application/src/main/data/json/system/widget_types/route_map___openstreet.json
index 9021fac8aa..92bb2516a9 100644
--- a/application/src/main/data/json/system/widget_types/route_map___openstreet.json
+++ b/application/src/main/data/json/system/widget_types/route_map___openstreet.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.route_map_openstreetmap",
"name": "Route Map - OpenStreet",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/route_map_openstreet_system_widget_image.png",
"description": "Visualize the entity trip on OpenStreetMap. Allows to visualize location history. Use the Trip Animation widget for advanced features.",
"descriptor": {
diff --git a/application/src/main/data/json/system/widget_types/route_map___tencent.json b/application/src/main/data/json/system/widget_types/route_map___tencent.json
index f2f01a5b57..3de304c8fc 100644
--- a/application/src/main/data/json/system/widget_types/route_map___tencent.json
+++ b/application/src/main/data/json/system/widget_types/route_map___tencent.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.route_map_tencent_maps",
"name": "Route Map - Tencent",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/route_map_tencent_system_widget_image.png",
"description": "Visualize the entity trip on Tencent Maps. Allows to visualize location history. Use the Trip Animation widget for advanced features.",
"descriptor": {
diff --git a/application/src/main/data/json/system/widget_types/tencent_map.json b/application/src/main/data/json/system/widget_types/tencent_map.json
index a3e480f836..ec41717ca4 100644
--- a/application/src/main/data/json/system/widget_types/tencent_map.json
+++ b/application/src/main/data/json/system/widget_types/tencent_map.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.tencent_maps",
"name": "Tencent Map",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/tencent_map_system_widget_image.png",
"description": "Displays the location of the entities on Tencent maps. Requires the Tencent map key to work properly. Highly customizable via custom markers, marker tooltips, and widget actions.",
"descriptor": {
@@ -14,7 +14,7 @@
"controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('tencent-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true\n };\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
- "settingsDirective": "tb-map-widget-settings",
+ "settingsDirective": "tb-map-widget-settings-legacy",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.24727730589425012,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.8437014651129422,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.7558240907832925,\"funcBody\":\"return \\\"colorpin\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]},{\"type\":\"function\",\"name\":\"Second Point\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.19266205227372524,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.7995830793603149,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.04902495467943502,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.44120841439482095,\"funcBody\":\"return \\\"thermometer\\\";\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"provider\":\"tencent-map\",\"tmApiKey\":\"84d6d83e0e51e481e50454ccbe8986b\",\"tmDefaultMapType\":\"roadmap\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"defaultCenterPosition\":\"0,0\",\"disableScrollZooming\":false,\"disableDoubleClickZooming\":false,\"disableZoomControl\":false,\"fitMapBounds\":true,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"draggableMarker\":false,\"showLabel\":true,\"useLabelFunction\":false,\"label\":\"${entityName}\",\"showTooltip\":true,\"showTooltipAction\":\"click\",\"autocloseTooltip\":true,\"useTooltipFunction\":false,\"tooltipPattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See advanced settings for details
\",\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"color\":\"#fe7569\",\"useColorFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"useMarkerImageFunction\":true,\"markerImageSize\":34,\"markerImageFunction\":\"var type = dsData[dsIndex]['Type'];\\nif (type == 'thermometer') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"markerImages\":[\"tb-image;/api/images/system/map_marker_image_0.png\",\"tb-image;/api/images/system/map_marker_image_1.png\",\"tb-image;/api/images/system/map_marker_image_2.png\",\"tb-image;/api/images/system/map_marker_image_3.png\"],\"showPolygon\":false,\"polygonKeyName\":\"coordinates\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonLabelFunction\":false,\"polygonLabel\":\"${entityName}\",\"showPolygonTooltip\":false,\"showPolygonTooltipAction\":\"click\",\"autoClosePolygonTooltip\":true,\"usePolygonTooltipFunction\":false,\"polygonTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"polygonColor\":\"#3388ff\",\"polygonOpacity\":0.5,\"usePolygonColorFunction\":false,\"polygonStrokeColor\":\"#3388ff\",\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"usePolygonStrokeColorFunction\":false,\"showCircle\":false,\"circleKeyName\":\"perimeter\",\"editableCircle\":false,\"showCircleLabel\":false,\"useCircleLabelFunction\":false,\"circleLabel\":\"${entityName}\",\"showCircleTooltip\":false,\"showCircleTooltipAction\":\"click\",\"autoCloseCircleTooltip\":true,\"useCircleTooltipFunction\":false,\"circleTooltipPattern\":\"${entityName}
TimeStamp: ${ts:7}\",\"circleFillColor\":\"#3388ff\",\"circleFillColorOpacity\":0.2,\"useCircleFillColorFunction\":false,\"circleStrokeColor\":\"#3388ff\",\"circleStrokeOpacity\":1,\"circleStrokeWeight\":3,\"useCircleStrokeColorFunction\":false,\"useClusterMarkers\":false,\"zoomOnClick\":true,\"maxClusterRadius\":80,\"animate\":true,\"spiderfyOnMaxZoom\":false,\"showCoverageOnHover\":true,\"chunkedLoading\":false,\"removeOutsideVisibleBounds\":true,\"useIconCreateFunction\":false},\"title\":\"Tencent Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
},
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/trip_animation.json b/application/src/main/data/json/system/widget_types/trip_animation.json
index e4429dd0b5..52eb7b8503 100644
--- a/application/src/main/data/json/system/widget_types/trip_animation.json
+++ b/application/src/main/data/json/system/widget_types/trip_animation.json
@@ -1,7 +1,7 @@
{
"fqn": "maps_v2.test",
"name": "Trip Animation",
- "deprecated": false,
+ "deprecated": true,
"image": "tb-image;/api/images/system/trip_animation_system_widget_image.png",
"description": "Displays the trip of the entity on the OpenStreetMap or other map providers. Allows to scroll and animate the movement of the entity. Highly customizable via custom markers, marker tooltips, and widget actions.",
"descriptor": {
diff --git a/application/src/main/data/json/system/widget_types/trip_map.json b/application/src/main/data/json/system/widget_types/trip_map.json
new file mode 100644
index 0000000000..a596e74c1f
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/trip_map.json
@@ -0,0 +1,64 @@
+{
+ "fqn": "trip_map",
+ "name": "Trip Map",
+ "deprecated": false,
+ "image": "tb-image;/api/images/system/trip-map-widget.png",
+ "description": "Displays an entity's trip on various map providers, allowing scrolling and animated movement. Supports custom markers, marker tooltips, widget actions, polygons, and circles for enhanced spatial representation.",
+ "descriptor": {
+ "type": "timeseries",
+ "sizeX": 8.5,
+ "sizeY": 6,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}",
+ "settingsForm": [],
+ "dataKeySettingsForm": [],
+ "latestDataKeySettingsForm": [],
+ "settingsDirective": "tb-map-widget-settings",
+ "dataKeySettingsDirective": "",
+ "latestDataKeySettingsDirective": "",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-map-basic-config",
+ "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"mapType\":\"geoMap\",\"markers\":[],\"polygons\":[],\"circles\":[],\"additionalDataSources\":[],\"trips\":[{\"dsType\":\"function\",\"dsLabel\":\"First point\",\"xKey\":{\"name\":\"f(x)\",\"label\":\"latitude\",\"type\":\"function\",\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : (value + 2) % gpsData.length)];\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"yKey\":{\"name\":\"f(x)\",\"label\":\"longitude\",\"type\":\"function\",\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : (value + 2) % gpsData.length)];\",\"settings\":{},\"color\":\"#2196f3\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},\"markerType\":\"shape\",\"markerShape\":{\"shape\":\"tripMarkerShape1\",\"size\":34,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerIcon\":{\"icon\":\"arrow_forward\",\"size\":48,\"color\":{\"type\":\"constant\",\"color\":\"#307FE5\"}},\"markerImage\":{\"type\":\"image\",\"image\":\"/assets/markers/tripShape1.svg\",\"imageSize\":34},\"markerOffsetX\":0.5,\"markerOffsetY\":0.5,\"positionFunction\":\"return {x: origXPos, y: origYPos};\",\"markerClustering\":{\"enable\":false,\"zoomOnClick\":true,\"maxZoom\":null,\"maxClusterRadius\":80,\"zoomAnimation\":true,\"showCoverageOnHover\":true,\"spiderfyOnMaxZoom\":false,\"chunkedLoad\":false,\"lazyLoad\":true,\"useClusterMarkerColorFunction\":false,\"clusterMarkerColorFunction\":null},\"label\":{\"show\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}\"},\"tooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}\",\"offsetX\":0,\"offsetY\":-0.5},\"click\":{\"type\":\"doNothing\"},\"edit\":{\"enabledActions\":[],\"snappable\":false},\"rotateMarker\":true,\"offsetAngle\":0,\"showPath\":true,\"pathStrokeWeight\":2,\"pathStrokeColor\":{\"type\":\"constant\",\"color\":\"#307FE5\"},\"usePathDecorator\":false,\"pathDecoratorSymbol\":\"arrowHead\",\"pathDecoratorSymbolSize\":10,\"pathDecoratorSymbolColor\":\"#307FE5\",\"pathDecoratorOffset\":20,\"pathEndDecoratorOffset\":20,\"pathDecoratorRepeat\":20,\"showPoints\":false,\"pointSize\":10,\"pointColor\":{\"type\":\"constant\",\"color\":\"#307FE5\"},\"pointTooltip\":{\"show\":true,\"trigger\":\"click\",\"autoclose\":true,\"type\":\"pattern\",\"pattern\":\"${entityName}
Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}\",\"offsetX\":0,\"offsetY\":-1}}],\"tripTimeline\":{\"showTimelineControl\":true}},\"title\":\"Trip Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"assistant_navigation\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":null,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":null},\"titleColor\":null,\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}"
+ },
+ "resources": [
+ {
+ "link": "/api/images/system/trip-map-widget.png",
+ "title": "\"Trip Map\" system widget image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "trip-map-widget.png",
+ "publicResourceKey": "nF3ox4p08cuECHLQYNdnhFUpkjK9Uw7P",
+ "mediaType": "image/png",
+ "data": "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",
+ "public": true
+ }
+ ],
+ "scada": false,
+ "tags": [
+ "trip",
+ "route",
+ "movement",
+ "tracking",
+ "path",
+ "point",
+ "timeline",
+ "marker",
+ "location",
+ "satellite",
+ "directions",
+ "placement",
+ "polygon",
+ "circle",
+ "layer",
+ "openstreet",
+ "google",
+ "tiles",
+ "roadmap",
+ "mapping",
+ "gps",
+ "navigation",
+ "geolocation"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
index 0f2473cde6..305dc04961 100644
--- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
+++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json
@@ -35,8 +35,11 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
- "configurationVersion": 2,
+ "configurationVersion": 3,
"configuration": {
+ "processingSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ },
"scope": "CLIENT_SCOPE",
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
index 8efda98c5b..a988c9d5eb 100644
--- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
+++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
@@ -34,8 +34,11 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
- "configurationVersion": 2,
+ "configurationVersion": 3,
"configuration": {
+ "processingSettings": {
+ "type": "ON_EVERY_MESSAGE"
+ },
"scope": "CLIENT_SCOPE",
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql
index 2929635949..29c7a084f4 100644
--- a/application/src/main/data/upgrade/basic/schema_update.sql
+++ b/application/src/main/data/upgrade/basic/schema_update.sql
@@ -16,50 +16,70 @@
-- UPDATE SAVE TIME SERIES NODES START
-DO $$
- BEGIN
- -- Check if the rule_node table exists
- IF EXISTS (
- SELECT 1
- FROM information_schema.tables
- WHERE table_name = 'rule_node'
- ) THEN
+UPDATE rule_node
+SET configuration = (
+ (configuration::jsonb - 'skipLatestPersistence')
+ || jsonb_build_object(
+ 'processingSettings', jsonb_build_object(
+ 'type', 'ADVANCED',
+ 'timeseries', jsonb_build_object('type', 'ON_EVERY_MESSAGE'),
+ 'latest', jsonb_build_object('type', 'SKIP'),
+ 'webSockets', jsonb_build_object('type', 'ON_EVERY_MESSAGE'),
+ 'calculatedFields', jsonb_build_object('type', 'ON_EVERY_MESSAGE')
+ )
+ )
+ )::text,
+ configuration_version = 1
+WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
+ AND configuration_version = 0
+ AND configuration::jsonb ->> 'skipLatestPersistence' = 'true';
- UPDATE rule_node
- SET configuration = (
- (configuration::jsonb - 'skipLatestPersistence')
- || jsonb_build_object(
- 'processingSettings', jsonb_build_object(
- 'type', 'ADVANCED',
- 'timeseries', jsonb_build_object('type', 'ON_EVERY_MESSAGE'),
- 'latest', jsonb_build_object('type', 'SKIP'),
- 'webSockets', jsonb_build_object('type', 'ON_EVERY_MESSAGE')
- )
- )
- )::text,
- configuration_version = 1
- WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
- AND configuration_version = 0
- AND configuration::jsonb ->> 'skipLatestPersistence' = 'true';
+UPDATE rule_node
+SET configuration = (
+ (configuration::jsonb - 'skipLatestPersistence')
+ || jsonb_build_object(
+ 'processingSettings', jsonb_build_object(
+ 'type', 'ON_EVERY_MESSAGE'
+ )
+ )
+ )::text,
+ configuration_version = 1
+WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
+ AND configuration_version = 0
+ AND (configuration::jsonb ->> 'skipLatestPersistence' != 'true' OR configuration::jsonb ->> 'skipLatestPersistence' IS NULL);
- UPDATE rule_node
- SET configuration = (
- (configuration::jsonb - 'skipLatestPersistence')
- || jsonb_build_object(
- 'processingSettings', jsonb_build_object(
- 'type', 'ON_EVERY_MESSAGE'
- )
- )
- )::text,
- configuration_version = 1
- WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode'
- AND configuration_version = 0
- AND (configuration::jsonb ->> 'skipLatestPersistence' != 'true' OR configuration::jsonb ->> 'skipLatestPersistence' IS NULL);
+-- UPDATE SAVE TIME SERIES NODES END
- END IF;
- END;
-$$;
+-- UPDATE SAVE ATTRIBUTES NODES START
--- UPDATE SAVE TIME SERIES NODES END
+UPDATE rule_node
+SET configuration = (
+ configuration::jsonb
+ || jsonb_build_object(
+ 'processingSettings', jsonb_build_object('type', 'ON_EVERY_MESSAGE')
+ )
+ )::text,
+ configuration_version = 3
+WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode'
+ AND configuration_version = 2;
+
+-- UPDATE SAVE ATTRIBUTES NODES END
+
+ALTER TABLE api_usage_state ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1;
+
+-- UPDATE TENANT PROFILE CALCULATED FIELD LIMITS START
+
+UPDATE tenant_profile
+SET profile_data = profile_data
+ || jsonb_build_object(
+ 'configuration', profile_data->'configuration' || jsonb_build_object(
+ 'maxCalculatedFieldsPerEntity', COALESCE(profile_data->'configuration'->>'maxCalculatedFieldsPerEntity', '5')::bigint,
+ 'maxArgumentsPerCF', COALESCE(profile_data->'configuration'->>'maxArgumentsPerCF', '10')::bigint,
+ 'maxDataPointsPerRollingArg', COALESCE(profile_data->'configuration'->>'maxDataPointsPerRollingArg', '1000')::bigint,
+ 'maxStateSizeInKBytes', COALESCE(profile_data->'configuration'->>'maxStateSizeInKBytes', '32')::bigint,
+ 'maxSingleValueArgumentSizeInKBytes', COALESCE(profile_data->'configuration'->>'maxSingleValueArgumentSizeInKBytes', '2')::bigint
+ )
+ )
+WHERE profile_data->'configuration'->>'maxCalculatedFieldsPerEntity' IS NULL;
-ALTER TABLE api_usage_state ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1;
\ No newline at end of file
+-- UPDATE TENANT PROFILE CALCULATED FIELD LIMITS END
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index eb76473386..1ed919e922 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -28,12 +28,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter;
-import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager;
+import org.thingsboard.rule.engine.api.DeviceStateManager;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.notification.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
@@ -41,13 +40,18 @@ import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
+import org.thingsboard.server.cache.limits.RateLimitService;
import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.event.CalculatedFieldDebugEvent;
import org.thingsboard.server.common.data.event.ErrorEvent;
import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.event.RuleChainDebugEvent;
import org.thingsboard.server.common.data.event.RuleNodeDebugEvent;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.limit.LimitedApi;
+import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
@@ -62,6 +66,7 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
+import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
@@ -94,6 +99,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@@ -101,6 +107,11 @@ import org.thingsboard.server.queue.discovery.DiscoveryService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
+import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
+import org.thingsboard.server.service.cf.CalculatedFieldQueueService;
+import org.thingsboard.server.service.cf.CalculatedFieldStateService;
+import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache;
+import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
@@ -121,13 +132,18 @@ import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
+import org.thingsboard.server.utils.DebugModeRateLimitsConfig;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
@Slf4j
@Component
@@ -156,6 +172,18 @@ public class ActorSystemContext {
}
};
+ private static final FutureCallback CALCULATED_FIELD_DEBUG_EVENT_ERROR_CALLBACK = new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void event) {
+
+ }
+
+ @Override
+ public void onFailure(Throwable th) {
+ log.error("Could not save debug Event for Calculated Field", th);
+ }
+ };
+
private final ConcurrentMap debugPerTenantLimits = new ConcurrentHashMap<>();
public ConcurrentMap getDebugPerTenantLimits() {
@@ -206,7 +234,7 @@ public class ActorSystemContext {
@Autowired(required = false)
@Getter
- private RuleEngineDeviceStateManager deviceStateManager;
+ private DeviceStateManager deviceStateManager;
@Autowired
@Getter
@@ -289,6 +317,7 @@ public class ActorSystemContext {
@Getter
private TbEntityViewService tbEntityViewService;
+ @Lazy
@Autowired
@Getter
private TelemetrySubscriptionService tsSubService;
@@ -394,15 +423,15 @@ public class ActorSystemContext {
@Getter
private SlackService slackService;
+ @Autowired
+ @Getter
+ private CalculatedFieldService calculatedFieldService;
+
@Lazy
@Autowired(required = false)
@Getter
private ClaimDevicesService claimDevicesService;
- @Autowired
- @Getter
- private JsInvokeStats jsInvokeStats;
-
//TODO: separate context for TbCore and TbRuleEngine
@Autowired(required = false)
@Getter
@@ -416,6 +445,21 @@ public class ActorSystemContext {
@Getter
private TbCoreToTransportService tbCoreToTransportService;
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private ApiLimitService apiLimitService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private RateLimitService rateLimitService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private DebugModeRateLimitsConfig debugModeRateLimitsConfig;
+
/**
* The following Service will be null if we operate in tb-core mode
*/
@@ -487,9 +531,29 @@ public class ActorSystemContext {
@Getter
private EntityService entityService;
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private CalculatedFieldProcessingService calculatedFieldProcessingService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private CalculatedFieldStateService calculatedFieldStateService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private CalculatedFieldQueueService calculatedFieldQueueService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private CalculatedFieldEntityProfileCache calculatedFieldEntityProfileCache;
+
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
- private long maxConcurrentSessionsPerDevice;
+ private int maxConcurrentSessionsPerDevice;
@Value("${actors.session.sync.timeout:10000}")
@Getter
@@ -527,17 +591,6 @@ public class ActorSystemContext {
this.localCacheType = "caffeine".equals(cacheType);
}
- @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
- public void printStats() {
- if (statisticsEnabled) {
- if (jsInvokeStats.getRequests() > 0 || jsInvokeStats.getResponses() > 0 || jsInvokeStats.getFailures() > 0) {
- log.info("Rule Engine JS Invoke Stats: requests [{}] responses [{}] failures [{}]",
- jsInvokeStats.getRequests(), jsInvokeStats.getResponses(), jsInvokeStats.getFailures());
- jsInvokeStats.reset();
- }
- }
- }
-
@Value("${actors.tenant.create_components_on_init:true}")
@Getter
private boolean tenantComponentsInitEnabled;
@@ -558,14 +611,6 @@ public class ActorSystemContext {
@Getter
private long sessionReportTimeout;
- @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
- @Getter
- private boolean debugPerTenantEnabled;
-
- @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration:50000:3600}")
- @Getter
- private String debugPerTenantLimitsConfiguration;
-
@Value("${actors.rpc.submit_strategy:BURST}")
@Getter
private String rpcSubmitStrategy;
@@ -590,6 +635,10 @@ public class ActorSystemContext {
@Getter
private String deviceStateNodeRateLimitConfig;
+ @Value("${actors.calculated_fields.calculation_timeout:5}")
+ @Getter
+ private long cfCalculationResultTimeout;
+
@Getter
@Setter
private TbActorSystem actorSystem;
@@ -719,9 +768,9 @@ public class ActorSystemContext {
}
private boolean checkLimits(TenantId tenantId, TbMsg tbMsg, Throwable error) {
- if (debugPerTenantEnabled) {
+ if (debugModeRateLimitsConfig.isRuleChainDebugPerTenantLimitsEnabled()) {
DebugTbRateLimits debugTbRateLimits = debugPerTenantLimits.computeIfAbsent(tenantId, id ->
- new DebugTbRateLimits(new TbRateLimits(debugPerTenantLimitsConfiguration), false));
+ new DebugTbRateLimits(new TbRateLimits(debugModeRateLimitsConfig.getRuleChainDebugPerTenantLimitsConfiguration()), false));
if (!debugTbRateLimits.getTbRateLimits().tryConsume()) {
if (!debugTbRateLimits.isRuleChainEventSaved()) {
@@ -751,6 +800,51 @@ public class ActorSystemContext {
Futures.addCallback(future, RULE_CHAIN_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
}
+ public void persistCalculatedFieldDebugEvent(TenantId tenantId, CalculatedFieldId calculatedFieldId, EntityId entityId, Map arguments, UUID tbMsgId, TbMsgType tbMsgType, String result, String errorMessage) {
+ if (checkLimits(tenantId)) {
+ try {
+ CalculatedFieldDebugEvent.CalculatedFieldDebugEventBuilder eventBuilder = CalculatedFieldDebugEvent.builder()
+ .tenantId(tenantId)
+ .entityId(calculatedFieldId.getId())
+ .serviceId(getServiceId())
+ .calculatedFieldId(calculatedFieldId)
+ .eventEntity(entityId);
+ if (tbMsgId != null) {
+ eventBuilder.msgId(tbMsgId);
+ }
+ if (tbMsgType != null) {
+ eventBuilder.msgType(tbMsgType.name());
+ }
+ if (arguments != null) {
+ eventBuilder.arguments(JacksonUtil.toString(
+ arguments.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toTbelCfArg()))
+ ));
+ }
+ if (result != null) {
+ eventBuilder.result(result);
+ }
+ if (errorMessage != null) {
+ eventBuilder.error(errorMessage);
+ }
+
+ ListenableFuture future = eventService.saveAsync(eventBuilder.build());
+ Futures.addCallback(future, CALCULATED_FIELD_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
+ } catch (IllegalArgumentException ex) {
+ log.warn("Failed to persist calculated field debug message", ex);
+ }
+ }
+ }
+
+ private boolean checkLimits(TenantId tenantId) {
+ if (debugModeRateLimitsConfig.isCalculatedFieldDebugPerTenantLimitsEnabled() &&
+ !rateLimitService.checkRateLimit(LimitedApi.CALCULATED_FIELD_DEBUG_EVENTS, (Object) tenantId, debugModeRateLimitsConfig.getCalculatedFieldDebugPerTenantLimitsConfiguration())) {
+ log.trace("[{}] Calculated field debug event limits exceeded!", tenantId);
+ return false;
+ }
+ return true;
+ }
+
public static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}
@@ -763,9 +857,9 @@ public class ActorSystemContext {
appActor.tellWithHighPriority(tbActorMsg);
}
- public void schedulePeriodicMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
+ public ScheduledFuture> schedulePeriodicMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
- getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS);
+ return getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS);
}
public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
index dc6a3bcf5e..904547a2b6 100644
--- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
@@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@@ -87,6 +88,7 @@ public class AppActor extends ContextAwareActor {
case APP_INIT_MSG:
break;
case PARTITION_CHANGE_MSG:
+ case CF_PARTITIONS_CHANGE_MSG:
ctx.broadcastToChildren(msg, true);
break;
case COMPONENT_LIFE_CYCLE_MSG:
@@ -111,6 +113,17 @@ public class AppActor extends ContextAwareActor {
case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break;
+ case CF_INIT_MSG:
+ case CF_LINK_INIT_MSG:
+ case CF_STATE_RESTORE_MSG:
+ //TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not.
+ // same for the Linked telemetry.
+ onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
+ break;
+ case CF_TELEMETRY_MSG:
+ case CF_LINKED_TELEMETRY_MSG:
+ onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, false);
+ break;
default:
return false;
}
@@ -175,6 +188,19 @@ public class AppActor extends ContextAwareActor {
}
}
+ private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
+ getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(tenantActor -> {
+ if (priority) {
+ tenantActor.tellWithHighPriority(msg);
+ } else {
+ tenantActor.tell(msg);
+ }
+ }, () -> {
+ msg.getCallback().onSuccess();
+ });
+ }
+
+
private void onToDeviceActorMsg(TenantAwareMsg msg, boolean priority) {
getOrCreateTenantActor(msg.getTenantId()).ifPresentOrElse(tenantActor -> {
if (priority) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/AbstractCalculatedFieldActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/AbstractCalculatedFieldActor.java
new file mode 100644
index 0000000000..6cf34599de
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/AbstractCalculatedFieldActor.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.common.util.DebugModeUtil;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.service.ContextAwareActor;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+
+@Slf4j
+public abstract class AbstractCalculatedFieldActor extends ContextAwareActor {
+
+ protected final TenantId tenantId;
+
+ public AbstractCalculatedFieldActor(ActorSystemContext systemContext, TenantId tenantId) {
+ super(systemContext);
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ protected boolean doProcess(TbActorMsg msg) {
+ if (msg instanceof ToCalculatedFieldSystemMsg cfm) {
+ Exception cause;
+ try {
+ return doProcessCfMsg(cfm);
+ } catch (CalculatedFieldException cfe) {
+ if (DebugModeUtil.isDebugFailuresAvailable(cfe.getCtx().getCalculatedField())) {
+ String message;
+ if (cfe.getErrorMessage() != null) {
+ message = cfe.getErrorMessage();
+ } else if (cfe.getCause() != null) {
+ message = cfe.getCause().getMessage();
+ } else {
+ message = "N/A";
+ }
+ systemContext.persistCalculatedFieldDebugEvent(tenantId, cfe.getCtx().getCfId(), cfe.getEventEntity(), cfe.getArguments(), cfe.getMsgId(), cfe.getMsgType(), null, message);
+ }
+ cause = cfe.getCause();
+ } catch (Exception e) {
+ logProcessingException(e);
+ cause = e;
+ }
+ cfm.getCallback().onFailure(cause);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ abstract void logProcessingException(Exception e);
+
+ abstract boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java
new file mode 100644
index 0000000000..350a5776cf
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActorCtx;
+import org.thingsboard.server.actors.TbActorException;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
+
+@Slf4j
+public class CalculatedFieldEntityActor extends AbstractCalculatedFieldActor {
+
+ private final CalculatedFieldEntityMessageProcessor processor;
+
+ CalculatedFieldEntityActor(ActorSystemContext systemContext, TenantId tenantId, EntityId entityId) {
+ super(systemContext, tenantId);
+ this.processor = new CalculatedFieldEntityMessageProcessor(systemContext, tenantId, entityId);
+ }
+
+ @Override
+ public void init(TbActorCtx ctx) throws TbActorException {
+ super.init(ctx);
+ log.debug("[{}][{}] Starting CF entity actor.", processor.tenantId, processor.entityId);
+ try {
+ processor.init(ctx);
+ log.debug("[{}][{}] CF entity actor started.", processor.tenantId, processor.entityId);
+ } catch (Exception e) {
+ log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.entityId, e);
+ throw new TbActorException("Failed to initialize CF entity actor", e);
+ }
+ }
+
+ @Override
+ protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
+ switch (msg.getMsgType()) {
+ case CF_PARTITIONS_CHANGE_MSG:
+ processor.process((CalculatedFieldPartitionChangeMsg) msg);
+ break;
+ case CF_STATE_RESTORE_MSG:
+ processor.process((CalculatedFieldStateRestoreMsg) msg);
+ break;
+ case CF_ENTITY_INIT_CF_MSG:
+ processor.process((EntityInitCalculatedFieldMsg) msg);
+ break;
+ case CF_ENTITY_DELETE_MSG:
+ processor.process((CalculatedFieldEntityDeleteMsg) msg);
+ break;
+ case CF_ENTITY_TELEMETRY_MSG:
+ processor.process((EntityCalculatedFieldTelemetryMsg) msg);
+ break;
+ case CF_LINKED_TELEMETRY_MSG:
+ processor.process((EntityCalculatedFieldLinkedTelemetryMsg) msg);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ void logProcessingException(Exception e) {
+ log.warn("[{}][{}] Processing failure", tenantId, processor.entityId, e);
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActorCreator.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActorCreator.java
new file mode 100644
index 0000000000..6dc2f26050
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActorCreator.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActor;
+import org.thingsboard.server.actors.TbActorId;
+import org.thingsboard.server.actors.TbCalculatedFieldEntityActorId;
+import org.thingsboard.server.actors.TbEntityActorId;
+import org.thingsboard.server.actors.device.DeviceActor;
+import org.thingsboard.server.actors.service.ContextBasedCreator;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+public class CalculatedFieldEntityActorCreator extends ContextBasedCreator {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+
+ public CalculatedFieldEntityActorCreator(ActorSystemContext context, TenantId tenantId, EntityId entityId) {
+ super(context);
+ this.tenantId = tenantId;
+ this.entityId = entityId;
+ }
+
+ @Override
+ public TbActorId createActorId() {
+ return new TbCalculatedFieldEntityActorId(entityId);
+ }
+
+ @Override
+ public TbActor createActor() {
+ return new CalculatedFieldEntityActor(context, tenantId, entityId);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityDeleteMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityDeleteMsg.java
new file mode 100644
index 0000000000..3ca6e8596a
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityDeleteMsg.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+
+@Data
+public class CalculatedFieldEntityDeleteMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+ private final TbCallback callback;
+
+ public CalculatedFieldEntityDeleteMsg(TenantId tenantId,
+ EntityId entityId,
+ TbCallback callback) {
+ this.tenantId = tenantId;
+ this.entityId = entityId;
+ this.callback = callback;
+ }
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_ENTITY_DELETE_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
new file mode 100644
index 0000000000..a185b71d56
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
@@ -0,0 +1,448 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.common.util.DebugModeUtil;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActorCtx;
+import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.cf.configuration.Argument;
+import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
+import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeScopeProto;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
+import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
+import org.thingsboard.server.service.cf.CalculatedFieldResult;
+import org.thingsboard.server.service.cf.CalculatedFieldStateService;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+
+/**
+ * @author Andrew Shvayka
+ */
+@Slf4j
+public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareMsgProcessor {
+ // (1 for result persistence + 1 for the state persistence )
+ public static final int CALLBACKS_PER_CF = 2;
+
+ final TenantId tenantId;
+ final EntityId entityId;
+ final CalculatedFieldProcessingService cfService;
+ final CalculatedFieldStateService cfStateService;
+
+ TbActorCtx ctx;
+ Map states = new HashMap<>();
+
+ CalculatedFieldEntityMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, EntityId entityId) {
+ super(systemContext);
+ this.tenantId = tenantId;
+ this.entityId = entityId;
+ this.cfService = systemContext.getCalculatedFieldProcessingService();
+ this.cfStateService = systemContext.getCalculatedFieldStateService();
+ }
+
+ void init(TbActorCtx ctx) {
+ this.ctx = ctx;
+ }
+
+ public void process(CalculatedFieldPartitionChangeMsg msg) {
+ if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) {
+ log.info("[{}] Stopping entity actor due to change partition event.", entityId);
+ ctx.stop(ctx.getSelf());
+ }
+ }
+
+ public void process(CalculatedFieldStateRestoreMsg msg) {
+ CalculatedFieldId cfId = msg.getId().cfId();
+ log.info("[{}] [{}] Processing CF state restore msg.", msg.getId().entityId(), cfId);
+ if (msg.getState() != null) {
+ states.put(cfId, msg.getState());
+ } else {
+ states.remove(cfId);
+ }
+ }
+
+ public void process(EntityInitCalculatedFieldMsg msg) throws CalculatedFieldException {
+ log.info("[{}] Processing entity init CF msg.", msg.getCtx().getCfId());
+ var ctx = msg.getCtx();
+ if (msg.isForceReinit()) {
+ log.info("Force reinitialization of CF: [{}].", ctx.getCfId());
+ states.remove(ctx.getCfId());
+ }
+ try {
+ var state = getOrInitState(ctx);
+ if (state.isSizeOk()) {
+ processStateIfReady(ctx, Collections.singletonList(ctx.getCfId()), state, null, null, msg.getCallback());
+ } else {
+ throw new RuntimeException(ctx.getSizeExceedsLimitMessage());
+ }
+ } catch (Exception e) {
+ if (e instanceof CalculatedFieldException cfe) {
+ throw cfe;
+ }
+ throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
+ }
+ }
+
+ public void process(CalculatedFieldEntityDeleteMsg msg) {
+ log.info("[{}] Processing CF entity delete msg.", msg.getEntityId());
+ if (this.entityId.equals(msg.getEntityId())) {
+ if (states.isEmpty()) {
+ msg.getCallback().onSuccess();
+ } else {
+ MultipleTbCallback multipleTbCallback = new MultipleTbCallback(states.size(), msg.getCallback());
+ states.forEach((cfId, state) -> cfStateService.removeState(new CalculatedFieldEntityCtxId(tenantId, cfId, entityId), multipleTbCallback));
+ ctx.stop(ctx.getSelf());
+ }
+ } else {
+ var cfId = new CalculatedFieldId(msg.getEntityId().getId());
+ var state = states.remove(cfId);
+ if (state != null) {
+ cfStateService.removeState(new CalculatedFieldEntityCtxId(tenantId, cfId, entityId), msg.getCallback());
+ } else {
+ msg.getCallback().onSuccess();
+ }
+ }
+ }
+
+ public void process(EntityCalculatedFieldTelemetryMsg msg) throws CalculatedFieldException {
+ log.debug("[{}] Processing CF telemetry msg.", msg.getEntityId());
+ var proto = msg.getProto();
+ var numberOfCallbacks = CALLBACKS_PER_CF * (msg.getEntityIdFields().size() + msg.getProfileIdFields().size());
+ MultipleTbCallback callback = new MultipleTbCallback(numberOfCallbacks, msg.getCallback());
+ List cfIdList = getCalculatedFieldIds(proto);
+ Set cfIdSet = new HashSet<>(cfIdList);
+ for (var ctx : msg.getEntityIdFields()) {
+ process(ctx, proto, cfIdSet, cfIdList, callback);
+ }
+ for (var ctx : msg.getProfileIdFields()) {
+ process(ctx, proto, cfIdSet, cfIdList, callback);
+ }
+ }
+
+ public void process(EntityCalculatedFieldLinkedTelemetryMsg msg) throws CalculatedFieldException {
+ log.debug("[{}] Processing CF link telemetry msg.", msg.getEntityId());
+ var proto = msg.getProto();
+ var ctx = msg.getCtx();
+ var callback = new MultipleTbCallback(CALLBACKS_PER_CF, msg.getCallback());
+ try {
+ List cfIds = getCalculatedFieldIds(proto);
+ if (cfIds.contains(ctx.getCfId())) {
+ callback.onSuccess(CALLBACKS_PER_CF);
+ } else {
+ if (proto.getTsDataCount() > 0) {
+ processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getTsDataList()), toTbMsgId(proto), toTbMsgType(proto));
+ } else if (proto.getAttrDataCount() > 0) {
+ processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toTbMsgType(proto));
+ } else if (proto.getRemovedTsKeysCount() > 0) {
+ processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithFetchedValue(ctx, proto.getRemovedTsKeysList()), toTbMsgId(proto), toTbMsgType(proto));
+ } else if (proto.getRemovedAttrKeysCount() > 0) {
+ processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithDefaultValue(ctx, msg.getEntityId(), proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toTbMsgType(proto));
+ } else {
+ callback.onSuccess(CALLBACKS_PER_CF);
+ }
+ }
+ } catch (Exception e) {
+ throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
+ }
+ }
+
+ private void process(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, Collection cfIds, List cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
+ try {
+ if (cfIds.contains(ctx.getCfId())) {
+ callback.onSuccess(CALLBACKS_PER_CF);
+ } else {
+ if (proto.getTsDataCount() > 0) {
+ processTelemetry(ctx, proto, cfIdList, callback);
+ } else if (proto.getAttrDataCount() > 0) {
+ processAttributes(ctx, proto, cfIdList, callback);
+ } else if (proto.getRemovedTsKeysCount() > 0) {
+ processRemovedTelemetry(ctx, proto, cfIdList, callback);
+ } else if (proto.getRemovedAttrKeysCount() > 0) {
+ processRemovedAttributes(ctx, proto, cfIdList, callback);
+ } else {
+ callback.onSuccess(CALLBACKS_PER_CF);
+ }
+ }
+ } catch (Exception e) {
+ if (e instanceof CalculatedFieldException cfe) {
+ throw cfe;
+ }
+ throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
+ }
+ }
+
+ private void processTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
+ processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getTsDataList()), toTbMsgId(proto), toTbMsgType(proto));
+ }
+
+ private void processAttributes(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
+ processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toTbMsgType(proto));
+ }
+
+ private void processRemovedTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
+ processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithFetchedValue(ctx, proto.getRemovedTsKeysList()), toTbMsgId(proto), toTbMsgType(proto));
+ }
+
+ private void processRemovedAttributes(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List cfIdList, MultipleTbCallback callback) throws CalculatedFieldException {
+ processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithDefaultValue(ctx, proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toTbMsgType(proto));
+ }
+
+ private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List cfIdList, MultipleTbCallback callback,
+ Map newArgValues, UUID tbMsgId, TbMsgType tbMsgType) throws CalculatedFieldException {
+ if (newArgValues.isEmpty()) {
+ log.info("[{}] No new argument values to process for CF.", ctx.getCfId());
+ callback.onSuccess(CALLBACKS_PER_CF);
+ }
+ CalculatedFieldState state = states.get(ctx.getCfId());
+ boolean justRestored = false;
+ if (state == null) {
+ state = getOrInitState(ctx);
+ justRestored = true;
+ }
+ if (state.isSizeOk()) {
+ if (state.updateState(ctx, newArgValues) || justRestored) {
+ cfIdList = new ArrayList<>(cfIdList);
+ cfIdList.add(ctx.getCfId());
+ processStateIfReady(ctx, cfIdList, state, tbMsgId, tbMsgType, callback);
+ } else {
+ callback.onSuccess(CALLBACKS_PER_CF);
+ }
+ } else {
+ throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
+ }
+ }
+
+ @SneakyThrows
+ private CalculatedFieldState getOrInitState(CalculatedFieldCtx ctx) {
+ CalculatedFieldState state = states.get(ctx.getCfId());
+ if (state != null) {
+ return state;
+ } else {
+ ListenableFuture stateFuture = systemContext.getCalculatedFieldProcessingService().fetchStateFromDb(ctx, entityId);
+ // Ugly but necessary. We do not expect to often fetch data from DB. Only once per pair lifetime.
+ // This call happens while processing the CF pack from the queue consumer. So the timeout should be relatively low.
+ // Alternatively, we can fetch the state outside the actor system and push separate command to create this actor,
+ // but this will significantly complicate the code.
+ state = stateFuture.get(1, TimeUnit.MINUTES);
+ state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize());
+ states.put(ctx.getCfId(), state);
+ }
+ return state;
+ }
+
+ private void processStateIfReady(CalculatedFieldCtx ctx, List cfIdList, CalculatedFieldState state, UUID tbMsgId, TbMsgType tbMsgType, TbCallback callback) throws CalculatedFieldException {
+ CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId);
+ boolean stateSizeChecked = false;
+ try {
+ if (ctx.isInitialized() && state.isReady()) {
+ CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
+ state.checkStateSize(ctxId, ctx.getMaxStateSize());
+ stateSizeChecked = true;
+ if (state.isSizeOk()) {
+ if (!calculationResult.isEmpty()) {
+ cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, callback);
+ } else {
+ callback.onSuccess();
+ }
+ if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
+ systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, JacksonUtil.writeValueAsString(calculationResult.getResult()), null);
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).msgId(tbMsgId).msgType(tbMsgType).arguments(state.getArguments()).cause(e).build();
+ } finally {
+ if (!stateSizeChecked) {
+ state.checkStateSize(ctxId, ctx.getMaxStateSize());
+ }
+ if (state.isSizeOk()) {
+ cfStateService.persistState(ctxId, state, callback);
+ } else {
+ removeStateAndRaiseSizeException(ctxId, CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build(), callback);
+ }
+ }
+ }
+
+ private void removeStateAndRaiseSizeException(CalculatedFieldEntityCtxId ctxId, CalculatedFieldException ex, TbCallback callback) throws CalculatedFieldException {
+ // We remove the state, but remember that it is over-sized in a local map.
+ cfStateService.removeState(ctxId, new TbCallback() {
+ @Override
+ public void onSuccess() {
+ callback.onFailure(ex);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure(ex);
+ }
+ });
+ throw ex;
+ }
+
+ private Map mapToArguments(CalculatedFieldCtx ctx, List data) {
+ return mapToArguments(ctx.getMainEntityArguments(), data);
+ }
+
+ private Map mapToArguments(CalculatedFieldCtx ctx, EntityId entityId, List data) {
+ var argNames = ctx.getLinkedEntityArguments().get(entityId);
+ if (argNames.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ return mapToArguments(argNames, data);
+ }
+
+ private Map mapToArguments(Map argNames, List data) {
+ if (argNames.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map arguments = new HashMap<>();
+ for (TsKvProto item : data) {
+ ReferencedEntityKey key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_LATEST, null);
+ String argName = argNames.get(key);
+ if (argName != null) {
+ arguments.put(argName, new SingleValueArgumentEntry(item));
+ }
+ key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_ROLLING, null);
+ argName = argNames.get(key);
+ if (argName != null) {
+ arguments.put(argName, new SingleValueArgumentEntry(item));
+ }
+ }
+ return arguments;
+ }
+
+ private Map mapToArguments(CalculatedFieldCtx ctx, AttributeScopeProto scope, List attrDataList) {
+ return mapToArguments(ctx.getMainEntityArguments(), scope, attrDataList);
+ }
+
+ private Map mapToArguments(CalculatedFieldCtx ctx, EntityId entityId, AttributeScopeProto scope, List attrDataList) {
+ var argNames = ctx.getLinkedEntityArguments().get(entityId);
+ if (argNames.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ return mapToArguments(argNames, scope, attrDataList);
+ }
+
+ private Map mapToArguments(Map argNames, AttributeScopeProto scope, List attrDataList) {
+ Map arguments = new HashMap<>();
+ for (AttributeValueProto item : attrDataList) {
+ ReferencedEntityKey key = new ReferencedEntityKey(item.getKey(), ArgumentType.ATTRIBUTE, AttributeScope.valueOf(scope.name()));
+ String argName = argNames.get(key);
+ if (argName != null) {
+ arguments.put(argName, new SingleValueArgumentEntry(item));
+ }
+ }
+ return arguments;
+ }
+
+ private Map mapToArgumentsWithDefaultValue(CalculatedFieldCtx ctx, EntityId entityId, AttributeScopeProto scope, List removedAttrKeys) {
+ var argNames = ctx.getLinkedEntityArguments().get(entityId);
+ if (argNames.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ return mapToArgumentsWithDefaultValue(argNames, ctx.getArguments(), scope, removedAttrKeys);
+ }
+
+ private Map mapToArgumentsWithDefaultValue(CalculatedFieldCtx ctx, AttributeScopeProto scope, List removedAttrKeys) {
+ return mapToArgumentsWithDefaultValue(ctx.getMainEntityArguments(), ctx.getArguments(), scope, removedAttrKeys);
+ }
+
+ private Map mapToArgumentsWithDefaultValue(Map argNames, Map configArguments, AttributeScopeProto scope, List removedAttrKeys) {
+ Map arguments = new HashMap<>();
+ for (String removedKey : removedAttrKeys) {
+ ReferencedEntityKey key = new ReferencedEntityKey(removedKey, ArgumentType.ATTRIBUTE, AttributeScope.valueOf(scope.name()));
+ String argName = argNames.get(key);
+ if (argName != null) {
+ Argument argument = configArguments.get(argName);
+ String defaultValue = (argument != null) ? argument.getDefaultValue() : null;
+ arguments.put(argName, StringUtils.isNotEmpty(defaultValue)
+ ? new SingleValueArgumentEntry(System.currentTimeMillis(), new StringDataEntry(removedKey, defaultValue), null)
+ : new SingleValueArgumentEntry());
+
+ }
+ }
+ return arguments;
+ }
+
+ private Map mapToArgumentsWithFetchedValue(CalculatedFieldCtx ctx, List removedTelemetryKeys) {
+ Map deletedArguments = ctx.getArguments().entrySet().stream()
+ .filter(entry -> removedTelemetryKeys.contains(entry.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ Map fetchedArgs = cfService.fetchArgsFromDb(tenantId, entityId, deletedArguments);
+
+ fetchedArgs.values().forEach(arg -> arg.setForceResetPrevious(true));
+ return fetchedArgs;
+ }
+
+ private static List getCalculatedFieldIds(CalculatedFieldTelemetryMsgProto proto) {
+ List cfIds = new LinkedList<>();
+ for (var cfId : proto.getPreviousCalculatedFieldsList()) {
+ cfIds.add(new CalculatedFieldId(new UUID(cfId.getCalculatedFieldIdMSB(), cfId.getCalculatedFieldIdLSB())));
+ }
+ return cfIds;
+ }
+
+ private UUID toTbMsgId(CalculatedFieldTelemetryMsgProto proto) {
+ if (proto.getTbMsgIdMSB() != 0 && proto.getTbMsgIdLSB() != 0) {
+ return new UUID(proto.getTbMsgIdMSB(), proto.getTbMsgIdLSB());
+ }
+ return null;
+ }
+
+ private TbMsgType toTbMsgType(CalculatedFieldTelemetryMsgProto proto) {
+ if (!proto.getTbMsgType().isEmpty()) {
+ return TbMsgType.valueOf(proto.getTbMsgType());
+ }
+ return null;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldException.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldException.java
new file mode 100644
index 0000000000..70c8dfbfd2
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldException.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Builder;
+import lombok.Getter;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.Map;
+import java.util.UUID;
+
+@Getter
+@Builder
+public class CalculatedFieldException extends Exception {
+
+ private final CalculatedFieldCtx ctx;
+ private final EntityId eventEntity;
+ private final UUID msgId;
+ private final TbMsgType msgType;
+ private Map arguments;
+ private String errorMessage;
+ private Exception cause;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldLinkedTelemetryMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldLinkedTelemetryMsg.java
new file mode 100644
index 0000000000..3e0fba2627
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldLinkedTelemetryMsg.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+
+@Data
+public class CalculatedFieldLinkedTelemetryMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+ private final CalculatedFieldLinkedTelemetryMsgProto proto;
+ private final TbCallback callback;
+
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_LINKED_TELEMETRY_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java
new file mode 100644
index 0000000000..a5c935e83f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActorCtx;
+import org.thingsboard.server.actors.TbActorException;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
+
+/**
+ * Created by ashvayka on 15.03.18.
+ */
+@Slf4j
+public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
+
+ private final CalculatedFieldManagerMessageProcessor processor;
+
+ public CalculatedFieldManagerActor(ActorSystemContext systemContext, TenantId tenantId) {
+ super(systemContext, tenantId);
+ this.processor = new CalculatedFieldManagerMessageProcessor(systemContext, tenantId);
+ }
+
+ @Override
+ public void init(TbActorCtx ctx) throws TbActorException {
+ super.init(ctx);
+ log.debug("[{}] Starting CF manager actor.", processor.tenantId);
+ try {
+ processor.init(ctx);
+ log.debug("[{}] CF manager actor started.", processor.tenantId);
+ } catch (Exception e) {
+ log.warn("[{}] Unknown failure", processor.tenantId, e);
+ throw new TbActorException("Failed to initialize manager actor", e);
+ }
+ }
+
+ @Override
+ protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
+ switch (msg.getMsgType()) {
+ case CF_PARTITIONS_CHANGE_MSG:
+ processor.onPartitionChange((CalculatedFieldPartitionChangeMsg) msg);
+ break;
+ case CF_INIT_MSG:
+ processor.onFieldInitMsg((CalculatedFieldInitMsg) msg);
+ break;
+ case CF_LINK_INIT_MSG:
+ processor.onLinkInitMsg((CalculatedFieldLinkInitMsg) msg);
+ break;
+ case CF_STATE_RESTORE_MSG:
+ processor.onStateRestoreMsg((CalculatedFieldStateRestoreMsg) msg);
+ break;
+ case CF_ENTITY_LIFECYCLE_MSG:
+ processor.onEntityLifecycleMsg((CalculatedFieldEntityLifecycleMsg) msg);
+ break;
+ case CF_TELEMETRY_MSG:
+ processor.onTelemetryMsg((CalculatedFieldTelemetryMsg) msg);
+ break;
+ case CF_LINKED_TELEMETRY_MSG:
+ processor.onLinkedTelemetryMsg((CalculatedFieldLinkedTelemetryMsg) msg);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ void logProcessingException(Exception e) {
+ log.warn("[{}] Processing failure", tenantId, e);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActorCreator.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActorCreator.java
new file mode 100644
index 0000000000..99bf3cdbe9
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActorCreator.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActor;
+import org.thingsboard.server.actors.TbActorId;
+import org.thingsboard.server.actors.TbEntityActorId;
+import org.thingsboard.server.actors.TbStringActorId;
+import org.thingsboard.server.actors.service.ContextBasedCreator;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+public class CalculatedFieldManagerActorCreator extends ContextBasedCreator {
+
+ private final TenantId tenantId;
+
+ public CalculatedFieldManagerActorCreator(ActorSystemContext context, TenantId tenantId) {
+ super(context);
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public TbActorId createActorId() {
+ return new TbStringActorId("CFM|" + tenantId);
+ }
+
+ @Override
+ public TbActor createActor() {
+ return new CalculatedFieldManagerActor(context, tenantId);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
new file mode 100644
index 0000000000..fc48e9ad3e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
@@ -0,0 +1,491 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActorCtx;
+import org.thingsboard.server.actors.TbActorRef;
+import org.thingsboard.server.actors.TbCalculatedFieldEntityActorId;
+import org.thingsboard.server.actors.service.DefaultActorService;
+import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.cf.CalculatedField;
+import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
+import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.dao.cf.CalculatedFieldService;
+import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
+import org.thingsboard.server.service.cf.CalculatedFieldStateService;
+import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+import org.thingsboard.server.service.profile.TbAssetProfileCache;
+import org.thingsboard.server.service.profile.TbDeviceProfileCache;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto;
+
+
+/**
+ * @author Andrew Shvayka
+ */
+@Slf4j
+public class CalculatedFieldManagerMessageProcessor extends AbstractContextAwareMsgProcessor {
+
+ private final Map calculatedFields = new HashMap<>();
+ private final Map> entityIdCalculatedFields = new HashMap<>();
+ private final Map> entityIdCalculatedFieldLinks = new HashMap<>();
+
+ private final CalculatedFieldProcessingService cfExecService;
+ private final CalculatedFieldStateService cfStateService;
+ private final CalculatedFieldEntityProfileCache cfEntityCache;
+ private final CalculatedFieldService cfDaoService;
+ private final TbAssetProfileCache assetProfileCache;
+ private final TbDeviceProfileCache deviceProfileCache;
+ protected final TenantId tenantId;
+
+ protected TbActorCtx ctx;
+
+ CalculatedFieldManagerMessageProcessor(ActorSystemContext systemContext, TenantId tenantId) {
+ super(systemContext);
+ this.cfEntityCache = systemContext.getCalculatedFieldEntityProfileCache();
+ this.cfExecService = systemContext.getCalculatedFieldProcessingService();
+ this.cfStateService = systemContext.getCalculatedFieldStateService();
+ this.cfDaoService = systemContext.getCalculatedFieldService();
+ this.assetProfileCache = systemContext.getAssetProfileCache();
+ this.deviceProfileCache = systemContext.getDeviceProfileCache();
+ this.tenantId = tenantId;
+ }
+
+ void init(TbActorCtx ctx) {
+ this.ctx = ctx;
+ }
+
+ public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
+ log.info("[{}] Processing CF init message.", msg.getCf().getId());
+ var cf = msg.getCf();
+ var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
+ try {
+ cfCtx.init();
+ } catch (Exception e) {
+ throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
+ }
+ calculatedFields.put(cf.getId(), cfCtx);
+ // We use copy on write lists to safely pass the reference to another actor for the iteration.
+ // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
+ entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
+ msg.getCallback().onSuccess();
+ }
+
+ public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) {
+ log.info("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId());
+ var link = msg.getLink();
+ // We use copy on write lists to safely pass the reference to another actor for the iteration.
+ // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
+ entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
+ msg.getCallback().onSuccess();
+ }
+
+ public void onStateRestoreMsg(CalculatedFieldStateRestoreMsg msg) {
+ var cfId = msg.getId().cfId();
+ var calculatedField = calculatedFields.get(cfId);
+
+ if (calculatedField != null) {
+ msg.getState().setRequiredArguments(calculatedField.getArgNames());
+ log.debug("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId());
+ getOrCreateActor(msg.getId().entityId()).tell(msg);
+ } else {
+ cfStateService.removeState(msg.getId(), msg.getCallback());
+ }
+ }
+
+ public void onEntityLifecycleMsg(CalculatedFieldEntityLifecycleMsg msg) throws CalculatedFieldException {
+ log.info("Processing entity lifecycle event: [{}] for entity: [{}]", msg.getData().getEvent(), msg.getData().getEntityId());
+ var entityType = msg.getData().getEntityId().getEntityType();
+ var event = msg.getData().getEvent();
+ switch (entityType) {
+ case CALCULATED_FIELD: {
+ switch (event) {
+ case CREATED:
+ onCfCreated(msg.getData(), msg.getCallback());
+ break;
+ case UPDATED:
+ onCfUpdated(msg.getData(), msg.getCallback());
+ break;
+ case DELETED:
+ onCfDeleted(msg.getData(), msg.getCallback());
+ break;
+ default:
+ msg.getCallback().onSuccess();
+ break;
+ }
+ break;
+ }
+ case DEVICE:
+ case ASSET: {
+ switch (event) {
+ case CREATED:
+ onEntityCreated(msg.getData(), msg.getCallback());
+ break;
+ case UPDATED:
+ onEntityUpdated(msg.getData(), msg.getCallback());
+ break;
+ case DELETED:
+ onEntityDeleted(msg.getData(), msg.getCallback());
+ break;
+ default:
+ msg.getCallback().onSuccess();
+ break;
+ }
+ break;
+ }
+ default: {
+ msg.getCallback().onSuccess();
+ }
+ }
+ }
+
+ private void onEntityCreated(ComponentLifecycleMsg msg, TbCallback callback) {
+ EntityId entityId = msg.getEntityId();
+ EntityId profileId = getProfileId(tenantId, entityId);
+ cfEntityCache.add(tenantId, profileId, entityId);
+ if (!isMyPartition(entityId, callback)) {
+ return;
+ }
+ var entityIdFields = getCalculatedFieldsByEntityId(entityId);
+ var profileIdFields = getCalculatedFieldsByEntityId(profileId);
+ var fieldsCount = entityIdFields.size() + profileIdFields.size();
+ if (fieldsCount > 0) {
+ MultipleTbCallback multiCallback = new MultipleTbCallback(fieldsCount, callback);
+ entityIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, true, multiCallback));
+ profileIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, true, multiCallback));
+ } else {
+ callback.onSuccess();
+ }
+ }
+
+ private void onEntityUpdated(ComponentLifecycleMsg msg, TbCallback callback) {
+ if (msg.getOldProfileId() != null && !msg.getOldProfileId().equals(msg.getProfileId())) {
+ cfEntityCache.update(tenantId, msg.getOldProfileId(), msg.getProfileId(), msg.getEntityId());
+ if (!isMyPartition(msg.getEntityId(), callback)) {
+ return;
+ }
+ var oldProfileCfs = getCalculatedFieldsByEntityId(msg.getOldProfileId());
+ var newProfileCfs = getCalculatedFieldsByEntityId(msg.getProfileId());
+ var fieldsCount = oldProfileCfs.size() + newProfileCfs.size();
+ if (fieldsCount > 0) {
+ MultipleTbCallback multiCallback = new MultipleTbCallback(fieldsCount, callback);
+ var entityId = msg.getEntityId();
+ oldProfileCfs.forEach(ctx -> deleteCfForEntity(entityId, ctx.getCfId(), multiCallback));
+ newProfileCfs.forEach(ctx -> initCfForEntity(entityId, ctx, true, multiCallback));
+ } else {
+ callback.onSuccess();
+ }
+ }
+ }
+
+ private void onEntityDeleted(ComponentLifecycleMsg msg, TbCallback callback) {
+ cfEntityCache.evict(tenantId, msg.getEntityId());
+ if (isMyPartition(msg.getEntityId(), callback)) {
+ log.debug("Pushing entity lifecycle msg to specific actor [{}]", msg.getEntityId());
+ getOrCreateActor(msg.getEntityId()).tell(new CalculatedFieldEntityDeleteMsg(tenantId, msg.getEntityId(), callback));
+ }
+ }
+
+ private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
+ var cfId = new CalculatedFieldId(msg.getEntityId().getId());
+ if (calculatedFields.containsKey(cfId)) {
+ log.warn("[{}] CF was already initialized [{}]", tenantId, cfId);
+ callback.onSuccess();
+ } else {
+ var cf = cfDaoService.findById(msg.getTenantId(), cfId);
+ if (cf == null) {
+ log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
+ callback.onSuccess();
+ } else {
+ var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
+ try {
+ cfCtx.init();
+ } catch (Exception e) {
+ throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
+ }
+ calculatedFields.put(cf.getId(), cfCtx);
+ // We use copy on write lists to safely pass the reference to another actor for the iteration.
+ // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
+ entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
+ addLinks(cf);
+ initCf(cfCtx, callback, false);
+ }
+ }
+ }
+
+ private void onCfUpdated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
+ var cfId = new CalculatedFieldId(msg.getEntityId().getId());
+ var oldCfCtx = calculatedFields.get(cfId);
+ if (oldCfCtx == null) {
+ onCfCreated(msg, callback);
+ } else {
+ var newCf = cfDaoService.findById(msg.getTenantId(), cfId);
+ if (newCf == null) {
+ log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
+ callback.onSuccess();
+ } else {
+ var newCfCtx = new CalculatedFieldCtx(newCf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
+ try {
+ newCfCtx.init();
+ } catch (Exception e) {
+ throw CalculatedFieldException.builder().ctx(newCfCtx).eventEntity(newCfCtx.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
+ }
+ calculatedFields.put(newCf.getId(), newCfCtx);
+ List oldCfList = entityIdCalculatedFields.get(newCf.getEntityId());
+ List newCfList = new CopyOnWriteArrayList<>();
+ boolean found = false;
+ for (CalculatedFieldCtx oldCtx : oldCfList) {
+ if (oldCtx.getCfId().equals(newCf.getId())) {
+ newCfList.add(newCfCtx);
+ found = true;
+ } else {
+ newCfList.add(oldCtx);
+ }
+ }
+ if (!found) {
+ newCfList.add(newCfCtx);
+ }
+ entityIdCalculatedFields.put(newCf.getEntityId(), newCfList);
+
+ deleteLinks(oldCfCtx);
+ addLinks(newCf);
+
+ // We use copy on write lists to safely pass the reference to another actor for the iteration.
+ // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
+ var stateChanges = newCfCtx.hasStateChanges(oldCfCtx);
+ if (stateChanges || newCfCtx.hasOtherSignificantChanges(oldCfCtx)) {
+ initCf(newCfCtx, callback, stateChanges);
+ } else {
+ callback.onSuccess();
+ }
+ }
+ }
+ }
+
+ private void onCfDeleted(ComponentLifecycleMsg msg, TbCallback callback) {
+ var cfId = new CalculatedFieldId(msg.getEntityId().getId());
+ var cfCtx = calculatedFields.remove(cfId);
+ if (cfCtx == null) {
+ log.warn("[{}] CF was already deleted [{}]", tenantId, cfId);
+ callback.onSuccess();
+ } else {
+ entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx);
+ deleteLinks(cfCtx);
+
+ EntityId entityId = cfCtx.getEntityId();
+ EntityType entityType = cfCtx.getEntityId().getEntityType();
+ if (isProfileEntity(entityType)) {
+ var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, entityId);
+ if (!entityIds.isEmpty()) {
+ //TODO: no need to do this if we cache all created actors and know which one belong to us;
+ var multiCallback = new MultipleTbCallback(entityIds.size(), callback);
+ entityIds.forEach(id -> deleteCfForEntity(id, cfId, multiCallback));
+ } else {
+ callback.onSuccess();
+ }
+ } else {
+ if (isMyPartition(entityId, callback)) {
+ deleteCfForEntity(entityId, cfId, callback);
+ }
+ }
+ }
+ }
+
+ public void onTelemetryMsg(CalculatedFieldTelemetryMsg msg) {
+ EntityId entityId = msg.getEntityId();
+ log.debug("Received telemetry msg from entity [{}]", entityId);
+ // 2 = 1 for CF processing + 1 for links processing
+ MultipleTbCallback callback = new MultipleTbCallback(2, msg.getCallback());
+ // process all cfs related to entity, or it's profile;
+ var entityIdFields = getCalculatedFieldsByEntityId(entityId);
+ var profileIdFields = getCalculatedFieldsByEntityId(getProfileId(tenantId, entityId));
+ if (!entityIdFields.isEmpty() || !profileIdFields.isEmpty()) {
+ log.debug("Pushing telemetry msg to specific actor [{}]", entityId);
+ getOrCreateActor(entityId).tell(new EntityCalculatedFieldTelemetryMsg(msg, entityIdFields, profileIdFields, callback));
+ } else {
+ callback.onSuccess();
+ }
+ // process all links (if any);
+ List linkedCalculatedFields = filterCalculatedFieldLinks(msg);
+ var linksSize = linkedCalculatedFields.size();
+ if (linksSize > 0) {
+ cfExecService.pushMsgToLinks(msg, linkedCalculatedFields, callback);
+ } else {
+ callback.onSuccess();
+ }
+ }
+
+ public void onLinkedTelemetryMsg(CalculatedFieldLinkedTelemetryMsg msg) {
+ EntityId sourceEntityId = msg.getEntityId();
+ log.debug("Received linked telemetry msg from entity [{}]", sourceEntityId);
+ var proto = msg.getProto();
+ var linksList = proto.getLinksList();
+ for (var linkProto : linksList) {
+ var link = fromProto(linkProto);
+ var targetEntityId = link.entityId();
+ var targetEntityType = targetEntityId.getEntityType();
+ var cf = calculatedFields.get(link.cfId());
+ if (EntityType.DEVICE_PROFILE.equals(targetEntityType) || EntityType.ASSET_PROFILE.equals(targetEntityType)) {
+ // iterate over all entities that belong to profile and push the message for corresponding CF
+ var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, targetEntityId);
+ if (!entityIds.isEmpty()) {
+ MultipleTbCallback callback = new MultipleTbCallback(entityIds.size(), msg.getCallback());
+ var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, callback);
+ entityIds.forEach(entityId -> {
+ log.debug("Pushing linked telemetry msg to specific actor [{}]", entityId);
+ getOrCreateActor(entityId).tell(newMsg);
+ });
+ } else {
+ msg.getCallback().onSuccess();
+ }
+ } else {
+ log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId);
+ var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback());
+ getOrCreateActor(targetEntityId).tell(newMsg);
+ }
+ }
+ }
+
+ private List filterCalculatedFieldLinks(CalculatedFieldTelemetryMsg msg) {
+ EntityId entityId = msg.getEntityId();
+ var proto = msg.getProto();
+ List result = new ArrayList<>();
+ for (var link : getCalculatedFieldLinksByEntityId(entityId)) {
+ CalculatedFieldCtx ctx = calculatedFields.get(link.getCalculatedFieldId());
+ if (ctx.linkMatches(entityId, proto)) {
+ result.add(ctx.toCalculatedFieldEntityCtxId());
+ }
+ }
+ return result;
+ }
+
+ private List getCalculatedFieldsByEntityId(EntityId entityId) {
+ if (entityId == null) {
+ return Collections.emptyList();
+ }
+ var result = entityIdCalculatedFields.get(entityId);
+ if (result == null) {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ private List getCalculatedFieldLinksByEntityId(EntityId entityId) {
+ if (entityId == null) {
+ return Collections.emptyList();
+ }
+ var result = entityIdCalculatedFieldLinks.get(entityId);
+ if (result == null) {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ private void initCf(CalculatedFieldCtx cfCtx, TbCallback callback, boolean forceStateReinit) {
+ EntityId entityId = cfCtx.getEntityId();
+ EntityType entityType = cfCtx.getEntityId().getEntityType();
+ if (isProfileEntity(entityType)) {
+ var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, entityId);
+ if (!entityIds.isEmpty()) {
+ var multiCallback = new MultipleTbCallback(entityIds.size(), callback);
+ entityIds.forEach(id -> initCfForEntity(id, cfCtx, forceStateReinit, multiCallback));
+ } else {
+ callback.onSuccess();
+ }
+ } else {
+ if (isMyPartition(entityId, callback)) {
+ initCfForEntity(entityId, cfCtx, forceStateReinit, callback);
+ }
+ }
+ }
+
+ private void deleteCfForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) {
+ log.debug("Pushing delete CF msg to specific actor [{}]", entityId);
+ getOrCreateActor(entityId).tell(new CalculatedFieldEntityDeleteMsg(tenantId, cfId, callback));
+ }
+
+ private void initCfForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, boolean forceStateReinit, TbCallback callback) {
+ log.debug("Pushing entity init CF msg to specific actor [{}]", entityId);
+ getOrCreateActor(entityId).tell(new EntityInitCalculatedFieldMsg(tenantId, cfCtx, callback, forceStateReinit));
+ }
+
+ private boolean isMyPartition(EntityId entityId, TbCallback callback) {
+ if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) {
+ log.debug("[{}] Entity belongs to external partition.", entityId);
+ callback.onSuccess();
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isProfileEntity(EntityType entityType) {
+ return EntityType.DEVICE_PROFILE.equals(entityType) || EntityType.ASSET_PROFILE.equals(entityType);
+ }
+
+ private EntityId getProfileId(TenantId tenantId, EntityId entityId) {
+ return switch (entityId.getEntityType()) {
+ case ASSET -> assetProfileCache.get(tenantId, (AssetId) entityId).getId();
+ case DEVICE -> deviceProfileCache.get(tenantId, (DeviceId) entityId).getId();
+ default -> null;
+ };
+ }
+
+ private TbActorRef getOrCreateActor(EntityId entityId) {
+ return ctx.getOrCreateChildActor(new TbCalculatedFieldEntityActorId(entityId),
+ () -> DefaultActorService.CF_ENTITY_DISPATCHER_NAME,
+ () -> new CalculatedFieldEntityActorCreator(systemContext, tenantId, entityId),
+ () -> true);
+ }
+
+ private void addLinks(CalculatedField newCf) {
+ var newLinks = newCf.getConfiguration().buildCalculatedFieldLinks(tenantId, newCf.getEntityId(), newCf.getId());
+ newLinks.forEach(link -> entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link));
+ }
+
+ private void deleteLinks(CalculatedFieldCtx cfCtx) {
+ var oldCf = cfCtx.getCalculatedField();
+ var oldLinks = oldCf.getConfiguration().buildCalculatedFieldLinks(tenantId, oldCf.getEntityId(), oldCf.getId());
+ oldLinks.forEach(link -> entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).remove(link));
+ }
+
+ public void onPartitionChange(CalculatedFieldPartitionChangeMsg msg) {
+ ctx.broadcastToChildren(msg, true);
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldStateRestoreMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldStateRestoreMsg.java
new file mode 100644
index 0000000000..19be7c02fa
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldStateRestoreMsg.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+
+@Data
+public class CalculatedFieldStateRestoreMsg implements ToCalculatedFieldSystemMsg {
+
+ private final CalculatedFieldEntityCtxId id;
+ private final CalculatedFieldState state;
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_STATE_RESTORE_MSG;
+ }
+
+ @Override
+ public TenantId getTenantId() {
+ return id.tenantId();
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldTelemetryMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldTelemetryMsg.java
new file mode 100644
index 0000000000..68cd149cdf
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldTelemetryMsg.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+
+@Data
+public class CalculatedFieldTelemetryMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+ private final CalculatedFieldTelemetryMsgProto proto;
+ private final TbCallback callback;
+
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_TELEMETRY_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldLinkedTelemetryMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldLinkedTelemetryMsg.java
new file mode 100644
index 0000000000..b83aeae416
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldLinkedTelemetryMsg.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.List;
+
+@Data
+public class EntityCalculatedFieldLinkedTelemetryMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+ private final CalculatedFieldTelemetryMsgProto proto;
+ private final CalculatedFieldCtx ctx;
+ private final TbCallback callback;
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_LINKED_TELEMETRY_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldTelemetryMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldTelemetryMsg.java
new file mode 100644
index 0000000000..8ded4b6028
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityCalculatedFieldTelemetryMsg.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.List;
+
+@Data
+public class EntityCalculatedFieldTelemetryMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final EntityId entityId;
+ private final CalculatedFieldTelemetryMsgProto proto;
+ // Both lists are effectively immutable in CalculatedFieldManagerMessageProcessor and must stay so.
+ private final List entityIdFields;
+ private final List profileIdFields;
+ private final TbCallback callback;
+
+ public EntityCalculatedFieldTelemetryMsg(CalculatedFieldTelemetryMsg msg,
+ List entityIdFields,
+ List profileIdFields,
+ TbCallback callback) {
+ this.tenantId = msg.getTenantId();
+ this.entityId = msg.getEntityId();
+ this.proto = msg.getProto();
+ this.entityIdFields = entityIdFields;
+ this.profileIdFields = profileIdFields;
+ this.callback = callback;
+ }
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_ENTITY_TELEMETRY_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java
new file mode 100644
index 0000000000..1e8990ff8d
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.List;
+
+@Data
+public class EntityInitCalculatedFieldMsg implements ToCalculatedFieldSystemMsg {
+
+ private final TenantId tenantId;
+ private final CalculatedFieldCtx ctx;
+ private final TbCallback callback;
+ private final boolean forceReinit;
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.CF_ENTITY_INIT_CF_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/MultipleTbCallback.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/MultipleTbCallback.java
new file mode 100644
index 0000000000..d1f4c9092e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/MultipleTbCallback.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2016-2025 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.actors.calculatedField;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+public class MultipleTbCallback implements TbCallback {
+ @Getter
+ private final UUID id;
+ private final AtomicInteger counter;
+ private final TbCallback callback;
+
+ public MultipleTbCallback(int count, TbCallback callback) {
+ id = UUID.randomUUID();
+ this.counter = new AtomicInteger(count);
+ this.callback = callback;
+ }
+
+ @Override
+ public void onSuccess() {
+ onSuccess(1);
+ }
+
+ public void onSuccess(int number) {
+ log.trace("[{}][{}] onSuccess({})", id, callback.getId(), number);
+ if (counter.addAndGet(-number) <= 0) {
+ log.trace("[{}][{}] Done.", id, callback.getId());
+ callback.onSuccess();
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.warn("[{}][{}] onFailure.", id, callback.getId());
+ callback.onFailure(t);
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index a1e8434b6e..033e10ca9a 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -28,8 +28,9 @@ import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService;
import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache;
+import org.thingsboard.rule.engine.api.RuleEngineCalculatedFieldQueueService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
-import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager;
+import org.thingsboard.rule.engine.api.DeviceStateManager;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.ScriptEngine;
@@ -79,6 +80,7 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
+import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
@@ -652,27 +654,6 @@ public class DefaultTbContext implements TbContext {
}
}
- @Override
- public void logJsEvalRequest() {
- if (mainCtx.isStatisticsEnabled()) {
- mainCtx.getJsInvokeStats().incrementRequests();
- }
- }
-
- @Override
- public void logJsEvalResponse() {
- if (mainCtx.isStatisticsEnabled()) {
- mainCtx.getJsInvokeStats().incrementResponses();
- }
- }
-
- @Override
- public void logJsEvalFailure() {
- if (mainCtx.isStatisticsEnabled()) {
- mainCtx.getJsInvokeStats().incrementFailures();
- }
- }
-
@Override
public String getServiceId() {
return mainCtx.getServiceInfoProvider().getServiceId();
@@ -724,7 +705,7 @@ public class DefaultTbContext implements TbContext {
}
@Override
- public RuleEngineDeviceStateManager getDeviceStateManager() {
+ public DeviceStateManager getDeviceStateManager() {
return mainCtx.getDeviceStateManager();
}
@@ -896,6 +877,16 @@ public class DefaultTbContext implements TbContext {
return mainCtx.getSlackService();
}
+ @Override
+ public CalculatedFieldService getCalculatedFieldService() {
+ return mainCtx.getCalculatedFieldService();
+ }
+
+ @Override
+ public RuleEngineCalculatedFieldQueueService getCalculatedFieldQueueService() {
+ return mainCtx.getCalculatedFieldQueueService();
+ }
+
@Override
public boolean isExternalNodeForceAck() {
return mainCtx.isExternalNodeForceAck();
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
index 5778f02663..7b390cd9f3 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
@@ -29,6 +29,9 @@ import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+
/**
* @author Andrew Shvayka
*/
@@ -41,6 +44,7 @@ public abstract class ComponentActor statsScheduledFuture = null;
public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) {
super(systemContext);
@@ -73,9 +77,9 @@ public abstract class ComponentActor x.cancel(false));
+ statsScheduledFuture = null;
} catch (Exception e) {
log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
logAndPersist("OnStop", e, true);
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
index 6c8f253138..c2131d9e74 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
@@ -49,6 +49,8 @@ public class DefaultActorService extends TbApplicationEventListener schedulePeriodicMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
+ return systemContext.schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs);
}
protected void scheduleMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
index d48a8f7684..76e5ce7899 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
@@ -27,6 +27,8 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
+import java.util.concurrent.ScheduledFuture;
+
@Slf4j
public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgProcessor {
@@ -77,8 +79,8 @@ public abstract class ComponentMsgProcessor extends Abstract
start(context);
}
- public void scheduleStatsPersistTick(TbActorCtx context, long statsPersistFrequency) {
- schedulePeriodicMsgWithDelay(context, StatsPersistTick.INSTANCE, statsPersistFrequency, statsPersistFrequency);
+ public ScheduledFuture> scheduleStatsPersistTick(TbActorCtx context, long statsPersistFrequency) {
+ return schedulePeriodicMsgWithDelay(context, StatsPersistTick.INSTANCE, statsPersistFrequency, statsPersistFrequency);
}
protected boolean checkMsgValid(TbMsg tbMsg) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index cc798616d6..846bde508d 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -26,6 +26,8 @@ import org.thingsboard.server.actors.TbActorNotRegisteredException;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
+import org.thingsboard.server.actors.TbStringActorId;
+import org.thingsboard.server.actors.calculatedField.CalculatedFieldManagerActorCreator;
import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@@ -44,8 +46,10 @@ import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@@ -64,8 +68,8 @@ public class TenantActor extends RuleChainManagerActor {
private boolean isRuleEngine;
private boolean isCore;
private ApiUsageState apiUsageState;
-
private Set deletedDevices;
+ private TbActorRef cfActor;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
@@ -88,6 +92,15 @@ public class TenantActor extends RuleChainManagerActor {
isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngine) {
if (systemContext.getPartitionService().isManagedByCurrentService(tenantId)) {
+ try {
+ //TODO: IM - extend API usage to have CF Exec Enabled? Not in 4.0;
+ cfActor = ctx.getOrCreateChildActor(new TbStringActorId("CFM|" + tenantId),
+ () -> DefaultActorService.CF_MANAGER_DISPATCHER_NAME,
+ () -> new CalculatedFieldManagerActorCreator(systemContext, tenantId),
+ () -> true);
+ } catch (Exception e) {
+ log.info("[{}] Failed to init CF Actor.", tenantId, e);
+ }
try {
if (getApiUsageState().isReExecEnabled()) {
log.debug("[{}] Going to init rule chains", tenantId);
@@ -100,7 +113,7 @@ public class TenantActor extends RuleChainManagerActor {
cantFindTenant = true;
}
} else {
- log.info("Tenant {} is not managed by current service, skipping rule chains init", tenantId);
+ log.info("Tenant {} is not managed by current service, skipping rule chains and cf actor init", tenantId);
}
}
log.debug("[{}] Tenant actor started.", tenantId);
@@ -159,12 +172,34 @@ public class TenantActor extends RuleChainManagerActor {
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
break;
+ case CF_INIT_MSG:
+ case CF_LINK_INIT_MSG:
+ case CF_STATE_RESTORE_MSG:
+ case CF_PARTITIONS_CHANGE_MSG:
+ onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
+ break;
+ case CF_TELEMETRY_MSG:
+ case CF_LINKED_TELEMETRY_MSG:
+ onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, false);
+ break;
default:
return false;
}
return true;
}
+ private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
+ if (cfActor == null) {
+ log.warn("[{}] CF Actor is not initialized.", tenantId);
+ return;
+ }
+ if (priority) {
+ cfActor.tellWithHighPriority(msg);
+ } else {
+ cfActor.tell(msg);
+ }
+ }
+
private boolean isMyPartition(EntityId entityId) {
return systemContext.resolve(ServiceType.TB_CORE, tenantId, entityId).isMyPartition();
}
@@ -224,11 +259,25 @@ public class TenantActor extends RuleChainManagerActor {
ServiceType serviceType = msg.getServiceType();
if (ServiceType.TB_RULE_ENGINE.equals(serviceType)) {
if (systemContext.getPartitionService().isManagedByCurrentService(tenantId)) {
+ if (cfActor == null) {
+ try {
+ //TODO: IM - extend API usage to have CF Exec Enabled? Not in 4.0;
+ cfActor = ctx.getOrCreateChildActor(new TbStringActorId("CFM|" + tenantId),
+ () -> DefaultActorService.CF_MANAGER_DISPATCHER_NAME,
+ () -> new CalculatedFieldManagerActorCreator(systemContext, tenantId),
+ () -> true);
+ } catch (Exception e) {
+ log.info("[{}] Failed to init CF Actor.", tenantId, e);
+ }
+ }
if (!ruleChainsInitialized) {
log.info("Tenant {} is now managed by this service, initializing rule chains", tenantId);
initRuleChains();
}
} else {
+ if (cfActor != null) {
+ ctx.stop(cfActor.getActorId());
+ }
if (ruleChainsInitialized) {
log.info("Tenant {} is no longer managed by this service, stopping rule chains", tenantId);
destroyRuleChains();
@@ -266,19 +315,26 @@ public class TenantActor extends RuleChainManagerActor {
onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true);
deletedDevices.add(deviceId);
}
- if (isRuleEngine && ruleChainsInitialized) {
- TbActorRef target = getEntityActorRef(msg.getEntityId());
- if (target != null) {
- if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
- RuleChain ruleChain = systemContext.getRuleChainService().
- findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
- if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
- visit(ruleChain, target);
+ if (isRuleEngine) {
+ if (ruleChainsInitialized) {
+ TbActorRef target = getEntityActorRef(msg.getEntityId());
+ if (target != null) {
+ if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
+ RuleChain ruleChain = systemContext.getRuleChainService().
+ findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
+ if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
+ visit(ruleChain, target);
+ }
}
+ target.tellWithHighPriority(msg);
+ } else {
+ log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
+ }
+ }
+ if (cfActor != null) {
+ if (msg.getEntityId().getEntityType().isOneOf(EntityType.CALCULATED_FIELD, EntityType.DEVICE, EntityType.ASSET)) {
+ cfActor.tellWithHighPriority(new CalculatedFieldEntityLifecycleMsg(tenantId, msg));
}
- target.tellWithHighPriority(msg);
- } else {
- log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
}
}
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index cf9d6444a2..73e278389a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -70,6 +70,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo;
@@ -80,6 +81,7 @@ import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -132,6 +134,7 @@ import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
+import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
@@ -367,6 +370,9 @@ public abstract class BaseController {
@Autowired
protected NotificationTargetService notificationTargetService;
+ @Autowired
+ protected CalculatedFieldService calculatedFieldService;
+
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@@ -672,6 +678,9 @@ public abstract class BaseController {
case MOBILE_APP_BUNDLE:
checkMobileAppBundleId(new MobileAppBundleId(entityId.getId()), operation);
return;
+ case CALCULATED_FIELD:
+ checkCalculatedFieldId(new CalculatedFieldId(entityId.getId()), operation);
+ return;
default:
checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation);
}
@@ -955,6 +964,10 @@ public abstract class BaseController {
}
}
+ protected CalculatedField checkCalculatedFieldId(CalculatedFieldId calculatedFieldId, Operation operation) throws ThingsboardException {
+ return checkEntityId(calculatedFieldId, calculatedFieldService::findById, operation);
+ }
+
protected HomeDashboardInfo getHomeDashboardInfo(SecurityUser securityUser, JsonNode additionalInfo) {
HomeDashboardInfo homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo);
if (homeDashboardInfo == null) {
@@ -982,7 +995,8 @@ public abstract class BaseController {
}
return new HomeDashboardInfo(dashboardId, hideDashboardToolbar);
}
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
return null;
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java
new file mode 100644
index 0000000000..f899d0f480
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java
@@ -0,0 +1,283 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.script.api.tbel.TbelCfArg;
+import org.thingsboard.script.api.tbel.TbelCfCtx;
+import org.thingsboard.script.api.tbel.TbelCfSingleValueArg;
+import org.thingsboard.script.api.tbel.TbelInvokeService;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EventInfo;
+import org.thingsboard.server.common.data.HasTenantId;
+import org.thingsboard.server.common.data.cf.CalculatedField;
+import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
+import org.thingsboard.server.common.data.event.EventType;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.HasId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.config.annotations.ApiOperation;
+import org.thingsboard.server.dao.event.EventService;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldScriptEngine;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldTbelScriptEngine;
+import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.permission.Operation;
+import org.thingsboard.server.service.security.permission.Resource;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static org.thingsboard.server.controller.ControllerConstants.CF_TEXT_SEARCH_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
+import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
+import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
+
+@RestController
+@TbCoreComponent
+@RequestMapping("/api")
+@RequiredArgsConstructor
+@Slf4j
+public class CalculatedFieldController extends BaseController {
+
+ private final TbCalculatedFieldService tbCalculatedFieldService;
+ private final EventService eventService;
+ private final TbelInvokeService tbelInvokeService;
+
+ public static final String CALCULATED_FIELD_ID = "calculatedFieldId";
+
+ public static final int TIMEOUT = 20;
+
+ private static final String TEST_SCRIPT_EXPRESSION = "Execute the Script expression and return the result. The format of request: \n\n"
+ + MARKDOWN_CODE_BLOCK_START
+ + "{\n" +
+ " \"expression\": \"var temp = 0; foreach(element: temperature.values) {temp += element.value;} var avgTemperature = temp / temperature.values.size(); var adjustedTemperature = avgTemperature + 0.1 * humidity.value; return {\\\"adjustedTemperature\\\": adjustedTemperature};\",\n" +
+ " \"arguments\": {\n" +
+ " \"temperature\": {\n" +
+ " \"type\": \"TS_ROLLING\",\n" +
+ " \"timeWindow\": {\n" +
+ " \"startTs\": 1739775630002,\n" +
+ " \"endTs\": 65432211,\n" +
+ " \"limit\": 5\n" +
+ " },\n" +
+ " \"values\": [\n" +
+ " { \"ts\": 1739775639851, \"value\": 23 },\n" +
+ " { \"ts\": 1739775664561, \"value\": 43 },\n" +
+ " { \"ts\": 1739775713079, \"value\": 15 },\n" +
+ " { \"ts\": 1739775999522, \"value\": 34 },\n" +
+ " { \"ts\": 1739776228452, \"value\": 22 }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"humidity\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 23 }\n" +
+ " }\n" +
+ "}"
+ + MARKDOWN_CODE_BLOCK_END
+ + "\n\n Expected result JSON contains \"output\" and \"error\".";
+
+ @ApiOperation(value = "Create Or Update Calculated Field (saveCalculatedField)",
+ notes = "Creates or Updates the Calculated Field. When creating calculated field, platform generates Calculated Field Id as " + UUID_WIKI_LINK +
+ "The newly created Calculated Field Id will be present in the response. " +
+ "Specify existing Calculated Field Id to update the calculated field. " +
+ "Referencing non-existing Calculated Field Id will cause 'Not Found' error. " +
+ "Remove 'id', 'tenantId' from the request body example (below) to create new Calculated Field entity. "
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/calculatedField", method = RequestMethod.POST)
+ @ResponseBody
+ public CalculatedField saveCalculatedField(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the calculated field.")
+ @RequestBody CalculatedField calculatedField) throws Exception {
+ calculatedField.setTenantId(getTenantId());
+ checkEntity(calculatedField.getId(), calculatedField, Resource.CALCULATED_FIELD);
+ checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD);
+ checkReferencedEntities(calculatedField.getConfiguration(), getCurrentUser());
+ return tbCalculatedFieldService.save(calculatedField, getCurrentUser());
+ }
+
+ @ApiOperation(value = "Get Calculated Field (getCalculatedFieldById)",
+ notes = "Fetch the Calculated Field object based on the provided Calculated Field Id."
+ )
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/calculatedField/{calculatedFieldId}", method = RequestMethod.GET)
+ @ResponseBody
+ public CalculatedField getCalculatedFieldById(@Parameter @PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws ThingsboardException {
+ checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
+ CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
+ CalculatedField calculatedField = tbCalculatedFieldService.findById(calculatedFieldId, getCurrentUser());
+ checkNotNull(calculatedField);
+ checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD);
+ return calculatedField;
+ }
+
+ @ApiOperation(value = "Get Calculated Fields by Entity Id (getCalculatedFieldsByEntityId)",
+ notes = "Fetch the Calculated Fields based on the provided Entity Id."
+ )
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/{entityType}/{entityId}/calculatedFields", params = {"pageSize", "page"}, method = RequestMethod.GET)
+ @ResponseBody
+ public PageData getCalculatedFieldsByEntityId(
+ @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
+ @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
+ @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize,
+ @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page,
+ @Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch,
+ @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) @RequestParam(required = false) String sortProperty,
+ @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException {
+ PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
+ checkParameter("entityId", entityIdStr);
+ EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityIdStr);
+ checkEntityId(entityId, Operation.READ_CALCULATED_FIELD);
+ return checkNotNull(tbCalculatedFieldService.findAllByTenantIdAndEntityId(entityId, getCurrentUser(), pageLink));
+ }
+
+ @ApiOperation(value = "Delete Calculated Field (deleteCalculatedField)",
+ notes = "Deletes the calculated field. Referencing non-existing Calculated Field Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/calculatedField/{calculatedFieldId}", method = RequestMethod.DELETE)
+ @ResponseStatus(value = HttpStatus.OK)
+ public void deleteCalculatedField(@PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws Exception {
+ checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
+ CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
+ CalculatedField calculatedField = checkCalculatedFieldId(calculatedFieldId, Operation.DELETE);
+ checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD);
+ tbCalculatedFieldService.delete(calculatedField, getCurrentUser());
+ }
+
+ @ApiOperation(value = "Get latest calculated field debug event (getLatestCalculatedFieldDebugEvent)",
+ notes = "Gets latest calculated field debug event for specified calculated field id. " +
+ "Referencing non-existing calculated field id will cause an error. " + TENANT_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/calculatedField/{calculatedFieldId}/debug", method = RequestMethod.GET)
+ @ResponseBody
+ public JsonNode getLatestCalculatedFieldDebugEvent(@Parameter @PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws ThingsboardException {
+ checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
+ CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
+ CalculatedField calculatedField = checkCalculatedFieldId(calculatedFieldId, Operation.READ);
+ checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD);
+ TenantId tenantId = getCurrentUser().getTenantId();
+ return Optional.ofNullable(eventService.findLatestEvents(tenantId, calculatedFieldId, EventType.DEBUG_CALCULATED_FIELD, 1))
+ .flatMap(events -> events.stream().map(EventInfo::getBody).findFirst())
+ .orElse(null);
+ }
+
+ @ApiOperation(value = "Test Script expression",
+ notes = TEST_SCRIPT_EXPRESSION + TENANT_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/calculatedField/testScript", method = RequestMethod.POST)
+ @ResponseBody
+ public JsonNode testScript(
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test calculated field TBEL expression.")
+ @RequestBody JsonNode inputParams) {
+ String expression = inputParams.get("expression").asText();
+ Map arguments = Objects.requireNonNullElse(
+ JacksonUtil.convertValue(inputParams.get("arguments"), new TypeReference<>() {
+ }),
+ Collections.emptyMap()
+ );
+
+ ArrayList ctxAndArgNames = new ArrayList<>(arguments.size() + 1);
+ ctxAndArgNames.add("ctx");
+ ctxAndArgNames.addAll(arguments.keySet());
+
+ String output = "";
+ String errorText = "";
+
+ try {
+ if (tbelInvokeService == null) {
+ throw new IllegalArgumentException("TBEL script engine is disabled!");
+ }
+
+ CalculatedFieldScriptEngine calculatedFieldScriptEngine = new CalculatedFieldTbelScriptEngine(
+ getTenantId(),
+ tbelInvokeService,
+ expression,
+ ctxAndArgNames.toArray(String[]::new)
+ );
+
+
+ Object[] args = new Object[ctxAndArgNames.size()];
+ args[0] = new TbelCfCtx(arguments);
+ for (int i = 1; i < ctxAndArgNames.size(); i++) {
+ var arg = arguments.get(ctxAndArgNames.get(i));
+ if (arg instanceof TbelCfSingleValueArg svArg) {
+ args[i] = svArg.getValue();
+ } else {
+ args[i] = arg;
+ }
+ }
+
+ JsonNode json = calculatedFieldScriptEngine.executeJsonAsync(args).get(TIMEOUT, TimeUnit.SECONDS);
+ output = JacksonUtil.toString(json);
+ } catch (Exception e) {
+ log.error("Error evaluating expression", e);
+ errorText = e.getMessage();
+ }
+
+ ObjectNode result = JacksonUtil.newObjectNode();
+ result.put("output", output);
+ result.put("error", errorText);
+ return result;
+ }
+
+ private & HasTenantId, I extends EntityId> void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig, SecurityUser user) throws ThingsboardException {
+ List referencedEntityIds = calculatedFieldConfig.getReferencedEntities();
+ for (EntityId referencedEntityId : referencedEntityIds) {
+ EntityType entityType = referencedEntityId.getEntityType();
+ switch (entityType) {
+ case TENANT, CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ);
+ default ->
+ throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities.");
+ }
+ }
+
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index 4987e5c26f..e45d041950 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
@@ -96,6 +96,7 @@ public class ControllerConstants {
protected static final String EDGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the edge name.";
protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching.";
protected static final String AUDIT_LOG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on one of the next properties: entityType, entityName, userName, actionType, actionStatus.";
+ protected static final String CF_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the calculated field name.";
protected static final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by";
protected static final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESC (DESCENDING)";
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
index 0387e8c24a..7fd0b12077 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
@@ -20,6 +20,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.common.data.edqs.ToCoreEdqsRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@@ -38,6 +41,8 @@ import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.common.msg.edqs.EdqsApiService;
+import org.thingsboard.server.common.msg.edqs.EdqsService;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.query.EntityQueryService;
@@ -55,6 +60,10 @@ public class EntityQueryController extends BaseController {
@Autowired
private EntityQueryService entityQueryService;
+ @Autowired
+ private EdqsService edqsService;
+ @Autowired
+ private EdqsApiService edqsApiService;
private static final int MAX_PAGE_SIZE = 100;
@@ -133,4 +142,16 @@ public class EntityQueryController extends BaseController {
return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes, scope);
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @PostMapping("/edqs/system/request")
+ public void processSystemEdqsRequest(@RequestBody ToCoreEdqsRequest request) {
+ edqsService.processSystemRequest(request);
+ }
+
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @GetMapping("/edqs/enabled")
+ public boolean isEdqsApiEnabled() {
+ return edqsApiService.isEnabled();
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
index bc22313a03..4ee86871d6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
@@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.SystemParams;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings;
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QRCodeConfig;
+import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
@@ -46,6 +46,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
+import org.thingsboard.server.utils.DebugModeRateLimitsConfig;
import java.util.Collections;
import java.util.List;
@@ -74,12 +75,6 @@ public class SystemInfoController extends BaseController {
@Value("${debug.settings.default_duration:15}")
private int defaultDebugDurationMinutes;
- @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
- private boolean ruleChainDebugPerTenantLimitsEnabled;
-
- @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration:50000:3600}")
- private String ruleChainDebugPerTenantLimitsConfiguration;
-
@Autowired(required = false)
private BuildProperties buildProperties;
@@ -89,6 +84,9 @@ public class SystemInfoController extends BaseController {
@Autowired
private QrCodeSettingService qrCodeSettingService;
+ @Autowired
+ private DebugModeRateLimitsConfig debugModeRateLimitsConfig;
+
@PostConstruct
public void init() {
JsonNode info = buildInfoObject();
@@ -152,9 +150,14 @@ public class SystemInfoController extends BaseController {
DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize());
systemParams.setMaxDebugModeDurationMinutes(DebugModeUtil.getMaxDebugAllDuration(tenantProfileConfiguration.getMaxDebugModeDurationMinutes(), defaultDebugDurationMinutes));
- if (ruleChainDebugPerTenantLimitsEnabled) {
- systemParams.setRuleChainDebugPerTenantLimitsConfiguration(ruleChainDebugPerTenantLimitsConfiguration);
+ if (debugModeRateLimitsConfig.isRuleChainDebugPerTenantLimitsEnabled()) {
+ systemParams.setRuleChainDebugPerTenantLimitsConfiguration(debugModeRateLimitsConfig.getRuleChainDebugPerTenantLimitsConfiguration());
+ }
+ if (debugModeRateLimitsConfig.isCalculatedFieldDebugPerTenantLimitsEnabled()) {
+ systemParams.setCalculatedFieldDebugPerTenantLimitsConfiguration(debugModeRateLimitsConfig.getCalculatedFieldDebugPerTenantLimitsConfiguration());
}
+ systemParams.setMaxArgumentsPerCF(tenantProfileConfiguration.getMaxArgumentsPerCF());
+ systemParams.setMaxDataPointsPerRollingArg(tenantProfileConfiguration.getMaxDataPointsPerRollingArg());
}
systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID))
.map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage)
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
index ff4de0ce0c..1a3bdce1f6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -160,7 +161,12 @@ public class TenantProfileController extends BaseController {
" \"rpcTtlDays\": 0,\n" +
" \"queueStatsTtlDays\": 0,\n" +
" \"ruleEngineExceptionsTtlDays\": 0,\n" +
- " \"warnThreshold\": 0\n" +
+ " \"warnThreshold\": 0,\n" +
+ " \"maxCalculatedFieldsPerEntity\": 5,\n" +
+ " \"maxArgumentsPerCF\": 10,\n" +
+ " \"maxDataPointsPerRollingArg\": 1000,\n" +
+ " \"maxStateSizeInKBytes\": 32,\n" +
+ " \"maxSingleValueArgumentSizeInKBytes\": 2" +
" }\n" +
" },\n" +
" \"default\": false\n" +
@@ -172,7 +178,7 @@ public class TenantProfileController extends BaseController {
@RequestMapping(value = "/tenantProfile", method = RequestMethod.POST)
@ResponseBody
public TenantProfile saveTenantProfile(@Parameter(description = "A JSON value representing the tenant profile.")
- @RequestBody TenantProfile tenantProfile) throws ThingsboardException {
+ @Valid @RequestBody TenantProfile tenantProfile) throws ThingsboardException {
TenantProfile oldProfile;
if (tenantProfile.getId() == null) {
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE);
diff --git a/application/src/main/java/org/thingsboard/server/exception/CalculatedFieldStateException.java b/application/src/main/java/org/thingsboard/server/exception/CalculatedFieldStateException.java
new file mode 100644
index 0000000000..a30b7218a1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/CalculatedFieldStateException.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016-2025 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.exception;
+
+public class CalculatedFieldStateException extends RuntimeException {
+
+ public CalculatedFieldStateException(String message) {
+ super(message);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
index aecb31ca0c..3dedea999c 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
@@ -15,13 +15,11 @@
*/
package org.thingsboard.server.service.apiusage;
-import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
@@ -56,10 +54,12 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.SchedulerUtils;
+import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
+import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@@ -92,15 +92,7 @@ import java.util.stream.Collectors;
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService implements TbApiUsageStateService {
public static final String HOURLY = "Hourly";
- public static final FutureCallback VOID_CALLBACK = new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
- @Override
- public void onFailure(Throwable t) {
- }
- };
private final PartitionService partitionService;
private final TenantService tenantService;
private final TimeseriesService tsService;
@@ -154,18 +146,40 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
}
@Override
- public void process(TbProtoQueueMsg msg, TbCallback callback) {
- ToUsageStatsServiceMsg statsMsg = msg.getValue();
-
- TenantId tenantId = TenantId.fromUUID(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
- EntityId ownerId;
- if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) {
- ownerId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB()));
+ public void process(TbProtoQueueMsg msgPack, TbCallback callback) {
+ ToUsageStatsServiceMsg serviceMsg = msgPack.getValue();
+ String serviceId = serviceMsg.getServiceId();
+
+ List msgs;
+
+ //For backward compatibility, remove after release
+ if (serviceMsg.getMsgsList().isEmpty()) {
+ TransportProtos.UsageStatsServiceMsg oldMsg = TransportProtos.UsageStatsServiceMsg.newBuilder()
+ .setTenantIdMSB(serviceMsg.getTenantIdMSB())
+ .setTenantIdLSB(serviceMsg.getTenantIdLSB())
+ .setCustomerIdMSB(serviceMsg.getCustomerIdMSB())
+ .setCustomerIdLSB(serviceMsg.getCustomerIdLSB())
+ .setEntityIdMSB(serviceMsg.getEntityIdMSB())
+ .setEntityIdLSB(serviceMsg.getEntityIdLSB())
+ .addAllValues(serviceMsg.getValuesList())
+ .build();
+
+ msgs = List.of(oldMsg);
} else {
- ownerId = tenantId;
+ msgs = serviceMsg.getMsgsList();
}
- processEntityUsageStats(tenantId, ownerId, statsMsg.getValuesList(), statsMsg.getServiceId());
+ msgs.forEach(msg -> {
+ TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
+ EntityId ownerId;
+ if (msg.getCustomerIdMSB() != 0 && msg.getCustomerIdLSB() != 0) {
+ ownerId = new CustomerId(new UUID(msg.getCustomerIdMSB(), msg.getCustomerIdLSB()));
+ } else {
+ ownerId = tenantId;
+ }
+
+ processEntityUsageStats(tenantId, ownerId, msg.getValuesList(), serviceId);
+ });
callback.onSuccess();
}
@@ -191,7 +205,14 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length);
Set apiFeatures = new HashSet<>();
for (UsageStatsKVProto statsItem : values) {
- ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(statsItem.getKey());
+ ApiUsageRecordKey recordKey;
+
+ //For backward compatibility, remove after release
+ if (StringUtils.isNotEmpty(statsItem.getKey())) {
+ recordKey = ApiUsageRecordKey.valueOf(statsItem.getKey());
+ } else {
+ recordKey = ProtoUtils.fromProto(statsItem.getRecordKey());
+ }
StatsCalculationResult calculationResult = usageState.calculate(recordKey, statsItem.getValue(), serviceId);
if (calculationResult.isValueChanged()) {
@@ -219,7 +240,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.tenantId(tenantId)
.entityId(usageState.getApiUsageState().getId())
.entries(updatedEntries)
- .callback(VOID_CALLBACK)
.build());
if (!result.isEmpty()) {
persistAndNotify(usageState, result);
@@ -331,7 +351,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.tenantId(tenantId)
.entityId(id)
.entries(profileThresholds)
- .callback(VOID_CALLBACK)
.build());
}
}
@@ -364,7 +383,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(stateTelemetry)
- .callback(VOID_CALLBACK)
.build());
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
@@ -457,7 +475,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(counts)
- .callback(VOID_CALLBACK)
.build());
}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldStateService.java b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldStateService.java
new file mode 100644
index 0000000000..91c08ab6e0
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldStateService.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.exception.CalculatedFieldStateException;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.common.state.QueueStateService;
+import org.thingsboard.server.queue.discovery.QueueKey;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto;
+import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
+
+public abstract class AbstractCalculatedFieldStateService implements CalculatedFieldStateService {
+
+ @Autowired
+ private ActorSystemContext actorSystemContext;
+
+ protected QueueStateService, TbProtoQueueMsg> stateService;
+
+ @Override
+ public final void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback) {
+ if (state.isSizeExceedsLimit()) {
+ throw new CalculatedFieldStateException("State size exceeds the maximum allowed limit. The state will not be persisted to RocksDB.");
+ }
+ doPersist(stateId, toProto(stateId, state), callback);
+ }
+
+ protected abstract void doPersist(CalculatedFieldEntityCtxId stateId, CalculatedFieldStateProto stateMsgProto, TbCallback callback);
+
+ @Override
+ public final void removeState(CalculatedFieldEntityCtxId stateId, TbCallback callback) {
+ doRemove(stateId, callback);
+ }
+
+ protected abstract void doRemove(CalculatedFieldEntityCtxId stateId, TbCallback callback);
+
+ protected void processRestoredState(CalculatedFieldStateProto stateMsg) {
+ var id = fromProto(stateMsg.getId());
+ var state = fromProto(stateMsg);
+ processRestoredState(id, state);
+ }
+
+ protected void processRestoredState(CalculatedFieldEntityCtxId id, CalculatedFieldState state) {
+ actorSystemContext.tell(new CalculatedFieldStateRestoreMsg(id, state));
+ }
+
+ @Override
+ public void restore(QueueKey queueKey, Set partitions) {
+ stateService.update(queueKey, partitions);
+ }
+
+ @Override
+ public void delete(Set partitions) {
+ stateService.delete(partitions);
+ }
+
+ @Override
+ public Set getPartitions() {
+ return stateService.getPartitions().values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void stop() {
+ stateService.stop();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java
new file mode 100644
index 0000000000..fb63432fed
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import org.thingsboard.server.common.data.cf.CalculatedField;
+import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.List;
+
+public interface CalculatedFieldCache {
+
+ CalculatedField getCalculatedField(CalculatedFieldId calculatedFieldId);
+
+ List getCalculatedFieldsByEntityId(EntityId entityId);
+
+ List getCalculatedFieldLinksByEntityId(EntityId entityId);
+
+ CalculatedFieldCtx getCalculatedFieldCtx(CalculatedFieldId calculatedFieldId);
+
+ List getCalculatedFieldCtxsByEntityId(EntityId entityId);
+
+ void addCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId);
+
+ void updateCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId);
+
+ void evict(CalculatedFieldId calculatedFieldId);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldInitService.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldInitService.java
new file mode 100644
index 0000000000..6505dae581
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldInitService.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+public interface CalculatedFieldInitService {
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java
new file mode 100644
index 0000000000..847caccaff
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
+import org.thingsboard.server.common.data.cf.configuration.Argument;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+
+import java.util.List;
+import java.util.Map;
+
+public interface CalculatedFieldProcessingService {
+
+ ListenableFuture fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId);
+
+ Map fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map arguments);
+
+ void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculationResult, List cfIds, TbCallback callback);
+
+ void pushMsgToLinks(CalculatedFieldTelemetryMsg msg, List linkedCalculatedFields, TbCallback callback);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldQueueService.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldQueueService.java
new file mode 100644
index 0000000000..eb86220361
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldQueueService.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import com.google.common.util.concurrent.FutureCallback;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.RuleEngineCalculatedFieldQueueService;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
+import org.thingsboard.server.common.data.kv.TimeseriesSaveResult;
+
+import java.util.List;
+
+public interface CalculatedFieldQueueService extends RuleEngineCalculatedFieldQueueService {
+
+ /**
+ * Filter CFs based on the request entity. Push to the queue if any matching CF exist;
+ *
+ * @param request - telemetry save request;
+ * @param callback
+ */
+ void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback callback);
+
+ void pushRequestToQueue(AttributesSaveRequest request, List result, FutureCallback callback);
+
+ void pushRequestToQueue(AttributesDeleteRequest request, List result, FutureCallback callback);
+
+ void pushRequestToQueue(TimeseriesDeleteRequest request, List result, FutureCallback callback);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java
new file mode 100644
index 0000000000..49acf6917c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.cf.configuration.OutputType;
+
+@Data
+public final class CalculatedFieldResult {
+
+ private final OutputType type;
+ private final AttributeScope scope;
+ private final JsonNode result;
+
+ public boolean isEmpty() {
+ return result == null || result.isMissingNode() || result.isNull() ||
+ (result.isObject() && result.isEmpty()) ||
+ (result.isArray() && result.isEmpty()) ||
+ (result.isTextual() && result.asText().isEmpty());
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldStateService.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldStateService.java
new file mode 100644
index 0000000000..d0b34f18e8
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldStateService.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.exception.CalculatedFieldStateException;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
+import org.thingsboard.server.queue.discovery.QueueKey;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+
+import java.util.Set;
+
+public interface CalculatedFieldStateService {
+
+ void init(PartitionedQueueConsumerManager> eventConsumer);
+
+ void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback) throws CalculatedFieldStateException;
+
+ void removeState(CalculatedFieldEntityCtxId stateId, TbCallback callback);
+
+ void restore(QueueKey queueKey, Set partitions);
+
+ void delete(Set partitions);
+
+ Set getPartitions();
+
+ void stop();
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java b/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java
new file mode 100644
index 0000000000..f95227bc24
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/CfRocksDb.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import org.rocksdb.Options;
+import org.rocksdb.WriteOptions;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.edqs.util.TbRocksDb;
+
+@Component
+@ConditionalOnExpression("'${queue.type:null}'=='in-memory'")
+public class CfRocksDb extends TbRocksDb {
+
+ public CfRocksDb(@Value("${queue.calculated_fields.rocks_db_path:${user.home}/.rocksdb/cf_states}") String path) {
+ super(path, new Options().setCreateIfMissing(true), new WriteOptions().setSync(true));
+ }
+
+ @PostConstruct
+ @Override
+ public void init() {
+ super.init();
+ }
+
+ @PreDestroy
+ @Override
+ public void close() {
+ super.close();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
new file mode 100644
index 0000000000..64487d9b3e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.thingsboard.script.api.tbel.TbelInvokeService;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.common.data.cf.CalculatedField;
+import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
+import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.PageDataIterable;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
+import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
+import org.thingsboard.server.dao.cf.CalculatedFieldService;
+import org.thingsboard.server.dao.usagerecord.ApiLimitService;
+import org.thingsboard.server.queue.util.AfterStartUp;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
+
+ private static final Integer UNKNOWN_PARTITION = -1;
+
+ private final Lock calculatedFieldFetchLock = new ReentrantLock();
+
+ private final CalculatedFieldService calculatedFieldService;
+ private final TbelInvokeService tbelInvokeService;
+ private final ActorSystemContext actorSystemContext;
+ private final ApiLimitService apiLimitService;
+
+ private final ConcurrentMap calculatedFields = new ConcurrentHashMap<>();
+ private final ConcurrentMap> entityIdCalculatedFields = new ConcurrentHashMap<>();
+ private final ConcurrentMap> calculatedFieldLinks = new ConcurrentHashMap<>();
+ private final ConcurrentMap> entityIdCalculatedFieldLinks = new ConcurrentHashMap<>();
+ private final ConcurrentMap calculatedFieldsCtx = new ConcurrentHashMap<>();
+
+ @Value("${calculatedField.initFetchPackSize:50000}")
+ @Getter
+ private int initFetchPackSize;
+
+ @AfterStartUp(order = AfterStartUp.CF_READ_CF_SERVICE)
+ public void init() {
+ //TODO: move to separate place to avoid circular references with the ActorSystemContext (@Lazy for tsSubService)
+ PageDataIterable cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
+ cfs.forEach(cf -> {
+ calculatedFields.putIfAbsent(cf.getId(), cf);
+ actorSystemContext.tell(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
+ });
+ calculatedFields.values().forEach(cf -> {
+ entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cf);
+ });
+ PageDataIterable cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize);
+ cfls.forEach(link -> {
+ calculatedFieldLinks.computeIfAbsent(link.getCalculatedFieldId(), id -> new CopyOnWriteArrayList<>()).add(link);
+ actorSystemContext.tell(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
+ });
+ calculatedFieldLinks.values().stream()
+ .flatMap(List::stream)
+ .forEach(link ->
+ entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link)
+ );
+ }
+
+ @Override
+ public CalculatedField getCalculatedField(CalculatedFieldId calculatedFieldId) {
+ return calculatedFields.get(calculatedFieldId);
+ }
+
+ @Override
+ public List getCalculatedFieldsByEntityId(EntityId entityId) {
+ return entityIdCalculatedFields.getOrDefault(entityId, new CopyOnWriteArrayList<>());
+ }
+
+ @Override
+ public List getCalculatedFieldLinksByEntityId(EntityId entityId) {
+ return entityIdCalculatedFieldLinks.getOrDefault(entityId, new CopyOnWriteArrayList<>());
+ }
+
+ @Override
+ public CalculatedFieldCtx getCalculatedFieldCtx(CalculatedFieldId calculatedFieldId) {
+ CalculatedFieldCtx ctx = calculatedFieldsCtx.get(calculatedFieldId);
+ if (ctx == null) {
+ calculatedFieldFetchLock.lock();
+ try {
+ ctx = calculatedFieldsCtx.get(calculatedFieldId);
+ if (ctx == null) {
+ CalculatedField calculatedField = getCalculatedField(calculatedFieldId);
+ if (calculatedField != null) {
+ ctx = new CalculatedFieldCtx(calculatedField, tbelInvokeService, apiLimitService);
+ calculatedFieldsCtx.put(calculatedFieldId, ctx);
+ log.debug("[{}] Put calculated field ctx into cache: {}", calculatedFieldId, ctx);
+ }
+ }
+ } finally {
+ calculatedFieldFetchLock.unlock();
+ }
+ }
+ log.trace("[{}] Found calculated field ctx in cache: {}", calculatedFieldId, ctx);
+ return ctx;
+ }
+
+ @Override
+ public List getCalculatedFieldCtxsByEntityId(EntityId entityId) {
+ if (entityId == null) {
+ return Collections.emptyList();
+ }
+ return getCalculatedFieldsByEntityId(entityId).stream()
+ .map(cf -> getCalculatedFieldCtx(cf.getId()))
+ .toList();
+ }
+
+ @Override
+ public void addCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
+ calculatedFieldFetchLock.lock();
+ try {
+ CalculatedField calculatedField = calculatedFieldService.findById(tenantId, calculatedFieldId);
+ EntityId cfEntityId = calculatedField.getEntityId();
+
+ calculatedFields.put(calculatedFieldId, calculatedField);
+
+ entityIdCalculatedFields.computeIfAbsent(cfEntityId, entityId -> new CopyOnWriteArrayList<>()).add(calculatedField);
+
+ CalculatedFieldConfiguration configuration = calculatedField.getConfiguration();
+ calculatedFieldLinks.put(calculatedFieldId, configuration.buildCalculatedFieldLinks(tenantId, cfEntityId, calculatedFieldId));
+
+ configuration.getReferencedEntities().stream()
+ .filter(referencedEntityId -> !referencedEntityId.equals(cfEntityId))
+ .forEach(referencedEntityId -> {
+ entityIdCalculatedFieldLinks.computeIfAbsent(referencedEntityId, entityId -> new CopyOnWriteArrayList<>())
+ .add(configuration.buildCalculatedFieldLink(tenantId, referencedEntityId, calculatedFieldId));
+ });
+ } finally {
+ calculatedFieldFetchLock.unlock();
+ }
+ }
+
+ @Override
+ public void updateCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
+ evict(calculatedFieldId);
+ addCalculatedField(tenantId, calculatedFieldId);
+ }
+
+ @Override
+ public void evict(CalculatedFieldId calculatedFieldId) {
+ CalculatedField oldCalculatedField = calculatedFields.remove(calculatedFieldId);
+ log.debug("[{}] evict calculated field from cache: {}", calculatedFieldId, oldCalculatedField);
+ calculatedFieldLinks.remove(calculatedFieldId);
+ log.debug("[{}] evict calculated field from cached calculated fields by entity id: {}", calculatedFieldId, oldCalculatedField);
+ entityIdCalculatedFields.forEach((entityId, calculatedFields) -> calculatedFields.removeIf(cf -> cf.getId().equals(calculatedFieldId)));
+ log.debug("[{}] evict calculated field links from cache: {}", calculatedFieldId, oldCalculatedField);
+ calculatedFieldsCtx.remove(calculatedFieldId);
+ log.debug("[{}] evict calculated field ctx from cache: {}", calculatedFieldId, oldCalculatedField);
+ entityIdCalculatedFieldLinks.forEach((entityId, calculatedFieldLinks) -> calculatedFieldLinks.removeIf(link -> link.getCalculatedFieldId().equals(calculatedFieldId)));
+ log.debug("[{}] evict calculated field links from cached links by entity id: {}", calculatedFieldId, oldCalculatedField);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldInitService.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldInitService.java
new file mode 100644
index 0000000000..cc3022fa29
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldInitService.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.ProfileEntityIdInfo;
+import org.thingsboard.server.common.data.page.PageDataIterable;
+import org.thingsboard.server.dao.asset.AssetService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.queue.util.AfterStartUp;
+import org.thingsboard.server.queue.util.TbRuleEngineComponent;
+import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache;
+
+@Slf4j
+@Service
+@TbRuleEngineComponent
+@RequiredArgsConstructor
+public class DefaultCalculatedFieldInitService implements CalculatedFieldInitService {
+
+ private final CalculatedFieldEntityProfileCache entityProfileCache;
+ private final AssetService assetService;
+ private final DeviceService deviceService;
+
+ @Value("${calculated_fields.init_fetch_pack_size:50000}")
+ @Getter
+ private int initFetchPackSize;
+
+ @AfterStartUp(order = AfterStartUp.CF_READ_PROFILE_ENTITIES_SERVICE)
+ public void initCalculatedFieldDefinitions() {
+ PageDataIterable deviceIdInfos = new PageDataIterable<>(deviceService::findProfileEntityIdInfos, initFetchPackSize);
+ for (ProfileEntityIdInfo idInfo : deviceIdInfos) {
+ log.trace("Processing device record: {}", idInfo);
+ try {
+ entityProfileCache.add(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId());
+ } catch (Exception e) {
+ log.error("Failed to process device record: {}", idInfo, e);
+ }
+ }
+ PageDataIterable assetIdInfos = new PageDataIterable<>(assetService::findProfileEntityIdInfos, initFetchPackSize);
+ for (ProfileEntityIdInfo idInfo : assetIdInfos) {
+ log.trace("Processing asset record: {}", idInfo);
+ try {
+ entityProfileCache.add(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId());
+ } catch (Exception e) {
+ log.error("Failed to process asset record: {}", idInfo, e);
+ }
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java
new file mode 100644
index 0000000000..e9a6cb09aa
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java
@@ -0,0 +1,327 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
+import org.thingsboard.server.actors.calculatedField.MultipleTbCallback;
+import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.cf.configuration.Argument;
+import org.thingsboard.server.common.data.cf.configuration.OutputType;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.Aggregation;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
+import org.thingsboard.server.common.data.kv.BooleanDataEntry;
+import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.dao.attributes.AttributesService;
+import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.dao.usagerecord.ApiLimitService;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto.Builder;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg;
+import org.thingsboard.server.queue.TbQueueCallback;
+import org.thingsboard.server.queue.TbQueueMsgMetadata;
+import org.thingsboard.server.queue.discovery.PartitionService;
+import org.thingsboard.server.queue.discovery.QueueKey;
+import org.thingsboard.server.queue.util.TbRuleEngineComponent;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+import org.thingsboard.server.service.cf.ctx.state.ScriptCalculatedFieldState;
+import org.thingsboard.server.service.cf.ctx.state.SimpleCalculatedFieldState;
+import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
+import org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static org.thingsboard.server.common.data.DataConstants.SCOPE;
+import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
+
+@TbRuleEngineComponent
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class DefaultCalculatedFieldProcessingService implements CalculatedFieldProcessingService {
+
+ private final AttributesService attributesService;
+ private final TimeseriesService timeseriesService;
+ private final TbClusterService clusterService;
+ private final ApiLimitService apiLimitService;
+ private final PartitionService partitionService;
+
+ private ListeningExecutorService calculatedFieldCallbackExecutor;
+
+ @PostConstruct
+ public void init() {
+ calculatedFieldCallbackExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
+ Math.max(4, Runtime.getRuntime().availableProcessors()), "calculated-field-callback"));
+ }
+
+ @PreDestroy
+ public void stop() {
+ if (calculatedFieldCallbackExecutor != null) {
+ calculatedFieldCallbackExecutor.shutdownNow();
+ }
+ }
+
+ @Override
+ public ListenableFuture fetchStateFromDb(CalculatedFieldCtx ctx, EntityId entityId) {
+ Map> argFutures = new HashMap<>();
+ for (var entry : ctx.getArguments().entrySet()) {
+ var argEntityId = entry.getValue().getRefEntityId() != null ? entry.getValue().getRefEntityId() : entityId;
+ var argValueFuture = fetchKvEntry(ctx.getTenantId(), argEntityId, entry.getValue());
+ argFutures.put(entry.getKey(), argValueFuture);
+ }
+ return Futures.whenAllComplete(argFutures.values()).call(() -> {
+ var result = createStateByType(ctx);
+ result.updateState(ctx, argFutures.entrySet().stream()
+ .collect(Collectors.toMap(
+ Entry::getKey, // Keep the key as is
+ entry -> {
+ try {
+ // Resolve the future to get the value
+ return entry.getValue().get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException("Error getting future result for key: " + entry.getKey(), e);
+ }
+ }
+ )));
+ return result;
+ }, calculatedFieldCallbackExecutor);
+ }
+
+ @Override
+ public Map fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map arguments) {
+ Map> argFutures = new HashMap<>();
+ for (var entry : arguments.entrySet()) {
+ var argEntityId = entry.getValue().getRefEntityId() != null ? entry.getValue().getRefEntityId() : entityId;
+ var argValueFuture = fetchKvEntry(tenantId, argEntityId, entry.getValue());
+ argFutures.put(entry.getKey(), argValueFuture);
+ }
+ return argFutures.entrySet().stream()
+ .collect(Collectors.toMap(
+ Entry::getKey, // Keep the key as is
+ entry -> {
+ try {
+ // Resolve the future to get the value
+ return entry.getValue().get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException("Error getting future result for key: " + entry.getKey(), e);
+ }
+ }
+ ));
+ }
+
+ @Override
+ public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, CalculatedFieldResult calculatedFieldResult, List cfIds, TbCallback callback) {
+ try {
+ OutputType type = calculatedFieldResult.getType();
+ TbMsgType msgType = OutputType.ATTRIBUTES.equals(type) ? TbMsgType.POST_ATTRIBUTES_REQUEST : TbMsgType.POST_TELEMETRY_REQUEST;
+ TbMsgMetaData md = OutputType.ATTRIBUTES.equals(type) ? new TbMsgMetaData(Map.of(SCOPE, calculatedFieldResult.getScope().name())) : TbMsgMetaData.EMPTY;
+ TbMsg msg = TbMsg.newMsg().type(msgType).originator(entityId).previousCalculatedFieldIds(cfIds).metaData(md).data(JacksonUtil.writeValueAsString(calculatedFieldResult.getResult())).build();
+ clusterService.pushMsgToRuleEngine(tenantId, entityId, msg, new TbQueueCallback() {
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ callback.onSuccess();
+ log.trace("[{}][{}] Pushed message to rule engine: {} ", tenantId, entityId, msg);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure(t);
+ }
+ });
+ } catch (Exception e) {
+ log.warn("[{}][{}] Failed to push message to rule engine. CalculatedFieldResult: {}", tenantId, entityId, calculatedFieldResult, e);
+ callback.onFailure(e);
+ }
+ }
+
+ @Override
+ public void pushMsgToLinks(CalculatedFieldTelemetryMsg msg, List linkedCalculatedFields, TbCallback callback) {
+ Map> unicasts = new HashMap<>();
+ List broadcasts = new ArrayList<>();
+ for (CalculatedFieldEntityCtxId link : linkedCalculatedFields) {
+ var linkEntityId = link.entityId();
+ var linkEntityType = linkEntityId.getEntityType();
+ // Let's assume number of entities in profile is N, and number of partitions is P. If N > P, we save by broadcasting to all partitions. Usually N >> P.
+ boolean broadcast = EntityType.DEVICE_PROFILE.equals(linkEntityType) || EntityType.ASSET_PROFILE.equals(linkEntityType);
+ if (broadcast) {
+ broadcasts.add(link);
+ } else {
+ TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, link.tenantId(), link.entityId());
+ unicasts.computeIfAbsent(tpi, k -> new ArrayList<>()).add(link);
+ }
+ }
+ MultipleTbCallback linkCallback = new MultipleTbCallback(2, callback);
+ if (!broadcasts.isEmpty()) {
+ broadcast(broadcasts, msg, linkCallback);
+ } else {
+ linkCallback.onSuccess();
+ }
+ if (!unicasts.isEmpty()) {
+ unicast(unicasts, msg, linkCallback);
+ } else {
+ linkCallback.onSuccess();
+ }
+ }
+
+ private void unicast(Map> unicasts, CalculatedFieldTelemetryMsg msg, MultipleTbCallback mainCallback) {
+ TbQueueCallback callback = new TbCallbackWrapper(new MultipleTbCallback(unicasts.size(), mainCallback));
+ unicasts.forEach((topicPartitionInfo, ctxIds) -> {
+ CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsgProto = buildLinkedTelemetryMsgProto(msg.getProto(), ctxIds);
+ clusterService.pushMsgToCalculatedFields(topicPartitionInfo, UUID.randomUUID(),
+ ToCalculatedFieldMsg.newBuilder().setLinkedTelemetryMsg(linkedTelemetryMsgProto).build(), callback);
+ });
+ }
+
+ private void broadcast(List broadcasts, CalculatedFieldTelemetryMsg msg, MultipleTbCallback mainCallback) {
+ TbQueueCallback callback = new TbCallbackWrapper(mainCallback);
+ CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsgProto = buildLinkedTelemetryMsgProto(msg.getProto(), broadcasts);
+ clusterService.broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setLinkedTelemetryMsg(linkedTelemetryMsgProto).build(), callback);
+ }
+
+ private CalculatedFieldLinkedTelemetryMsgProto buildLinkedTelemetryMsgProto(CalculatedFieldTelemetryMsgProto telemetryProto, List links) {
+ Builder builder = CalculatedFieldLinkedTelemetryMsgProto.newBuilder();
+ builder.setMsg(telemetryProto);
+ for (CalculatedFieldEntityCtxId link : links) {
+ builder.addLinks(toProto(link));
+ }
+ return builder.build();
+ }
+
+ private ListenableFuture fetchKvEntry(TenantId tenantId, EntityId entityId, Argument argument) {
+ return switch (argument.getRefEntityKey().getType()) {
+ case TS_ROLLING -> fetchTsRolling(tenantId, entityId, argument);
+ case ATTRIBUTE -> transformSingleValueArgument(
+ Futures.transform(
+ attributesService.find(tenantId, entityId, argument.getRefEntityKey().getScope(), argument.getRefEntityKey().getKey()),
+ result -> result.or(() -> Optional.of(new BaseAttributeKvEntry(createDefaultKvEntry(argument), System.currentTimeMillis(), 0L))),
+ calculatedFieldCallbackExecutor)
+ );
+ case TS_LATEST -> transformSingleValueArgument(
+ Futures.transform(
+ timeseriesService.findLatest(tenantId, entityId, argument.getRefEntityKey().getKey()),
+ result -> result.or(() -> Optional.of(new BasicTsKvEntry(System.currentTimeMillis(), createDefaultKvEntry(argument), 0L))),
+ calculatedFieldCallbackExecutor));
+ };
+ }
+
+ private ListenableFuture transformSingleValueArgument(ListenableFuture> kvEntryFuture) {
+ return Futures.transform(kvEntryFuture, kvEntry -> {
+ if (kvEntry.isPresent() && kvEntry.get().getValue() != null) {
+ return ArgumentEntry.createSingleValueArgument(kvEntry.get());
+ } else {
+ return new SingleValueArgumentEntry();
+ }
+ }, calculatedFieldCallbackExecutor);
+ }
+
+ private ListenableFuture fetchTsRolling(TenantId tenantId, EntityId entityId, Argument argument) {
+ long currentTime = System.currentTimeMillis();
+ long timeWindow = argument.getTimeWindow() == 0 ? System.currentTimeMillis() : argument.getTimeWindow();
+ long startTs = currentTime - timeWindow;
+ long maxDataPoints = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxDataPointsPerRollingArg);
+ int argumentLimit = argument.getLimit();
+ int limit = argumentLimit == 0 || argumentLimit > maxDataPoints ? (int) maxDataPoints : argument.getLimit();
+
+ ReadTsKvQuery query = new BaseReadTsKvQuery(argument.getRefEntityKey().getKey(), startTs, currentTime, 0, limit, Aggregation.NONE);
+ ListenableFuture> tsRollingFuture = timeseriesService.findAll(tenantId, entityId, List.of(query));
+
+ return Futures.transform(tsRollingFuture, tsRolling -> tsRolling == null ? new TsRollingArgumentEntry(limit, timeWindow) : ArgumentEntry.createTsRollingArgument(tsRolling, limit, timeWindow), calculatedFieldCallbackExecutor);
+ }
+
+ private KvEntry createDefaultKvEntry(Argument argument) {
+ String key = argument.getRefEntityKey().getKey();
+ String defaultValue = argument.getDefaultValue();
+ if (StringUtils.isBlank(defaultValue)) {
+ return new StringDataEntry(key, null);
+ }
+ if (NumberUtils.isParsable(defaultValue)) {
+ return new DoubleDataEntry(key, Double.parseDouble(defaultValue));
+ }
+ if ("true".equalsIgnoreCase(defaultValue) || "false".equalsIgnoreCase(defaultValue)) {
+ return new BooleanDataEntry(key, Boolean.parseBoolean(defaultValue));
+ }
+ return new StringDataEntry(key, defaultValue);
+ }
+
+ private CalculatedFieldState createStateByType(CalculatedFieldCtx ctx) {
+ return switch (ctx.getCfType()) {
+ case SIMPLE -> new SimpleCalculatedFieldState(ctx.getArgNames());
+ case SCRIPT -> new ScriptCalculatedFieldState(ctx.getArgNames());
+ };
+ }
+
+ private static class TbCallbackWrapper implements TbQueueCallback {
+ private final TbCallback callback;
+
+ public TbCallbackWrapper(TbCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ callback.onSuccess();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure(t);
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java
new file mode 100644
index 0000000000..8289e4db42
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java
@@ -0,0 +1,282 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf;
+
+import com.google.common.util.concurrent.FutureCallback;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
+import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.TimeseriesSaveResult;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.common.util.ProtoUtils;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeScopeProto;
+import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
+import org.thingsboard.server.queue.TbQueueCallback;
+import org.thingsboard.server.queue.TbQueueMsgMetadata;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
+import org.thingsboard.server.service.profile.TbAssetProfileCache;
+import org.thingsboard.server.service.profile.TbDeviceProfileCache;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static org.thingsboard.server.common.util.ProtoUtils.toTsKvProto;
+import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class DefaultCalculatedFieldQueueService implements CalculatedFieldQueueService {
+
+ public static final TbQueueCallback DUMMY_TB_QUEUE_CALLBACK = new TbQueueCallback() {
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ }
+ };
+
+ private final TbAssetProfileCache assetProfileCache;
+ private final TbDeviceProfileCache deviceProfileCache;
+ private final CalculatedFieldCache calculatedFieldCache;
+ private final TbClusterService clusterService;
+
+ private static final Set supportedReferencedEntities = EnumSet.of(
+ EntityType.DEVICE, EntityType.ASSET, EntityType.CUSTOMER, EntityType.TENANT
+ );
+
+ @Override
+ public void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback callback) {
+ var tenantId = request.getTenantId();
+ var entityId = request.getEntityId();
+ checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries()), cf -> cf.linkMatches(entityId, request.getEntries()),
+ () -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
+ }
+
+ @Override
+ public void pushRequestToQueue(TimeseriesSaveRequest request, FutureCallback callback) {
+ pushRequestToQueue(request, null, callback);
+ }
+
+ @Override
+ public void pushRequestToQueue(AttributesSaveRequest request, List result, FutureCallback callback) {
+ var tenantId = request.getTenantId();
+ var entityId = request.getEntityId();
+ checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries(), request.getScope()), cf -> cf.linkMatches(entityId, request.getEntries(), request.getScope()),
+ () -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
+ }
+
+ @Override
+ public void pushRequestToQueue(AttributesSaveRequest request, FutureCallback callback) {
+ pushRequestToQueue(request, null, callback);
+ }
+
+ @Override
+ public void pushRequestToQueue(AttributesDeleteRequest request, List result, FutureCallback callback) {
+ var tenantId = request.getTenantId();
+ var entityId = request.getEntityId();
+ checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matchesKeys(result, request.getScope()), cf -> cf.linkMatchesAttrKeys(entityId, result, request.getScope()),
+ () -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
+ }
+
+ @Override
+ public void pushRequestToQueue(TimeseriesDeleteRequest request, List result, FutureCallback callback) {
+ var tenantId = request.getTenantId();
+ var entityId = request.getEntityId();
+
+ checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matchesKeys(result), cf -> cf.linkMatchesTsKeys(entityId, result),
+ () -> toCalculatedFieldTelemetryMsgProto(request, result), callback);
+ }
+
+ private void checkEntityAndPushToQueue(TenantId tenantId, EntityId entityId,
+ Predicate mainEntityFilter, Predicate linkedEntityFilter,
+ Supplier msg, FutureCallback callback) {
+ if (EntityType.TENANT.equals(entityId.getEntityType())) {
+ tenantId = (TenantId) entityId;
+ }
+ boolean send = checkEntityForCalculatedFields(tenantId, entityId, mainEntityFilter, linkedEntityFilter);
+ if (send) {
+ clusterService.pushMsgToCalculatedFields(tenantId, entityId, msg.get(), wrap(callback));
+ } else {
+ if (callback != null) {
+ callback.onSuccess(null);
+ }
+ }
+ }
+
+ private boolean checkEntityForCalculatedFields(TenantId tenantId, EntityId entityId, Predicate filter, Predicate linkedEntityFilter) {
+ boolean send = false;
+ if (supportedReferencedEntities.contains(entityId.getEntityType())) {
+ send = calculatedFieldCache.getCalculatedFieldCtxsByEntityId(entityId).stream().anyMatch(filter);
+ if (!send) {
+ send = calculatedFieldCache.getCalculatedFieldCtxsByEntityId(getProfileId(tenantId, entityId)).stream().anyMatch(filter);
+ }
+ if (!send) {
+ send = calculatedFieldCache.getCalculatedFieldLinksByEntityId(entityId).stream()
+ .map(CalculatedFieldLink::getCalculatedFieldId)
+ .map(calculatedFieldCache::getCalculatedFieldCtx)
+ .anyMatch(linkedEntityFilter);
+ }
+ }
+ return send;
+ }
+
+ private EntityId getProfileId(TenantId tenantId, EntityId entityId) {
+ return switch (entityId.getEntityType()) {
+ case ASSET -> assetProfileCache.get(tenantId, (AssetId) entityId).getId();
+ case DEVICE -> deviceProfileCache.get(tenantId, (DeviceId) entityId).getId();
+ default -> null;
+ };
+ }
+
+ private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(TimeseriesSaveRequest request, TimeseriesSaveResult result) {
+ ToCalculatedFieldMsg.Builder msg = ToCalculatedFieldMsg.newBuilder();
+
+ CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType());
+
+ List entries = request.getEntries();
+ List versions = result != null ? result.getVersions() : Collections.emptyList();
+
+ for (int i = 0; i < entries.size(); i++) {
+ TsKvProto.Builder tsProtoBuilder = toTsKvProto(entries.get(i)).toBuilder();
+ if (result != null) {
+ tsProtoBuilder.setVersion(versions.get(i));
+ }
+ telemetryMsg.addTsData(tsProtoBuilder.build());
+ }
+
+ msg.setTelemetryMsg(telemetryMsg.build());
+ return msg.build();
+ }
+
+ private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(AttributesSaveRequest request, List versions) {
+ ToCalculatedFieldMsg.Builder msg = ToCalculatedFieldMsg.newBuilder();
+
+ CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType());
+ telemetryMsg.setScope(AttributeScopeProto.valueOf(request.getScope().name()));
+ List entries = request.getEntries();
+ for (int i = 0; i < entries.size(); i++) {
+ AttributeValueProto.Builder attrProtoBuilder = ProtoUtils.toProto(entries.get(i)).toBuilder();
+ if (versions != null) {
+ attrProtoBuilder.setVersion(versions.get(i));
+ }
+ telemetryMsg.addAttrData(attrProtoBuilder.build());
+ }
+ msg.setTelemetryMsg(telemetryMsg.build());
+
+ return msg.build();
+ }
+
+ private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(AttributesDeleteRequest request, List removedKeys) {
+ CalculatedFieldTelemetryMsgProto telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType())
+ .setScope(AttributeScopeProto.valueOf(request.getScope().name()))
+ .addAllRemovedAttrKeys(removedKeys).build();
+ return ToCalculatedFieldMsg.newBuilder()
+ .setTelemetryMsg(telemetryMsg)
+ .build();
+ }
+
+ private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(TimeseriesDeleteRequest request, List removedKeys) {
+ CalculatedFieldTelemetryMsgProto telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType())
+ .addAllRemovedTsKeys(removedKeys).build();
+ return ToCalculatedFieldMsg.newBuilder()
+ .setTelemetryMsg(telemetryMsg)
+ .build();
+ }
+
+ private CalculatedFieldTelemetryMsgProto.Builder buildTelemetryMsgProto(TenantId tenantId, EntityId entityId, List calculatedFieldIds, UUID tbMsgId, TbMsgType tbMsgType) {
+ CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = CalculatedFieldTelemetryMsgProto.newBuilder();
+
+ if (EntityType.TENANT.equals(entityId.getEntityType())) {
+ tenantId = (TenantId) entityId;
+ }
+
+ telemetryMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
+ telemetryMsg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
+
+ telemetryMsg.setEntityType(entityId.getEntityType().name());
+ telemetryMsg.setEntityIdMSB(entityId.getId().getMostSignificantBits());
+ telemetryMsg.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
+
+ if (calculatedFieldIds != null) {
+ for (CalculatedFieldId cfId : calculatedFieldIds) {
+ telemetryMsg.addPreviousCalculatedFields(toProto(cfId));
+ }
+ }
+
+ if (tbMsgId != null) {
+ telemetryMsg.setTbMsgIdMSB(tbMsgId.getMostSignificantBits());
+ telemetryMsg.setTbMsgIdLSB(tbMsgId.getLeastSignificantBits());
+ }
+
+ if (tbMsgType != null) {
+ telemetryMsg.setTbMsgType(tbMsgType.name());
+ }
+
+ return telemetryMsg;
+ }
+
+ private static TbQueueCallback wrap(FutureCallback callback) {
+ if (callback != null) {
+ return new FutureCallbackWrapper(callback);
+ } else {
+ return DUMMY_TB_QUEUE_CALLBACK;
+ }
+ }
+
+ private static class FutureCallbackWrapper implements TbQueueCallback {
+ private final FutureCallback callback;
+
+ public FutureCallbackWrapper(FutureCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ callback.onSuccess(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure(t);
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/cache/CalculatedFieldEntityProfileCache.java b/application/src/main/java/org/thingsboard/server/service/cf/cache/CalculatedFieldEntityProfileCache.java
new file mode 100644
index 0000000000..bb5ef91974
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/cache/CalculatedFieldEntityProfileCache.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.cache;
+
+import org.springframework.context.ApplicationListener;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
+
+import java.util.Collection;
+
+public interface CalculatedFieldEntityProfileCache extends ApplicationListener {
+
+ void add(TenantId tenantId, EntityId profileId, EntityId entityId);
+
+ void update(TenantId tenantId, EntityId oldProfileId, EntityId newProfileId, EntityId entityId);
+
+ void evict(TenantId tenantId, EntityId entityId);
+
+ Collection getMyEntityIdsByProfileId(TenantId tenantId, EntityId profileId);
+
+ int getEntityIdPartition(TenantId tenantId, EntityId entityId);
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/cache/DefaultCalculatedFieldEntityProfileCache.java b/application/src/main/java/org/thingsboard/server/service/cf/cache/DefaultCalculatedFieldEntityProfileCache.java
new file mode 100644
index 0000000000..2f5772ae50
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/cache/DefaultCalculatedFieldEntityProfileCache.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.cache;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.queue.discovery.PartitionService;
+import org.thingsboard.server.queue.discovery.QueueKey;
+import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
+import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
+import org.thingsboard.server.queue.util.TbRuleEngineComponent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+@TbRuleEngineComponent
+@Service
+@Slf4j
+@RequiredArgsConstructor
+//TODO ashvayka: remove and use TenantEntityProfileCache in each CalculatedFieldManagerMessageProcessor;
+public class DefaultCalculatedFieldEntityProfileCache extends TbApplicationEventListener implements CalculatedFieldEntityProfileCache {
+
+ private static final Integer UNKNOWN = 0;
+ private final ConcurrentMap tenantCache = new ConcurrentHashMap<>();
+ private final PartitionService partitionService;
+ private volatile List myPartitions = Collections.emptyList();
+
+ @Override
+ protected void onTbApplicationEvent(PartitionChangeEvent event) {
+ myPartitions = event.getCfPartitions().stream()
+ .filter(TopicPartitionInfo::isMyPartition)
+ .map(tpi -> tpi.getPartition().orElse(UNKNOWN)).collect(Collectors.toList());
+ //Naive approach that need to be improved.
+ tenantCache.values().forEach(cache -> cache.setMyPartitions(myPartitions));
+ }
+
+ @Override
+ public void add(TenantId tenantId, EntityId profileId, EntityId entityId) {
+ var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
+ var partition = tpi.getPartition().orElse(UNKNOWN);
+ tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache())
+ .add(profileId, entityId, partition, tpi.isMyPartition());
+ }
+
+ @Override
+ public void update(TenantId tenantId, EntityId oldProfileId, EntityId newProfileId, EntityId entityId) {
+ var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
+ var partition = tpi.getPartition().orElse(UNKNOWN);
+ var cache = tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache());
+ //TODO: make this method atomic;
+ cache.remove(oldProfileId, entityId);
+ cache.add(newProfileId, entityId, partition, tpi.isMyPartition());
+ }
+
+ @Override
+ public void evict(TenantId tenantId, EntityId entityId) {
+ var cache = tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache());
+ cache.removeEntityId(entityId);
+ }
+
+ @Override
+ public Collection getMyEntityIdsByProfileId(TenantId tenantId, EntityId profileId) {
+ return tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache()).getMyEntityIdsByProfileId(profileId);
+ }
+
+ @Override
+ public int getEntityIdPartition(TenantId tenantId, EntityId entityId) {
+ var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
+ return tpi.getPartition().orElse(UNKNOWN);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/cache/TenantEntityProfileCache.java b/application/src/main/java/org/thingsboard/server/service/cf/cache/TenantEntityProfileCache.java
new file mode 100644
index 0000000000..1a17b9b8be
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/cache/TenantEntityProfileCache.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.cache;
+
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.EntityId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class TenantEntityProfileCache {
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ private final Map>> allEntities = new HashMap<>();
+ private final Map> myEntities = new HashMap<>();
+
+ public void setMyPartitions(List myPartitions) {
+ lock.writeLock().lock();
+ try {
+ myEntities.clear();
+ myPartitions.forEach(partitionId -> {
+ var map = allEntities.get(partitionId);
+ if (map != null) {
+ map.forEach((profileId, entityIds) -> myEntities.computeIfAbsent(profileId, k -> new HashSet<>()).addAll(entityIds));
+ }
+ });
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public void removeProfileId(EntityId profileId) {
+ lock.writeLock().lock();
+ try {
+ // Remove from allEntities
+ allEntities.values().forEach(map -> map.remove(profileId));
+ // Remove from myEntities
+ myEntities.remove(profileId);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public void removeEntityId(EntityId entityId) {
+ lock.writeLock().lock();
+ try {
+ // Remove from allEntities
+ allEntities.values().forEach(map -> map.values().forEach(set -> set.remove(entityId)));
+ // Remove from myEntities
+ myEntities.values().forEach(set -> set.remove(entityId));
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public void remove(EntityId profileId, EntityId entityId) {
+ lock.writeLock().lock();
+ try {
+ // Remove from allEntities
+ allEntities.values().forEach(map -> removeSafely(map, profileId, entityId));
+ // Remove from myEntities
+ removeSafely(myEntities, profileId, entityId);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public void add(EntityId profileId, EntityId entityId, Integer partition, boolean mine) {
+ lock.writeLock().lock();
+ try {
+ if(EntityType.DEVICE.equals(profileId.getEntityType())){
+ throw new RuntimeException("WTF?");
+ }
+ if (mine) {
+ myEntities.computeIfAbsent(profileId, k -> new HashSet<>()).add(entityId);
+ }
+ allEntities.computeIfAbsent(partition, k -> new HashMap<>()).computeIfAbsent(profileId, p -> new HashSet<>()).add(entityId);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public Collection getMyEntityIdsByProfileId(EntityId profileId) {
+ lock.readLock().lock();
+ try {
+ var entities = myEntities.getOrDefault(profileId, Collections.emptySet());
+ List result = new ArrayList<>(entities.size());
+ result.addAll(entities);
+ return result;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ private void removeSafely(Map> map, EntityId profileId, EntityId entityId) {
+ var set = map.get(profileId);
+ if (set != null) {
+ set.remove(entityId);
+ }
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtx.java
new file mode 100644
index 0000000000..6694252652
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtx.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
+
+@Data
+@NoArgsConstructor
+public class CalculatedFieldEntityCtx {
+
+ private CalculatedFieldEntityCtxId id;
+ private CalculatedFieldState state;
+
+ public CalculatedFieldEntityCtx(CalculatedFieldEntityCtxId id, CalculatedFieldState state) {
+ this.id = id;
+ this.state = state;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtxId.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtxId.java
new file mode 100644
index 0000000000..329028eda4
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/CalculatedFieldEntityCtxId.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx;
+
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+public record CalculatedFieldEntityCtxId(TenantId tenantId, CalculatedFieldId cfId, EntityId entityId) {
+
+ public String toKey() {
+ return cfId + "_" + entityId;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java
new file mode 100644
index 0000000000..83e10b8194
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx.state;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.thingsboard.script.api.tbel.TbelCfArg;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+
+import java.util.List;
+
+@JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type"
+)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = SingleValueArgumentEntry.class, name = "SINGLE_VALUE"),
+ @JsonSubTypes.Type(value = TsRollingArgumentEntry.class, name = "TS_ROLLING")
+})
+public interface ArgumentEntry {
+
+ @JsonIgnore
+ ArgumentEntryType getType();
+
+ Object getValue();
+
+ boolean updateEntry(ArgumentEntry entry);
+
+ boolean isEmpty();
+
+ TbelCfArg toTbelCfArg();
+
+ boolean isForceResetPrevious();
+
+ void setForceResetPrevious(boolean forceResetPrevious);
+
+ static ArgumentEntry createSingleValueArgument(KvEntry kvEntry) {
+ return new SingleValueArgumentEntry(kvEntry);
+ }
+
+ static ArgumentEntry createTsRollingArgument(List kvEntries, int limit, long timeWindow) {
+ return new TsRollingArgumentEntry(kvEntries, limit, timeWindow);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntryType.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntryType.java
new file mode 100644
index 0000000000..68f973c7c1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntryType.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx.state;
+
+public enum ArgumentEntryType {
+ SINGLE_VALUE, TS_ROLLING
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
new file mode 100644
index 0000000000..80b003b3cc
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx.state;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+import org.thingsboard.server.utils.CalculatedFieldUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.thingsboard.server.utils.CalculatedFieldUtils.toSingleValueArgumentProto;
+
+@Data
+@AllArgsConstructor
+public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
+
+ protected List requiredArguments;
+ protected Map arguments;
+ protected boolean sizeExceedsLimit;
+
+ public BaseCalculatedFieldState(List requiredArguments) {
+ this.requiredArguments = requiredArguments;
+ this.arguments = new HashMap<>();
+ }
+
+ public BaseCalculatedFieldState() {
+ this(new ArrayList<>(), new HashMap<>(), false);
+ }
+
+ @Override
+ public boolean updateState(CalculatedFieldCtx ctx, Map argumentValues) {
+ if (arguments == null) {
+ arguments = new HashMap<>();
+ }
+
+ boolean stateUpdated = false;
+
+ for (Map.Entry entry : argumentValues.entrySet()) {
+ String key = entry.getKey();
+ ArgumentEntry newEntry = entry.getValue();
+
+ checkArgumentSize(key, newEntry, ctx);
+
+ ArgumentEntry existingEntry = arguments.get(key);
+
+ if (existingEntry == null || newEntry.isForceResetPrevious()) {
+ validateNewEntry(newEntry);
+ arguments.put(key, newEntry);
+ stateUpdated = true;
+ } else {
+ stateUpdated = existingEntry.updateEntry(newEntry);
+ }
+ }
+
+ return stateUpdated;
+ }
+
+ @Override
+ public boolean isReady() {
+ return arguments.keySet().containsAll(requiredArguments) &&
+ arguments.values().stream().noneMatch(ArgumentEntry::isEmpty);
+ }
+
+ @Override
+ public void checkStateSize(CalculatedFieldEntityCtxId ctxId, long maxStateSize) {
+ if (!sizeExceedsLimit && maxStateSize > 0 && CalculatedFieldUtils.toProto(ctxId, this).getSerializedSize() > maxStateSize) {
+ arguments.clear();
+ sizeExceedsLimit = true;
+ }
+ }
+
+ @Override
+ public void checkArgumentSize(String name, ArgumentEntry entry, CalculatedFieldCtx ctx) {
+ if (entry instanceof TsRollingArgumentEntry) {
+ return;
+ }
+ if (entry instanceof SingleValueArgumentEntry singleValueArgumentEntry) {
+ if (ctx.getMaxSingleValueArgumentSize() > 0 && toSingleValueArgumentProto(name, singleValueArgumentEntry).getSerializedSize() > ctx.getMaxSingleValueArgumentSize()) {
+ throw new IllegalArgumentException("Single value size exceeds the maximum allowed limit. The argument will not be used for calculation.");
+ }
+ }
+ }
+
+ protected abstract void validateNewEntry(ArgumentEntry newEntry);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
new file mode 100644
index 0000000000..0c4352dcea
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
@@ -0,0 +1,273 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx.state;
+
+import lombok.Data;
+import net.objecthunter.exp4j.Expression;
+import net.objecthunter.exp4j.ExpressionBuilder;
+import org.mvel2.MVEL;
+import org.thingsboard.script.api.tbel.TbelInvokeService;
+import org.thingsboard.server.common.data.AttributeScope;
+import org.thingsboard.server.common.data.cf.CalculatedField;
+import org.thingsboard.server.common.data.cf.CalculatedFieldType;
+import org.thingsboard.server.common.data.cf.configuration.Argument;
+import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
+import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
+import org.thingsboard.server.common.data.cf.configuration.Output;
+import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
+import org.thingsboard.server.common.data.id.CalculatedFieldId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.common.util.ProtoUtils;
+import org.thingsboard.server.dao.usagerecord.ApiLimitService;
+import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
+import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class CalculatedFieldCtx {
+
+ private CalculatedField calculatedField;
+
+ private CalculatedFieldId cfId;
+ private TenantId tenantId;
+ private EntityId entityId;
+ private CalculatedFieldType cfType;
+ private final Map arguments;
+ private final Map mainEntityArguments;
+ private final Map> linkedEntityArguments;
+ private final List argNames;
+ private Output output;
+ private String expression;
+ private TbelInvokeService tbelInvokeService;
+ private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
+ private ThreadLocal customExpression;
+
+ private boolean initialized;
+
+ private long maxDataPointsPerRollingArg;
+ private long maxStateSize;
+ private long maxSingleValueArgumentSize;
+
+ public CalculatedFieldCtx(CalculatedField calculatedField, TbelInvokeService tbelInvokeService, ApiLimitService apiLimitService) {
+ this.calculatedField = calculatedField;
+
+ this.cfId = calculatedField.getId();
+ this.tenantId = calculatedField.getTenantId();
+ this.entityId = calculatedField.getEntityId();
+ this.cfType = calculatedField.getType();
+ CalculatedFieldConfiguration configuration = calculatedField.getConfiguration();
+ this.arguments = configuration.getArguments();
+ this.mainEntityArguments = new HashMap<>();
+ this.linkedEntityArguments = new HashMap<>();
+ for (Map.Entry entry : arguments.entrySet()) {
+ var refId = entry.getValue().getRefEntityId();
+ var refKey = entry.getValue().getRefEntityKey();
+ if (refId == null || refId.equals(calculatedField.getEntityId())) {
+ mainEntityArguments.put(refKey, entry.getKey());
+ } else {
+ linkedEntityArguments.computeIfAbsent(refId, key -> new HashMap<>()).put(refKey, entry.getKey());
+ }
+ }
+ this.argNames = new ArrayList<>(arguments.keySet());
+ this.output = configuration.getOutput();
+ this.expression = configuration.getExpression();
+ this.tbelInvokeService = tbelInvokeService;
+
+ this.maxDataPointsPerRollingArg = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxDataPointsPerRollingArg);
+ this.maxStateSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxStateSizeInKBytes) * 1024;
+ this.maxSingleValueArgumentSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxSingleValueArgumentSizeInKBytes) * 1024;
+ }
+
+ public void init() {
+ if (CalculatedFieldType.SCRIPT.equals(cfType)) {
+ try {
+ this.calculatedFieldScriptEngine = initEngine(tenantId, expression, tbelInvokeService);
+ initialized = true;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to init calculated field ctx. Invalid expression syntax.", e);
+ }
+ } else {
+ if (isValidExpression(expression)) {
+ this.customExpression = ThreadLocal.withInitial(() ->
+ new ExpressionBuilder(expression)
+ .implicitMultiplication(true)
+ .variables(this.arguments.keySet())
+ .build()
+ );
+ initialized = true;
+ } else {
+ throw new RuntimeException("Failed to init calculated field ctx. Invalid expression syntax.");
+ }
+ }
+ }
+
+ private CalculatedFieldScriptEngine initEngine(TenantId tenantId, String expression, TbelInvokeService tbelInvokeService) {
+ if (tbelInvokeService == null) {
+ throw new IllegalArgumentException("TBEL script engine is disabled!");
+ }
+
+ List ctxAndArgNames = new ArrayList<>(argNames.size() + 1);
+ ctxAndArgNames.add("ctx");
+ ctxAndArgNames.addAll(argNames);
+ return new CalculatedFieldTbelScriptEngine(
+ tenantId,
+ tbelInvokeService,
+ expression,
+ ctxAndArgNames.toArray(String[]::new)
+ );
+ }
+
+ private boolean isValidExpression(String expression) {
+ try {
+ MVEL.compileExpression(expression);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public boolean matches(List values, AttributeScope scope) {
+ return matchesAttributes(mainEntityArguments, values, scope);
+ }
+
+ public boolean linkMatches(EntityId entityId, List values, AttributeScope scope) {
+ var map = linkedEntityArguments.get(entityId);
+ return map != null && matchesAttributes(map, values, scope);
+ }
+
+ public boolean matches(List values) {
+ return matchesTimeSeries(mainEntityArguments, values);
+ }
+
+ public boolean linkMatches(EntityId entityId, List values) {
+ var map = linkedEntityArguments.get(entityId);
+ return map != null && matchesTimeSeries(map, values);
+ }
+
+ private boolean matchesAttributes(Map argMap, List values, AttributeScope scope) {
+ for (AttributeKvEntry attrKv : values) {
+ ReferencedEntityKey attrKey = new ReferencedEntityKey(attrKv.getKey(), ArgumentType.ATTRIBUTE, scope);
+ if (argMap.containsKey(attrKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean matchesTimeSeries(Map argMap, List values) {
+ for (TsKvEntry tsKv : values) {
+ ReferencedEntityKey latestKey = new ReferencedEntityKey(tsKv.getKey(), ArgumentType.TS_LATEST, null);
+ if (argMap.containsKey(latestKey)) {
+ return true;
+ }
+ ReferencedEntityKey rollingKey = new ReferencedEntityKey(tsKv.getKey(), ArgumentType.TS_ROLLING, null);
+ if (argMap.containsKey(rollingKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean matchesKeys(List keys, AttributeScope scope) {
+ return matchesAttributesKeys(mainEntityArguments, keys, scope);
+ }
+
+ public boolean matchesKeys(List keys) {
+ return matchesTimeSeriesKeys(mainEntityArguments, keys);
+ }
+
+ private boolean matchesAttributesKeys(Map argMap, List keys, AttributeScope scope) {
+ for (String key : keys) {
+ ReferencedEntityKey attrKey = new ReferencedEntityKey(key, ArgumentType.ATTRIBUTE, scope);
+ if (argMap.containsKey(attrKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean matchesTimeSeriesKeys(Map argMap, List keys) {
+ for (String key : keys) {
+ ReferencedEntityKey latestKey = new ReferencedEntityKey(key, ArgumentType.TS_LATEST, null);
+ if (argMap.containsKey(latestKey)) {
+ return true;
+ }
+ ReferencedEntityKey rollingKey = new ReferencedEntityKey(key, ArgumentType.TS_ROLLING, null);
+ if (argMap.containsKey(rollingKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean linkMatchesAttrKeys(EntityId entityId, List keys, AttributeScope scope) {
+ var map = linkedEntityArguments.get(entityId);
+ return map != null && matchesAttributesKeys(map, keys, scope);
+ }
+
+ public boolean linkMatchesTsKeys(EntityId entityId, List keys) {
+ var map = linkedEntityArguments.get(entityId);
+ return map != null && matchesTimeSeriesKeys(map, keys);
+ }
+
+ public boolean linkMatches(EntityId entityId, CalculatedFieldTelemetryMsgProto proto) {
+ if (!proto.getTsDataList().isEmpty()) {
+ List updatedTelemetry = proto.getTsDataList().stream()
+ .map(ProtoUtils::fromProto)
+ .toList();
+ return linkMatches(entityId, updatedTelemetry);
+ } else if (!proto.getAttrDataList().isEmpty()) {
+ AttributeScope scope = AttributeScope.valueOf(proto.getScope().name());
+ List updatedTelemetry = proto.getAttrDataList().stream()
+ .map(ProtoUtils::fromProto)
+ .toList();
+ return linkMatches(entityId, updatedTelemetry, scope);
+ } else if (!proto.getRemovedTsKeysList().isEmpty()) {
+ return linkMatchesTsKeys(entityId, proto.getRemovedTsKeysList());
+ } else {
+ return linkMatchesAttrKeys(entityId, proto.getRemovedAttrKeysList(), AttributeScope.valueOf(proto.getScope().name()));
+ }
+ }
+
+ public CalculatedFieldEntityCtxId toCalculatedFieldEntityCtxId() {
+ return new CalculatedFieldEntityCtxId(tenantId, cfId, entityId);
+ }
+
+ public boolean hasOtherSignificantChanges(CalculatedFieldCtx other) {
+ boolean expressionChanged = !expression.equals(other.expression);
+ boolean outputChanged = !output.equals(other.output);
+ return expressionChanged || outputChanged;
+ }
+
+ public boolean hasStateChanges(CalculatedFieldCtx other) {
+ boolean typeChanged = !cfType.equals(other.cfType);
+ boolean argumentsChanged = !arguments.equals(other.arguments);
+ return typeChanged || argumentsChanged;
+ }
+
+ public String getSizeExceedsLimitMessage() {
+ return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!";
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldScriptEngine.java
new file mode 100644
index 0000000000..caad1e4cfe
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldScriptEngine.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.cf.ctx.state;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.util.concurrent.ListenableFuture;
+
+public interface CalculatedFieldScriptEngine {
+
+ ListenableFuture