Commit 8abb7183499a28e916323d98529d501aea22454c

Authored by Andrii Shvaika
2 parents ba140644 86f21023

Merge with develop

Showing 53 changed files with 3870 additions and 166 deletions

Too many changes to show.

To preserve performance only 53 of 80 files are displayed.

  1 +{
  2 + "title": "Gateways",
  3 + "configuration": {
  4 + "widgets": {
  5 + "94715984-ae74-76e4-20b7-2f956b01ed80": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "entity_admin_widgets",
  8 + "typeAlias": "device_admin_table2",
  9 + "type": "latest",
  10 + "title": "New widget",
  11 + "sizeX": 24,
  12 + "sizeY": 12,
  13 + "config": {
  14 + "timewindow": {
  15 + "realtime": {
  16 + "interval": 1000,
  17 + "timewindowMs": 86400000
  18 + },
  19 + "aggregation": {
  20 + "type": "NONE",
  21 + "limit": 200
  22 + }
  23 + },
  24 + "showTitle": true,
  25 + "backgroundColor": "rgb(255, 255, 255)",
  26 + "color": "rgba(0, 0, 0, 0.87)",
  27 + "padding": "4px",
  28 + "settings": {
  29 + "enableSearch": true,
  30 + "displayPagination": true,
  31 + "defaultPageSize": 10,
  32 + "defaultSortOrder": "entityName",
  33 + "displayEntityName": true,
  34 + "displayEntityType": false,
  35 + "entitiesTitle": "List of gateways",
  36 + "enableSelectColumnDisplay": true,
  37 + "displayEntityLabel": false,
  38 + "entityNameColumnTitle": "Gateway Name"
  39 + },
  40 + "title": "Devices gateway table",
  41 + "dropShadow": true,
  42 + "enableFullscreen": true,
  43 + "titleStyle": {
  44 + "fontSize": "16px",
  45 + "fontWeight": 400,
  46 + "padding": "5px 10px 5px 10px"
  47 + },
  48 + "useDashboardTimewindow": false,
  49 + "showLegend": false,
  50 + "datasources": [
  51 + {
  52 + "type": "entity",
  53 + "dataKeys": [
  54 + {
  55 + "name": "active",
  56 + "type": "attribute",
  57 + "label": "Active",
  58 + "color": "#2196f3",
  59 + "settings": {
  60 + "columnWidth": "0px",
  61 + "useCellStyleFunction": true,
  62 + "useCellContentFunction": true,
  63 + "cellContentFunction": "value = '⬤';\nreturn value;",
  64 + "cellStyleFunction": "var color;\nif (value == 'false') {\n color = '#EB5757';\n} else {\n color = '#27AE60';\n}\nreturn {\n color: color,\n fontSize: '18px'\n};"
  65 + },
  66 + "_hash": 0.3646047595211721
  67 + },
  68 + {
  69 + "name": "eventsSent",
  70 + "type": "timeseries",
  71 + "label": "Sent",
  72 + "color": "#4caf50",
  73 + "settings": {
  74 + "columnWidth": "0px",
  75 + "useCellStyleFunction": false,
  76 + "useCellContentFunction": false
  77 + },
  78 + "_hash": 0.7235710720767985
  79 + },
  80 + {
  81 + "name": "eventsProduced",
  82 + "type": "timeseries",
  83 + "label": "Events",
  84 + "color": "#f44336",
  85 + "settings": {
  86 + "columnWidth": "0px",
  87 + "useCellStyleFunction": false,
  88 + "useCellContentFunction": false
  89 + },
  90 + "_hash": 0.5085933386303254
  91 + },
  92 + {
  93 + "name": "LOGS",
  94 + "type": "timeseries",
  95 + "label": "Latest log",
  96 + "color": "#ffc107",
  97 + "settings": {
  98 + "columnWidth": "0px",
  99 + "useCellStyleFunction": false,
  100 + "useCellContentFunction": false
  101 + },
  102 + "_hash": 0.3504240371585048,
  103 + "postFuncBody": "if(value) {\n return value.substring(0, 31) + \"...\";\n} else {\n return '';\n}"
  104 + },
  105 + {
  106 + "name": "RemoteLoggingLevel",
  107 + "type": "attribute",
  108 + "label": "Log level",
  109 + "color": "#607d8b",
  110 + "settings": {
  111 + "columnWidth": "0px",
  112 + "useCellStyleFunction": false,
  113 + "useCellContentFunction": false
  114 + },
  115 + "_hash": 0.9785994222542516
  116 + }
  117 + ],
  118 + "entityAliasId": "3e0f533a-0db1-3292-184f-06e73535061a"
  119 + }
  120 + ],
  121 + "showTitleIcon": true,
  122 + "titleIcon": "list",
  123 + "iconColor": "rgba(0, 0, 0, 0.87)",
  124 + "iconSize": "24px",
  125 + "titleTooltip": "List device",
  126 + "widgetStyle": {},
  127 + "displayTimewindow": true,
  128 + "actions": {
  129 + "headerButton": [
  130 + {
  131 + "id": "70837a9d-c3de-a9a7-03c5-dccd14998758",
  132 + "name": "Add device",
  133 + "icon": "add",
  134 + "type": "customPretty",
  135 + "customHtml": "<md-dialog aria-label=\"Add entity\" style=\"width: 480px\">\n <form name=\"addDeviceForm\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Add device</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-progress-linear class=\"md-warn\" md-mode=\"indeterminate\" ng-disabled=\"!$root.loading && !vm.loading\" ng-show=\"$root.loading || vm.loading\"></md-progress-linear>\n <span style=\"min-height: 5px;\" flex=\"\" ng-show=\"!$root.loading && !vm.loading\"></span>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <fieldset ng-disabled=\"$root.loading || vm.loading\">\n <md-input-container flex class=\"md-block\">\n <label>Device name</label>\n <input ng-model=\"vm.deviceName\" name=deviceName required>\n <div ng-messages=\"addDeviceForm.deviceName.$error\">\n <div ng-message=\"required\">Device name is required.</div>\n </div>\n </md-input-container>\n <div flex layout=\"row\">\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Latitude</label>\n <input type=\"number\" step=\"any\" name=\"latitude\" ng-model=\"vm.attributes.latitude\">\n </md-input-container>\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Longitude</label>\n <input type=\"number\" step=\"any\" name=\"longitude\" ng-model=\"vm.attributes.longitude\">\n </md-input-container>\n </div>\n <md-input-container class=\"md-block\">\n <label>Label</label>\n <input name=\"deviceLabel\" ng-model=\"vm.deviceLabel\">\n </md-input-container>\n </fieldset>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"vm.loading || addDeviceForm.$invalid || !addDeviceForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>\n",
  136 + "customCss": "",
  137 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n\nopenAddDeviceDialog();\n\nfunction openAddDeviceDialog() {\n $mdDialog.show({\n controller: ['$scope', '$mdDialog',\n AddDeviceDialogController\n ],\n controllerAs: 'vm',\n template: htmlTemplate,\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction AddDeviceDialogController($scope, $mdDialog) {\n let vm = this;\n vm.types = types;\n vm.attributes = {};\n vm.deviceType = \"gateway\";\n\n vm.cancel = () => {\n $mdDialog.hide();\n };\n\n vm.save = () => {\n vm.loading = true;\n $scope.addDeviceForm.$setPristine();\n let device = {\n additionalInfo: {gateway: true},\n name: vm.deviceName,\n type: vm.deviceType,\n label: vm.deviceLabel\n };\n deviceService.saveDevice(device).then(\n (device) => {\n saveAttributes(device.id).then(\n () => {\n vm.loading = false;\n updateAliasData();\n $mdDialog.hide();\n }\n );\n },\n () => {\n vm.loading = false;\n }\n );\n };\n\n function saveAttributes(entityId) {\n let attributesArray = [];\n for (let key in vm.attributes) {\n attributesArray.push({\n key: key,\n value: vm.attributes[key]\n });\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(\n entityId.entityType, entityId.id,\n \"SERVER_SCOPE\", attributesArray);\n } else {\n return $q.when([]);\n }\n }\n\n function updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController\n .resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController\n .setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController\n .getAliasInfo(aliasId));\n });\n $q.all(tasks).then(() => {\n $rootScope.$broadcast(\n 'widgetForceReInit');\n });\n }\n}"
  138 + }
  139 + ],
  140 + "actionCellButton": [
  141 + {
  142 + "id": "78845501-234e-a452-6819-82b5b776e99f",
  143 + "name": "Configuration",
  144 + "icon": "settings",
  145 + "type": "openDashboardState",
  146 + "targetDashboardStateId": "__entityname__config",
  147 + "openRightLayout": false,
  148 + "setEntityId": true
  149 + },
  150 + {
  151 + "id": "f6ffdba8-e40f-2b8d-851b-f5ecaf18606b",
  152 + "name": "Graphs",
  153 + "icon": "show_chart",
  154 + "type": "openDashboardState",
  155 + "targetDashboardStateId": "__entityname_grafic",
  156 + "setEntityId": true
  157 + },
  158 + {
  159 + "id": "242671f3-76c6-6982-7acc-6f12addf0ccc",
  160 + "name": "Edit device",
  161 + "icon": "edit",
  162 + "type": "customPretty",
  163 + "customHtml": "<md-dialog aria-label=\"Edit entity\" style=\"width: 480px\">\n <form name=\"editDeviceForm\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Edit device</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-progress-linear class=\"md-warn\" md-mode=\"indeterminate\" ng-disabled=\"!$root.loading && !vm.loading\" ng-show=\"$root.loading || vm.loading\"></md-progress-linear>\n <span style=\"min-height: 5px;\" flex=\"\" ng-show=\"!$root.loading && !vm.loading\"></span>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <fieldset ng-disabled=\"$root.loading || vm.loading\">\n <md-input-container flex class=\"md-block\">\n <label>Device name</label>\n <input ng-model=\"vm.device.name\" name=deviceName required>\n <div ng-messages=\"editDeviceForm.deviceName.$error\">\n <div ng-message=\"required\">Device name is required.</div>\n </div>\n </md-input-container>\n <!--<div flex layout=\"row\">-->\n <!--<tb-entity-subtype-autocomplete flex=\"50\"-->\n <!-- ng-disabled=\"true\"-->\n <!-- tb-required=\"true\"-->\n <!-- the-form=\"editDeviceForm\"-->\n <!-- ng-model=\"vm.device.type\"-->\n <!-- entity-type=\"vm.types.entityType.device\">-->\n <!--</tb-entity-subtype-autocomplete>-->\n <!-- <md-input-container flex=\"50\" class=\"md-block\">-->\n <!-- <label>Label</label>-->\n <!-- <input name=\"deviceLabel\" ng-model=\"vm.device.label\">-->\n <!-- </md-input-container>-->\n <!--</div>-->\n <div flex layout=\"row\">\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Latitude</label>\n <input type=\"number\" step=\"any\" name=\"latitude\" ng-model=\"vm.attributes.latitude\">\n </md-input-container>\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Longitude</label>\n <input type=\"number\" step=\"any\" name=\"longitude\" ng-model=\"vm.attributes.longitude\">\n </md-input-container>\n </div>\n <md-input-container class=\"md-block\">\n <label>Label</label>\n <input name=\"deviceLabel\" ng-model=\"vm.device.label\">\n </md-input-container>\n </fieldset>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"vm.loading || editDeviceForm.$invalid || !editDeviceForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  164 + "customCss": "/*=======================================================================*/\n/*========== There are two examples: for edit and add entity ==========*/\n/*=======================================================================*/\n/*======================== Edit entity example ========================*/\n/*=======================================================================*/\n/*\n.edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n\n.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n display: none;\n} \n*/\n/*========================================================================*/\n/*========================= Add entity example =========================*/\n/*========================================================================*/\n/*\n.add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n*/\n",
  165 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n \nopenEditDeviceDialog();\n\nfunction openEditDeviceDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', EditDeviceDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction EditDeviceDialogController($scope,$mdDialog) {\n let vm = this;\n vm.types = types;\n vm.loading = false;\n vm.attributes = {};\n \n getEntityInfo();\n \n function getEntityInfo() {\n vm.loading = true;\n deviceService.getDevice(entityId.id).then(\n (device) => {\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n (data) => {\n if (data.length) {\n getEntityAttributes(data);\n }\n vm.device = device;\n vm.loading = false;\n } \n );\n }\n )\n }\n \n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n vm.save = () => {\n vm.loading = true;\n $scope.editDeviceForm.$setPristine();\n deviceService.saveDevice(vm.device).then(\n () => {\n saveAttributes().then(\n () => {\n updateAliasData();\n vm.loading = false;\n $mdDialog.hide();\n }\n );\n },\n () => {\n vm.loading = false;\n }\n );\n }\n \n function getEntityAttributes(attributes) {\n for (let i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n }\n \n function saveAttributes() {\n let attributesArray = [];\n for (let key in vm.attributes) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } else {\n return $q.when([]);\n }\n }\n \n function updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n console.log(widgetContext);\n $q.all(tasks).then(() => {\n $rootScope.$broadcast('widgetForceReInit');\n });\n }\n}\n"
  166 + },
  167 + {
  168 + "id": "862ec2b7-fbcf-376e-f85f-b77c07f36efa",
  169 + "name": "Delete device",
  170 + "icon": "delete",
  171 + "type": "custom",
  172 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n $rootScope = $injector.get('$rootScope'),\n $q = $injector.get('$q');\n\nopenDeleteDeviceDialog();\n\nfunction openDeleteDeviceDialog() {\n let title = \"Are you sure you want to delete the device \" + entityName + \"?\";\n let content = \"Be careful, after the confirmation, the device and all related data will become unrecoverable!\";\n let confirm = $mdDialog.confirm()\n .targetEvent($event)\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .cancel('Cancel')\n .ok('Delete');\n $mdDialog.show(confirm).then(() => {\n deleteDevice();\n })\n}\n\nfunction deleteDevice() {\n deviceService.deleteDevice(entityId.id).then(\n () => {\n updateAliasData();\n }\n );\n}\n\nfunction updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(() => {\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n });\n}"
  173 + }
  174 + ],
  175 + "rowClick": [
  176 + {
  177 + "id": "ad5fc7e1-5e60-e056-6940-a75a383466a1",
  178 + "name": "to_entityname__config",
  179 + "icon": "more_horiz",
  180 + "type": "openDashboardState",
  181 + "targetDashboardStateId": "__entityname__config",
  182 + "setEntityId": true,
  183 + "stateEntityParamName": ""
  184 + }
  185 + ]
  186 + }
  187 + },
  188 + "id": "94715984-ae74-76e4-20b7-2f956b01ed80"
  189 + },
  190 + "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
  191 + "isSystemType": true,
  192 + "bundleAlias": "cards",
  193 + "typeAlias": "timeseries_table",
  194 + "type": "timeseries",
  195 + "title": "New widget",
  196 + "sizeX": 14,
  197 + "sizeY": 13,
  198 + "config": {
  199 + "datasources": [
  200 + {
  201 + "type": "entity",
  202 + "dataKeys": [
  203 + {
  204 + "name": "LOGS",
  205 + "type": "timeseries",
  206 + "label": "LOGS",
  207 + "color": "#2196f3",
  208 + "settings": {
  209 + "useCellStyleFunction": false,
  210 + "useCellContentFunction": false
  211 + },
  212 + "_hash": 0.3496649158709739,
  213 + "postFuncBody": "return value.replace(/ - (.*) - \\[/gi, ' - <b style=\"color:#0f0;\">$1</b> - [');"
  214 + }
  215 + ],
  216 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  217 + }
  218 + ],
  219 + "timewindow": {
  220 + "realtime": {
  221 + "interval": 1000,
  222 + "timewindowMs": 2592000000
  223 + },
  224 + "aggregation": {
  225 + "type": "NONE",
  226 + "limit": 200
  227 + }
  228 + },
  229 + "showTitle": true,
  230 + "backgroundColor": "rgb(255, 255, 255)",
  231 + "color": "rgba(0, 0, 0, 0.87)",
  232 + "padding": "8px",
  233 + "settings": {
  234 + "showTimestamp": true,
  235 + "displayPagination": true,
  236 + "defaultPageSize": 10
  237 + },
  238 + "title": "Debug events (logs)",
  239 + "dropShadow": true,
  240 + "enableFullscreen": true,
  241 + "titleStyle": {
  242 + "fontSize": "16px",
  243 + "fontWeight": 400
  244 + },
  245 + "useDashboardTimewindow": false,
  246 + "showLegend": false,
  247 + "widgetStyle": {},
  248 + "actions": {},
  249 + "showTitleIcon": false,
  250 + "titleIcon": null,
  251 + "iconColor": "rgba(0, 0, 0, 0.87)",
  252 + "iconSize": "24px",
  253 + "titleTooltip": "",
  254 + "displayTimewindow": true
  255 + },
  256 + "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10"
  257 + },
  258 + "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
  259 + "isSystemType": true,
  260 + "bundleAlias": "charts",
  261 + "typeAlias": "basic_timeseries",
  262 + "type": "timeseries",
  263 + "title": "New widget",
  264 + "sizeX": 17,
  265 + "sizeY": 4,
  266 + "config": {
  267 + "datasources": [
  268 + {
  269 + "type": "entity",
  270 + "dataKeys": [
  271 + {
  272 + "name": "opcuaEventsProduced",
  273 + "type": "timeseries",
  274 + "label": "opcuaEventsProduced",
  275 + "color": "#2196f3",
  276 + "settings": {
  277 + "excludeFromStacking": false,
  278 + "hideDataByDefault": false,
  279 + "disableDataHiding": false,
  280 + "removeFromLegend": false,
  281 + "showLines": true,
  282 + "fillLines": false,
  283 + "showPoints": false,
  284 + "showPointShape": "circle",
  285 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  286 + "showPointsLineWidth": 5,
  287 + "showPointsRadius": 3,
  288 + "tooltipValueFormatter": "",
  289 + "showSeparateAxis": false,
  290 + "axisTitle": "",
  291 + "axisPosition": "left",
  292 + "axisTicksFormatter": "",
  293 + "comparisonSettings": {
  294 + "showValuesForComparison": true,
  295 + "comparisonValuesLabel": "",
  296 + "color": ""
  297 + }
  298 + },
  299 + "_hash": 0.1477920581839779
  300 + },
  301 + {
  302 + "name": "opcuaEventsSent",
  303 + "type": "timeseries",
  304 + "label": "opcuaEventsSent",
  305 + "color": "#4caf50",
  306 + "settings": {
  307 + "excludeFromStacking": false,
  308 + "hideDataByDefault": false,
  309 + "disableDataHiding": false,
  310 + "removeFromLegend": false,
  311 + "showLines": true,
  312 + "fillLines": false,
  313 + "showPoints": false,
  314 + "showPointShape": "circle",
  315 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  316 + "showPointsLineWidth": 5,
  317 + "showPointsRadius": 3,
  318 + "tooltipValueFormatter": "",
  319 + "showSeparateAxis": false,
  320 + "axisTitle": "",
  321 + "axisPosition": "left",
  322 + "axisTicksFormatter": "",
  323 + "comparisonSettings": {
  324 + "showValuesForComparison": true,
  325 + "comparisonValuesLabel": "",
  326 + "color": ""
  327 + }
  328 + },
  329 + "_hash": 0.6500957113784758
  330 + }
  331 + ],
  332 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  333 + }
  334 + ],
  335 + "timewindow": {
  336 + "realtime": {
  337 + "interval": 1000,
  338 + "timewindowMs": 120000
  339 + },
  340 + "aggregation": {
  341 + "type": "NONE",
  342 + "limit": 25000
  343 + },
  344 + "hideInterval": false,
  345 + "hideAggregation": false
  346 + },
  347 + "showTitle": true,
  348 + "backgroundColor": "#fff",
  349 + "color": "rgba(0, 0, 0, 0.87)",
  350 + "padding": "8px",
  351 + "settings": {
  352 + "shadowSize": 4,
  353 + "fontColor": "#545454",
  354 + "fontSize": 10,
  355 + "xaxis": {
  356 + "showLabels": true,
  357 + "color": "#545454"
  358 + },
  359 + "yaxis": {
  360 + "showLabels": true,
  361 + "color": "#545454"
  362 + },
  363 + "grid": {
  364 + "color": "#545454",
  365 + "tickColor": "#DDDDDD",
  366 + "verticalLines": true,
  367 + "horizontalLines": true,
  368 + "outlineWidth": 1
  369 + },
  370 + "stack": false,
  371 + "tooltipIndividual": false,
  372 + "timeForComparison": "months",
  373 + "xaxisSecond": {
  374 + "axisPosition": "top",
  375 + "showLabels": true
  376 + }
  377 + },
  378 + "title": "Real time information",
  379 + "dropShadow": true,
  380 + "enableFullscreen": true,
  381 + "titleStyle": {
  382 + "fontSize": "16px",
  383 + "fontWeight": 400
  384 + },
  385 + "mobileHeight": null,
  386 + "showTitleIcon": false,
  387 + "titleIcon": null,
  388 + "iconColor": "rgba(0, 0, 0, 0.87)",
  389 + "iconSize": "24px",
  390 + "titleTooltip": "",
  391 + "widgetStyle": {},
  392 + "useDashboardTimewindow": false,
  393 + "displayTimewindow": true,
  394 + "showLegend": true,
  395 + "legendConfig": {
  396 + "direction": "column",
  397 + "position": "right",
  398 + "showMin": true,
  399 + "showMax": true,
  400 + "showAvg": true,
  401 + "showTotal": true
  402 + },
  403 + "actions": {}
  404 + },
  405 + "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155"
  406 + },
  407 + "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
  408 + "isSystemType": true,
  409 + "bundleAlias": "cards",
  410 + "typeAlias": "timeseries_table",
  411 + "type": "timeseries",
  412 + "title": "New widget",
  413 + "sizeX": 7,
  414 + "sizeY": 7,
  415 + "config": {
  416 + "datasources": [
  417 + {
  418 + "type": "entity",
  419 + "dataKeys": [
  420 + {
  421 + "name": "eventsSent",
  422 + "type": "timeseries",
  423 + "label": "Events",
  424 + "color": "#2196f3",
  425 + "settings": {
  426 + "useCellStyleFunction": false,
  427 + "useCellContentFunction": false
  428 + },
  429 + "_hash": 0.8156044798125357
  430 + },
  431 + {
  432 + "name": "eventsProduced",
  433 + "type": "timeseries",
  434 + "label": "Produced",
  435 + "color": "#4caf50",
  436 + "settings": {
  437 + "useCellStyleFunction": false,
  438 + "useCellContentFunction": false
  439 + },
  440 + "_hash": 0.6538259344015449
  441 + }
  442 + ],
  443 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  444 + }
  445 + ],
  446 + "timewindow": {
  447 + "realtime": {
  448 + "interval": 1000,
  449 + "timewindowMs": 604800000
  450 + },
  451 + "aggregation": {
  452 + "type": "NONE",
  453 + "limit": 200
  454 + }
  455 + },
  456 + "showTitle": true,
  457 + "backgroundColor": "rgb(255, 255, 255)",
  458 + "color": "rgba(0, 0, 0, 0.87)",
  459 + "padding": "8px",
  460 + "settings": {
  461 + "showTimestamp": true,
  462 + "displayPagination": true,
  463 + "defaultPageSize": 6,
  464 + "hideEmptyLines": true
  465 + },
  466 + "title": "Total Messages",
  467 + "dropShadow": true,
  468 + "enableFullscreen": true,
  469 + "titleStyle": {
  470 + "fontSize": "16px",
  471 + "fontWeight": 400
  472 + },
  473 + "useDashboardTimewindow": false,
  474 + "showLegend": false,
  475 + "widgetStyle": {},
  476 + "actions": {},
  477 + "showTitleIcon": false,
  478 + "titleIcon": null,
  479 + "iconColor": "rgba(0, 0, 0, 0.87)",
  480 + "iconSize": "24px",
  481 + "titleTooltip": "",
  482 + "displayTimewindow": true,
  483 + "legendConfig": {
  484 + "direction": "column",
  485 + "position": "bottom",
  486 + "showMin": false,
  487 + "showMax": false,
  488 + "showAvg": true,
  489 + "showTotal": false
  490 + }
  491 + },
  492 + "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a"
  493 + },
  494 + "aaa69366-aacc-9028-65aa-645c0f8533ec": {
  495 + "isSystemType": true,
  496 + "bundleAlias": "charts",
  497 + "typeAlias": "basic_timeseries",
  498 + "type": "timeseries",
  499 + "title": "New widget",
  500 + "sizeX": 17,
  501 + "sizeY": 4,
  502 + "config": {
  503 + "datasources": [
  504 + {
  505 + "type": "entity",
  506 + "dataKeys": [
  507 + {
  508 + "name": "eventsSent",
  509 + "type": "timeseries",
  510 + "label": "eventsSent",
  511 + "color": "#2196f3",
  512 + "settings": {
  513 + "excludeFromStacking": false,
  514 + "hideDataByDefault": false,
  515 + "disableDataHiding": false,
  516 + "removeFromLegend": false,
  517 + "showLines": true,
  518 + "fillLines": false,
  519 + "showPoints": false,
  520 + "showPointShape": "circle",
  521 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  522 + "showPointsLineWidth": 5,
  523 + "showPointsRadius": 3,
  524 + "tooltipValueFormatter": "",
  525 + "showSeparateAxis": false,
  526 + "axisTitle": "",
  527 + "axisPosition": "left",
  528 + "axisTicksFormatter": "",
  529 + "comparisonSettings": {
  530 + "showValuesForComparison": true,
  531 + "comparisonValuesLabel": "",
  532 + "color": ""
  533 + }
  534 + },
  535 + "_hash": 0.41414001784591314
  536 + },
  537 + {
  538 + "name": "eventsProduced",
  539 + "type": "timeseries",
  540 + "label": "eventsProduced",
  541 + "color": "#4caf50",
  542 + "settings": {
  543 + "excludeFromStacking": false,
  544 + "hideDataByDefault": false,
  545 + "disableDataHiding": false,
  546 + "removeFromLegend": false,
  547 + "showLines": true,
  548 + "fillLines": false,
  549 + "showPoints": false,
  550 + "showPointShape": "circle",
  551 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  552 + "showPointsLineWidth": 5,
  553 + "showPointsRadius": 3,
  554 + "tooltipValueFormatter": "",
  555 + "showSeparateAxis": false,
  556 + "axisTitle": "",
  557 + "axisPosition": "left",
  558 + "axisTicksFormatter": "",
  559 + "comparisonSettings": {
  560 + "showValuesForComparison": true,
  561 + "comparisonValuesLabel": "",
  562 + "color": ""
  563 + }
  564 + },
  565 + "_hash": 0.7819101846284422
  566 + }
  567 + ],
  568 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  569 + }
  570 + ],
  571 + "timewindow": {
  572 + "realtime": {
  573 + "timewindowMs": 60000
  574 + }
  575 + },
  576 + "showTitle": true,
  577 + "backgroundColor": "#fff",
  578 + "color": "rgba(0, 0, 0, 0.87)",
  579 + "padding": "8px",
  580 + "settings": {
  581 + "shadowSize": 4,
  582 + "fontColor": "#545454",
  583 + "fontSize": 10,
  584 + "xaxis": {
  585 + "showLabels": true,
  586 + "color": "#545454"
  587 + },
  588 + "yaxis": {
  589 + "showLabels": true,
  590 + "color": "#545454"
  591 + },
  592 + "grid": {
  593 + "color": "#545454",
  594 + "tickColor": "#DDDDDD",
  595 + "verticalLines": true,
  596 + "horizontalLines": true,
  597 + "outlineWidth": 1
  598 + },
  599 + "stack": false,
  600 + "tooltipIndividual": false,
  601 + "timeForComparison": "months",
  602 + "xaxisSecond": {
  603 + "axisPosition": "top",
  604 + "showLabels": true
  605 + }
  606 + },
  607 + "title": "History information",
  608 + "dropShadow": true,
  609 + "enableFullscreen": true,
  610 + "titleStyle": {
  611 + "fontSize": "16px",
  612 + "fontWeight": 400
  613 + },
  614 + "mobileHeight": null,
  615 + "showTitleIcon": false,
  616 + "titleIcon": null,
  617 + "iconColor": "rgba(0, 0, 0, 0.87)",
  618 + "iconSize": "24px",
  619 + "titleTooltip": "",
  620 + "widgetStyle": {},
  621 + "useDashboardTimewindow": true,
  622 + "displayTimewindow": true,
  623 + "showLegend": true,
  624 + "legendConfig": {
  625 + "direction": "column",
  626 + "position": "right",
  627 + "showMin": true,
  628 + "showMax": true,
  629 + "showAvg": true,
  630 + "showTotal": true
  631 + },
  632 + "actions": {}
  633 + },
  634 + "id": "aaa69366-aacc-9028-65aa-645c0f8533ec"
  635 + },
  636 + "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
  637 + "isSystemType": true,
  638 + "bundleAlias": "charts",
  639 + "typeAlias": "basic_timeseries",
  640 + "type": "timeseries",
  641 + "title": "New widget",
  642 + "sizeX": 17,
  643 + "sizeY": 4,
  644 + "config": {
  645 + "datasources": [
  646 + {
  647 + "type": "entity",
  648 + "dataKeys": [
  649 + {
  650 + "name": "bleEventsProduced",
  651 + "type": "timeseries",
  652 + "label": "bleEventsProduced",
  653 + "color": "#2196f3",
  654 + "settings": {
  655 + "excludeFromStacking": false,
  656 + "hideDataByDefault": false,
  657 + "disableDataHiding": false,
  658 + "removeFromLegend": false,
  659 + "showLines": true,
  660 + "fillLines": false,
  661 + "showPoints": false,
  662 + "showPointShape": "circle",
  663 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  664 + "showPointsLineWidth": 5,
  665 + "showPointsRadius": 3,
  666 + "tooltipValueFormatter": "",
  667 + "showSeparateAxis": false,
  668 + "axisTitle": "",
  669 + "axisPosition": "left",
  670 + "axisTicksFormatter": "",
  671 + "comparisonSettings": {
  672 + "showValuesForComparison": true,
  673 + "comparisonValuesLabel": "",
  674 + "color": ""
  675 + }
  676 + },
  677 + "_hash": 0.5625165504526104
  678 + },
  679 + {
  680 + "name": "bleEventsSent",
  681 + "type": "timeseries",
  682 + "label": "bleEventsSent",
  683 + "color": "#4caf50",
  684 + "settings": {
  685 + "excludeFromStacking": false,
  686 + "hideDataByDefault": false,
  687 + "disableDataHiding": false,
  688 + "removeFromLegend": false,
  689 + "showLines": true,
  690 + "fillLines": false,
  691 + "showPoints": false,
  692 + "showPointShape": "circle",
  693 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  694 + "showPointsLineWidth": 5,
  695 + "showPointsRadius": 3,
  696 + "tooltipValueFormatter": "",
  697 + "showSeparateAxis": false,
  698 + "axisTitle": "",
  699 + "axisPosition": "left",
  700 + "axisTicksFormatter": "",
  701 + "comparisonSettings": {
  702 + "showValuesForComparison": true,
  703 + "comparisonValuesLabel": "",
  704 + "color": ""
  705 + }
  706 + },
  707 + "_hash": 0.6817950080745288
  708 + }
  709 + ],
  710 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  711 + }
  712 + ],
  713 + "timewindow": {
  714 + "realtime": {
  715 + "interval": 5000,
  716 + "timewindowMs": 120000
  717 + },
  718 + "aggregation": {
  719 + "type": "AVG",
  720 + "limit": 25000
  721 + }
  722 + },
  723 + "showTitle": true,
  724 + "backgroundColor": "#fff",
  725 + "color": "rgba(0, 0, 0, 0.87)",
  726 + "padding": "8px",
  727 + "settings": {
  728 + "shadowSize": 4,
  729 + "fontColor": "#545454",
  730 + "fontSize": 10,
  731 + "xaxis": {
  732 + "showLabels": true,
  733 + "color": "#545454"
  734 + },
  735 + "yaxis": {
  736 + "showLabels": true,
  737 + "color": "#545454"
  738 + },
  739 + "grid": {
  740 + "color": "#545454",
  741 + "tickColor": "#DDDDDD",
  742 + "verticalLines": true,
  743 + "horizontalLines": true,
  744 + "outlineWidth": 1
  745 + },
  746 + "stack": false,
  747 + "tooltipIndividual": false,
  748 + "timeForComparison": "months",
  749 + "xaxisSecond": {
  750 + "axisPosition": "top",
  751 + "showLabels": true
  752 + }
  753 + },
  754 + "title": "Real time information",
  755 + "dropShadow": true,
  756 + "enableFullscreen": true,
  757 + "titleStyle": {
  758 + "fontSize": "16px",
  759 + "fontWeight": 400
  760 + },
  761 + "mobileHeight": null,
  762 + "showTitleIcon": false,
  763 + "titleIcon": null,
  764 + "iconColor": "rgba(0, 0, 0, 0.87)",
  765 + "iconSize": "24px",
  766 + "titleTooltip": "",
  767 + "widgetStyle": {},
  768 + "useDashboardTimewindow": false,
  769 + "displayTimewindow": true,
  770 + "showLegend": true,
  771 + "legendConfig": {
  772 + "direction": "column",
  773 + "position": "right",
  774 + "showMin": true,
  775 + "showMax": true,
  776 + "showAvg": true,
  777 + "showTotal": true
  778 + },
  779 + "actions": {}
  780 + },
  781 + "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0"
  782 + },
  783 + "466f046d-6005-a168-b107-60fcb2469cd5": {
  784 + "isSystemType": true,
  785 + "bundleAlias": "gateway_widgets",
  786 + "typeAlias": "attributes_card",
  787 + "type": "latest",
  788 + "title": "New widget",
  789 + "sizeX": 7,
  790 + "sizeY": 5,
  791 + "config": {
  792 + "datasources": [
  793 + {
  794 + "type": "entity",
  795 + "dataKeys": [],
  796 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  797 + }
  798 + ],
  799 + "timewindow": {
  800 + "realtime": {
  801 + "timewindowMs": 60000
  802 + }
  803 + },
  804 + "showTitle": true,
  805 + "backgroundColor": "#fff",
  806 + "color": "rgba(0, 0, 0, 0.87)",
  807 + "padding": "8px",
  808 + "settings": {
  809 + "eventsTitle": "Gateway Events Form",
  810 + "eventsReg": [
  811 + "EventsProduced",
  812 + "EventsSent"
  813 + ]
  814 + },
  815 + "title": "Gateway events",
  816 + "showTitleIcon": false,
  817 + "titleIcon": null,
  818 + "iconColor": "rgba(0, 0, 0, 0.87)",
  819 + "iconSize": "24px",
  820 + "titleTooltip": "",
  821 + "dropShadow": true,
  822 + "enableFullscreen": true,
  823 + "widgetStyle": {},
  824 + "titleStyle": {
  825 + "fontSize": "16px",
  826 + "fontWeight": 400
  827 + },
  828 + "useDashboardTimewindow": true,
  829 + "displayTimewindow": true,
  830 + "showLegend": false,
  831 + "actions": {}
  832 + },
  833 + "id": "466f046d-6005-a168-b107-60fcb2469cd5"
  834 + },
  835 + "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
  836 + "isSystemType": true,
  837 + "bundleAlias": "gateway_widgets",
  838 + "typeAlias": "config_form_latest",
  839 + "type": "latest",
  840 + "title": "New widget",
  841 + "sizeX": 10,
  842 + "sizeY": 9,
  843 + "config": {
  844 + "datasources": [
  845 + {
  846 + "type": "entity",
  847 + "dataKeys": [],
  848 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  849 + }
  850 + ],
  851 + "timewindow": {
  852 + "realtime": {
  853 + "timewindowMs": 60000
  854 + }
  855 + },
  856 + "showTitle": true,
  857 + "backgroundColor": "#fff",
  858 + "color": "rgba(0, 0, 0, 0.87)",
  859 + "padding": "8px",
  860 + "settings": {
  861 + "gatewayTitle": "Gateway configuration (Single device)",
  862 + "readOnly": false
  863 + },
  864 + "title": "New Gateway configuration (Single device)",
  865 + "showTitleIcon": false,
  866 + "titleIcon": null,
  867 + "iconColor": "rgba(0, 0, 0, 0.87)",
  868 + "iconSize": "24px",
  869 + "titleTooltip": "",
  870 + "dropShadow": true,
  871 + "enableFullscreen": true,
  872 + "widgetStyle": {},
  873 + "titleStyle": {
  874 + "fontSize": "16px",
  875 + "fontWeight": 400
  876 + },
  877 + "useDashboardTimewindow": true,
  878 + "displayTimewindow": true,
  879 + "showLegend": false,
  880 + "actions": {}
  881 + },
  882 + "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b"
  883 + },
  884 + "063fc179-c9fd-f952-e714-f24e9c43c05c": {
  885 + "isSystemType": true,
  886 + "bundleAlias": "control_widgets",
  887 + "typeAlias": "rpcbutton",
  888 + "type": "rpc",
  889 + "title": "New widget",
  890 + "sizeX": 4,
  891 + "sizeY": 2,
  892 + "config": {
  893 + "targetDeviceAliases": [],
  894 + "showTitle": false,
  895 + "backgroundColor": "#e6e7e8",
  896 + "color": "rgba(0, 0, 0, 0.87)",
  897 + "padding": "0px",
  898 + "settings": {
  899 + "requestTimeout": 5000,
  900 + "oneWayElseTwoWay": true,
  901 + "styleButton": {
  902 + "isRaised": true,
  903 + "isPrimary": false
  904 + },
  905 + "methodParams": "{}",
  906 + "methodName": "gateway_reboot",
  907 + "buttonText": "GATEWAY REBOOT"
  908 + },
  909 + "title": "New RPC Button",
  910 + "dropShadow": true,
  911 + "enableFullscreen": false,
  912 + "widgetStyle": {},
  913 + "titleStyle": {
  914 + "fontSize": "16px",
  915 + "fontWeight": 400
  916 + },
  917 + "useDashboardTimewindow": true,
  918 + "showLegend": false,
  919 + "actions": {},
  920 + "datasources": [],
  921 + "showTitleIcon": false,
  922 + "titleIcon": null,
  923 + "iconColor": "rgba(0, 0, 0, 0.87)",
  924 + "iconSize": "24px",
  925 + "titleTooltip": "",
  926 + "displayTimewindow": true,
  927 + "targetDeviceAliasIds": [
  928 + "b2487e75-2fa4-f211-142c-434dfd50c70c"
  929 + ]
  930 + },
  931 + "id": "063fc179-c9fd-f952-e714-f24e9c43c05c"
  932 + },
  933 + "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
  934 + "isSystemType": true,
  935 + "bundleAlias": "control_widgets",
  936 + "typeAlias": "rpcbutton",
  937 + "type": "rpc",
  938 + "title": "New widget",
  939 + "sizeX": 4,
  940 + "sizeY": 2,
  941 + "config": {
  942 + "targetDeviceAliases": [],
  943 + "showTitle": false,
  944 + "backgroundColor": "#e6e7e8",
  945 + "color": "rgba(0, 0, 0, 0.87)",
  946 + "padding": "0px",
  947 + "settings": {
  948 + "requestTimeout": 5000,
  949 + "oneWayElseTwoWay": true,
  950 + "styleButton": {
  951 + "isRaised": true,
  952 + "isPrimary": false
  953 + },
  954 + "methodName": "gateway_restart",
  955 + "methodParams": "{}",
  956 + "buttonText": "gateway restart"
  957 + },
  958 + "title": "New RPC Button",
  959 + "dropShadow": true,
  960 + "enableFullscreen": false,
  961 + "widgetStyle": {},
  962 + "titleStyle": {
  963 + "fontSize": "16px",
  964 + "fontWeight": 400
  965 + },
  966 + "useDashboardTimewindow": true,
  967 + "showLegend": false,
  968 + "actions": {},
  969 + "datasources": [],
  970 + "showTitleIcon": false,
  971 + "titleIcon": null,
  972 + "iconColor": "rgba(0, 0, 0, 0.87)",
  973 + "iconSize": "24px",
  974 + "titleTooltip": "",
  975 + "displayTimewindow": true,
  976 + "targetDeviceAliasIds": [
  977 + "b2487e75-2fa4-f211-142c-434dfd50c70c"
  978 + ]
  979 + },
  980 + "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7"
  981 + },
  982 + "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
  983 + "isSystemType": true,
  984 + "bundleAlias": "input_widgets",
  985 + "typeAlias": "markers_placement_openstreetmap",
  986 + "type": "latest",
  987 + "title": "New widget",
  988 + "sizeX": 6,
  989 + "sizeY": 4,
  990 + "config": {
  991 + "datasources": [
  992 + {
  993 + "type": "entity",
  994 + "dataKeys": [
  995 + {
  996 + "name": "latitude",
  997 + "type": "attribute",
  998 + "label": "latitude",
  999 + "color": "#2196f3",
  1000 + "settings": {},
  1001 + "_hash": 0.9743324774725604
  1002 + },
  1003 + {
  1004 + "name": "longitude",
  1005 + "type": "attribute",
  1006 + "label": "longitude",
  1007 + "color": "#4caf50",
  1008 + "settings": {},
  1009 + "_hash": 0.5530093635101525
  1010 + }
  1011 + ],
  1012 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  1013 + }
  1014 + ],
  1015 + "timewindow": {
  1016 + "realtime": {
  1017 + "timewindowMs": 60000
  1018 + }
  1019 + },
  1020 + "showTitle": false,
  1021 + "backgroundColor": "#fff",
  1022 + "color": "rgba(0, 0, 0, 0.87)",
  1023 + "padding": "8px",
  1024 + "settings": {
  1025 + "fitMapBounds": true,
  1026 + "latKeyName": "latitude",
  1027 + "lngKeyName": "longitude",
  1028 + "showLabel": true,
  1029 + "label": "${entityName}",
  1030 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><br/><link-act name='delete'>Delete</link-act>",
  1031 + "markerImageSize": 34,
  1032 + "useColorFunction": false,
  1033 + "markerImages": [],
  1034 + "useMarkerImageFunction": false,
  1035 + "color": "#fe7569",
  1036 + "mapProvider": "OpenStreetMap.Mapnik",
  1037 + "showTooltip": true,
  1038 + "autocloseTooltip": true,
  1039 + "defaultCenterPosition": [
  1040 + 0,
  1041 + 0
  1042 + ],
  1043 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  1044 + "showTooltipAction": "click",
  1045 + "polygonKeyName": "coordinates",
  1046 + "polygonOpacity": 0.5,
  1047 + "polygonStrokeOpacity": 1,
  1048 + "polygonStrokeWeight": 1,
  1049 + "zoomOnClick": true,
  1050 + "showCoverageOnHover": true,
  1051 + "animate": true,
  1052 + "maxClusterRadius": 80,
  1053 + "removeOutsideVisibleBounds": true,
  1054 + "defaultZoomLevel": 5
  1055 + },
  1056 + "title": "Gateway Location",
  1057 + "dropShadow": true,
  1058 + "enableFullscreen": false,
  1059 + "titleStyle": {
  1060 + "fontSize": "16px",
  1061 + "fontWeight": 400
  1062 + },
  1063 + "useDashboardTimewindow": true,
  1064 + "showLegend": false,
  1065 + "widgetStyle": {},
  1066 + "actions": {
  1067 + "tooltipAction": [
  1068 + {
  1069 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  1070 + "name": "delete",
  1071 + "icon": "more_horiz",
  1072 + "type": "custom",
  1073 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  1074 + }
  1075 + ]
  1076 + },
  1077 + "showTitleIcon": false,
  1078 + "titleIcon": null,
  1079 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1080 + "iconSize": "24px",
  1081 + "titleTooltip": "",
  1082 + "displayTimewindow": true
  1083 + },
  1084 + "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842"
  1085 + }
  1086 + },
  1087 + "states": {
  1088 + "main_gateway": {
  1089 + "name": "Gateways",
  1090 + "root": true,
  1091 + "layouts": {
  1092 + "main": {
  1093 + "widgets": {
  1094 + "94715984-ae74-76e4-20b7-2f956b01ed80": {
  1095 + "sizeX": 24,
  1096 + "sizeY": 12,
  1097 + "row": 0,
  1098 + "col": 0
  1099 + }
  1100 + },
  1101 + "gridSettings": {
  1102 + "backgroundColor": "#eeeeee",
  1103 + "color": "rgba(0,0,0,0.870588)",
  1104 + "columns": 24,
  1105 + "margins": [
  1106 + 10,
  1107 + 10
  1108 + ],
  1109 + "backgroundSizeMode": "100%",
  1110 + "autoFillHeight": true,
  1111 + "mobileAutoFillHeight": false,
  1112 + "mobileRowHeight": 70
  1113 + }
  1114 + }
  1115 + }
  1116 + },
  1117 + "__entityname__config": {
  1118 + "name": "${entityName} Configuration",
  1119 + "root": false,
  1120 + "layouts": {
  1121 + "main": {
  1122 + "widgets": {
  1123 + "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
  1124 + "sizeX": 14,
  1125 + "sizeY": 13,
  1126 + "row": 0,
  1127 + "col": 10
  1128 + },
  1129 + "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
  1130 + "sizeX": 10,
  1131 + "sizeY": 9,
  1132 + "row": 0,
  1133 + "col": 0
  1134 + },
  1135 + "063fc179-c9fd-f952-e714-f24e9c43c05c": {
  1136 + "sizeX": 4,
  1137 + "sizeY": 2,
  1138 + "row": 9,
  1139 + "col": 0
  1140 + },
  1141 + "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
  1142 + "sizeX": 4,
  1143 + "sizeY": 2,
  1144 + "row": 11,
  1145 + "col": 0
  1146 + },
  1147 + "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
  1148 + "sizeX": 6,
  1149 + "sizeY": 4,
  1150 + "row": 9,
  1151 + "col": 4
  1152 + }
  1153 + },
  1154 + "gridSettings": {
  1155 + "backgroundColor": "#eeeeee",
  1156 + "color": "rgba(0,0,0,0.870588)",
  1157 + "columns": 24,
  1158 + "margins": [
  1159 + 10,
  1160 + 10
  1161 + ],
  1162 + "backgroundSizeMode": "100%",
  1163 + "autoFillHeight": true,
  1164 + "mobileAutoFillHeight": false,
  1165 + "mobileRowHeight": 70
  1166 + }
  1167 + }
  1168 + }
  1169 + },
  1170 + "__entityname_grafic": {
  1171 + "name": "${entityName} Details",
  1172 + "root": false,
  1173 + "layouts": {
  1174 + "main": {
  1175 + "widgets": {
  1176 + "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
  1177 + "sizeX": 17,
  1178 + "sizeY": 4,
  1179 + "mobileHeight": null,
  1180 + "row": 4,
  1181 + "col": 7
  1182 + },
  1183 + "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
  1184 + "sizeX": 7,
  1185 + "sizeY": 7,
  1186 + "row": 5,
  1187 + "col": 0
  1188 + },
  1189 + "aaa69366-aacc-9028-65aa-645c0f8533ec": {
  1190 + "sizeX": 17,
  1191 + "sizeY": 4,
  1192 + "mobileHeight": null,
  1193 + "row": 0,
  1194 + "col": 7
  1195 + },
  1196 + "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
  1197 + "sizeX": 17,
  1198 + "sizeY": 4,
  1199 + "mobileHeight": null,
  1200 + "row": 8,
  1201 + "col": 7
  1202 + },
  1203 + "466f046d-6005-a168-b107-60fcb2469cd5": {
  1204 + "sizeX": 7,
  1205 + "sizeY": 5,
  1206 + "row": 0,
  1207 + "col": 0
  1208 + }
  1209 + },
  1210 + "gridSettings": {
  1211 + "backgroundColor": "#eeeeee",
  1212 + "color": "rgba(0,0,0,0.870588)",
  1213 + "columns": 24,
  1214 + "margins": [
  1215 + 10,
  1216 + 10
  1217 + ],
  1218 + "backgroundSizeMode": "auto 100%",
  1219 + "autoFillHeight": true,
  1220 + "mobileAutoFillHeight": true,
  1221 + "mobileRowHeight": 70
  1222 + }
  1223 + }
  1224 + }
  1225 + }
  1226 + },
  1227 + "entityAliases": {
  1228 + "3e0f533a-0db1-3292-184f-06e73535061a": {
  1229 + "id": "3e0f533a-0db1-3292-184f-06e73535061a",
  1230 + "alias": "Gateways",
  1231 + "filter": {
  1232 + "type": "deviceType",
  1233 + "resolveMultiple": true,
  1234 + "deviceType": "gateway",
  1235 + "deviceNameFilter": ""
  1236 + }
  1237 + },
  1238 + "b2487e75-2fa4-f211-142c-434dfd50c70c": {
  1239 + "id": "b2487e75-2fa4-f211-142c-434dfd50c70c",
  1240 + "alias": "Current Gateway",
  1241 + "filter": {
  1242 + "type": "stateEntity",
  1243 + "resolveMultiple": false,
  1244 + "stateEntityParamName": "",
  1245 + "defaultStateEntity": null
  1246 + }
  1247 + }
  1248 + },
  1249 + "timewindow": {
  1250 + "realtime": {
  1251 + "interval": 1000,
  1252 + "timewindowMs": 86400000
  1253 + },
  1254 + "aggregation": {
  1255 + "type": "NONE",
  1256 + "limit": 25000
  1257 + },
  1258 + "hideInterval": false,
  1259 + "hideAggregation": false,
  1260 + "hideAggInterval": false
  1261 + },
  1262 + "settings": {
  1263 + "stateControllerId": "entity",
  1264 + "showTitle": true,
  1265 + "showDashboardsSelect": true,
  1266 + "showEntitiesSelect": true,
  1267 + "showDashboardTimewindow": true,
  1268 + "showDashboardExport": true,
  1269 + "toolbarAlwaysOpen": true,
  1270 + "titleColor": "rgba(0,0,0,0.870588)"
  1271 + }
  1272 + },
  1273 + "name": "Gateways"
  1274 +}
  1 +{
  2 + "title": "Rule Engine Statistics",
  3 + "configuration": {
  4 + "widgets": {
  5 + "81987f19-3eac-e4ce-b790-d96e9b54d9a0": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "charts",
  8 + "typeAlias": "basic_timeseries",
  9 + "type": "timeseries",
  10 + "title": "New widget",
  11 + "sizeX": 12,
  12 + "sizeY": 7,
  13 + "config": {
  14 + "datasources": [
  15 + {
  16 + "type": "entity",
  17 + "dataKeys": [
  18 + {
  19 + "name": "successfulMsgs",
  20 + "type": "timeseries",
  21 + "label": "${entityName} Successful",
  22 + "color": "#4caf50",
  23 + "settings": {
  24 + "excludeFromStacking": false,
  25 + "hideDataByDefault": false,
  26 + "disableDataHiding": false,
  27 + "removeFromLegend": false,
  28 + "showLines": true,
  29 + "fillLines": false,
  30 + "showPoints": false,
  31 + "showPointShape": "circle",
  32 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  33 + "showPointsLineWidth": 5,
  34 + "showPointsRadius": 3,
  35 + "showSeparateAxis": false,
  36 + "axisPosition": "left",
  37 + "thresholds": [
  38 + {
  39 + "thresholdValueSource": "predefinedValue"
  40 + }
  41 + ],
  42 + "comparisonSettings": {
  43 + "showValuesForComparison": true
  44 + }
  45 + },
  46 + "_hash": 0.15490750967648736
  47 + },
  48 + {
  49 + "name": "failedMsgs",
  50 + "type": "timeseries",
  51 + "label": "${entityName} Permanent Failures",
  52 + "color": "#ef5350",
  53 + "settings": {
  54 + "excludeFromStacking": false,
  55 + "hideDataByDefault": false,
  56 + "disableDataHiding": false,
  57 + "removeFromLegend": false,
  58 + "showLines": true,
  59 + "fillLines": false,
  60 + "showPoints": false,
  61 + "showPointShape": "circle",
  62 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  63 + "showPointsLineWidth": 5,
  64 + "showPointsRadius": 3,
  65 + "showSeparateAxis": false,
  66 + "axisPosition": "left",
  67 + "thresholds": [
  68 + {
  69 + "thresholdValueSource": "predefinedValue"
  70 + }
  71 + ],
  72 + "comparisonSettings": {
  73 + "showValuesForComparison": true
  74 + }
  75 + },
  76 + "_hash": 0.4186621166514697
  77 + },
  78 + {
  79 + "name": "tmpFailed",
  80 + "type": "timeseries",
  81 + "label": "${entityName} Processing Failures",
  82 + "color": "#ffc107",
  83 + "settings": {
  84 + "excludeFromStacking": false,
  85 + "hideDataByDefault": false,
  86 + "disableDataHiding": false,
  87 + "removeFromLegend": false,
  88 + "showLines": true,
  89 + "fillLines": false,
  90 + "showPoints": false,
  91 + "showPointShape": "circle",
  92 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  93 + "showPointsLineWidth": 5,
  94 + "showPointsRadius": 3,
  95 + "showSeparateAxis": false,
  96 + "axisPosition": "left",
  97 + "thresholds": [
  98 + {
  99 + "thresholdValueSource": "predefinedValue"
  100 + }
  101 + ],
  102 + "comparisonSettings": {
  103 + "showValuesForComparison": true
  104 + }
  105 + },
  106 + "_hash": 0.49891007198715376
  107 + }
  108 + ],
  109 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  110 + }
  111 + ],
  112 + "timewindow": {
  113 + "realtime": {
  114 + "interval": 1000,
  115 + "timewindowMs": 300000
  116 + },
  117 + "aggregation": {
  118 + "type": "NONE",
  119 + "limit": 8640
  120 + },
  121 + "hideInterval": false,
  122 + "hideAggregation": false,
  123 + "hideAggInterval": false
  124 + },
  125 + "showTitle": true,
  126 + "backgroundColor": "#fff",
  127 + "color": "rgba(0, 0, 0, 0.87)",
  128 + "padding": "8px",
  129 + "settings": {
  130 + "shadowSize": 4,
  131 + "fontColor": "#545454",
  132 + "fontSize": 10,
  133 + "xaxis": {
  134 + "showLabels": true,
  135 + "color": "#545454"
  136 + },
  137 + "yaxis": {
  138 + "showLabels": true,
  139 + "color": "#545454"
  140 + },
  141 + "grid": {
  142 + "color": "#545454",
  143 + "tickColor": "#DDDDDD",
  144 + "verticalLines": true,
  145 + "horizontalLines": true,
  146 + "outlineWidth": 1
  147 + },
  148 + "stack": false,
  149 + "tooltipIndividual": false,
  150 + "timeForComparison": "months",
  151 + "xaxisSecond": {
  152 + "axisPosition": "top",
  153 + "showLabels": true
  154 + }
  155 + },
  156 + "title": "Queue Stats",
  157 + "dropShadow": true,
  158 + "enableFullscreen": true,
  159 + "titleStyle": {
  160 + "fontSize": "16px",
  161 + "fontWeight": 400
  162 + },
  163 + "mobileHeight": null,
  164 + "showTitleIcon": false,
  165 + "titleIcon": null,
  166 + "iconColor": "rgba(0, 0, 0, 0.87)",
  167 + "iconSize": "24px",
  168 + "titleTooltip": "",
  169 + "widgetStyle": {},
  170 + "useDashboardTimewindow": false,
  171 + "displayTimewindow": true,
  172 + "showLegend": true,
  173 + "actions": {},
  174 + "legendConfig": {
  175 + "direction": "column",
  176 + "position": "bottom",
  177 + "showMin": true,
  178 + "showMax": true,
  179 + "showAvg": false,
  180 + "showTotal": true
  181 + }
  182 + },
  183 + "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0"
  184 + },
  185 + "5eb79712-5c24-3060-7e4f-6af36b8f842d": {
  186 + "isSystemType": true,
  187 + "bundleAlias": "cards",
  188 + "typeAlias": "timeseries_table",
  189 + "type": "timeseries",
  190 + "title": "New widget",
  191 + "sizeX": 24,
  192 + "sizeY": 5,
  193 + "config": {
  194 + "datasources": [
  195 + {
  196 + "type": "entity",
  197 + "dataKeys": [
  198 + {
  199 + "name": "ruleEngineException",
  200 + "type": "timeseries",
  201 + "label": "Rule Chain",
  202 + "color": "#2196f3",
  203 + "settings": {
  204 + "useCellStyleFunction": false,
  205 + "useCellContentFunction": true,
  206 + "cellContentFunction": "return JSON.parse(value).ruleChainName;"
  207 + },
  208 + "_hash": 0.9954481282345906
  209 + },
  210 + {
  211 + "name": "ruleEngineException",
  212 + "type": "timeseries",
  213 + "label": "Rule Node",
  214 + "color": "#4caf50",
  215 + "settings": {
  216 + "useCellStyleFunction": false,
  217 + "useCellContentFunction": true,
  218 + "cellContentFunction": "return JSON.parse(value).ruleNodeName;"
  219 + },
  220 + "_hash": 0.18580357036589978
  221 + },
  222 + {
  223 + "name": "ruleEngineException",
  224 + "type": "timeseries",
  225 + "label": "Latest Error",
  226 + "color": "#f44336",
  227 + "settings": {
  228 + "useCellStyleFunction": false,
  229 + "useCellContentFunction": true,
  230 + "cellContentFunction": "return JSON.parse(value).message;"
  231 + },
  232 + "_hash": 0.7255162989552142
  233 + }
  234 + ],
  235 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  236 + }
  237 + ],
  238 + "timewindow": {
  239 + "realtime": {
  240 + "interval": 1000,
  241 + "timewindowMs": 86400000
  242 + },
  243 + "aggregation": {
  244 + "type": "NONE",
  245 + "limit": 200
  246 + }
  247 + },
  248 + "showTitle": true,
  249 + "backgroundColor": "rgb(255, 255, 255)",
  250 + "color": "rgba(0, 0, 0, 0.87)",
  251 + "padding": "8px",
  252 + "settings": {
  253 + "showTimestamp": true,
  254 + "displayPagination": true,
  255 + "defaultPageSize": 10
  256 + },
  257 + "title": "Exceptions",
  258 + "dropShadow": true,
  259 + "enableFullscreen": true,
  260 + "titleStyle": {
  261 + "fontSize": "16px",
  262 + "fontWeight": 400
  263 + },
  264 + "useDashboardTimewindow": false,
  265 + "showLegend": false,
  266 + "widgetStyle": {},
  267 + "actions": {},
  268 + "showTitleIcon": false,
  269 + "titleIcon": null,
  270 + "iconColor": "rgba(0, 0, 0, 0.87)",
  271 + "iconSize": "24px",
  272 + "titleTooltip": "",
  273 + "displayTimewindow": true
  274 + },
  275 + "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d"
  276 + },
  277 + "ad3f1417-87a8-750e-fc67-49a2de1466d4": {
  278 + "isSystemType": true,
  279 + "bundleAlias": "charts",
  280 + "typeAlias": "basic_timeseries",
  281 + "type": "timeseries",
  282 + "title": "New widget",
  283 + "sizeX": 12,
  284 + "sizeY": 7,
  285 + "config": {
  286 + "datasources": [
  287 + {
  288 + "type": "entity",
  289 + "dataKeys": [
  290 + {
  291 + "name": "timeoutMsgs",
  292 + "type": "timeseries",
  293 + "label": "${entityName} Permanent Timeouts",
  294 + "color": "#4caf50",
  295 + "settings": {
  296 + "excludeFromStacking": false,
  297 + "hideDataByDefault": false,
  298 + "disableDataHiding": false,
  299 + "removeFromLegend": false,
  300 + "showLines": true,
  301 + "fillLines": false,
  302 + "showPoints": false,
  303 + "showPointShape": "circle",
  304 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  305 + "showPointsLineWidth": 5,
  306 + "showPointsRadius": 3,
  307 + "showSeparateAxis": false,
  308 + "axisPosition": "left",
  309 + "thresholds": [
  310 + {
  311 + "thresholdValueSource": "predefinedValue"
  312 + }
  313 + ],
  314 + "comparisonSettings": {
  315 + "showValuesForComparison": true
  316 + }
  317 + },
  318 + "_hash": 0.565222981550328
  319 + },
  320 + {
  321 + "name": "tmpTimeout",
  322 + "type": "timeseries",
  323 + "label": "${entityName} Processing Timeouts",
  324 + "color": "#9c27b0",
  325 + "settings": {
  326 + "excludeFromStacking": false,
  327 + "hideDataByDefault": false,
  328 + "disableDataHiding": false,
  329 + "removeFromLegend": false,
  330 + "showLines": true,
  331 + "fillLines": false,
  332 + "showPoints": false,
  333 + "showPointShape": "circle",
  334 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  335 + "showPointsLineWidth": 5,
  336 + "showPointsRadius": 3,
  337 + "showSeparateAxis": false,
  338 + "axisPosition": "left",
  339 + "thresholds": [
  340 + {
  341 + "thresholdValueSource": "predefinedValue"
  342 + }
  343 + ],
  344 + "comparisonSettings": {
  345 + "showValuesForComparison": true
  346 + }
  347 + },
  348 + "_hash": 0.2679547062508352
  349 + }
  350 + ],
  351 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  352 + }
  353 + ],
  354 + "timewindow": {
  355 + "realtime": {
  356 + "interval": 1000,
  357 + "timewindowMs": 300000
  358 + },
  359 + "aggregation": {
  360 + "type": "NONE",
  361 + "limit": 8640
  362 + },
  363 + "hideInterval": false,
  364 + "hideAggregation": false,
  365 + "hideAggInterval": false
  366 + },
  367 + "showTitle": true,
  368 + "backgroundColor": "#fff",
  369 + "color": "rgba(0, 0, 0, 0.87)",
  370 + "padding": "8px",
  371 + "settings": {
  372 + "shadowSize": 4,
  373 + "fontColor": "#545454",
  374 + "fontSize": 10,
  375 + "xaxis": {
  376 + "showLabels": true,
  377 + "color": "#545454"
  378 + },
  379 + "yaxis": {
  380 + "showLabels": true,
  381 + "color": "#545454"
  382 + },
  383 + "grid": {
  384 + "color": "#545454",
  385 + "tickColor": "#DDDDDD",
  386 + "verticalLines": true,
  387 + "horizontalLines": true,
  388 + "outlineWidth": 1
  389 + },
  390 + "stack": false,
  391 + "tooltipIndividual": false,
  392 + "timeForComparison": "months",
  393 + "xaxisSecond": {
  394 + "axisPosition": "top",
  395 + "showLabels": true
  396 + }
  397 + },
  398 + "title": "Processing Failures and Timeouts",
  399 + "dropShadow": true,
  400 + "enableFullscreen": true,
  401 + "titleStyle": {
  402 + "fontSize": "16px",
  403 + "fontWeight": 400
  404 + },
  405 + "mobileHeight": null,
  406 + "showTitleIcon": false,
  407 + "titleIcon": null,
  408 + "iconColor": "rgba(0, 0, 0, 0.87)",
  409 + "iconSize": "24px",
  410 + "titleTooltip": "",
  411 + "widgetStyle": {},
  412 + "useDashboardTimewindow": false,
  413 + "displayTimewindow": true,
  414 + "showLegend": true,
  415 + "actions": {},
  416 + "legendConfig": {
  417 + "direction": "column",
  418 + "position": "bottom",
  419 + "showMin": true,
  420 + "showMax": true,
  421 + "showAvg": false,
  422 + "showTotal": true
  423 + }
  424 + },
  425 + "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4"
  426 + }
  427 + },
  428 + "states": {
  429 + "default": {
  430 + "name": "Rule Engine Statistics",
  431 + "root": true,
  432 + "layouts": {
  433 + "main": {
  434 + "widgets": {
  435 + "81987f19-3eac-e4ce-b790-d96e9b54d9a0": {
  436 + "sizeX": 12,
  437 + "sizeY": 7,
  438 + "mobileHeight": null,
  439 + "row": 0,
  440 + "col": 0
  441 + },
  442 + "5eb79712-5c24-3060-7e4f-6af36b8f842d": {
  443 + "sizeX": 24,
  444 + "sizeY": 5,
  445 + "row": 7,
  446 + "col": 0
  447 + },
  448 + "ad3f1417-87a8-750e-fc67-49a2de1466d4": {
  449 + "sizeX": 12,
  450 + "sizeY": 7,
  451 + "mobileHeight": null,
  452 + "row": 0,
  453 + "col": 12
  454 + }
  455 + },
  456 + "gridSettings": {
  457 + "backgroundColor": "#eeeeee",
  458 + "color": "rgba(0,0,0,0.870588)",
  459 + "columns": 24,
  460 + "margins": [
  461 + 10,
  462 + 10
  463 + ],
  464 + "backgroundSizeMode": "100%",
  465 + "autoFillHeight": true,
  466 + "mobileAutoFillHeight": false,
  467 + "mobileRowHeight": 70
  468 + }
  469 + }
  470 + }
  471 + }
  472 + },
  473 + "entityAliases": {
  474 + "140f23dd-e3a0-ed98-6189-03c49d2d8018": {
  475 + "id": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
  476 + "alias": "TbServiceQueues",
  477 + "filter": {
  478 + "type": "assetType",
  479 + "resolveMultiple": true,
  480 + "assetType": "TbServiceQueue",
  481 + "assetNameFilter": ""
  482 + }
  483 + }
  484 + },
  485 + "timewindow": {
  486 + "displayValue": "",
  487 + "selectedTab": 0,
  488 + "hideInterval": false,
  489 + "hideAggregation": false,
  490 + "hideAggInterval": false,
  491 + "realtime": {
  492 + "interval": 1000,
  493 + "timewindowMs": 60000
  494 + },
  495 + "history": {
  496 + "historyType": 0,
  497 + "interval": 1000,
  498 + "timewindowMs": 60000,
  499 + "fixedTimewindow": {
  500 + "startTimeMs": 1586176634823,
  501 + "endTimeMs": 1586263034823
  502 + }
  503 + },
  504 + "aggregation": {
  505 + "type": "AVG",
  506 + "limit": 25000
  507 + }
  508 + },
  509 + "settings": {
  510 + "stateControllerId": "entity",
  511 + "showTitle": false,
  512 + "showDashboardsSelect": true,
  513 + "showEntitiesSelect": true,
  514 + "showDashboardTimewindow": true,
  515 + "showDashboardExport": true,
  516 + "toolbarAlwaysOpen": true
  517 + }
  518 + },
  519 + "name": "Rule Engine Statistics"
  520 +}
  1 +{
  2 + "title": "Thermostats",
  3 + "configuration": {
  4 + "widgets": {
  5 + "f33c746c-0dfc-c212-395b-b448c8a17209": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "cards",
  8 + "typeAlias": "entities_table",
  9 + "type": "latest",
  10 + "title": "New widget",
  11 + "sizeX": 11,
  12 + "sizeY": 11,
  13 + "config": {
  14 + "timewindow": {
  15 + "realtime": {
  16 + "interval": 1000,
  17 + "timewindowMs": 86400000
  18 + },
  19 + "aggregation": {
  20 + "type": "NONE",
  21 + "limit": 200
  22 + }
  23 + },
  24 + "showTitle": true,
  25 + "backgroundColor": "rgb(255, 255, 255)",
  26 + "color": "rgba(0, 0, 0, 0.87)",
  27 + "padding": "4px",
  28 + "settings": {
  29 + "enableSearch": true,
  30 + "displayPagination": true,
  31 + "defaultPageSize": 10,
  32 + "defaultSortOrder": "entityName",
  33 + "displayEntityName": true,
  34 + "displayEntityType": false,
  35 + "enableSelectColumnDisplay": false,
  36 + "entitiesTitle": "Thermostats",
  37 + "displayEntityLabel": false,
  38 + "entityNameColumnTitle": "Thermostat name"
  39 + },
  40 + "title": "Thermostats",
  41 + "dropShadow": true,
  42 + "enableFullscreen": false,
  43 + "titleStyle": {
  44 + "fontSize": "16px",
  45 + "fontWeight": 400,
  46 + "padding": "5px 10px 5px 10px"
  47 + },
  48 + "useDashboardTimewindow": false,
  49 + "showLegend": false,
  50 + "datasources": [
  51 + {
  52 + "type": "entity",
  53 + "dataKeys": [
  54 + {
  55 + "name": "active",
  56 + "type": "attribute",
  57 + "label": "Active",
  58 + "color": "#2196f3",
  59 + "settings": {
  60 + "columnWidth": "0px",
  61 + "useCellStyleFunction": true,
  62 + "useCellContentFunction": true,
  63 + "cellContentFunction": "value = '&#11044;';\nreturn value;",
  64 + "cellStyleFunction": "var color;\nif (value === \"true\") {\n color = 'rgb(39, 134, 34)';\n} else {\n color = 'rgb(255, 0, 0)';\n}\nreturn {\n color: color,\n fontSize: '18px'\n};"
  65 + },
  66 + "_hash": 0.9264526512320641
  67 + },
  68 + {
  69 + "name": "temperature",
  70 + "type": "timeseries",
  71 + "label": "Temperature",
  72 + "color": "#4caf50",
  73 + "settings": {
  74 + "columnWidth": "0px",
  75 + "useCellStyleFunction": false,
  76 + "useCellContentFunction": false
  77 + },
  78 + "_hash": 0.9801965063904188,
  79 + "units": "°C",
  80 + "decimals": 1
  81 + },
  82 + {
  83 + "name": "humidity",
  84 + "type": "timeseries",
  85 + "label": "Humidity",
  86 + "color": "#f44336",
  87 + "settings": {
  88 + "columnWidth": "0px",
  89 + "useCellStyleFunction": false,
  90 + "useCellContentFunction": false
  91 + },
  92 + "_hash": 0.5726727868178358,
  93 + "units": "%",
  94 + "decimals": 0
  95 + }
  96 + ],
  97 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  98 + }
  99 + ],
  100 + "showTitleIcon": false,
  101 + "titleIcon": null,
  102 + "iconColor": "rgba(0, 0, 0, 0.87)",
  103 + "iconSize": "24px",
  104 + "titleTooltip": "",
  105 + "widgetStyle": {},
  106 + "displayTimewindow": true,
  107 + "actions": {
  108 + "headerButton": [
  109 + {
  110 + "id": "85b803db-90f2-5c63-1388-a378e0eb10d6",
  111 + "name": "Edit location",
  112 + "icon": "map",
  113 + "type": "openDashboardState",
  114 + "targetDashboardStateId": "map",
  115 + "setEntityId": false
  116 + },
  117 + {
  118 + "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2",
  119 + "name": "Add",
  120 + "icon": "add",
  121 + "type": "customPretty",
  122 + "customHtml": "<md-dialog aria-label=\"Add entity\">\n <form name=\"addEntityForm\" class=\"add-entity-form\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Add thermostat</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <md-input-container flex class=\"md-block\">\n <label>Thermostat name</label>\n <input ng-model=\"vm.entityName\" name=entityName required>\n <div ng-messages=\"addEntityForm.entityName.$error\">\n <div ng-message=\"required\">Thermostat name is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmTemperature\">\n High temperature alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>High temperature threshold, °C</label>\n <input name=\"thresholdTemperature\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdTemperature\"\n ng-disabled=\"!vm.attributes.alarmTemperature\"\n ng-required=\"vm.attributes.alarmTemperature\">\n <div ng-messages=\"addEntityForm.thresholdTemperature.$error\">\n <div ng-message=\"required\">High temperature threshold is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmHumidity\">\n Low humidity alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>Low humidity threshold, %</label>\n <input name=\"thresholdHumidity\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdHumidity\"\n ng-disabled=\"!vm.attributes.alarmHumidity\"\n ng-required=\"vm.attributes.alarmHumidity\">\n <div ng-messages=\"addEntityForm.thresholdHumidity.$error\">\n <div ng-message=\"required\">Low humidity threshold is required.</div>\n </div>\n </md-input-container>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"addEntityForm.$invalid || !addEntityForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  123 + "customCss": ".add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n",
  124 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', AddEntityDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n locals: {\n entityId: entityId\n },\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction AddEntityDialogController($scope, $mdDialog) {\n var vm = this;\n vm.attributes = {};\n\n vm.save = function() {\n $scope.addEntityForm.$setPristine();\n saveEntityPromise().then(\n function (entity) {\n saveAttributes(entity.id);\n updateAliasData();\n $mdDialog.hide();\n }\n );\n };\n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n \n function saveEntityPromise() {\n var entity = {\n name: vm.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n var attributesArray = [];\n for (var key in vm.attributes) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n if (attributesArray.length > 0) {\n attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } \n }\n \n function updateAliasData() {\n var aliasIds = [];\n for (var id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n var tasks = [];\n aliasIds.forEach(function(aliasId) {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(function() {\n $rootScope.$broadcast('widgetForceReInit');\n });\n }\n}\n"
  125 + }
  126 + ],
  127 + "actionCellButton": [
  128 + {
  129 + "id": "ca241cd8-788d-5508-a9ce-74b03ef42a7f",
  130 + "name": "Chart",
  131 + "icon": "show_chart",
  132 + "type": "openDashboardState",
  133 + "targetDashboardStateId": "chart",
  134 + "setEntityId": true
  135 + },
  136 + {
  137 + "id": "7506576f-87ba-d3a0-88fb-e304d451776d",
  138 + "name": "Edit",
  139 + "icon": "edit",
  140 + "type": "customPretty",
  141 + "customHtml": "<md-dialog aria-label=\"Edit entity\">\n <form name=\"editEntityForm\" class=\"edit-entity-form\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Edit thermostat {{vm.entityName}}</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <md-input-container flex class=\"md-block\">\n <label>Thermostat name</label>\n <input ng-model=\"vm.entityName\" readonly>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmTemperature\">\n High temperature alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>High temperature threshold, °C</label>\n <input name=\"thresholdTemperature\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdTemperature\"\n ng-disabled=\"!vm.attributes.alarmTemperature\"\n ng-required=\"vm.attributes.alarmTemperature\">\n <div ng-messages=\"editEntityForm.thresholdTemperature.$error\">\n <div ng-message=\"required\">High temperature threshold is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmHumidity\">\n Low humidity alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>Low humidity threshold, %</label>\n <input name=\"thresholdHumidity\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdHumidity\"\n ng-disabled=\"!vm.attributes.alarmHumidity\"\n ng-required=\"vm.attributes.alarmHumidity\">\n <div ng-messages=\"editEntityForm.thresholdHumidity.$error\">\n <div ng-message=\"required\">Low humidity threshold is required.</div>\n </div>\n </md-input-container>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"editEntityForm.$invalid || !editEntityForm.$dirty\" class=\"md-raised md-primary\">Save</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  142 + "customCss": ".edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n",
  143 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n attributeService = $injector.get('attributeService');\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', EditEntityDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n locals: {\n entityId: entityId\n },\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction EditEntityDialogController($scope,$mdDialog) {\n var vm = this;\n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.entityType = entityId.entityType;\n vm.attributes = {};\n vm.serverAttributes = {};\n getEntityInfo();\n \n vm.save = function() {\n saveAttributes();\n $mdDialog.hide();\n };\n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n vm.serverAttributes = angular.copy(vm.attributes);\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n function(data){\n if (data.length) {\n getEntityAttributes(data);\n }\n });\n }\n \n function saveAttributes() {\n var attributesArray = [];\n for (var key in vm.attributes) {\n if (vm.attributes[key] !== vm.serverAttributes[key]) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } \n }\n}"
  144 + },
  145 + {
  146 + "id": "3488848b-e47d-6af6-659f-5d78369ece5e",
  147 + "name": "Delete",
  148 + "icon": "delete",
  149 + "type": "custom",
  150 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n deviceService = $injector.get('deviceService')\n $rootScope = $injector.get('$rootScope'),\n $q = $injector.get('$q');\n\nopenDeleteEntityDialog();\n\nfunction openDeleteEntityDialog() {\n var title = 'Delete thermostat \"' + entityName + '\"';\n var content = 'Are you sure you want to delete the thermostat \"' +\n entityName + '\"?';\n var confirm = $mdDialog.confirm()\n .targetEvent($event)\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .cancel('Cancel')\n .ok('Delete');\n $mdDialog.show(confirm).then(function() {\n deleteEntity();\n })\n}\n\nfunction deleteEntity() {\n deviceService.deleteDevice(entityId.id).then(\n function success() {\n updateAliasData();\n },\n function fail() {\n showErrorDialog();\n }\n );\n}\n\nfunction updateAliasData() {\n var aliasIds = [];\n for (var id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n var tasks = [];\n aliasIds.forEach(function(aliasId) {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(function() {\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n });\n}\n\nfunction showErrorDialog() {\n var title = 'Error';\n var content = 'An error occurred while deleting the thermostat. Please try again.';\n var alert = $mdDialog.alert()\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .parent(angular.element($document[0].body))\n .targetEvent($event)\n .multiple(true)\n .clickOutsideToClose(true)\n .ok('CLOSE');\n $mdDialog.show(alert);\n}"
  151 + }
  152 + ],
  153 + "rowClick": [
  154 + {
  155 + "id": "e3928f23-c135-0766-71d5-65ed61e0ce8d",
  156 + "name": "show alarm",
  157 + "icon": "more_horiz",
  158 + "type": "updateDashboardState",
  159 + "targetDashboardStateId": "default",
  160 + "setEntityId": true,
  161 + "stateEntityParamName": "alarm"
  162 + }
  163 + ]
  164 + }
  165 + },
  166 + "id": "f33c746c-0dfc-c212-395b-b448c8a17209"
  167 + },
  168 + "7943196b-eedb-d422-f9c3-b32d379ad172": {
  169 + "isSystemType": true,
  170 + "bundleAlias": "alarm_widgets",
  171 + "typeAlias": "alarms_table",
  172 + "type": "alarm",
  173 + "title": "New widget",
  174 + "sizeX": 13,
  175 + "sizeY": 5,
  176 + "config": {
  177 + "timewindow": {
  178 + "realtime": {
  179 + "interval": 1000,
  180 + "timewindowMs": 86400000
  181 + },
  182 + "aggregation": {
  183 + "type": "NONE",
  184 + "limit": 200
  185 + }
  186 + },
  187 + "showTitle": true,
  188 + "backgroundColor": "rgb(255, 255, 255)",
  189 + "color": "rgba(0, 0, 0, 0.87)",
  190 + "padding": "4px",
  191 + "settings": {
  192 + "enableSelection": true,
  193 + "enableSearch": true,
  194 + "displayDetails": true,
  195 + "allowAcknowledgment": true,
  196 + "allowClear": true,
  197 + "displayPagination": true,
  198 + "defaultPageSize": 10,
  199 + "defaultSortOrder": "-createdTime",
  200 + "enableSelectColumnDisplay": false,
  201 + "enableStatusFilter": true,
  202 + "alarmsTitle": "Alarms"
  203 + },
  204 + "title": "New Alarms table",
  205 + "dropShadow": true,
  206 + "enableFullscreen": false,
  207 + "titleStyle": {
  208 + "fontSize": "16px",
  209 + "fontWeight": 400,
  210 + "padding": "5px 10px 5px 10px"
  211 + },
  212 + "useDashboardTimewindow": false,
  213 + "showLegend": false,
  214 + "alarmSource": {
  215 + "type": "entity",
  216 + "dataKeys": [
  217 + {
  218 + "name": "createdTime",
  219 + "type": "alarm",
  220 + "label": "Created time",
  221 + "color": "#2196f3",
  222 + "settings": {},
  223 + "_hash": 0.7308410188824108
  224 + },
  225 + {
  226 + "name": "originator",
  227 + "type": "alarm",
  228 + "label": "Originator",
  229 + "color": "#4caf50",
  230 + "settings": {},
  231 + "_hash": 0.056085530105439485
  232 + },
  233 + {
  234 + "name": "type",
  235 + "type": "alarm",
  236 + "label": "Type",
  237 + "color": "#f44336",
  238 + "settings": {},
  239 + "_hash": 0.10212012352561795
  240 + },
  241 + {
  242 + "name": "severity",
  243 + "type": "alarm",
  244 + "label": "Severity",
  245 + "color": "#ffc107",
  246 + "settings": {},
  247 + "_hash": 0.1777349980531262
  248 + },
  249 + {
  250 + "name": "status",
  251 + "type": "alarm",
  252 + "label": "Status",
  253 + "color": "#607d8b",
  254 + "settings": {},
  255 + "_hash": 0.7977920750136249
  256 + }
  257 + ],
  258 + "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
  259 + "name": "alarms"
  260 + },
  261 + "alarmSearchStatus": "ANY",
  262 + "alarmsPollingInterval": 5,
  263 + "showTitleIcon": false,
  264 + "titleIcon": null,
  265 + "iconColor": "rgba(0, 0, 0, 0.87)",
  266 + "iconSize": "24px",
  267 + "titleTooltip": "",
  268 + "widgetStyle": {},
  269 + "displayTimewindow": true,
  270 + "actions": {},
  271 + "datasources": [],
  272 + "alarmsMaxCountLoad": 0,
  273 + "alarmsFetchSize": 100
  274 + },
  275 + "id": "7943196b-eedb-d422-f9c3-b32d379ad172"
  276 + },
  277 + "14a19183-f0b2-d6be-0f62-9863f0a51111": {
  278 + "isSystemType": true,
  279 + "bundleAlias": "charts",
  280 + "typeAlias": "basic_timeseries",
  281 + "type": "timeseries",
  282 + "title": "New widget",
  283 + "sizeX": 18,
  284 + "sizeY": 6,
  285 + "config": {
  286 + "datasources": [
  287 + {
  288 + "type": "entity",
  289 + "dataKeys": [
  290 + {
  291 + "name": "temperature",
  292 + "type": "timeseries",
  293 + "label": "Temperature",
  294 + "color": "#ef5350",
  295 + "settings": {
  296 + "excludeFromStacking": false,
  297 + "hideDataByDefault": false,
  298 + "disableDataHiding": false,
  299 + "removeFromLegend": false,
  300 + "showLines": true,
  301 + "fillLines": true,
  302 + "showPoints": false,
  303 + "showPointShape": "circle",
  304 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  305 + "showPointsLineWidth": 5,
  306 + "showPointsRadius": 3,
  307 + "showSeparateAxis": false,
  308 + "axisPosition": "left",
  309 + "thresholds": [
  310 + {
  311 + "thresholdValueSource": "predefinedValue"
  312 + }
  313 + ],
  314 + "comparisonSettings": {
  315 + "showValuesForComparison": true
  316 + }
  317 + },
  318 + "_hash": 0.7852346160709658,
  319 + "units": "°C",
  320 + "decimals": 1
  321 + }
  322 + ],
  323 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  324 + }
  325 + ],
  326 + "timewindow": {
  327 + "realtime": {
  328 + "interval": 30000,
  329 + "timewindowMs": 3600000
  330 + },
  331 + "aggregation": {
  332 + "type": "AVG",
  333 + "limit": 25000
  334 + }
  335 + },
  336 + "showTitle": true,
  337 + "backgroundColor": "#fff",
  338 + "color": "rgba(0, 0, 0, 0.87)",
  339 + "padding": "8px",
  340 + "settings": {
  341 + "shadowSize": 4,
  342 + "fontColor": "#545454",
  343 + "fontSize": 10,
  344 + "xaxis": {
  345 + "showLabels": true,
  346 + "color": "#545454"
  347 + },
  348 + "yaxis": {
  349 + "showLabels": true,
  350 + "color": "#545454"
  351 + },
  352 + "grid": {
  353 + "color": "#545454",
  354 + "tickColor": "#DDDDDD",
  355 + "verticalLines": true,
  356 + "horizontalLines": true,
  357 + "outlineWidth": 1
  358 + },
  359 + "stack": false,
  360 + "tooltipIndividual": false,
  361 + "timeForComparison": "months",
  362 + "xaxisSecond": {
  363 + "axisPosition": "top",
  364 + "showLabels": true
  365 + },
  366 + "smoothLines": true
  367 + },
  368 + "title": "Temperature",
  369 + "dropShadow": true,
  370 + "enableFullscreen": true,
  371 + "titleStyle": {
  372 + "fontSize": "16px",
  373 + "fontWeight": 400
  374 + },
  375 + "mobileHeight": null,
  376 + "showTitleIcon": false,
  377 + "titleIcon": null,
  378 + "iconColor": "rgba(0, 0, 0, 0.87)",
  379 + "iconSize": "24px",
  380 + "titleTooltip": "",
  381 + "widgetStyle": {},
  382 + "useDashboardTimewindow": false,
  383 + "displayTimewindow": true,
  384 + "showLegend": true,
  385 + "legendConfig": {
  386 + "direction": "column",
  387 + "position": "bottom",
  388 + "showMin": true,
  389 + "showMax": true,
  390 + "showAvg": true,
  391 + "showTotal": false
  392 + },
  393 + "actions": {}
  394 + },
  395 + "id": "14a19183-f0b2-d6be-0f62-9863f0a51111"
  396 + },
  397 + "07f49fd5-a73b-d74c-c220-362c20af81f4": {
  398 + "isSystemType": true,
  399 + "bundleAlias": "charts",
  400 + "typeAlias": "basic_timeseries",
  401 + "type": "timeseries",
  402 + "title": "New widget",
  403 + "sizeX": 18,
  404 + "sizeY": 6,
  405 + "config": {
  406 + "datasources": [
  407 + {
  408 + "type": "entity",
  409 + "dataKeys": [
  410 + {
  411 + "name": "humidity",
  412 + "type": "timeseries",
  413 + "label": "Humidity",
  414 + "color": "#2196f3",
  415 + "settings": {
  416 + "excludeFromStacking": false,
  417 + "hideDataByDefault": false,
  418 + "disableDataHiding": false,
  419 + "removeFromLegend": false,
  420 + "showLines": true,
  421 + "fillLines": true,
  422 + "showPoints": false,
  423 + "showPointShape": "circle",
  424 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  425 + "showPointsLineWidth": 5,
  426 + "showPointsRadius": 3,
  427 + "showSeparateAxis": false,
  428 + "axisPosition": "left",
  429 + "thresholds": [
  430 + {
  431 + "thresholdValueSource": "predefinedValue"
  432 + }
  433 + ],
  434 + "comparisonSettings": {
  435 + "showValuesForComparison": true
  436 + }
  437 + },
  438 + "_hash": 0.28640715926957183,
  439 + "units": "%",
  440 + "decimals": 0
  441 + }
  442 + ],
  443 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  444 + }
  445 + ],
  446 + "timewindow": {
  447 + "realtime": {
  448 + "interval": 30000,
  449 + "timewindowMs": 3600000
  450 + },
  451 + "aggregation": {
  452 + "type": "AVG",
  453 + "limit": 25000
  454 + }
  455 + },
  456 + "showTitle": true,
  457 + "backgroundColor": "#fff",
  458 + "color": "rgba(0, 0, 0, 0.87)",
  459 + "padding": "8px",
  460 + "settings": {
  461 + "shadowSize": 4,
  462 + "fontColor": "#545454",
  463 + "fontSize": 10,
  464 + "xaxis": {
  465 + "showLabels": true,
  466 + "color": "#545454"
  467 + },
  468 + "yaxis": {
  469 + "showLabels": true,
  470 + "color": "#545454"
  471 + },
  472 + "grid": {
  473 + "color": "#545454",
  474 + "tickColor": "#DDDDDD",
  475 + "verticalLines": true,
  476 + "horizontalLines": true,
  477 + "outlineWidth": 1
  478 + },
  479 + "stack": false,
  480 + "tooltipIndividual": false,
  481 + "timeForComparison": "months",
  482 + "xaxisSecond": {
  483 + "axisPosition": "top",
  484 + "showLabels": true
  485 + },
  486 + "smoothLines": true
  487 + },
  488 + "title": "Humidity",
  489 + "dropShadow": true,
  490 + "enableFullscreen": true,
  491 + "titleStyle": {
  492 + "fontSize": "16px",
  493 + "fontWeight": 400
  494 + },
  495 + "mobileHeight": null,
  496 + "showTitleIcon": false,
  497 + "titleIcon": null,
  498 + "iconColor": "rgba(0, 0, 0, 0.87)",
  499 + "iconSize": "24px",
  500 + "titleTooltip": "",
  501 + "widgetStyle": {},
  502 + "useDashboardTimewindow": false,
  503 + "displayTimewindow": true,
  504 + "showLegend": true,
  505 + "legendConfig": {
  506 + "direction": "column",
  507 + "position": "bottom",
  508 + "showMin": true,
  509 + "showMax": true,
  510 + "showAvg": true,
  511 + "showTotal": false
  512 + },
  513 + "actions": {}
  514 + },
  515 + "id": "07f49fd5-a73b-d74c-c220-362c20af81f4"
  516 + },
  517 + "c4631f94-2db3-523b-4d09-2a1a0a75d93f": {
  518 + "isSystemType": true,
  519 + "bundleAlias": "input_widgets",
  520 + "typeAlias": "update_multiple_attributes",
  521 + "type": "latest",
  522 + "title": "New widget",
  523 + "sizeX": 6,
  524 + "sizeY": 6,
  525 + "config": {
  526 + "datasources": [
  527 + {
  528 + "type": "entity",
  529 + "dataKeys": [
  530 + {
  531 + "name": "alarmTemperature",
  532 + "type": "attribute",
  533 + "label": "High temperature alarm",
  534 + "color": "#4caf50",
  535 + "settings": {
  536 + "dataKeyType": "server",
  537 + "dataKeyValueType": "booleanCheckbox",
  538 + "required": false,
  539 + "isEditable": "editable",
  540 + "dataKeyHidden": false,
  541 + "step": 1
  542 + },
  543 + "_hash": 0.8725278440159361
  544 + },
  545 + {
  546 + "name": "thresholdTemperature",
  547 + "type": "attribute",
  548 + "label": "High temperature threshold, °C",
  549 + "color": "#f44336",
  550 + "settings": {
  551 + "dataKeyType": "server",
  552 + "dataKeyValueType": "double",
  553 + "required": false,
  554 + "isEditable": "editable",
  555 + "dataKeyHidden": false,
  556 + "step": 1,
  557 + "disabledOnDataKey": "alarmTemperature"
  558 + },
  559 + "_hash": 0.7316078472857874
  560 + },
  561 + {
  562 + "name": "alarmHumidity",
  563 + "type": "attribute",
  564 + "label": "Low humidity alarm",
  565 + "color": "#ffc107",
  566 + "settings": {
  567 + "dataKeyType": "server",
  568 + "dataKeyValueType": "booleanCheckbox",
  569 + "required": false,
  570 + "isEditable": "editable",
  571 + "dataKeyHidden": false,
  572 + "step": 1
  573 + },
  574 + "_hash": 0.5339673667431057
  575 + },
  576 + {
  577 + "name": "thresholdHumidity",
  578 + "type": "attribute",
  579 + "label": "Low humidity threshold, %",
  580 + "color": "#607d8b",
  581 + "settings": {
  582 + "dataKeyType": "server",
  583 + "dataKeyValueType": "double",
  584 + "required": false,
  585 + "isEditable": "editable",
  586 + "dataKeyHidden": false,
  587 + "step": 1,
  588 + "disabledOnDataKey": "alarmHumidity"
  589 + },
  590 + "_hash": 0.2687091190358901
  591 + }
  592 + ],
  593 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  594 + }
  595 + ],
  596 + "timewindow": {
  597 + "realtime": {
  598 + "timewindowMs": 60000
  599 + }
  600 + },
  601 + "showTitle": true,
  602 + "backgroundColor": "#fff",
  603 + "color": "rgba(0, 0, 0, 0.87)",
  604 + "padding": "8px",
  605 + "settings": {
  606 + "showActionButtons": false,
  607 + "showResultMessage": true,
  608 + "fieldsAlignment": "column",
  609 + "fieldsInRow": 2,
  610 + "groupTitle": "${entityName}",
  611 + "widgetTitle": "Termostat settings"
  612 + },
  613 + "title": "New Update Multiple Attributes",
  614 + "dropShadow": true,
  615 + "enableFullscreen": false,
  616 + "enableDataExport": false,
  617 + "widgetStyle": {},
  618 + "titleStyle": {
  619 + "fontSize": "16px",
  620 + "fontWeight": 400
  621 + },
  622 + "useDashboardTimewindow": true,
  623 + "showLegend": false,
  624 + "actions": {},
  625 + "showTitleIcon": false,
  626 + "titleIcon": null,
  627 + "iconColor": "rgba(0, 0, 0, 0.87)",
  628 + "iconSize": "24px",
  629 + "titleTooltip": "",
  630 + "displayTimewindow": true
  631 + },
  632 + "id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f"
  633 + },
  634 + "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
  635 + "isSystemType": true,
  636 + "bundleAlias": "maps_v2",
  637 + "typeAlias": "openstreetmap",
  638 + "type": "latest",
  639 + "title": "New widget",
  640 + "sizeX": 13,
  641 + "sizeY": 6,
  642 + "config": {
  643 + "datasources": [
  644 + {
  645 + "type": "entity",
  646 + "dataKeys": [
  647 + {
  648 + "name": "temperature",
  649 + "type": "timeseries",
  650 + "label": "temperature",
  651 + "color": "#2196f3",
  652 + "settings": {},
  653 + "_hash": 0.1371919646686739,
  654 + "decimals": 1,
  655 + "postFuncBody": "return value || \"\";"
  656 + },
  657 + {
  658 + "name": "humidity",
  659 + "type": "timeseries",
  660 + "label": "humidity",
  661 + "color": "#4caf50",
  662 + "settings": {},
  663 + "_hash": 0.043177186765847475,
  664 + "decimals": 0,
  665 + "postFuncBody": "return value || \"\";"
  666 + },
  667 + {
  668 + "name": "longitude",
  669 + "type": "attribute",
  670 + "label": "longitude",
  671 + "color": "#f44336",
  672 + "settings": {},
  673 + "_hash": 0.5548964320315584
  674 + },
  675 + {
  676 + "name": "latitude",
  677 + "type": "attribute",
  678 + "label": "latitude",
  679 + "color": "#ffc107",
  680 + "settings": {},
  681 + "_hash": 0.1803778014971602
  682 + },
  683 + {
  684 + "name": "active",
  685 + "type": "attribute",
  686 + "label": "active",
  687 + "color": "#607d8b",
  688 + "settings": {},
  689 + "_hash": 0.30926987994082844
  690 + }
  691 + ],
  692 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  693 + }
  694 + ],
  695 + "timewindow": {
  696 + "realtime": {
  697 + "timewindowMs": 60000
  698 + }
  699 + },
  700 + "showTitle": false,
  701 + "backgroundColor": "#fff",
  702 + "color": "rgba(0, 0, 0, 0.87)",
  703 + "padding": "8px",
  704 + "settings": {
  705 + "fitMapBounds": true,
  706 + "latKeyName": "latitude",
  707 + "lngKeyName": "longitude",
  708 + "showLabel": true,
  709 + "label": "${entityName}",
  710 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name=\"navigate_to_details\">Thermostat details</link-act><br/>",
  711 + "markerImageSize": 48,
  712 + "useColorFunction": false,
  713 + "markerImages": [
  714 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  715 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  716 + ],
  717 + "useMarkerImageFunction": true,
  718 + "colorFunction": "\n",
  719 + "color": "#fe7569",
  720 + "mapProvider": "OpenStreetMap.HOT",
  721 + "showTooltip": true,
  722 + "autocloseTooltip": true,
  723 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  724 + "defaultCenterPosition": [
  725 + 0,
  726 + 0
  727 + ],
  728 + "showTooltipAction": "click",
  729 + "polygonKeyName": "coordinates",
  730 + "polygonOpacity": 0.5,
  731 + "polygonStrokeOpacity": 1,
  732 + "polygonStrokeWeight": 1,
  733 + "zoomOnClick": true,
  734 + "showCoverageOnHover": true,
  735 + "animate": true,
  736 + "maxClusterRadius": 80,
  737 + "removeOutsideVisibleBounds": true,
  738 + "useLabelFunction": true,
  739 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  740 + "defaultZoomLevel": 14,
  741 + "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;"
  742 + },
  743 + "title": "Thermostat maps",
  744 + "dropShadow": true,
  745 + "enableFullscreen": false,
  746 + "titleStyle": {
  747 + "fontSize": "16px",
  748 + "fontWeight": 400
  749 + },
  750 + "useDashboardTimewindow": true,
  751 + "showLegend": false,
  752 + "widgetStyle": {},
  753 + "actions": {
  754 + "headerButton": [],
  755 + "tooltipAction": [
  756 + {
  757 + "id": "bef25673-b37a-8821-bc0f-5d6dd3680f24",
  758 + "name": "navigate_to_details",
  759 + "icon": "more_horiz",
  760 + "type": "openDashboardState",
  761 + "targetDashboardStateId": "chart",
  762 + "setEntityId": true
  763 + }
  764 + ]
  765 + },
  766 + "showTitleIcon": false,
  767 + "titleIcon": null,
  768 + "iconColor": "rgba(0, 0, 0, 0.87)",
  769 + "iconSize": "24px",
  770 + "titleTooltip": "",
  771 + "displayTimewindow": true
  772 + },
  773 + "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb"
  774 + },
  775 + "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
  776 + "isSystemType": true,
  777 + "bundleAlias": "input_widgets",
  778 + "typeAlias": "markers_placement_openstreetmap",
  779 + "type": "latest",
  780 + "title": "New widget",
  781 + "sizeX": 24,
  782 + "sizeY": 12,
  783 + "config": {
  784 + "datasources": [
  785 + {
  786 + "type": "entity",
  787 + "dataKeys": [
  788 + {
  789 + "name": "longitude",
  790 + "type": "attribute",
  791 + "label": "longitude",
  792 + "color": "#2196f3",
  793 + "settings": {},
  794 + "_hash": 0.3640193654284214
  795 + },
  796 + {
  797 + "name": "latitude",
  798 + "type": "attribute",
  799 + "label": "latitude",
  800 + "color": "#4caf50",
  801 + "settings": {},
  802 + "_hash": 0.49020393887695923
  803 + },
  804 + {
  805 + "name": "temperature",
  806 + "type": "timeseries",
  807 + "label": "temperature",
  808 + "color": "#f44336",
  809 + "settings": {},
  810 + "_hash": 0.5885892766009955,
  811 + "postFuncBody": "return value || \"\";"
  812 + },
  813 + {
  814 + "name": "humidity",
  815 + "type": "timeseries",
  816 + "label": "humidity",
  817 + "color": "#ffc107",
  818 + "settings": {},
  819 + "_hash": 0.21077893588180707,
  820 + "postFuncBody": "return value || \"\";"
  821 + },
  822 + {
  823 + "name": "active",
  824 + "type": "attribute",
  825 + "label": "active",
  826 + "color": "#607d8b",
  827 + "settings": {},
  828 + "_hash": 0.34722983638504346
  829 + }
  830 + ],
  831 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  832 + }
  833 + ],
  834 + "timewindow": {
  835 + "realtime": {
  836 + "timewindowMs": 60000
  837 + }
  838 + },
  839 + "showTitle": false,
  840 + "backgroundColor": "#fff",
  841 + "color": "rgba(0, 0, 0, 0.87)",
  842 + "padding": "8px",
  843 + "settings": {
  844 + "fitMapBounds": true,
  845 + "latKeyName": "latitude",
  846 + "lngKeyName": "longitude",
  847 + "showLabel": true,
  848 + "label": "${entityName}",
  849 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name='delete'>Delete</link-act>",
  850 + "markerImageSize": 34,
  851 + "useColorFunction": false,
  852 + "markerImages": [
  853 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  854 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  855 + ],
  856 + "useMarkerImageFunction": true,
  857 + "color": "#fe7569",
  858 + "mapProvider": "OpenStreetMap.HOT",
  859 + "showTooltip": true,
  860 + "autocloseTooltip": true,
  861 + "defaultCenterPosition": [
  862 + 0,
  863 + 0
  864 + ],
  865 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  866 + "showTooltipAction": "click",
  867 + "polygonKeyName": "coordinates",
  868 + "polygonOpacity": 0.5,
  869 + "polygonStrokeOpacity": 1,
  870 + "polygonStrokeWeight": 1,
  871 + "zoomOnClick": true,
  872 + "showCoverageOnHover": true,
  873 + "animate": true,
  874 + "maxClusterRadius": 80,
  875 + "removeOutsideVisibleBounds": true,
  876 + "defaultZoomLevel": 12,
  877 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  878 + "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;",
  879 + "useLabelFunction": true
  880 + },
  881 + "title": "New Markers Placement - OpenStreetMap",
  882 + "dropShadow": true,
  883 + "enableFullscreen": false,
  884 + "titleStyle": {
  885 + "fontSize": "16px",
  886 + "fontWeight": 400
  887 + },
  888 + "useDashboardTimewindow": true,
  889 + "showLegend": false,
  890 + "widgetStyle": {},
  891 + "actions": {
  892 + "tooltipAction": [
  893 + {
  894 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  895 + "name": "delete",
  896 + "icon": "more_horiz",
  897 + "type": "custom",
  898 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  899 + }
  900 + ]
  901 + },
  902 + "showTitleIcon": false,
  903 + "titleIcon": null,
  904 + "iconColor": "rgba(0, 0, 0, 0.87)",
  905 + "iconSize": "24px",
  906 + "titleTooltip": "",
  907 + "displayTimewindow": true
  908 + },
  909 + "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06"
  910 + },
  911 + "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
  912 + "isSystemType": true,
  913 + "bundleAlias": "input_widgets",
  914 + "typeAlias": "markers_placement_openstreetmap",
  915 + "type": "latest",
  916 + "title": "New widget",
  917 + "sizeX": 6,
  918 + "sizeY": 6,
  919 + "config": {
  920 + "datasources": [
  921 + {
  922 + "type": "entity",
  923 + "dataKeys": [
  924 + {
  925 + "name": "longitude",
  926 + "type": "attribute",
  927 + "label": "longitude",
  928 + "color": "#2196f3",
  929 + "settings": {},
  930 + "_hash": 0.3640193654284214
  931 + },
  932 + {
  933 + "name": "latitude",
  934 + "type": "attribute",
  935 + "label": "latitude",
  936 + "color": "#4caf50",
  937 + "settings": {},
  938 + "_hash": 0.49020393887695923
  939 + },
  940 + {
  941 + "name": "temperature",
  942 + "type": "timeseries",
  943 + "label": "temperature",
  944 + "color": "#f44336",
  945 + "settings": {},
  946 + "_hash": 0.5885892766009955,
  947 + "postFuncBody": "return value || \"\";"
  948 + },
  949 + {
  950 + "name": "humidity",
  951 + "type": "timeseries",
  952 + "label": "humidity",
  953 + "color": "#ffc107",
  954 + "settings": {},
  955 + "_hash": 0.21077893588180707,
  956 + "postFuncBody": "return value || \"\";"
  957 + },
  958 + {
  959 + "name": "active",
  960 + "type": "attribute",
  961 + "label": "active",
  962 + "color": "#607d8b",
  963 + "settings": {},
  964 + "_hash": 0.34722983638504346
  965 + }
  966 + ],
  967 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  968 + }
  969 + ],
  970 + "timewindow": {
  971 + "realtime": {
  972 + "timewindowMs": 60000
  973 + }
  974 + },
  975 + "showTitle": false,
  976 + "backgroundColor": "#fff",
  977 + "color": "rgba(0, 0, 0, 0.87)",
  978 + "padding": "8px",
  979 + "settings": {
  980 + "fitMapBounds": true,
  981 + "latKeyName": "latitude",
  982 + "lngKeyName": "longitude",
  983 + "showLabel": true,
  984 + "label": "${entityName}",
  985 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name='delete'>Delete</link-act>",
  986 + "markerImageSize": 34,
  987 + "useColorFunction": false,
  988 + "markerImages": [
  989 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  990 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  991 + ],
  992 + "useMarkerImageFunction": true,
  993 + "color": "#fe7569",
  994 + "mapProvider": "OpenStreetMap.HOT",
  995 + "showTooltip": true,
  996 + "autocloseTooltip": true,
  997 + "defaultCenterPosition": [
  998 + 0,
  999 + 0
  1000 + ],
  1001 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  1002 + "showTooltipAction": "click",
  1003 + "polygonKeyName": "coordinates",
  1004 + "polygonOpacity": 0.5,
  1005 + "polygonStrokeOpacity": 1,
  1006 + "polygonStrokeWeight": 1,
  1007 + "zoomOnClick": true,
  1008 + "showCoverageOnHover": true,
  1009 + "animate": true,
  1010 + "maxClusterRadius": 80,
  1011 + "removeOutsideVisibleBounds": true,
  1012 + "defaultZoomLevel": 12,
  1013 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  1014 + "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;",
  1015 + "useLabelFunction": true
  1016 + },
  1017 + "title": "New Markers Placement - OpenStreetMap",
  1018 + "dropShadow": true,
  1019 + "enableFullscreen": false,
  1020 + "titleStyle": {
  1021 + "fontSize": "16px",
  1022 + "fontWeight": 400
  1023 + },
  1024 + "useDashboardTimewindow": true,
  1025 + "showLegend": false,
  1026 + "widgetStyle": {},
  1027 + "actions": {
  1028 + "tooltipAction": [
  1029 + {
  1030 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  1031 + "name": "delete",
  1032 + "icon": "more_horiz",
  1033 + "type": "custom",
  1034 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  1035 + }
  1036 + ]
  1037 + },
  1038 + "showTitleIcon": false,
  1039 + "titleIcon": null,
  1040 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1041 + "iconSize": "24px",
  1042 + "titleTooltip": "",
  1043 + "displayTimewindow": true
  1044 + },
  1045 + "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf"
  1046 + }
  1047 + },
  1048 + "states": {
  1049 + "default": {
  1050 + "name": "Thermostat",
  1051 + "root": true,
  1052 + "layouts": {
  1053 + "main": {
  1054 + "widgets": {
  1055 + "f33c746c-0dfc-c212-395b-b448c8a17209": {
  1056 + "sizeX": 11,
  1057 + "sizeY": 11,
  1058 + "row": 0,
  1059 + "col": 0
  1060 + },
  1061 + "7943196b-eedb-d422-f9c3-b32d379ad172": {
  1062 + "sizeX": 13,
  1063 + "sizeY": 5,
  1064 + "row": 0,
  1065 + "col": 11
  1066 + },
  1067 + "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
  1068 + "sizeX": 13,
  1069 + "sizeY": 6,
  1070 + "row": 5,
  1071 + "col": 11
  1072 + }
  1073 + },
  1074 + "gridSettings": {
  1075 + "backgroundColor": "#eeeeee",
  1076 + "color": "rgba(0,0,0,0.870588)",
  1077 + "columns": 24,
  1078 + "margins": [
  1079 + 10,
  1080 + 10
  1081 + ],
  1082 + "backgroundSizeMode": "100%",
  1083 + "autoFillHeight": true,
  1084 + "mobileAutoFillHeight": false,
  1085 + "mobileRowHeight": 70
  1086 + }
  1087 + }
  1088 + }
  1089 + },
  1090 + "map": {
  1091 + "name": "Edit location",
  1092 + "root": false,
  1093 + "layouts": {
  1094 + "main": {
  1095 + "widgets": {
  1096 + "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
  1097 + "sizeX": 24,
  1098 + "sizeY": 12,
  1099 + "row": 0,
  1100 + "col": 0
  1101 + }
  1102 + },
  1103 + "gridSettings": {
  1104 + "backgroundColor": "#eeeeee",
  1105 + "color": "rgba(0,0,0,0.870588)",
  1106 + "columns": 24,
  1107 + "margins": [
  1108 + 10,
  1109 + 10
  1110 + ],
  1111 + "backgroundSizeMode": "100%",
  1112 + "autoFillHeight": true,
  1113 + "mobileAutoFillHeight": false,
  1114 + "mobileRowHeight": 70
  1115 + }
  1116 + }
  1117 + }
  1118 + },
  1119 + "chart": {
  1120 + "name": "${entityName}",
  1121 + "root": false,
  1122 + "layouts": {
  1123 + "main": {
  1124 + "widgets": {
  1125 + "14a19183-f0b2-d6be-0f62-9863f0a51111": {
  1126 + "sizeX": 18,
  1127 + "sizeY": 6,
  1128 + "mobileHeight": null,
  1129 + "row": 0,
  1130 + "col": 6
  1131 + },
  1132 + "07f49fd5-a73b-d74c-c220-362c20af81f4": {
  1133 + "sizeX": 18,
  1134 + "sizeY": 6,
  1135 + "mobileHeight": null,
  1136 + "row": 6,
  1137 + "col": 6
  1138 + },
  1139 + "c4631f94-2db3-523b-4d09-2a1a0a75d93f": {
  1140 + "sizeX": 6,
  1141 + "sizeY": 6,
  1142 + "row": 0,
  1143 + "col": 0
  1144 + },
  1145 + "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
  1146 + "sizeX": 6,
  1147 + "sizeY": 6,
  1148 + "row": 6,
  1149 + "col": 0
  1150 + }
  1151 + },
  1152 + "gridSettings": {
  1153 + "backgroundColor": "#eeeeee",
  1154 + "color": "rgba(0,0,0,0.870588)",
  1155 + "columns": 24,
  1156 + "margins": [
  1157 + 10,
  1158 + 10
  1159 + ],
  1160 + "backgroundSizeMode": "100%",
  1161 + "autoFillHeight": true,
  1162 + "mobileAutoFillHeight": false,
  1163 + "mobileRowHeight": 70
  1164 + }
  1165 + }
  1166 + }
  1167 + }
  1168 + },
  1169 + "entityAliases": {
  1170 + "68a058e1-fdda-8482-715b-3ae4a488568e": {
  1171 + "id": "68a058e1-fdda-8482-715b-3ae4a488568e",
  1172 + "alias": "Thermostats",
  1173 + "filter": {
  1174 + "type": "deviceType",
  1175 + "resolveMultiple": true,
  1176 + "deviceType": "thermostat",
  1177 + "deviceNameFilter": ""
  1178 + }
  1179 + },
  1180 + "12ae98c7-1ea2-52cf-64d5-763e9d993547": {
  1181 + "id": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
  1182 + "alias": "Thermostat",
  1183 + "filter": {
  1184 + "type": "stateEntity",
  1185 + "resolveMultiple": false,
  1186 + "stateEntityParamName": null,
  1187 + "defaultStateEntity": null
  1188 + }
  1189 + },
  1190 + "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": {
  1191 + "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
  1192 + "alias": "Thermostat-alarm",
  1193 + "filter": {
  1194 + "type": "stateEntity",
  1195 + "resolveMultiple": false,
  1196 + "stateEntityParamName": "alarm",
  1197 + "defaultStateEntity": null
  1198 + }
  1199 + }
  1200 + },
  1201 + "timewindow": {
  1202 + "displayValue": "",
  1203 + "selectedTab": 0,
  1204 + "hideInterval": false,
  1205 + "hideAggregation": false,
  1206 + "hideAggInterval": false,
  1207 + "realtime": {
  1208 + "interval": 1000,
  1209 + "timewindowMs": 60000
  1210 + },
  1211 + "history": {
  1212 + "historyType": 0,
  1213 + "interval": 1000,
  1214 + "timewindowMs": 60000,
  1215 + "fixedTimewindow": {
  1216 + "startTimeMs": 1587473857304,
  1217 + "endTimeMs": 1587560257304
  1218 + }
  1219 + },
  1220 + "aggregation": {
  1221 + "type": "AVG",
  1222 + "limit": 25000
  1223 + }
  1224 + },
  1225 + "settings": {
  1226 + "stateControllerId": "entity",
  1227 + "showTitle": false,
  1228 + "showDashboardsSelect": true,
  1229 + "showEntitiesSelect": true,
  1230 + "showDashboardTimewindow": true,
  1231 + "showDashboardExport": true,
  1232 + "toolbarAlwaysOpen": true
  1233 + }
  1234 + },
  1235 + "name": "Thermostats"
  1236 +}
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Root Rule Chain",
  5 + "firstRuleNodeId": null,
  6 + "root": true,
  7 + "debugMode": false,
  8 + "configuration": null
  9 + },
  10 + "metadata": {
  11 + "firstNodeIndex": 2,
  12 + "nodes": [
  13 + {
  14 + "additionalInfo": {
  15 + "layoutX": 824,
  16 + "layoutY": 156
  17 + },
  18 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  19 + "name": "Save Timeseries",
  20 + "debugMode": false,
  21 + "configuration": {
  22 + "defaultTTL": 0
  23 + }
  24 + },
  25 + {
  26 + "additionalInfo": {
  27 + "layoutX": 825,
  28 + "layoutY": 52
  29 + },
  30 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  31 + "name": "Save Client Attributes",
  32 + "debugMode": false,
  33 + "configuration": {
  34 + "scope": "CLIENT_SCOPE"
  35 + }
  36 + },
  37 + {
  38 + "additionalInfo": {
  39 + "layoutX": 347,
  40 + "layoutY": 149
  41 + },
  42 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  43 + "name": "Message Type Switch",
  44 + "debugMode": false,
  45 + "configuration": {
  46 + "version": 0
  47 + }
  48 + },
  49 + {
  50 + "additionalInfo": {
  51 + "layoutX": 825,
  52 + "layoutY": 266
  53 + },
  54 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  55 + "name": "Log RPC from Device",
  56 + "debugMode": false,
  57 + "configuration": {
  58 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  59 + }
  60 + },
  61 + {
  62 + "additionalInfo": {
  63 + "layoutX": 825,
  64 + "layoutY": 379
  65 + },
  66 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  67 + "name": "Log Other",
  68 + "debugMode": false,
  69 + "configuration": {
  70 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  71 + }
  72 + },
  73 + {
  74 + "additionalInfo": {
  75 + "layoutX": 825,
  76 + "layoutY": 468
  77 + },
  78 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  79 + "name": "RPC Call Request",
  80 + "debugMode": false,
  81 + "configuration": {
  82 + "timeoutInSeconds": 60
  83 + }
  84 + },
  85 + {
  86 + "additionalInfo": {
  87 + "layoutX": 1069,
  88 + "layoutY": 90
  89 + },
  90 + "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
  91 + "name": "Is Thermostat?",
  92 + "debugMode": false,
  93 + "configuration": {
  94 + "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";"
  95 + }
  96 + }
  97 + ],
  98 + "connections": [
  99 + {
  100 + "fromIndex": 0,
  101 + "toIndex": 6,
  102 + "type": "Success"
  103 + },
  104 + {
  105 + "fromIndex": 2,
  106 + "toIndex": 4,
  107 + "type": "Other"
  108 + },
  109 + {
  110 + "fromIndex": 2,
  111 + "toIndex": 1,
  112 + "type": "Post attributes"
  113 + },
  114 + {
  115 + "fromIndex": 2,
  116 + "toIndex": 0,
  117 + "type": "Post telemetry"
  118 + },
  119 + {
  120 + "fromIndex": 2,
  121 + "toIndex": 3,
  122 + "type": "RPC Request from Device"
  123 + },
  124 + {
  125 + "fromIndex": 2,
  126 + "toIndex": 5,
  127 + "type": "RPC Request to Device"
  128 + }
  129 + ],
  130 + "ruleChainConnections": [
  131 + {
  132 + "fromIndex": 6,
  133 + "targetRuleChainId": {
  134 + "entityType": "RULE_CHAIN",
  135 + "id": "83d42540-85fd-11ea-aee2-794850541ced"
  136 + },
  137 + "additionalInfo": {
  138 + "layoutX": 1088,
  139 + "layoutY": 203,
  140 + "ruleChainNodeId": "rule-chain-node-9"
  141 + },
  142 + "type": "True"
  143 + }
  144 + ]
  145 + }
  146 +}
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Thermostat Alarms",
  5 + "firstRuleNodeId": null,
  6 + "root": false,
  7 + "debugMode": false,
  8 + "configuration": null
  9 + },
  10 + "metadata": {
  11 + "firstNodeIndex": 5,
  12 + "nodes": [
  13 + {
  14 + "additionalInfo": {
  15 + "layoutX": 929,
  16 + "layoutY": 67
  17 + },
  18 + "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",
  19 + "name": "Create Temp Alarm",
  20 + "debugMode": false,
  21 + "configuration": {
  22 + "alarmType": "High Temperature",
  23 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;",
  24 + "severity": "MAJOR",
  25 + "propagate": true,
  26 + "useMessageAlarmData": false,
  27 + "relationTypes": []
  28 + }
  29 + },
  30 + {
  31 + "additionalInfo": {
  32 + "layoutX": 930,
  33 + "layoutY": 201
  34 + },
  35 + "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",
  36 + "name": "Clear Temp Alarm",
  37 + "debugMode": false,
  38 + "configuration": {
  39 + "alarmType": "High Temperature",
  40 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;"
  41 + }
  42 + },
  43 + {
  44 + "additionalInfo": {
  45 + "layoutX": 930,
  46 + "layoutY": 131
  47 + },
  48 + "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",
  49 + "name": "Create Humidity Alarm",
  50 + "debugMode": false,
  51 + "configuration": {
  52 + "alarmType": "Low Humidity",
  53 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;",
  54 + "severity": "MINOR",
  55 + "propagate": true,
  56 + "useMessageAlarmData": false,
  57 + "relationTypes": []
  58 + }
  59 + },
  60 + {
  61 + "additionalInfo": {
  62 + "layoutX": 929,
  63 + "layoutY": 275
  64 + },
  65 + "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",
  66 + "name": "Clear Humidity Alarm",
  67 + "debugMode": false,
  68 + "configuration": {
  69 + "alarmType": "Low Humidity",
  70 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;"
  71 + }
  72 + },
  73 + {
  74 + "additionalInfo": {
  75 + "layoutX": 586,
  76 + "layoutY": 148
  77 + },
  78 + "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode",
  79 + "name": "Check Alarms",
  80 + "debugMode": true,
  81 + "configuration": {
  82 + "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;"
  83 + }
  84 + },
  85 + {
  86 + "additionalInfo": {
  87 + "layoutX": 321,
  88 + "layoutY": 149
  89 + },
  90 + "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode",
  91 + "name": "Fetch Configuration",
  92 + "debugMode": true,
  93 + "configuration": {
  94 + "clientAttributeNames": [],
  95 + "sharedAttributeNames": [],
  96 + "serverAttributeNames": [
  97 + "alarmTemperature",
  98 + "thresholdTemperature",
  99 + "alarmHumidity",
  100 + "thresholdHumidity"
  101 + ],
  102 + "latestTsKeyNames": [],
  103 + "tellFailureIfAbsent": false,
  104 + "getLatestValueWithTs": false
  105 + }
  106 + }
  107 + ],
  108 + "connections": [
  109 + {
  110 + "fromIndex": 4,
  111 + "toIndex": 0,
  112 + "type": "NewTempAlarm"
  113 + },
  114 + {
  115 + "fromIndex": 4,
  116 + "toIndex": 1,
  117 + "type": "ClearTempAlarm"
  118 + },
  119 + {
  120 + "fromIndex": 4,
  121 + "toIndex": 2,
  122 + "type": "NewHumidityAlarm"
  123 + },
  124 + {
  125 + "fromIndex": 4,
  126 + "toIndex": 3,
  127 + "type": "ClearHumidityAlarm"
  128 + },
  129 + {
  130 + "fromIndex": 5,
  131 + "toIndex": 4,
  132 + "type": "Success"
  133 + }
  134 + ],
  135 + "ruleChainConnections": null
  136 + }
  137 +}
@@ -123,3 +123,28 @@ BEGIN @@ -123,3 +123,28 @@ BEGIN
123 END LOOP; 123 END LOOP;
124 END 124 END
125 $$; 125 $$;
  126 +
  127 +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
  128 + LANGUAGE plpgsql AS
  129 +$$
  130 +DECLARE
  131 + ttl_ts bigint;
  132 + debug_ttl_ts bigint;
  133 + ttl_deleted_count bigint DEFAULT 0;
  134 + debug_ttl_deleted_count bigint DEFAULT 0;
  135 +BEGIN
  136 + IF ttl > 0 THEN
  137 + ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
  138 + EXECUTE format(
  139 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
  140 + END IF;
  141 + IF debug_ttl > 0 THEN
  142 + debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
  143 + EXECUTE format(
  144 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
  145 + END IF;
  146 + RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
  147 + RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
  148 + deleted := ttl_deleted_count + debug_ttl_deleted_count;
  149 +END
  150 +$$;
@@ -32,6 +32,7 @@ import lombok.Setter; @@ -32,6 +32,7 @@ import lombok.Setter;
32 import lombok.extern.slf4j.Slf4j; 32 import lombok.extern.slf4j.Slf4j;
33 import org.springframework.beans.factory.annotation.Autowired; 33 import org.springframework.beans.factory.annotation.Autowired;
34 import org.springframework.beans.factory.annotation.Value; 34 import org.springframework.beans.factory.annotation.Value;
  35 +import org.springframework.context.annotation.Lazy;
35 import org.springframework.data.redis.core.RedisTemplate; 36 import org.springframework.data.redis.core.RedisTemplate;
36 import org.springframework.scheduling.annotation.Scheduled; 37 import org.springframework.scheduling.annotation.Scheduled;
37 import org.springframework.stereotype.Component; 38 import org.springframework.stereotype.Component;
@@ -233,6 +234,7 @@ public class ActorSystemContext { @@ -233,6 +234,7 @@ public class ActorSystemContext {
233 /** 234 /**
234 * The following Service will be null if we operate in tb-core mode 235 * The following Service will be null if we operate in tb-core mode
235 */ 236 */
  237 + @Lazy
236 @Autowired(required = false) 238 @Autowired(required = false)
237 @Getter 239 @Getter
238 private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService; 240 private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService;
@@ -240,6 +242,7 @@ public class ActorSystemContext { @@ -240,6 +242,7 @@ public class ActorSystemContext {
240 /** 242 /**
241 * The following Service will be null if we operate in tb-rule-engine mode 243 * The following Service will be null if we operate in tb-rule-engine mode
242 */ 244 */
  245 + @Lazy
243 @Autowired(required = false) 246 @Autowired(required = false)
244 @Getter 247 @Getter
245 private TbCoreDeviceRpcService tbCoreDeviceRpcService; 248 private TbCoreDeviceRpcService tbCoreDeviceRpcService;
@@ -43,7 +43,7 @@ public class QueueController extends BaseController { @@ -43,7 +43,7 @@ public class QueueController extends BaseController {
43 ServiceType type = ServiceType.valueOf(serviceType); 43 ServiceType type = ServiceType.valueOf(serviceType);
44 switch (type) { 44 switch (type) {
45 case TB_RULE_ENGINE: 45 case TB_RULE_ENGINE:
46 - return Arrays.asList("HighPriority", "Main"); 46 + return Arrays.asList("Main", "HighPriority", "SequentialByOriginator");
47 default: 47 default:
48 return Collections.emptyList(); 48 return Collections.emptyList();
49 } 49 }
@@ -161,15 +161,15 @@ public class RuleChainController extends BaseController { @@ -161,15 +161,15 @@ public class RuleChainController extends BaseController {
161 TenantId tenantId = getCurrentUser().getTenantId(); 161 TenantId tenantId = getCurrentUser().getTenantId();
162 RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); 162 RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
163 if (ruleChainService.setRootRuleChain(getTenantId(), ruleChainId)) { 163 if (ruleChainService.setRootRuleChain(getTenantId(), ruleChainId)) {
  164 + if (previousRootRuleChain != null) {
  165 + previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId());
164 166
165 - previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId());  
166 -  
167 - tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),  
168 - ComponentLifecycleEvent.UPDATED);  
169 -  
170 - logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,  
171 - null, ActionType.UPDATED, null); 167 + tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
  168 + ComponentLifecycleEvent.UPDATED);
172 169
  170 + logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
  171 + null, ActionType.UPDATED, null);
  172 + }
173 ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId); 173 ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId);
174 174
175 tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), 175 tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(),
@@ -397,11 +397,6 @@ public class TelemetryController extends BaseController { @@ -397,11 +397,6 @@ public class TelemetryController extends BaseController {
397 @Override 397 @Override
398 public void onSuccess(@Nullable Void tmp) { 398 public void onSuccess(@Nullable Void tmp) {
399 logAttributesUpdated(user, entityId, scope, attributes, null); 399 logAttributesUpdated(user, entityId, scope, attributes, null);
400 - if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {  
401 - DeviceId deviceId = new DeviceId(entityId.getId());  
402 - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(  
403 - user.getTenantId(), deviceId, scope, attributes), null);  
404 - }  
405 result.setResult(new ResponseEntity(HttpStatus.OK)); 400 result.setResult(new ResponseEntity(HttpStatus.OK));
406 } 401 }
407 402
@@ -23,8 +23,8 @@ import org.springframework.context.ApplicationContext; @@ -23,8 +23,8 @@ import org.springframework.context.ApplicationContext;
23 import org.springframework.context.annotation.Profile; 23 import org.springframework.context.annotation.Profile;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.thingsboard.server.service.component.ComponentDiscoveryService; 25 import org.thingsboard.server.service.component.ComponentDiscoveryService;
26 -import org.thingsboard.server.service.install.DatabaseTsUpgradeService;  
27 import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService; 26 import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService;
  27 +import org.thingsboard.server.service.install.DatabaseTsUpgradeService;
28 import org.thingsboard.server.service.install.EntityDatabaseSchemaService; 28 import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
29 import org.thingsboard.server.service.install.SystemDataLoaderService; 29 import org.thingsboard.server.service.install.SystemDataLoaderService;
30 import org.thingsboard.server.service.install.TsDatabaseSchemaService; 30 import org.thingsboard.server.service.install.TsDatabaseSchemaService;
@@ -25,24 +25,32 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -25,24 +25,32 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
25 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
26 import org.thingsboard.server.common.data.AdminSettings; 26 import org.thingsboard.server.common.data.AdminSettings;
27 import org.thingsboard.server.common.data.Customer; 27 import org.thingsboard.server.common.data.Customer;
  28 +import org.thingsboard.server.common.data.DataConstants;
28 import org.thingsboard.server.common.data.Device; 29 import org.thingsboard.server.common.data.Device;
29 import org.thingsboard.server.common.data.Tenant; 30 import org.thingsboard.server.common.data.Tenant;
30 import org.thingsboard.server.common.data.User; 31 import org.thingsboard.server.common.data.User;
31 import org.thingsboard.server.common.data.id.CustomerId; 32 import org.thingsboard.server.common.data.id.CustomerId;
  33 +import org.thingsboard.server.common.data.id.DeviceId;
32 import org.thingsboard.server.common.data.id.TenantId; 34 import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  36 +import org.thingsboard.server.common.data.kv.BooleanDataEntry;
  37 +import org.thingsboard.server.common.data.kv.DoubleDataEntry;
  38 +import org.thingsboard.server.common.data.kv.LongDataEntry;
33 import org.thingsboard.server.common.data.security.Authority; 39 import org.thingsboard.server.common.data.security.Authority;
34 import org.thingsboard.server.common.data.security.DeviceCredentials; 40 import org.thingsboard.server.common.data.security.DeviceCredentials;
35 import org.thingsboard.server.common.data.security.UserCredentials; 41 import org.thingsboard.server.common.data.security.UserCredentials;
36 import org.thingsboard.server.common.data.widget.WidgetsBundle; 42 import org.thingsboard.server.common.data.widget.WidgetsBundle;
  43 +import org.thingsboard.server.dao.attributes.AttributesService;
37 import org.thingsboard.server.dao.customer.CustomerService; 44 import org.thingsboard.server.dao.customer.CustomerService;
38 import org.thingsboard.server.dao.device.DeviceCredentialsService; 45 import org.thingsboard.server.dao.device.DeviceCredentialsService;
39 import org.thingsboard.server.dao.device.DeviceService; 46 import org.thingsboard.server.dao.device.DeviceService;
40 -import org.thingsboard.server.dao.model.ModelConstants;  
41 import org.thingsboard.server.dao.settings.AdminSettingsService; 47 import org.thingsboard.server.dao.settings.AdminSettingsService;
42 import org.thingsboard.server.dao.tenant.TenantService; 48 import org.thingsboard.server.dao.tenant.TenantService;
43 import org.thingsboard.server.dao.user.UserService; 49 import org.thingsboard.server.dao.user.UserService;
44 import org.thingsboard.server.dao.widget.WidgetsBundleService; 50 import org.thingsboard.server.dao.widget.WidgetsBundleService;
45 51
  52 +import java.util.Arrays;
  53 +
46 @Service 54 @Service
47 @Profile("install") 55 @Profile("install")
48 @Slf4j 56 @Slf4j
@@ -77,6 +85,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -77,6 +85,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
77 private DeviceService deviceService; 85 private DeviceService deviceService;
78 86
79 @Autowired 87 @Autowired
  88 + private AttributesService attributesService;
  89 +
  90 + @Autowired
80 private DeviceCredentialsService deviceCredentialsService; 91 private DeviceCredentialsService deviceCredentialsService;
81 92
82 @Bean 93 @Bean
@@ -120,7 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -120,7 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
120 demoTenant.setRegion("Global"); 131 demoTenant.setRegion("Global");
121 demoTenant.setTitle("Tenant"); 132 demoTenant.setTitle("Tenant");
122 demoTenant = tenantService.saveTenant(demoTenant); 133 demoTenant = tenantService.saveTenant(demoTenant);
123 - installScripts.createDefaultRuleChains(demoTenant.getId()); 134 + installScripts.loadDemoRuleChains(demoTenant.getId());
124 createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant"); 135 createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
125 136
126 Customer customerA = new Customer(); 137 Customer customerA = new Customer();
@@ -152,6 +163,25 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -152,6 +163,25 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
152 createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + 163 createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
153 "Raspberry Pi GPIO control sample application"); 164 "Raspberry Pi GPIO control sample application");
154 165
  166 + DeviceId t1Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  167 + DeviceId t2Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  168 +
  169 + attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
  170 + Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
  171 + new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)),
  172 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),
  173 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),
  174 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)),
  175 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50))));
  176 +
  177 + attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE,
  178 + Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)),
  179 + new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)),
  180 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),
  181 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),
  182 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)),
  183 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30))));
  184 +
155 installScripts.loadDashboards(demoTenant.getId(), null); 185 installScripts.loadDashboards(demoTenant.getId(), null);
156 } 186 }
157 187
@@ -24,6 +24,7 @@ import org.springframework.util.StringUtils; @@ -24,6 +24,7 @@ import org.springframework.util.StringUtils;
24 import org.thingsboard.server.common.data.Dashboard; 24 import org.thingsboard.server.common.data.Dashboard;
25 import org.thingsboard.server.common.data.id.CustomerId; 25 import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.RuleChainId;
27 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.common.data.rule.RuleChain; 29 import org.thingsboard.server.common.data.rule.RuleChain;
29 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 30 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@@ -182,4 +183,30 @@ public class InstallScripts { @@ -182,4 +183,30 @@ public class InstallScripts {
182 } 183 }
183 184
184 185
  186 + public void loadDemoRuleChains(TenantId tenantId) throws Exception {
  187 + Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR);
  188 + try {
  189 + JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile());
  190 + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
  191 + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
  192 + ruleChain.setTenantId(tenantId);
  193 + ruleChain = ruleChainService.saveRuleChain(ruleChain);
  194 + ruleChainMetaData.setRuleChainId(ruleChain.getId());
  195 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  196 +
  197 + JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile());
  198 + RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class);
  199 + RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class);
  200 +
  201 + RuleChainId thermostatsRuleChainId = ruleChain.getId();
  202 + rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId));
  203 + rootChain.setTenantId(tenantId);
  204 + rootChain = ruleChainService.saveRuleChain(rootChain);
  205 + rootChainMetaData.setRuleChainId(rootChain.getId());
  206 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData);
  207 + } catch (Exception e) {
  208 + log.error("Unable to load dashboard from json", e);
  209 + throw new RuntimeException("Unable to load dashboard from json", e);
  210 + }
  211 + }
185 } 212 }
@@ -225,6 +225,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -225,6 +225,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
225 conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN isolated_tb_core boolean DEFAULT (false), ADD COLUMN isolated_tb_rule_engine boolean DEFAULT (false)"); 225 conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN isolated_tb_core boolean DEFAULT (false), ADD COLUMN isolated_tb_rule_engine boolean DEFAULT (false)");
226 } catch (Exception e) { 226 } catch (Exception e) {
227 } 227 }
  228 + try {
  229 + long ts = System.currentTimeMillis();
  230 + conn.createStatement().execute("ALTER TABLE event ADD COLUMN ts bigint DEFAULT " + ts + ";"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  231 + } catch (Exception e) {
  232 + }
228 log.info("Schema updated."); 233 log.info("Schema updated.");
229 } 234 }
230 break; 235 break;
@@ -52,6 +52,7 @@ import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -52,6 +52,7 @@ import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
52 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; 52 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
53 53
54 import javax.annotation.PostConstruct; 54 import javax.annotation.PostConstruct;
  55 +import javax.annotation.PreDestroy;
55 import java.util.List; 56 import java.util.List;
56 import java.util.Optional; 57 import java.util.Optional;
57 import java.util.UUID; 58 import java.util.UUID;
@@ -98,6 +99,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -98,6 +99,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
98 super.init("tb-core-consumer", "tb-core-notifications-consumer"); 99 super.init("tb-core-consumer", "tb-core-notifications-consumer");
99 } 100 }
100 101
  102 + @PreDestroy
  103 + public void destroy(){
  104 + super.destroy();
  105 + }
  106 +
101 @Override 107 @Override
102 public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { 108 public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
103 if (partitionChangeEvent.getServiceType().equals(getServiceType())) { 109 if (partitionChangeEvent.getServiceType().equals(getServiceType())) {
@@ -117,11 +123,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -117,11 +123,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
117 } 123 }
118 ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect( 124 ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect(
119 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); 125 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
120 - ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> failedMap = new ConcurrentHashMap<>();  
121 CountDownLatch processingTimeoutLatch = new CountDownLatch(1); 126 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
  127 + TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>(
  128 + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
122 pendingMap.forEach((id, msg) -> { 129 pendingMap.forEach((id, msg) -> {
123 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); 130 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
124 - TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap); 131 + TbCallback callback = new TbPackCallback<>(id, ctx);
125 try { 132 try {
126 ToCoreMsg toCoreMsg = msg.getValue(); 133 ToCoreMsg toCoreMsg = msg.getValue();
127 if (toCoreMsg.hasToSubscriptionMgrMsg()) { 134 if (toCoreMsg.hasToSubscriptionMgrMsg()) {
@@ -147,8 +154,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -147,8 +154,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
147 } 154 }
148 }); 155 });
149 if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { 156 if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) {
150 - pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));  
151 - failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); 157 + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));
  158 + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
152 } 159 }
153 mainConsumer.commit(); 160 mainConsumer.commit();
154 } catch (Exception e) { 161 } catch (Exception e) {
@@ -31,7 +31,6 @@ import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -31,7 +31,6 @@ import org.thingsboard.server.common.msg.queue.ServiceQueue;
31 import org.thingsboard.server.common.msg.queue.ServiceType; 31 import org.thingsboard.server.common.msg.queue.ServiceType;
32 import org.thingsboard.server.common.msg.queue.TbCallback; 32 import org.thingsboard.server.common.msg.queue.TbCallback;
33 import org.thingsboard.server.common.msg.queue.TbMsgCallback; 33 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
34 -import org.thingsboard.server.gen.transport.TransportProtos;  
35 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 34 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
36 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; 35 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
37 import org.thingsboard.server.queue.TbQueueConsumer; 36 import org.thingsboard.server.queue.TbQueueConsumer;
@@ -64,9 +63,6 @@ import java.util.concurrent.ConcurrentMap; @@ -64,9 +63,6 @@ import java.util.concurrent.ConcurrentMap;
64 import java.util.concurrent.ExecutorService; 63 import java.util.concurrent.ExecutorService;
65 import java.util.concurrent.Executors; 64 import java.util.concurrent.Executors;
66 import java.util.concurrent.TimeUnit; 65 import java.util.concurrent.TimeUnit;
67 -import java.util.function.BiConsumer;  
68 -import java.util.function.Function;  
69 -import java.util.stream.Collectors;  
70 66
71 @Service 67 @Service
72 @TbRuleEngineComponent 68 @TbRuleEngineComponent
@@ -116,10 +112,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -116,10 +112,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
116 112
117 @PreDestroy 113 @PreDestroy
118 public void stop() { 114 public void stop() {
  115 + super.destroy();
119 if (submitExecutor != null) { 116 if (submitExecutor != null) {
120 submitExecutor.shutdownNow(); 117 submitExecutor.shutdownNow();
121 } 118 }
122 -  
123 ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config)); 119 ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config));
124 } 120 }
125 121
@@ -156,7 +152,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @@ -156,7 +152,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
156 submitStrategy.init(msgs); 152 submitStrategy.init(msgs);
157 153
158 while (!stopped) { 154 while (!stopped) {
159 - ProcessingAttemptContext ctx = new ProcessingAttemptContext(submitStrategy); 155 + TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
160 submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> { 156 submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
161 log.trace("[{}] Creating callback for message: {}", id, msg.getValue()); 157 log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
162 ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); 158 ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
@@ -79,7 +79,7 @@ public class TbCoreConsumerStats { @@ -79,7 +79,7 @@ public class TbCoreConsumerStats {
79 public void printStats() { 79 public void printStats() {
80 int total = totalCounter.getAndSet(0); 80 int total = totalCounter.getAndSet(0);
81 if (total > 0) { 81 if (total > 0) {
82 - log.info("Transport total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" + 82 + log.info("Total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" +
83 " deviceState [{}] subMgr [{}] coreNfs [{}]", 83 " deviceState [{}] subMgr [{}] coreNfs [{}]",
84 total, sessionEventCounter.getAndSet(0), 84 total, sessionEventCounter.getAndSet(0),
85 getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0), 85 getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0),
@@ -18,21 +18,17 @@ package org.thingsboard.server.service.queue; @@ -18,21 +18,17 @@ package org.thingsboard.server.service.queue;
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.thingsboard.server.common.data.id.TenantId; 19 import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.msg.queue.RuleEngineException; 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
21 -import org.thingsboard.server.common.msg.queue.RuleNodeException;  
22 -import org.thingsboard.server.common.msg.queue.TbCallback;  
23 import org.thingsboard.server.common.msg.queue.TbMsgCallback; 21 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
24 22
25 import java.util.UUID; 23 import java.util.UUID;
26 -import java.util.concurrent.ConcurrentMap;  
27 -import java.util.concurrent.CountDownLatch;  
28 24
29 @Slf4j 25 @Slf4j
30 public class TbMsgPackCallback implements TbMsgCallback { 26 public class TbMsgPackCallback implements TbMsgCallback {
31 private final UUID id; 27 private final UUID id;
32 private final TenantId tenantId; 28 private final TenantId tenantId;
33 - private final ProcessingAttemptContext ctx; 29 + private final TbMsgPackProcessingContext ctx;
34 30
35 - public TbMsgPackCallback(UUID id, TenantId tenantId, ProcessingAttemptContext ctx) { 31 + public TbMsgPackCallback(UUID id, TenantId tenantId, TbMsgPackProcessingContext ctx) {
36 this.id = id; 32 this.id = id;
37 this.tenantId = tenantId; 33 this.tenantId = tenantId;
38 this.ctx = ctx; 34 this.ctx = ctx;
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java renamed from application/src/main/java/org/thingsboard/server/service/queue/ProcessingAttemptContext.java
@@ -29,7 +29,7 @@ import java.util.concurrent.CountDownLatch; @@ -29,7 +29,7 @@ import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.TimeUnit; 29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.atomic.AtomicInteger; 30 import java.util.concurrent.atomic.AtomicInteger;
31 31
32 -public class ProcessingAttemptContext { 32 +public class TbMsgPackProcessingContext {
33 33
34 private final TbRuleEngineSubmitStrategy submitStrategy; 34 private final TbRuleEngineSubmitStrategy submitStrategy;
35 35
@@ -44,7 +44,7 @@ public class ProcessingAttemptContext { @@ -44,7 +44,7 @@ public class ProcessingAttemptContext {
44 @Getter 44 @Getter
45 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>(); 45 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>();
46 46
47 - public ProcessingAttemptContext(TbRuleEngineSubmitStrategy submitStrategy) { 47 + public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
48 this.submitStrategy = submitStrategy; 48 this.submitStrategy = submitStrategy;
49 this.pendingMap = submitStrategy.getPendingMap(); 49 this.pendingMap = submitStrategy.getPendingMap();
50 this.pendingCount = new AtomicInteger(pendingMap.size()); 50 this.pendingCount = new AtomicInteger(pendingMap.size());
@@ -19,44 +19,26 @@ import lombok.extern.slf4j.Slf4j; @@ -19,44 +19,26 @@ import lombok.extern.slf4j.Slf4j;
19 import org.thingsboard.server.common.msg.queue.TbCallback; 19 import org.thingsboard.server.common.msg.queue.TbCallback;
20 20
21 import java.util.UUID; 21 import java.util.UUID;
22 -import java.util.concurrent.ConcurrentMap;  
23 -import java.util.concurrent.CountDownLatch;  
24 22
25 @Slf4j 23 @Slf4j
26 public class TbPackCallback<T> implements TbCallback { 24 public class TbPackCallback<T> implements TbCallback {
27 - private final CountDownLatch processingTimeoutLatch;  
28 - private final ConcurrentMap<UUID, T> ackMap;  
29 - private final ConcurrentMap<UUID, T> failedMap; 25 + private final TbPackProcessingContext<T> ctx;
30 private final UUID id; 26 private final UUID id;
31 27
32 - public TbPackCallback(UUID id,  
33 - CountDownLatch processingTimeoutLatch,  
34 - ConcurrentMap<UUID, T> ackMap,  
35 - ConcurrentMap<UUID, T> failedMap) { 28 + public TbPackCallback(UUID id, TbPackProcessingContext<T> ctx) {
36 this.id = id; 29 this.id = id;
37 - this.processingTimeoutLatch = processingTimeoutLatch;  
38 - this.ackMap = ackMap;  
39 - this.failedMap = failedMap; 30 + this.ctx = ctx;
40 } 31 }
41 32
42 @Override 33 @Override
43 public void onSuccess() { 34 public void onSuccess() {
44 log.trace("[{}] ON SUCCESS", id); 35 log.trace("[{}] ON SUCCESS", id);
45 - T msg = ackMap.remove(id);  
46 - if (msg != null && ackMap.isEmpty()) {  
47 - processingTimeoutLatch.countDown();  
48 - } 36 + ctx.onSuccess(id);
49 } 37 }
50 38
51 @Override 39 @Override
52 public void onFailure(Throwable t) { 40 public void onFailure(Throwable t) {
53 log.trace("[{}] ON FAILURE", id, t); 41 log.trace("[{}] ON FAILURE", id, t);
54 - T msg = ackMap.remove(id);  
55 - if (msg != null) {  
56 - failedMap.put(id, msg);  
57 - }  
58 - if (ackMap.isEmpty()) {  
59 - processingTimeoutLatch.countDown();  
60 - } 42 + ctx.onFailure(id, t);
61 } 43 }
62 } 44 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +
  20 +import java.util.UUID;
  21 +import java.util.concurrent.ConcurrentMap;
  22 +import java.util.concurrent.CountDownLatch;
  23 +import java.util.concurrent.TimeUnit;
  24 +import java.util.concurrent.atomic.AtomicInteger;
  25 +
  26 +@Slf4j
  27 +public class TbPackProcessingContext<T> {
  28 +
  29 + private final AtomicInteger pendingCount;
  30 + private final CountDownLatch processingTimeoutLatch;
  31 + private final ConcurrentMap<UUID, T> ackMap;
  32 + private final ConcurrentMap<UUID, T> failedMap;
  33 +
  34 + public TbPackProcessingContext(CountDownLatch processingTimeoutLatch,
  35 + ConcurrentMap<UUID, T> ackMap,
  36 + ConcurrentMap<UUID, T> failedMap) {
  37 + this.processingTimeoutLatch = processingTimeoutLatch;
  38 + this.pendingCount = new AtomicInteger(ackMap.size());
  39 + this.ackMap = ackMap;
  40 + this.failedMap = failedMap;
  41 + }
  42 +
  43 + public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
  44 + return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  45 + }
  46 +
  47 + public void onSuccess(UUID id) {
  48 + boolean empty = false;
  49 + T msg = ackMap.remove(id);
  50 + if (msg != null) {
  51 + empty = pendingCount.decrementAndGet() == 0;
  52 + }
  53 + if (empty) {
  54 + processingTimeoutLatch.countDown();
  55 + } else {
  56 + if (log.isTraceEnabled()) {
  57 + log.trace("Items left: {}", ackMap.size());
  58 + for (T t : ackMap.values()) {
  59 + log.trace("left item: {}", t);
  60 + }
  61 + }
  62 + }
  63 + }
  64 +
  65 + public void onFailure(UUID id, Throwable t) {
  66 + boolean empty = false;
  67 + T msg = ackMap.remove(id);
  68 + if (msg != null) {
  69 + empty = pendingCount.decrementAndGet() == 0;
  70 + failedMap.put(id, msg);
  71 + if (log.isTraceEnabled()) {
  72 + log.trace("Items left: {}", ackMap.size());
  73 + for (T v : ackMap.values()) {
  74 + log.trace("left item: {}", v);
  75 + }
  76 + }
  77 + }
  78 + if (empty) {
  79 + processingTimeoutLatch.countDown();
  80 + }
  81 + }
  82 +
  83 + public ConcurrentMap<UUID, T> getAckMap() {
  84 + return ackMap;
  85 + }
  86 +
  87 + public ConcurrentMap<UUID, T> getFailedMap() {
  88 + return failedMap;
  89 + }
  90 +}
@@ -23,11 +23,13 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -23,11 +23,13 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
23 import org.thingsboard.server.actors.ActorSystemContext; 23 import org.thingsboard.server.actors.ActorSystemContext;
24 import org.thingsboard.server.common.msg.queue.ServiceType; 24 import org.thingsboard.server.common.msg.queue.ServiceType;
25 import org.thingsboard.server.common.msg.queue.TbCallback; 25 import org.thingsboard.server.common.msg.queue.TbCallback;
  26 +import org.thingsboard.server.gen.transport.TransportProtos;
26 import org.thingsboard.server.queue.TbQueueConsumer; 27 import org.thingsboard.server.queue.TbQueueConsumer;
27 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 28 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
28 import org.thingsboard.server.queue.discovery.PartitionChangeEvent; 29 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
29 import org.thingsboard.server.service.encoding.DataDecodingEncodingService; 30 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
30 import org.thingsboard.server.service.queue.TbPackCallback; 31 import org.thingsboard.server.service.queue.TbPackCallback;
  32 +import org.thingsboard.server.service.queue.TbPackProcessingContext;
31 33
32 import javax.annotation.PreDestroy; 34 import javax.annotation.PreDestroy;
33 import java.util.List; 35 import java.util.List;
@@ -92,11 +94,12 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene @@ -92,11 +94,12 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
92 } 94 }
93 ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = msgs.stream().collect( 95 ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = msgs.stream().collect(
94 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); 96 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
95 - ConcurrentMap<UUID, TbProtoQueueMsg<N>> failedMap = new ConcurrentHashMap<>();  
96 CountDownLatch processingTimeoutLatch = new CountDownLatch(1); 97 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
  98 + TbPackProcessingContext<TbProtoQueueMsg<N>> ctx = new TbPackProcessingContext<>(
  99 + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
97 pendingMap.forEach((id, msg) -> { 100 pendingMap.forEach((id, msg) -> {
98 log.trace("[{}] Creating notification callback for message: {}", id, msg.getValue()); 101 log.trace("[{}] Creating notification callback for message: {}", id, msg.getValue());
99 - TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap); 102 + TbCallback callback = new TbPackCallback<>(id, ctx);
100 try { 103 try {
101 handleNotification(id, msg, callback); 104 handleNotification(id, msg, callback);
102 } catch (Throwable e) { 105 } catch (Throwable e) {
@@ -105,8 +108,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene @@ -105,8 +108,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
105 } 108 }
106 }); 109 });
107 if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { 110 if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) {
108 - pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue()));  
109 - failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue())); 111 + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue()));
  112 + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue()));
110 } 113 }
111 nfConsumer.commit(); 114 nfConsumer.commit();
112 } catch (Exception e) { 115 } catch (Exception e) {
@@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.msg.queue.RuleEngineException; 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
21 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 21 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
22 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 22 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
23 -import org.thingsboard.server.service.queue.ProcessingAttemptContext; 23 +import org.thingsboard.server.service.queue.TbMsgPackProcessingContext;
24 24
25 import java.util.UUID; 25 import java.util.UUID;
26 import java.util.concurrent.ConcurrentMap; 26 import java.util.concurrent.ConcurrentMap;
@@ -32,9 +32,9 @@ public class TbRuleEngineProcessingResult { @@ -32,9 +32,9 @@ public class TbRuleEngineProcessingResult {
32 @Getter 32 @Getter
33 private final boolean timeout; 33 private final boolean timeout;
34 @Getter 34 @Getter
35 - private final ProcessingAttemptContext ctx; 35 + private final TbMsgPackProcessingContext ctx;
36 36
37 - public TbRuleEngineProcessingResult(boolean timeout, ProcessingAttemptContext ctx) { 37 + public TbRuleEngineProcessingResult(boolean timeout, TbMsgPackProcessingContext ctx) {
38 this.timeout = timeout; 38 this.timeout = timeout;
39 this.ctx = ctx; 39 this.ctx = ctx;
40 this.success = !timeout && ctx.getPendingMap().isEmpty() && ctx.getFailedMap().isEmpty(); 40 this.success = !timeout && ctx.getPendingMap().isEmpty() && ctx.getFailedMap().isEmpty();
@@ -29,9 +29,9 @@ public class TbRuleEngineSubmitStrategyFactory { @@ -29,9 +29,9 @@ public class TbRuleEngineSubmitStrategyFactory {
29 return new BurstTbRuleEngineSubmitStrategy(name); 29 return new BurstTbRuleEngineSubmitStrategy(name);
30 case "BATCH": 30 case "BATCH":
31 return new BatchTbRuleEngineSubmitStrategy(name, configuration.getBatchSize()); 31 return new BatchTbRuleEngineSubmitStrategy(name, configuration.getBatchSize());
32 - case "SEQUENTIAL_WITHIN_ORIGINATOR": 32 + case "SEQUENTIAL_BY_ORIGINATOR":
33 return new SequentialByOriginatorIdTbRuleEngineSubmitStrategy(name); 33 return new SequentialByOriginatorIdTbRuleEngineSubmitStrategy(name);
34 - case "SEQUENTIAL_WITHIN_TENANT": 34 + case "SEQUENTIAL_BY_TENANT":
35 return new SequentialByTenantIdTbRuleEngineSubmitStrategy(name); 35 return new SequentialByTenantIdTbRuleEngineSubmitStrategy(name);
36 case "SEQUENTIAL": 36 case "SEQUENTIAL":
37 return new SequentialTbRuleEngineSubmitStrategy(name); 37 return new SequentialTbRuleEngineSubmitStrategy(name);
@@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicInteger;
46 @Service 46 @Service
47 public class RemoteJsInvokeService extends AbstractJsInvokeService { 47 public class RemoteJsInvokeService extends AbstractJsInvokeService {
48 48
49 - @Value("${js.remote.max_requests_timeout}") 49 + @Value("${queue.js.max_requests_timeout}")
50 private long maxRequestsTimeout; 50 private long maxRequestsTimeout;
51 51
52 @Getter 52 @Getter
@@ -345,6 +345,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -345,6 +345,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
345 keys.forEach(key -> subState.put(key, 0L)); 345 keys.forEach(key -> subState.put(key, 0L));
346 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); 346 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
347 347
  348 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  349 +
348 TbAttributeSubscription sub = TbAttributeSubscription.builder() 350 TbAttributeSubscription sub = TbAttributeSubscription.builder()
349 .serviceId(serviceId) 351 .serviceId(serviceId)
350 .sessionId(sessionId) 352 .sessionId(sessionId)
@@ -353,7 +355,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -353,7 +355,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
353 .entityId(entityId) 355 .entityId(entityId)
354 .allKeys(false) 356 .allKeys(false)
355 .keyStates(subState) 357 .keyStates(subState)
356 - .scope(TbAttributeSubscriptionScope.valueOf(cmd.getScope())).build(); 358 + .scope(scope).build();
357 subService.addSubscription(sub); 359 subService.addSubscription(sub);
358 } 360 }
359 361
@@ -440,6 +442,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -440,6 +442,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
440 Map<String, Long> subState = new HashMap<>(attributesData.size()); 442 Map<String, Long> subState = new HashMap<>(attributesData.size());
441 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); 443 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
442 444
  445 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  446 +
443 TbAttributeSubscription sub = TbAttributeSubscription.builder() 447 TbAttributeSubscription sub = TbAttributeSubscription.builder()
444 .serviceId(serviceId) 448 .serviceId(serviceId)
445 .sessionId(sessionId) 449 .sessionId(sessionId)
@@ -448,7 +452,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -448,7 +452,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
448 .entityId(entityId) 452 .entityId(entityId)
449 .allKeys(true) 453 .allKeys(true)
450 .keyStates(subState) 454 .keyStates(subState)
451 - .scope(TbAttributeSubscriptionScope.valueOf(cmd.getScope())).build(); 455 + .scope(scope).build();
452 subService.addSubscription(sub); 456 subService.addSubscription(sub);
453 } 457 }
454 458
application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/AbstractTimeseriesCleanUpService.java
@@ -17,47 +17,27 @@ package org.thingsboard.server.service.ttl; @@ -17,47 +17,27 @@ package org.thingsboard.server.service.ttl;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Value; 19 import org.springframework.beans.factory.annotation.Value;
20 -import org.springframework.scheduling.annotation.Scheduled;  
21 -import org.thingsboard.server.dao.util.PsqlTsAnyDao; 20 +import org.thingsboard.server.dao.util.PsqlDao;
22 21
23 import java.sql.Connection; 22 import java.sql.Connection;
24 -import java.sql.DriverManager;  
25 import java.sql.ResultSet; 23 import java.sql.ResultSet;
26 import java.sql.SQLException; 24 import java.sql.SQLException;
27 import java.sql.SQLWarning; 25 import java.sql.SQLWarning;
28 import java.sql.Statement; 26 import java.sql.Statement;
29 27
30 -@PsqlTsAnyDao  
31 -@Slf4j  
32 -public abstract class AbstractTimeseriesCleanUpService {  
33 -  
34 - @Value("${sql.ttl.ts_key_value_ttl}")  
35 - protected long systemTtl;  
36 28
37 - @Value("${sql.ttl.enabled}")  
38 - private boolean ttlTaskExecutionEnabled; 29 +@Slf4j
  30 +@PsqlDao
  31 +public abstract class AbstractCleanUpService {
39 32
40 @Value("${spring.datasource.url}") 33 @Value("${spring.datasource.url}")
41 - private String dbUrl; 34 + protected String dbUrl;
42 35
43 @Value("${spring.datasource.username}") 36 @Value("${spring.datasource.username}")
44 - private String dbUserName; 37 + protected String dbUserName;
45 38
46 @Value("${spring.datasource.password}") 39 @Value("${spring.datasource.password}")
47 - private String dbPassword;  
48 -  
49 - @Scheduled(initialDelayString = "${sql.ttl.execution_interval_ms}", fixedDelayString = "${sql.ttl.execution_interval_ms}")  
50 - public void cleanUp() {  
51 - if (ttlTaskExecutionEnabled) {  
52 - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {  
53 - doCleanUp(conn);  
54 - } catch (SQLException e) {  
55 - log.error("SQLException occurred during TTL task execution ", e);  
56 - }  
57 - }  
58 - }  
59 -  
60 - protected abstract void doCleanUp(Connection connection); 40 + protected String dbPassword;
61 41
62 protected long executeQuery(Connection conn, String query) { 42 protected long executeQuery(Connection conn, String query) {
63 long removed = 0L; 43 long removed = 0L;
@@ -74,7 +54,7 @@ public abstract class AbstractTimeseriesCleanUpService { @@ -74,7 +54,7 @@ public abstract class AbstractTimeseriesCleanUpService {
74 return removed; 54 return removed;
75 } 55 }
76 56
77 - private void getWarnings(Statement statement) throws SQLException { 57 + protected void getWarnings(Statement statement) throws SQLException {
78 SQLWarning warnings = statement.getWarnings(); 58 SQLWarning warnings = statement.getWarnings();
79 if (warnings != null) { 59 if (warnings != null) {
80 log.debug("{}", warnings.getMessage()); 60 log.debug("{}", warnings.getMessage());
@@ -86,4 +66,6 @@ public abstract class AbstractTimeseriesCleanUpService { @@ -86,4 +66,6 @@ public abstract class AbstractTimeseriesCleanUpService {
86 } 66 }
87 } 67 }
88 68
89 -}  
  69 + protected abstract void doCleanUp(Connection connection);
  70 +
  71 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.ttl.events;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.scheduling.annotation.Scheduled;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.dao.util.PsqlDao;
  23 +import org.thingsboard.server.service.ttl.AbstractCleanUpService;
  24 +
  25 +import java.sql.Connection;
  26 +import java.sql.DriverManager;
  27 +import java.sql.SQLException;
  28 +
  29 +@PsqlDao
  30 +@Slf4j
  31 +@Service
  32 +public class EventsCleanUpService extends AbstractCleanUpService {
  33 +
  34 + @Value("${sql.ttl.events.events_ttl}")
  35 + private long ttl;
  36 +
  37 + @Value("${sql.ttl.events.debug_events_ttl}")
  38 + private long debugTtl;
  39 +
  40 + @Value("${sql.ttl.events.enabled}")
  41 + private boolean ttlTaskExecutionEnabled;
  42 +
  43 + @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
  44 + public void cleanUp() {
  45 + if (ttlTaskExecutionEnabled) {
  46 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  47 + doCleanUp(conn);
  48 + } catch (SQLException e) {
  49 + log.error("SQLException occurred during TTL task execution ", e);
  50 + }
  51 + }
  52 + }
  53 +
  54 + @Override
  55 + protected void doCleanUp(Connection connection) {
  56 + long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
  57 + log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
  58 + }
  59 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.ttl.timeseries;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.scheduling.annotation.Scheduled;
  21 +import org.thingsboard.server.dao.util.PsqlTsAnyDao;
  22 +import org.thingsboard.server.service.ttl.AbstractCleanUpService;
  23 +
  24 +import java.sql.Connection;
  25 +import java.sql.DriverManager;
  26 +import java.sql.SQLException;
  27 +
  28 +@PsqlTsAnyDao
  29 +@Slf4j
  30 +public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService {
  31 +
  32 + @Value("${sql.ttl.ts.ts_key_value_ttl}")
  33 + protected long systemTtl;
  34 +
  35 + @Value("${sql.ttl.ts.enabled}")
  36 + private boolean ttlTaskExecutionEnabled;
  37 +
  38 + @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}")
  39 + public void cleanUp() {
  40 + if (ttlTaskExecutionEnabled) {
  41 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  42 + doCleanUp(conn);
  43 + } catch (SQLException e) {
  44 + log.error("SQLException occurred during TTL task execution ", e);
  45 + }
  46 + }
  47 + }
  48 +
  49 +}
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/PsqlTimeseriesCleanUpService.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.service.ttl; 16 +package org.thingsboard.server.service.ttl.timeseries;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Value; 19 import org.springframework.beans.factory.annotation.Value;
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/TimescaleTimeseriesCleanUpService.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.service.ttl; 16 +package org.thingsboard.server.service.ttl.timeseries;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
@@ -181,31 +181,37 @@ cassandra: @@ -181,31 +181,37 @@ cassandra:
181 181
182 # SQL configuration parameters 182 # SQL configuration parameters
183 sql: 183 sql:
184 - # Specify batch size for persisting attribute updates  
185 - attributes:  
186 - batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"  
187 - batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"  
188 - stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"  
189 - ts:  
190 - batch_size: "${SQL_TS_BATCH_SIZE:10000}"  
191 - batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"  
192 - stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"  
193 - ts_latest:  
194 - batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"  
195 - batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"  
196 - stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"  
197 - # Specify whether to remove null characters from strValue of attributes and timeseries before insert  
198 - remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"  
199 - postgres:  
200 - # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.  
201 - ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"  
202 - timescale:  
203 - # Specify Interval size for new data chunks storage.  
204 - chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"  
205 - ttl:  
206 - enabled: "${SQL_TTL_ENABLED:true}"  
207 - execution_interval_ms: "${SQL_TTL_EXECUTION_INTERVAL:86400000}" # Number of miliseconds  
208 - ts_key_value_ttl: "${SQL_TTL_TS_KEY_VALUE_TTL:0}" # Number of seconds 184 + # Specify batch size for persisting attribute updates
  185 + attributes:
  186 + batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
  187 + batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
  188 + stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
  189 + ts:
  190 + batch_size: "${SQL_TS_BATCH_SIZE:10000}"
  191 + batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
  192 + stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
  193 + ts_latest:
  194 + batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
  195 + batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
  196 + stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
  197 + # Specify whether to remove null characters from strValue of attributes and timeseries before insert
  198 + remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
  199 + postgres:
  200 + # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
  201 + ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
  202 + timescale:
  203 + # Specify Interval size for new data chunks storage.
  204 + chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
  205 + ttl:
  206 + ts:
  207 + enabled: "${SQL_TTL_TS_ENABLED:true}"
  208 + execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of miliseconds. The current value corresponds to one day
  209 + ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds
  210 + events:
  211 + enabled: "${SQL_TTL_EVENTS_ENABLED:true}"
  212 + execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of miliseconds. The current value corresponds to one day
  213 + events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds
  214 + debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week
209 215
210 # Actor system parameters 216 # Actor system parameters
211 actors: 217 actors:
@@ -410,8 +416,9 @@ audit-log: @@ -410,8 +416,9 @@ audit-log:
410 password: "${AUDIT_LOG_SINK_PASSWORD:}" 416 password: "${AUDIT_LOG_SINK_PASSWORD:}"
411 417
412 state: 418 state:
413 - defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:10}"  
414 - defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}" 419 + # Should be greater then transport.sessions.report_timeout
  420 + defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}"
  421 + defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}"
415 persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" 422 persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}"
416 423
417 js: 424 js:
@@ -595,7 +602,7 @@ queue: @@ -595,7 +602,7 @@ queue:
595 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" 602 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
596 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" 603 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}"
597 stats: 604 stats:
598 - enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" 605 + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
599 print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" 606 print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}"
600 js: 607 js:
601 # JS Eval request topic 608 # JS Eval request topic
@@ -624,7 +631,7 @@ queue: @@ -624,7 +631,7 @@ queue:
624 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" 631 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
625 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}" 632 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
626 submit-strategy: 633 submit-strategy:
627 - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL 634 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
628 # For BATCH only 635 # For BATCH only
629 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch 636 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
630 processing-strategy: 637 processing-strategy:
@@ -636,10 +643,10 @@ queue: @@ -636,10 +643,10 @@ queue:
636 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" 643 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
637 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" 644 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
638 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" 645 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
639 - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:3}" 646 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
640 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}" 647 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
641 submit-strategy: 648 submit-strategy:
642 - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_WITHIN_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL 649 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
643 # For BATCH only 650 # For BATCH only
644 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch 651 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
645 processing-strategy: 652 processing-strategy:
@@ -648,6 +655,21 @@ queue: @@ -648,6 +655,21 @@ queue:
648 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited 655 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
649 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; 656 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
650 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; 657 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  658 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  659 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  660 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  661 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  662 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  663 + submit-strategy:
  664 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  665 + # For BATCH only
  666 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  667 + processing-strategy:
  668 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  669 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  670 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  671 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  672 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
651 transport: 673 transport:
652 # For high priority notifications that require minimum latency and processing time 674 # For high priority notifications that require minimum latency and processing time
653 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" 675 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
@@ -97,7 +97,8 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr @@ -97,7 +97,8 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
97 assertEquals("4", values.get("key4").get(0).get("value")); 97 assertEquals("4", values.get("key4").get(0).get("value"));
98 } 98 }
99 99
100 - @Test 100 +
  101 +// @Test - Unstable
101 public void testMqttQoSLevel() throws Exception { 102 public void testMqttQoSLevel() throws Exception {
102 String clientId = MqttAsyncClient.generateClientId(); 103 String clientId = MqttAsyncClient.generateClientId();
103 MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId); 104 MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
@@ -109,7 +110,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr @@ -109,7 +110,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
109 client.setCallback(callback); 110 client.setCallback(callback);
110 client.connect(options).waitForCompletion(5000); 111 client.connect(options).waitForCompletion(5000);
111 client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value()); 112 client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
112 - String payload = "{\"key\":\"value\"}"; 113 + String payload = "{\"key\":\"uniqueValue\"}";
113 // TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue. 114 // TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
114 // MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed) 115 // MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
115 // MqttClient <- SUB_ACK <- Transport 116 // MqttClient <- SUB_ACK <- Transport
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.cluster.routing; @@ -17,6 +17,7 @@ package org.thingsboard.server.service.cluster.routing;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.Assert;
20 import org.junit.Before; 21 import org.junit.Before;
21 import org.junit.Test; 22 import org.junit.Test;
22 import org.junit.runner.RunWith; 23 import org.junit.runner.RunWith;
@@ -31,6 +32,8 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; @@ -31,6 +32,8 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
31 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 32 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
32 import org.thingsboard.server.gen.transport.TransportProtos; 33 import org.thingsboard.server.gen.transport.TransportProtos;
33 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; 34 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
  35 +import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
  36 +import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
34 37
35 import java.util.ArrayList; 38 import java.util.ArrayList;
36 import java.util.Collections; 39 import java.util.Collections;
@@ -41,6 +44,7 @@ import java.util.Map; @@ -41,6 +44,7 @@ import java.util.Map;
41 import java.util.stream.Collectors; 44 import java.util.stream.Collectors;
42 45
43 import static org.mockito.Mockito.mock; 46 import static org.mockito.Mockito.mock;
  47 +import static org.mockito.Mockito.when;
44 48
45 @Slf4j 49 @Slf4j
46 @RunWith(MockitoJUnitRunner.class) 50 @RunWith(MockitoJUnitRunner.class)
@@ -52,6 +56,7 @@ public class ConsistentHashParitionServiceTest { @@ -52,6 +56,7 @@ public class ConsistentHashParitionServiceTest {
52 private TbServiceInfoProvider discoveryService; 56 private TbServiceInfoProvider discoveryService;
53 private TenantRoutingInfoService routingInfoService; 57 private TenantRoutingInfoService routingInfoService;
54 private ApplicationEventPublisher applicationEventPublisher; 58 private ApplicationEventPublisher applicationEventPublisher;
  59 + private TbQueueRuleEngineSettings ruleEngineSettings;
55 60
56 private String hashFunctionName = "murmur3_128"; 61 private String hashFunctionName = "murmur3_128";
57 private Integer virtualNodesSize = 16; 62 private Integer virtualNodesSize = 16;
@@ -62,12 +67,15 @@ public class ConsistentHashParitionServiceTest { @@ -62,12 +67,15 @@ public class ConsistentHashParitionServiceTest {
62 discoveryService = mock(TbServiceInfoProvider.class); 67 discoveryService = mock(TbServiceInfoProvider.class);
63 applicationEventPublisher = mock(ApplicationEventPublisher.class); 68 applicationEventPublisher = mock(ApplicationEventPublisher.class);
64 routingInfoService = mock(TenantRoutingInfoService.class); 69 routingInfoService = mock(TenantRoutingInfoService.class);
65 - clusterRoutingService = new ConsistentHashPartitionService(discoveryService, routingInfoService, applicationEventPublisher); 70 + ruleEngineSettings = mock(TbQueueRuleEngineSettings.class);
  71 + clusterRoutingService = new ConsistentHashPartitionService(discoveryService,
  72 + routingInfoService,
  73 + applicationEventPublisher,
  74 + ruleEngineSettings
  75 + );
  76 + when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList());
66 ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core"); 77 ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core");
67 ReflectionTestUtils.setField(clusterRoutingService, "corePartitions", 3); 78 ReflectionTestUtils.setField(clusterRoutingService, "corePartitions", 3);
68 - ReflectionTestUtils.setField(clusterRoutingService, "ruleEngineTopic", "tb.rule-engine");  
69 - ReflectionTestUtils.setField(clusterRoutingService, "ruleEnginePartitions", 100);  
70 -  
71 ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName); 79 ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName);
72 ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize); 80 ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize);
73 TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder() 81 TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder()
@@ -107,8 +115,9 @@ public class ConsistentHashParitionServiceTest { @@ -107,8 +115,9 @@ public class ConsistentHashParitionServiceTest {
107 List<Map.Entry<Integer, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); 115 List<Map.Entry<Integer, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList());
108 long end = System.currentTimeMillis(); 116 long end = System.currentTimeMillis();
109 double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue()); 117 double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue());
110 - System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", (diff / ITERATIONS) * 100.0) + "%)");  
111 - 118 + double diffPercent = (diff / ITERATIONS) * 100.0;
  119 + System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", diffPercent) + "%)");
  120 + Assert.assertTrue(diffPercent < 0.5);
112 for (Map.Entry<Integer, Integer> entry : data) { 121 for (Map.Entry<Integer, Integer> entry : data) {
113 System.out.println(entry.getKey() + ": " + entry.getValue()); 122 System.out.println(entry.getKey() + ": " + entry.getValue());
114 } 123 }
application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java renamed from application/src/test/java/org/thingsboard/server/service/queue/ProcessingAttemptContextTest.java
@@ -37,7 +37,7 @@ import static org.mockito.Mockito.when; @@ -37,7 +37,7 @@ import static org.mockito.Mockito.when;
37 37
38 @Slf4j 38 @Slf4j
39 @RunWith(MockitoJUnitRunner.class) 39 @RunWith(MockitoJUnitRunner.class)
40 -public class ProcessingAttemptContextTest { 40 +public class TbMsgPackProcessingContextTest {
41 41
42 @Test 42 @Test
43 public void testHighConcurrencyCase() throws InterruptedException { 43 public void testHighConcurrencyCase() throws InterruptedException {
@@ -51,7 +51,7 @@ public class ProcessingAttemptContextTest { @@ -51,7 +51,7 @@ public class ProcessingAttemptContextTest {
51 messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null)); 51 messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null));
52 } 52 }
53 when(strategyMock.getPendingMap()).thenReturn(messages); 53 when(strategyMock.getPendingMap()).thenReturn(messages);
54 - ProcessingAttemptContext context = new ProcessingAttemptContext(strategyMock); 54 + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext(strategyMock);
55 for (UUID uuid : messages.keySet()) { 55 for (UUID uuid : messages.keySet()) {
56 for (int i = 0; i < parallelCount; i++) { 56 for (int i = 0; i < parallelCount; i++) {
57 executorService.submit(() -> context.onSuccess(uuid)); 57 executorService.submit(() -> context.onSuccess(uuid));
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 </encoder> 7 </encoder>
8 </appender> 8 </appender>
9 9
10 - <logger name="org.thingsboard.server" level="TRACE"/> 10 + <logger name="org.thingsboard.server" level="WARN"/>
11 <logger name="org.springframework" level="WARN"/> 11 <logger name="org.springframework" level="WARN"/>
12 <logger name="org.springframework.boot.test" level="WARN"/> 12 <logger name="org.springframework.boot.test" level="WARN"/>
13 <logger name="org.apache.cassandra" level="WARN"/> 13 <logger name="org.apache.cassandra" level="WARN"/>
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
30 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 30 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
31 import org.thingsboard.server.gen.transport.TransportProtos; 31 import org.thingsboard.server.gen.transport.TransportProtos;
32 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; 32 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
  33 +import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
33 34
34 import javax.annotation.PostConstruct; 35 import javax.annotation.PostConstruct;
35 import java.nio.charset.StandardCharsets; 36 import java.nio.charset.StandardCharsets;
@@ -61,6 +62,7 @@ public class ConsistentHashPartitionService implements PartitionService { @@ -61,6 +62,7 @@ public class ConsistentHashPartitionService implements PartitionService {
61 private final ApplicationEventPublisher applicationEventPublisher; 62 private final ApplicationEventPublisher applicationEventPublisher;
62 private final TbServiceInfoProvider serviceInfoProvider; 63 private final TbServiceInfoProvider serviceInfoProvider;
63 private final TenantRoutingInfoService tenantRoutingInfoService; 64 private final TenantRoutingInfoService tenantRoutingInfoService;
  65 + private final TbQueueRuleEngineSettings tbQueueRuleEngineSettings;
64 private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>(); 66 private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>();
65 private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>(); 67 private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>();
66 private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>(); 68 private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>();
@@ -74,10 +76,14 @@ public class ConsistentHashPartitionService implements PartitionService { @@ -74,10 +76,14 @@ public class ConsistentHashPartitionService implements PartitionService {
74 76
75 private HashFunction hashFunction; 77 private HashFunction hashFunction;
76 78
77 - public ConsistentHashPartitionService(TbServiceInfoProvider serviceInfoProvider, TenantRoutingInfoService tenantRoutingInfoService, ApplicationEventPublisher applicationEventPublisher) { 79 + public ConsistentHashPartitionService(TbServiceInfoProvider serviceInfoProvider,
  80 + TenantRoutingInfoService tenantRoutingInfoService,
  81 + ApplicationEventPublisher applicationEventPublisher,
  82 + TbQueueRuleEngineSettings tbQueueRuleEngineSettings) {
78 this.serviceInfoProvider = serviceInfoProvider; 83 this.serviceInfoProvider = serviceInfoProvider;
79 this.tenantRoutingInfoService = tenantRoutingInfoService; 84 this.tenantRoutingInfoService = tenantRoutingInfoService;
80 this.applicationEventPublisher = applicationEventPublisher; 85 this.applicationEventPublisher = applicationEventPublisher;
  86 + this.tbQueueRuleEngineSettings = tbQueueRuleEngineSettings;
81 } 87 }
82 88
83 @PostConstruct 89 @PostConstruct
@@ -85,6 +91,10 @@ public class ConsistentHashPartitionService implements PartitionService { @@ -85,6 +91,10 @@ public class ConsistentHashPartitionService implements PartitionService {
85 this.hashFunction = forName(hashFunctionName); 91 this.hashFunction = forName(hashFunctionName);
86 partitionSizes.put(new ServiceQueue(ServiceType.TB_CORE), corePartitions); 92 partitionSizes.put(new ServiceQueue(ServiceType.TB_CORE), corePartitions);
87 partitionTopics.put(new ServiceQueue(ServiceType.TB_CORE), coreTopic); 93 partitionTopics.put(new ServiceQueue(ServiceType.TB_CORE), coreTopic);
  94 + tbQueueRuleEngineSettings.getQueues().forEach(queueConfiguration -> {
  95 + partitionTopics.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getTopic());
  96 + partitionSizes.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getPartitions());
  97 + });
88 } 98 }
89 99
90 @Override 100 @Override
@@ -544,6 +544,9 @@ public class DefaultTransportService implements TransportService { @@ -544,6 +544,9 @@ public class DefaultTransportService implements TransportService {
544 544
545 protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) { 545 protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
546 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); 546 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo));
  547 + if (log.isTraceEnabled()) {
  548 + log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg);
  549 + }
547 tbCoreMsgProducer.send(tpi, 550 tbCoreMsgProducer.send(tpi,
548 new TbProtoQueueMsg<>(getRoutingKey(sessionInfo), 551 new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
549 ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ? 552 ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ?
@@ -552,6 +555,9 @@ public class DefaultTransportService implements TransportService { @@ -552,6 +555,9 @@ public class DefaultTransportService implements TransportService {
552 555
553 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { 556 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
554 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); 557 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
  558 + if (log.isTraceEnabled()) {
  559 + log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg);
  560 + }
555 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) 561 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
556 .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) 562 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
557 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); 563 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
@@ -32,6 +32,9 @@ public class ModelConstants { @@ -32,6 +32,9 @@ public class ModelConstants {
32 public static final String NULL_UUID_STR = UUIDConverter.fromTimeUUID(NULL_UUID); 32 public static final String NULL_UUID_STR = UUIDConverter.fromTimeUUID(NULL_UUID);
33 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); 33 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
34 34
  35 + // this is the difference between midnight October 15, 1582 UTC and midnight January 1, 1970 UTC as 100 nanosecond units
  36 + public static final long EPOCH_DIFF = 122192928000000000L;
  37 +
35 /** 38 /**
36 * Generic constants. 39 * Generic constants.
37 */ 40 */
@@ -37,6 +37,9 @@ import javax.persistence.EnumType; @@ -37,6 +37,9 @@ import javax.persistence.EnumType;
37 import javax.persistence.Enumerated; 37 import javax.persistence.Enumerated;
38 import javax.persistence.Table; 38 import javax.persistence.Table;
39 39
  40 +import java.util.UUID;
  41 +
  42 +import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF;
40 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BODY_PROPERTY; 43 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BODY_PROPERTY;
41 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME; 44 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME;
42 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_ID_PROPERTY; 45 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_ID_PROPERTY;
@@ -44,6 +47,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_TYPE_ @@ -44,6 +47,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_TYPE_
44 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TENANT_ID_PROPERTY; 47 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TENANT_ID_PROPERTY;
45 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TYPE_PROPERTY; 48 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TYPE_PROPERTY;
46 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_UID_PROPERTY; 49 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_UID_PROPERTY;
  50 +import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN;
47 51
48 @Data 52 @Data
49 @EqualsAndHashCode(callSuper = true) 53 @EqualsAndHashCode(callSuper = true)
@@ -73,9 +77,15 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve @@ -73,9 +77,15 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve
73 @Column(name = EVENT_BODY_PROPERTY) 77 @Column(name = EVENT_BODY_PROPERTY)
74 private JsonNode body; 78 private JsonNode body;
75 79
  80 + @Column(name = TS_COLUMN)
  81 + private long ts;
  82 +
76 public EventEntity(Event event) { 83 public EventEntity(Event event) {
77 if (event.getId() != null) { 84 if (event.getId() != null) {
78 this.setUuid(event.getId().getId()); 85 this.setUuid(event.getId().getId());
  86 + this.ts = getTs(event.getId().getId());
  87 + } else {
  88 + this.ts = System.currentTimeMillis();
79 } 89 }
80 if (event.getTenantId() != null) { 90 if (event.getTenantId() != null) {
81 this.tenantId = toString(event.getTenantId().getId()); 91 this.tenantId = toString(event.getTenantId().getId());
@@ -101,4 +111,8 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve @@ -101,4 +111,8 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve
101 event.setUid(eventUid); 111 event.setUid(eventUid);
102 return event; 112 return event;
103 } 113 }
  114 +
  115 + private long getTs(UUID uuid) {
  116 + return (uuid.timestamp() - EPOCH_DIFF) / 10000;
  117 + }
104 } 118 }
@@ -88,26 +88,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @@ -88,26 +88,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
88 RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); 88 RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
89 if (!ruleChain.isRoot()) { 89 if (!ruleChain.isRoot()) {
90 RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); 90 RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
91 - if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {  
92 - try { 91 + try {
  92 + if (previousRootRuleChain == null) {
  93 + setRootAndSave(tenantId, ruleChain);
  94 + return true;
  95 + } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
93 deleteRelation(tenantId, new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), 96 deleteRelation(tenantId, new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
94 EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); 97 EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
95 previousRootRuleChain.setRoot(false); 98 previousRootRuleChain.setRoot(false);
96 ruleChainDao.save(tenantId, previousRootRuleChain); 99 ruleChainDao.save(tenantId, previousRootRuleChain);
97 - createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),  
98 - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));  
99 - ruleChain.setRoot(true);  
100 - ruleChainDao.save(tenantId, ruleChain); 100 + setRootAndSave(tenantId, ruleChain);
101 return true; 101 return true;
102 - } catch (ExecutionException | InterruptedException e) {  
103 - log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);  
104 - throw new RuntimeException(e);  
105 } 102 }
  103 + } catch (ExecutionException | InterruptedException e) {
  104 + log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
  105 + throw new RuntimeException(e);
106 } 106 }
107 } 107 }
108 return false; 108 return false;
109 } 109 }
110 110
  111 + private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) throws ExecutionException, InterruptedException {
  112 + createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
  113 + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
  114 + ruleChain.setRoot(true);
  115 + ruleChainDao.save(tenantId, ruleChain);
  116 + }
  117 +
111 @Override 118 @Override
112 public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) { 119 public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) {
113 Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); 120 Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
@@ -75,7 +75,8 @@ public abstract class AbstractEventInsertRepository implements EventInsertReposi @@ -75,7 +75,8 @@ public abstract class AbstractEventInsertRepository implements EventInsertReposi
75 .setParameter("entity_type", entity.getEntityType().name()) 75 .setParameter("entity_type", entity.getEntityType().name())
76 .setParameter("event_type", entity.getEventType()) 76 .setParameter("event_type", entity.getEventType())
77 .setParameter("event_uid", entity.getEventUid()) 77 .setParameter("event_uid", entity.getEventUid())
78 - .setParameter("tenant_id", entity.getTenantId()); 78 + .setParameter("tenant_id", entity.getTenantId())
  79 + .setParameter("ts", entity.getTs());
79 } 80 }
80 81
81 private EventEntity processSaveOrUpdate(EventEntity entity, String query) { 82 private EventEntity processSaveOrUpdate(EventEntity entity, String query) {
@@ -44,7 +44,7 @@ public class HsqlEventInsertRepository extends AbstractEventInsertRepository { @@ -44,7 +44,7 @@ public class HsqlEventInsertRepository extends AbstractEventInsertRepository {
44 } 44 }
45 45
46 private static String getInsertString(String conflictStatement) { 46 private static String getInsertString(String conflictStatement) {
47 - return "MERGE INTO event USING (VALUES :id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id) I (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id" +  
48 - " WHEN NOT MATCHED THEN INSERT (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) VALUES (I.id, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id)"; 47 + return "MERGE INTO event USING (VALUES :id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) I (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id, event.ts = I.ts" +
  48 + " WHEN NOT MATCHED THEN INSERT (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (I.id, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id, I.ts)";
49 } 49 }
50 } 50 }
@@ -48,6 +48,6 @@ public class PsqlEventInsertRepository extends AbstractEventInsertRepository { @@ -48,6 +48,6 @@ public class PsqlEventInsertRepository extends AbstractEventInsertRepository {
48 } 48 }
49 49
50 private static String getInsertOrUpdateString(String eventKeyStatement, String updateKeyStatement) { 50 private static String getInsertOrUpdateString(String eventKeyStatement, String updateKeyStatement) {
51 - return "INSERT INTO event (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) VALUES (:id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id) ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, " + updateKeyStatement + " returning *"; 51 + return "INSERT INTO event (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (:id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, ts = :ts," + updateKeyStatement + " returning *";
52 } 52 }
53 } 53 }
@@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event ( @@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event (
144 event_type varchar(255), 144 event_type varchar(255),
145 event_uid varchar(255), 145 event_uid varchar(255),
146 tenant_id varchar(31), 146 tenant_id varchar(31),
  147 + ts bigint NOT NULL,
147 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid) 148 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
148 ); 149 );
149 150
@@ -251,3 +252,4 @@ CREATE TABLE IF NOT EXISTS entity_view ( @@ -251,3 +252,4 @@ CREATE TABLE IF NOT EXISTS entity_view (
251 search_text varchar(255), 252 search_text varchar(255),
252 additional_info varchar 253 additional_info varchar
253 ); 254 );
  255 +
@@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event ( @@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event (
144 event_type varchar(255), 144 event_type varchar(255),
145 event_uid varchar(255), 145 event_uid varchar(255),
146 tenant_id varchar(31), 146 tenant_id varchar(31),
  147 + ts bigint NOT NULL,
147 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid) 148 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
148 ); 149 );
149 150
@@ -251,3 +252,28 @@ CREATE TABLE IF NOT EXISTS entity_view ( @@ -251,3 +252,28 @@ CREATE TABLE IF NOT EXISTS entity_view (
251 search_text varchar(255), 252 search_text varchar(255),
252 additional_info varchar 253 additional_info varchar
253 ); 254 );
  255 +
  256 +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
  257 + LANGUAGE plpgsql AS
  258 +$$
  259 +DECLARE
  260 + ttl_ts bigint;
  261 + debug_ttl_ts bigint;
  262 + ttl_deleted_count bigint DEFAULT 0;
  263 + debug_ttl_deleted_count bigint DEFAULT 0;
  264 +BEGIN
  265 + IF ttl > 0 THEN
  266 + ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
  267 + EXECUTE format(
  268 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
  269 + END IF;
  270 + IF debug_ttl > 0 THEN
  271 + debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
  272 + EXECUTE format(
  273 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
  274 + END IF;
  275 + RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
  276 + RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
  277 + deleted := ttl_deleted_count + debug_ttl_deleted_count;
  278 +END
  279 +$$;
@@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings @@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version) 52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
53 ); 53 );
54 54
55 -INSERT INTO tb_schema_settings (schema_version) VALUES (2005000); 55 +INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000;
56 56
57 CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS 57 CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
58 $$ 58 $$
@@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings @@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version) 52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
53 ); 53 );
54 54
55 -INSERT INTO tb_schema_settings (schema_version) VALUES (2005000); 55 +INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000;
56 56
57 CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) 57 CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint)
58 LANGUAGE plpgsql AS 58 LANGUAGE plpgsql AS
@@ -37,6 +37,9 @@ service.type=monolith @@ -37,6 +37,9 @@ service.type=monolith
37 #spring.datasource.driverClassName=org.postgresql.Driver 37 #spring.datasource.driverClassName=org.postgresql.Driver
38 #spring.datasource.hikari.maximumPoolSize = 50 38 #spring.datasource.hikari.maximumPoolSize = 50
39 39
  40 +queue.core.pack-processing-timeout=3000
  41 +queue.rule-engine.pack-processing-timeout=3000
  42 +
40 queue.rule-engine.queues[0].name=Main 43 queue.rule-engine.queues[0].name=Main
41 queue.rule-engine.queues[0].topic=tb_rule_engine.main 44 queue.rule-engine.queues[0].topic=tb_rule_engine.main
42 queue.rule-engine.queues[0].poll-interval=25 45 queue.rule-engine.queues[0].poll-interval=25
@@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors. @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors.
58 For example to see ThingsBoard node logs execute the following command: 58 For example to see ThingsBoard node logs execute the following command:
59 59
60 ` 60 `
61 -$ docker-compose logs -f tb1 61 +$ docker-compose logs -f tb-core1 tb-rule-engine1
62 ` 62 `
63 63
64 Or use `docker-compose ps` to see the state of all the containers. 64 Or use `docker-compose ps` to see the state of all the containers.
@@ -24,14 +24,28 @@ services: @@ -24,14 +24,28 @@ services:
24 - "9042" 24 - "9042"
25 volumes: 25 volumes:
26 - ./tb-node/cassandra:/var/lib/cassandra 26 - ./tb-node/cassandra:/var/lib/cassandra
27 - tb1: 27 + tb-core1:
28 env_file: 28 env_file:
29 - tb-node.cassandra.env 29 - tb-node.cassandra.env
30 depends_on: 30 depends_on:
31 - kafka 31 - kafka
32 - redis 32 - redis
33 - cassandra 33 - cassandra
34 - tb2: 34 + tb-core2:
  35 + env_file:
  36 + - tb-node.cassandra.env
  37 + depends_on:
  38 + - kafka
  39 + - redis
  40 + - cassandra
  41 + tb-rule-engine1:
  42 + env_file:
  43 + - tb-node.cassandra.env
  44 + depends_on:
  45 + - kafka
  46 + - redis
  47 + - cassandra
  48 + tb-rule-engine2:
35 env_file: 49 env_file:
36 - tb-node.cassandra.env 50 - tb-node.cassandra.env
37 depends_on: 51 depends_on:
@@ -20,10 +20,16 @@ services: @@ -20,10 +20,16 @@ services:
20 postgres: 20 postgres:
21 volumes: 21 volumes:
22 - postgres-db-volume:/var/lib/postgresql/data 22 - postgres-db-volume:/var/lib/postgresql/data
23 - tb1: 23 + tb-core1:
24 volumes: 24 volumes:
25 - tb-log-volume:/var/log/thingsboard 25 - tb-log-volume:/var/log/thingsboard
26 - tb2: 26 + tb-core2:
  27 + volumes:
  28 + - tb-log-volume:/var/log/thingsboard
  29 + tb-rule-engine1:
  30 + volumes:
  31 + - tb-log-volume:/var/log/thingsboard
  32 + tb-rule-engine2:
27 volumes: 33 volumes:
28 - tb-log-volume:/var/log/thingsboard 34 - tb-log-volume:/var/log/thingsboard
29 tb-coap-transport: 35 tb-coap-transport:
@@ -27,14 +27,28 @@ services: @@ -27,14 +27,28 @@ services:
27 POSTGRES_PASSWORD: postgres 27 POSTGRES_PASSWORD: postgres
28 volumes: 28 volumes:
29 - ./tb-node/postgres:/var/lib/postgresql/data 29 - ./tb-node/postgres:/var/lib/postgresql/data
30 - tb1: 30 + tb-core1:
31 env_file: 31 env_file:
32 - tb-node.postgres.env 32 - tb-node.postgres.env
33 depends_on: 33 depends_on:
34 - kafka 34 - kafka
35 - redis 35 - redis
36 - postgres 36 - postgres
37 - tb2: 37 + tb-core2:
  38 + env_file:
  39 + - tb-node.postgres.env
  40 + depends_on:
  41 + - kafka
  42 + - redis
  43 + - postgres
  44 + tb-rule-engine1:
  45 + env_file:
  46 + - tb-node.postgres.env
  47 + depends_on:
  48 + - kafka
  49 + - redis
  50 + - postgres
  51 + tb-rule-engine2:
38 env_file: 52 env_file:
39 - tb-node.postgres.env 53 - tb-node.postgres.env
40 depends_on: 54 depends_on: