Commit 5b1cc8ac3309b5a65ffe5835939dd528fcb7c512

Authored by zbeacon
2 parents 7a4d302c 3b74a806

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature…

…/firmware-checksum-autogenerating
Showing 78 changed files with 3586 additions and 200 deletions

Too many changes to show.

To preserve performance only 78 of 160 files are displayed.

... ... @@ -90,6 +90,10 @@
90 90 <artifactId>lwm2m</artifactId>
91 91 </dependency>
92 92 <dependency>
  93 + <groupId>org.thingsboard.common.transport</groupId>
  94 + <artifactId>snmp</artifactId>
  95 + </dependency>
  96 + <dependency>
93 97 <groupId>org.thingsboard</groupId>
94 98 <artifactId>dao</artifactId>
95 99 </dependency>
... ...
  1 +{
  2 + "title": "Firmware",
  3 + "configuration": {
  4 + "description": "",
  5 + "widgets": {
  6 + "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
  7 + "isSystemType": true,
  8 + "bundleAlias": "cards",
  9 + "typeAlias": "entities_table",
  10 + "type": "latest",
  11 + "title": "New widget",
  12 + "image": null,
  13 + "description": null,
  14 + "sizeX": 7.5,
  15 + "sizeY": 6.5,
  16 + "config": {
  17 + "timewindow": {
  18 + "realtime": {
  19 + "interval": 1000,
  20 + "timewindowMs": 86400000
  21 + },
  22 + "aggregation": {
  23 + "type": "NONE",
  24 + "limit": 200
  25 + }
  26 + },
  27 + "showTitle": true,
  28 + "backgroundColor": "rgb(255, 255, 255)",
  29 + "color": "rgba(0, 0, 0, 0.87)",
  30 + "padding": "4px",
  31 + "settings": {
  32 + "enableSearch": true,
  33 + "displayPagination": true,
  34 + "defaultPageSize": 10,
  35 + "defaultSortOrder": "entityLabel",
  36 + "displayEntityName": false,
  37 + "displayEntityType": false,
  38 + "enableSelectColumnDisplay": false,
  39 + "enableStickyHeader": true,
  40 + "enableStickyAction": false,
  41 + "entitiesTitle": "Devices",
  42 + "displayEntityLabel": true,
  43 + "entityLabelColumnTitle": "Device"
  44 + },
  45 + "title": "New Entities table",
  46 + "dropShadow": true,
  47 + "enableFullscreen": true,
  48 + "titleStyle": {
  49 + "fontSize": "16px",
  50 + "fontWeight": 400,
  51 + "padding": "5px 10px 5px 10px"
  52 + },
  53 + "useDashboardTimewindow": false,
  54 + "showLegend": false,
  55 + "datasources": [
  56 + {
  57 + "type": "entity",
  58 + "name": null,
  59 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  60 + "filterId": "8fdb88d0-50ac-2232-fdb7-69c30c16544e",
  61 + "dataKeys": [
  62 + {
  63 + "name": "current_fw_title",
  64 + "type": "timeseries",
  65 + "label": "Current FW title",
  66 + "color": "#2196f3",
  67 + "settings": {
  68 + "columnWidth": "0px",
  69 + "useCellStyleFunction": false,
  70 + "cellStyleFunction": "",
  71 + "useCellContentFunction": false,
  72 + "defaultColumnVisibility": "visible",
  73 + "columnSelectionToDisplay": "enabled"
  74 + },
  75 + "_hash": 0.09545533885166413,
  76 + "units": null,
  77 + "decimals": null,
  78 + "funcBody": null,
  79 + "usePostProcessing": null,
  80 + "postFuncBody": null
  81 + },
  82 + {
  83 + "name": "current_fw_version",
  84 + "type": "timeseries",
  85 + "label": "Current FW version",
  86 + "color": "#4caf50",
  87 + "settings": {
  88 + "columnWidth": "0px",
  89 + "useCellStyleFunction": false,
  90 + "cellStyleFunction": "",
  91 + "useCellContentFunction": false,
  92 + "defaultColumnVisibility": "visible",
  93 + "columnSelectionToDisplay": "enabled"
  94 + },
  95 + "_hash": 0.7206056602328659,
  96 + "units": null,
  97 + "decimals": null,
  98 + "funcBody": null,
  99 + "usePostProcessing": null,
  100 + "postFuncBody": null
  101 + },
  102 + {
  103 + "name": "target_fw_title",
  104 + "type": "timeseries",
  105 + "label": "Target FW title",
  106 + "color": "#ffc107",
  107 + "settings": {
  108 + "columnWidth": "0px",
  109 + "useCellStyleFunction": false,
  110 + "cellStyleFunction": "",
  111 + "useCellContentFunction": false,
  112 + "defaultColumnVisibility": "visible",
  113 + "columnSelectionToDisplay": "enabled"
  114 + },
  115 + "_hash": 0.9934225682766313,
  116 + "units": null,
  117 + "decimals": null,
  118 + "funcBody": null,
  119 + "usePostProcessing": null,
  120 + "postFuncBody": null
  121 + },
  122 + {
  123 + "name": "target_fw_version",
  124 + "type": "timeseries",
  125 + "label": "Target FW version",
  126 + "color": "#607d8b",
  127 + "settings": {
  128 + "columnWidth": "0px",
  129 + "useCellStyleFunction": false,
  130 + "cellStyleFunction": "",
  131 + "useCellContentFunction": false,
  132 + "cellContentFunction": "",
  133 + "defaultColumnVisibility": "visible",
  134 + "columnSelectionToDisplay": "enabled"
  135 + },
  136 + "_hash": 0.5251724416842531,
  137 + "units": null,
  138 + "decimals": null,
  139 + "funcBody": null,
  140 + "usePostProcessing": null,
  141 + "postFuncBody": null
  142 + },
  143 + {
  144 + "name": "target_fw_ts",
  145 + "type": "timeseries",
  146 + "label": "Target FW set time",
  147 + "color": "#e91e63",
  148 + "settings": {
  149 + "columnWidth": "0px",
  150 + "useCellStyleFunction": false,
  151 + "cellStyleFunction": "",
  152 + "useCellContentFunction": true,
  153 + "defaultColumnVisibility": "visible",
  154 + "columnSelectionToDisplay": "enabled",
  155 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  156 + },
  157 + "_hash": 0.31823244858578237,
  158 + "units": null,
  159 + "decimals": null,
  160 + "funcBody": null,
  161 + "usePostProcessing": null,
  162 + "postFuncBody": null
  163 + },
  164 + {
  165 + "name": "fw_state",
  166 + "type": "timeseries",
  167 + "label": "Progress",
  168 + "color": "#9c27b0",
  169 + "settings": {
  170 + "columnWidth": "30%",
  171 + "useCellStyleFunction": true,
  172 + "useCellContentFunction": true,
  173 + "defaultColumnVisibility": "visible",
  174 + "columnSelectionToDisplay": "enabled",
  175 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  176 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  177 + },
  178 + "_hash": 0.8174211757846257,
  179 + "units": null,
  180 + "decimals": null,
  181 + "funcBody": null,
  182 + "usePostProcessing": null,
  183 + "postFuncBody": null
  184 + },
  185 + {
  186 + "name": "fw_state",
  187 + "type": "timeseries",
  188 + "label": "Status",
  189 + "color": "#f44336",
  190 + "settings": {
  191 + "columnWidth": "130px",
  192 + "useCellStyleFunction": true,
  193 + "useCellContentFunction": true,
  194 + "defaultColumnVisibility": "visible",
  195 + "columnSelectionToDisplay": "enabled",
  196 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  197 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  198 + },
  199 + "_hash": 0.7764426948615217,
  200 + "units": null,
  201 + "decimals": null,
  202 + "funcBody": null,
  203 + "usePostProcessing": null,
  204 + "postFuncBody": null
  205 + },
  206 + {
  207 + "name": "fw_checksum",
  208 + "type": "attribute",
  209 + "label": "fw_checksum",
  210 + "color": "#3f51b5",
  211 + "settings": {
  212 + "columnWidth": "0px",
  213 + "useCellStyleFunction": false,
  214 + "cellStyleFunction": "",
  215 + "useCellContentFunction": false,
  216 + "defaultColumnVisibility": "hidden",
  217 + "columnSelectionToDisplay": "disabled"
  218 + },
  219 + "_hash": 0.5594087842471693,
  220 + "units": null,
  221 + "decimals": null,
  222 + "funcBody": null,
  223 + "usePostProcessing": null,
  224 + "postFuncBody": null
  225 + }
  226 + ]
  227 + }
  228 + ],
  229 + "actions": {
  230 + "actionCellButton": [
  231 + {
  232 + "name": "History firmware update",
  233 + "icon": "history",
  234 + "type": "openDashboardState",
  235 + "targetDashboardStateId": "device_firmware_history",
  236 + "setEntityId": true,
  237 + "stateEntityParamName": null,
  238 + "openInSeparateDialog": false,
  239 + "dialogTitle": "",
  240 + "dialogHideDashboardToolbar": true,
  241 + "dialogWidth": null,
  242 + "dialogHeight": null,
  243 + "openRightLayout": false,
  244 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  245 + },
  246 + {
  247 + "name": "Edit firmware",
  248 + "icon": "edit",
  249 + "type": "customPretty",
  250 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  251 + "customCss": "",
  252 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
  253 + "customResources": [],
  254 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  255 + },
  256 + {
  257 + "name": "Download firware",
  258 + "icon": "file_download",
  259 + "type": "custom",
  260 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  261 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  262 + },
  263 + {
  264 + "name": "Copy checksum",
  265 + "icon": "content_copy",
  266 + "type": "custom",
  267 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  268 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  269 + }
  270 + ]
  271 + },
  272 + "showTitleIcon": false,
  273 + "iconColor": "rgba(0, 0, 0, 0.87)",
  274 + "iconSize": "24px",
  275 + "titleTooltip": "",
  276 + "widgetStyle": {}
  277 + },
  278 + "row": 0,
  279 + "col": 0,
  280 + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016"
  281 + },
  282 + "100b756c-0082-6505-3ae1-3603e6deea48": {
  283 + "isSystemType": true,
  284 + "bundleAlias": "cards",
  285 + "typeAlias": "timeseries_table",
  286 + "type": "timeseries",
  287 + "title": "New widget",
  288 + "image": null,
  289 + "description": null,
  290 + "sizeX": 8,
  291 + "sizeY": 6.5,
  292 + "config": {
  293 + "datasources": [
  294 + {
  295 + "type": "entity",
  296 + "name": null,
  297 + "entityAliasId": "19f41c21-d9af-e666-8f50-e1748778f955",
  298 + "filterId": null,
  299 + "dataKeys": [
  300 + {
  301 + "name": "current_fw_title",
  302 + "type": "timeseries",
  303 + "label": "Current firmware title",
  304 + "color": "#2196f3",
  305 + "settings": {
  306 + "useCellStyleFunction": false,
  307 + "cellStyleFunction": "",
  308 + "useCellContentFunction": false,
  309 + "cellContentFunction": ""
  310 + },
  311 + "_hash": 0.5978079905579401,
  312 + "units": null,
  313 + "decimals": null,
  314 + "funcBody": null,
  315 + "usePostProcessing": null,
  316 + "postFuncBody": null
  317 + },
  318 + {
  319 + "name": "current_fw_version",
  320 + "type": "timeseries",
  321 + "label": "Current firmware version",
  322 + "color": "#4caf50",
  323 + "settings": {
  324 + "useCellStyleFunction": false,
  325 + "cellStyleFunction": "",
  326 + "useCellContentFunction": false,
  327 + "cellContentFunction": ""
  328 + },
  329 + "_hash": 0.027392025058568192,
  330 + "units": null,
  331 + "decimals": null,
  332 + "funcBody": null,
  333 + "usePostProcessing": null,
  334 + "postFuncBody": null
  335 + },
  336 + {
  337 + "name": "target_fw_title",
  338 + "type": "timeseries",
  339 + "label": "Target firmware title",
  340 + "color": "#f44336",
  341 + "settings": {
  342 + "useCellStyleFunction": false,
  343 + "cellStyleFunction": "",
  344 + "useCellContentFunction": false,
  345 + "cellContentFunction": ""
  346 + },
  347 + "_hash": 0.9496350796287059,
  348 + "units": null,
  349 + "decimals": null,
  350 + "funcBody": null,
  351 + "usePostProcessing": null,
  352 + "postFuncBody": null
  353 + },
  354 + {
  355 + "name": "target_fw_version",
  356 + "type": "timeseries",
  357 + "label": "Target firmware version",
  358 + "color": "#ffc107",
  359 + "settings": {
  360 + "useCellStyleFunction": false,
  361 + "cellStyleFunction": "",
  362 + "useCellContentFunction": false,
  363 + "cellContentFunction": ""
  364 + },
  365 + "_hash": 0.6734152252264187,
  366 + "units": null,
  367 + "decimals": null,
  368 + "funcBody": null,
  369 + "usePostProcessing": null,
  370 + "postFuncBody": null
  371 + },
  372 + {
  373 + "name": "fw_state",
  374 + "type": "timeseries",
  375 + "label": "Status",
  376 + "color": "#607d8b",
  377 + "settings": {
  378 + "useCellStyleFunction": false,
  379 + "cellStyleFunction": "",
  380 + "useCellContentFunction": false,
  381 + "cellContentFunction": ""
  382 + },
  383 + "_hash": 0.2983399718643074,
  384 + "units": null,
  385 + "decimals": null,
  386 + "funcBody": null,
  387 + "usePostProcessing": true,
  388 + "postFuncBody": "function capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\nif (value !== '') {\n return capitalize(value);\n}\nreturn value;"
  389 + }
  390 + ]
  391 + }
  392 + ],
  393 + "timewindow": {
  394 + "hideInterval": false,
  395 + "hideAggregation": false,
  396 + "hideAggInterval": false,
  397 + "hideTimezone": false,
  398 + "selectedTab": 0,
  399 + "realtime": {
  400 + "realtimeType": 0,
  401 + "timewindowMs": 2592000000,
  402 + "quickInterval": "CURRENT_DAY",
  403 + "interval": 1000
  404 + },
  405 + "aggregation": {
  406 + "type": "NONE",
  407 + "limit": 200
  408 + }
  409 + },
  410 + "showTitle": false,
  411 + "backgroundColor": "rgb(255, 255, 255)",
  412 + "color": "rgba(0, 0, 0, 0.87)",
  413 + "padding": "8px",
  414 + "settings": {
  415 + "showTimestamp": true,
  416 + "displayPagination": true,
  417 + "defaultPageSize": 10,
  418 + "enableSearch": true,
  419 + "enableStickyHeader": true,
  420 + "enableStickyAction": true
  421 + },
  422 + "title": "Firmware history",
  423 + "dropShadow": false,
  424 + "enableFullscreen": false,
  425 + "titleStyle": {
  426 + "fontSize": "16px",
  427 + "fontWeight": 400,
  428 + "padding": "5px 10px 5px 10px"
  429 + },
  430 + "useDashboardTimewindow": false,
  431 + "showLegend": false,
  432 + "widgetStyle": {},
  433 + "actions": {},
  434 + "showTitleIcon": false,
  435 + "iconColor": "rgba(0, 0, 0, 0.87)",
  436 + "iconSize": "24px",
  437 + "displayTimewindow": true,
  438 + "titleTooltip": ""
  439 + },
  440 + "row": 0,
  441 + "col": 0,
  442 + "id": "100b756c-0082-6505-3ae1-3603e6deea48"
  443 + },
  444 + "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
  445 + "isSystemType": true,
  446 + "bundleAlias": "cards",
  447 + "typeAlias": "html_value_card",
  448 + "type": "latest",
  449 + "title": "New widget",
  450 + "sizeX": 8,
  451 + "sizeY": 3,
  452 + "config": {
  453 + "datasources": [
  454 + {
  455 + "type": "entityCount",
  456 + "name": "",
  457 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  458 + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  459 + "dataKeys": [
  460 + {
  461 + "name": "count",
  462 + "type": "count",
  463 + "label": "waitingDevicesNumber",
  464 + "color": "#4caf50",
  465 + "settings": {},
  466 + "_hash": 0.7404827038869322,
  467 + "units": null,
  468 + "decimals": null,
  469 + "funcBody": null,
  470 + "usePostProcessing": null,
  471 + "postFuncBody": null
  472 + }
  473 + ]
  474 + }
  475 + ],
  476 + "timewindow": {
  477 + "realtime": {
  478 + "timewindowMs": 60000
  479 + }
  480 + },
  481 + "showTitle": false,
  482 + "backgroundColor": "#fff",
  483 + "color": "rgba(0, 0, 0, 0.87)",
  484 + "padding": "0px",
  485 + "settings": {
  486 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"24\" height=\"40\" viewBox=\"0 0 24 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M0 0V12H0.0200005L0 12.02L8 20L0 28L0.0200005 28.02H0V40H24V28.02H23.98L24 28L16 20L24 12.02L23.98 12H24V0H0ZM20 29V36H4V29L12 21L20 29ZM12 19L4 11V4H20V11L12 19Z\" fill=\"black\"/>\n</svg>\n'>\n <div class='value' id=\"activeDevices\">\n ${waitingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Waiting\n </div>\n </div>\n</div>",
  487 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  488 + },
  489 + "title": "New HTML Value Card",
  490 + "dropShadow": true,
  491 + "enableFullscreen": false,
  492 + "widgetStyle": {},
  493 + "titleStyle": {
  494 + "fontSize": "16px",
  495 + "fontWeight": 400
  496 + },
  497 + "useDashboardTimewindow": true,
  498 + "showLegend": false,
  499 + "actions": {
  500 + "elementClick": [
  501 + {
  502 + "name": "activeDevices",
  503 + "icon": "more_horiz",
  504 + "type": "openDashboardState",
  505 + "targetDashboardStateId": "device_waiting",
  506 + "setEntityId": false,
  507 + "stateEntityParamName": null,
  508 + "openInSeparateDialog": false,
  509 + "dialogTitle": "",
  510 + "dialogHideDashboardToolbar": true,
  511 + "dialogWidth": null,
  512 + "dialogHeight": null,
  513 + "openRightLayout": false,
  514 + "id": "4d9a77a2-f0a5-690c-a83b-b0e940be788c"
  515 + }
  516 + ]
  517 + },
  518 + "showTitleIcon": false,
  519 + "titleIcon": null,
  520 + "iconColor": "rgba(0, 0, 0, 0.87)",
  521 + "iconSize": "24px",
  522 + "titleTooltip": "",
  523 + "enableDataExport": false,
  524 + "displayTimewindow": true
  525 + },
  526 + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6"
  527 + },
  528 + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
  529 + "isSystemType": true,
  530 + "bundleAlias": "cards",
  531 + "typeAlias": "html_value_card",
  532 + "type": "latest",
  533 + "title": "New widget",
  534 + "sizeX": 8,
  535 + "sizeY": 3,
  536 + "config": {
  537 + "datasources": [
  538 + {
  539 + "type": "entityCount",
  540 + "name": "",
  541 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  542 + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  543 + "dataKeys": [
  544 + {
  545 + "name": "count",
  546 + "type": "count",
  547 + "label": "updatingDevicesNumber",
  548 + "color": "#4caf50",
  549 + "settings": {},
  550 + "_hash": 0.7404827038869322,
  551 + "units": null,
  552 + "decimals": null,
  553 + "funcBody": null,
  554 + "usePostProcessing": null,
  555 + "postFuncBody": null
  556 + }
  557 + ]
  558 + }
  559 + ],
  560 + "timewindow": {
  561 + "realtime": {
  562 + "timewindowMs": 60000
  563 + }
  564 + },
  565 + "showTitle": false,
  566 + "backgroundColor": "#fff",
  567 + "color": "rgba(0, 0, 0, 0.87)",
  568 + "padding": "0px",
  569 + "settings": {
  570 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"36\" height=\"36\" viewBox=\"0 0 36 36\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M36 14.24H22.44L27.92 8.6C22.46 3.2 13.62 3 8.16001 8.4C2.70001 13.82 2.70001 22.56 8.16001 27.98C13.62 33.4 22.46 33.4 27.92 27.98C30.64 25.3 32 22.16 32 18.2H36C36 22.16 34.24 27.3 30.72 30.78C23.7 37.74 12.3 37.74 5.28001 30.78C-1.71999 23.84 -1.77999 12.56 5.24001 5.62C12.26 -1.32 23.52 -1.32 30.54 5.62L36 0V14.24ZM19 10V18.5L26 22.66L24.56 25.08L16 20V10H19Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updating\n </div>\n </div>\n</div>",
  571 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  572 + },
  573 + "title": "New HTML Value Card",
  574 + "dropShadow": true,
  575 + "enableFullscreen": false,
  576 + "widgetStyle": {},
  577 + "titleStyle": {
  578 + "fontSize": "16px",
  579 + "fontWeight": 400
  580 + },
  581 + "useDashboardTimewindow": true,
  582 + "showLegend": false,
  583 + "actions": {
  584 + "elementClick": [
  585 + {
  586 + "name": "activeDevices",
  587 + "icon": "more_horiz",
  588 + "type": "openDashboardState",
  589 + "targetDashboardStateId": "device_updating",
  590 + "setEntityId": false,
  591 + "stateEntityParamName": null,
  592 + "openInSeparateDialog": false,
  593 + "dialogTitle": "",
  594 + "dialogHideDashboardToolbar": true,
  595 + "dialogWidth": null,
  596 + "dialogHeight": null,
  597 + "openRightLayout": false,
  598 + "id": "57d39904-2350-b29b-78ed-56b8268814cb"
  599 + }
  600 + ]
  601 + },
  602 + "showTitleIcon": false,
  603 + "titleIcon": null,
  604 + "iconColor": "rgba(0, 0, 0, 0.87)",
  605 + "iconSize": "24px",
  606 + "titleTooltip": "",
  607 + "enableDataExport": false,
  608 + "displayTimewindow": true
  609 + },
  610 + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6"
  611 + },
  612 + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
  613 + "isSystemType": true,
  614 + "bundleAlias": "cards",
  615 + "typeAlias": "html_value_card",
  616 + "type": "latest",
  617 + "title": "New widget",
  618 + "sizeX": 8,
  619 + "sizeY": 3,
  620 + "config": {
  621 + "datasources": [
  622 + {
  623 + "type": "entityCount",
  624 + "name": "",
  625 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  626 + "filterId": "6044e198-df64-cd76-f339-696f220c4943",
  627 + "dataKeys": [
  628 + {
  629 + "name": "count",
  630 + "type": "count",
  631 + "label": "updatedDevicesNumber",
  632 + "color": "#4caf50",
  633 + "settings": {},
  634 + "_hash": 0.7404827038869322,
  635 + "units": null,
  636 + "decimals": null,
  637 + "funcBody": null,
  638 + "usePostProcessing": null,
  639 + "postFuncBody": null
  640 + }
  641 + ]
  642 + }
  643 + ],
  644 + "timewindow": {
  645 + "realtime": {
  646 + "timewindowMs": 60000
  647 + }
  648 + },
  649 + "showTitle": false,
  650 + "backgroundColor": "#fff",
  651 + "color": "rgba(0, 0, 0, 0.87)",
  652 + "padding": "0px",
  653 + "settings": {
  654 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <img id=\"activeDevices\" src='data:image/svg+xml;utf8,<svg width=\"34\" height=\"32\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/>\n<path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/>\n</svg>'>\n <div class='value' id=\"activeDevices\">\n ${updatedDevicesNumber:0}\n </div> \n <div class='description' id=\"activeDevices\">\n Device Updated\n </div>\n </div>\n</div>",
  655 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  656 + },
  657 + "title": "New HTML Value Card",
  658 + "dropShadow": true,
  659 + "enableFullscreen": false,
  660 + "widgetStyle": {},
  661 + "titleStyle": {
  662 + "fontSize": "16px",
  663 + "fontWeight": 400
  664 + },
  665 + "useDashboardTimewindow": true,
  666 + "showLegend": false,
  667 + "actions": {
  668 + "elementClick": [
  669 + {
  670 + "name": "activeDevices",
  671 + "icon": "more_horiz",
  672 + "type": "openDashboardState",
  673 + "targetDashboardStateId": "device_updated",
  674 + "setEntityId": false,
  675 + "stateEntityParamName": null,
  676 + "openInSeparateDialog": false,
  677 + "dialogTitle": "",
  678 + "dialogHideDashboardToolbar": true,
  679 + "dialogWidth": null,
  680 + "dialogHeight": null,
  681 + "openRightLayout": false,
  682 + "id": "d787c212-8c56-34f0-349a-5aae2ffd1eae"
  683 + }
  684 + ]
  685 + },
  686 + "showTitleIcon": false,
  687 + "titleIcon": null,
  688 + "iconColor": "rgba(0, 0, 0, 0.87)",
  689 + "iconSize": "24px",
  690 + "titleTooltip": "",
  691 + "enableDataExport": false,
  692 + "displayTimewindow": true
  693 + },
  694 + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81"
  695 + },
  696 + "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
  697 + "isSystemType": true,
  698 + "bundleAlias": "cards",
  699 + "typeAlias": "html_value_card",
  700 + "type": "latest",
  701 + "title": "New widget",
  702 + "sizeX": 8,
  703 + "sizeY": 3,
  704 + "config": {
  705 + "datasources": [
  706 + {
  707 + "type": "entityCount",
  708 + "name": "",
  709 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  710 + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  711 + "dataKeys": [
  712 + {
  713 + "name": "count",
  714 + "type": "count",
  715 + "label": "updatingDevicesNumber",
  716 + "color": "#4caf50",
  717 + "settings": {},
  718 + "_hash": 0.7404827038869322,
  719 + "units": null,
  720 + "decimals": null,
  721 + "funcBody": null,
  722 + "usePostProcessing": null,
  723 + "postFuncBody": null
  724 + }
  725 + ]
  726 + }
  727 + ],
  728 + "timewindow": {
  729 + "realtime": {
  730 + "timewindowMs": 60000
  731 + }
  732 + },
  733 + "showTitle": false,
  734 + "backgroundColor": "#fff",
  735 + "color": "rgba(0, 0, 0, 0.87)",
  736 + "padding": "0px",
  737 + "settings": {
  738 + "cardHtml": "<div class='card' id=\"activeDevices\">\n <div class='content' id=\"activeDevices\">\n <div class=\"container-svg\" id=\"activeDevices\">\n <svg viewBox=\"0 0 24 24\" id=\"activeDevices\">\n <path id=\"activeDevices\" fill=\"currentColor\" d=\"M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z\" />\n </svg>\n </div>\n <div class='value error_firmware_failed_count' id=\"activeDevices\">\n ${updatingDevicesNumber:0}\n </div> \n <script type=\"text/javascript\">\n function init() {\n var counter = $('.error_firmware_failed_count');\n var value = +counter.text();\n if(value) {\n counter.css('color', '#D93025');\n }\n };\n init();\n </script>\n <div class='description' id=\"activeDevices\">\n Device Failed\n </div>\n </div>\n</div>",
  739 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .container-svg {\n height: 40px;\n width: 40px;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .container-svg {\n height: 28px;\n width: 28px;\n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}"
  740 + },
  741 + "title": "New HTML Value Card",
  742 + "dropShadow": true,
  743 + "enableFullscreen": false,
  744 + "widgetStyle": {},
  745 + "titleStyle": {
  746 + "fontSize": "16px",
  747 + "fontWeight": 400
  748 + },
  749 + "useDashboardTimewindow": true,
  750 + "showLegend": false,
  751 + "actions": {
  752 + "elementClick": [
  753 + {
  754 + "name": "activeDevices",
  755 + "icon": "more_horiz",
  756 + "type": "openDashboardState",
  757 + "targetDashboardStateId": "device_error",
  758 + "setEntityId": false,
  759 + "stateEntityParamName": null,
  760 + "openInSeparateDialog": false,
  761 + "dialogTitle": "",
  762 + "dialogHideDashboardToolbar": true,
  763 + "dialogWidth": null,
  764 + "dialogHeight": null,
  765 + "openRightLayout": false,
  766 + "id": "0b3d2887-9929-84d5-3795-0763dca15cba"
  767 + }
  768 + ]
  769 + },
  770 + "showTitleIcon": false,
  771 + "titleIcon": null,
  772 + "iconColor": "rgba(0, 0, 0, 0.87)",
  773 + "iconSize": "24px",
  774 + "titleTooltip": "",
  775 + "enableDataExport": false,
  776 + "displayTimewindow": true
  777 + },
  778 + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8"
  779 + },
  780 + "21be08bb-ec90-f760-ad6f-e7678f12c401": {
  781 + "isSystemType": true,
  782 + "bundleAlias": "cards",
  783 + "typeAlias": "entities_table",
  784 + "type": "latest",
  785 + "title": "New widget",
  786 + "image": null,
  787 + "description": null,
  788 + "sizeX": 7.5,
  789 + "sizeY": 6.5,
  790 + "config": {
  791 + "timewindow": {
  792 + "realtime": {
  793 + "interval": 1000,
  794 + "timewindowMs": 86400000
  795 + },
  796 + "aggregation": {
  797 + "type": "NONE",
  798 + "limit": 200
  799 + }
  800 + },
  801 + "showTitle": true,
  802 + "backgroundColor": "rgb(255, 255, 255)",
  803 + "color": "rgba(0, 0, 0, 0.87)",
  804 + "padding": "4px",
  805 + "settings": {
  806 + "enableSearch": true,
  807 + "displayPagination": true,
  808 + "defaultPageSize": 10,
  809 + "defaultSortOrder": "entityLabel",
  810 + "displayEntityName": false,
  811 + "displayEntityType": false,
  812 + "enableSelectColumnDisplay": false,
  813 + "enableStickyHeader": true,
  814 + "enableStickyAction": true,
  815 + "entitiesTitle": "Devices",
  816 + "displayEntityLabel": true,
  817 + "entityLabelColumnTitle": "Device"
  818 + },
  819 + "title": "New Entities table",
  820 + "dropShadow": true,
  821 + "enableFullscreen": true,
  822 + "titleStyle": {
  823 + "fontSize": "16px",
  824 + "fontWeight": 400,
  825 + "padding": "5px 10px 5px 10px"
  826 + },
  827 + "useDashboardTimewindow": false,
  828 + "showLegend": false,
  829 + "datasources": [
  830 + {
  831 + "type": "entity",
  832 + "name": null,
  833 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  834 + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  835 + "dataKeys": [
  836 + {
  837 + "name": "current_fw_title",
  838 + "type": "timeseries",
  839 + "label": "Current FW title",
  840 + "color": "#2196f3",
  841 + "settings": {
  842 + "columnWidth": "0px",
  843 + "useCellStyleFunction": false,
  844 + "cellStyleFunction": "",
  845 + "useCellContentFunction": false,
  846 + "defaultColumnVisibility": "visible",
  847 + "columnSelectionToDisplay": "enabled"
  848 + },
  849 + "_hash": 0.09545533885166413,
  850 + "units": null,
  851 + "decimals": null,
  852 + "funcBody": null,
  853 + "usePostProcessing": null,
  854 + "postFuncBody": null
  855 + },
  856 + {
  857 + "name": "current_fw_version",
  858 + "type": "timeseries",
  859 + "label": "Current FW version",
  860 + "color": "#4caf50",
  861 + "settings": {
  862 + "columnWidth": "0px",
  863 + "useCellStyleFunction": false,
  864 + "cellStyleFunction": "",
  865 + "useCellContentFunction": false,
  866 + "defaultColumnVisibility": "visible",
  867 + "columnSelectionToDisplay": "enabled"
  868 + },
  869 + "_hash": 0.7206056602328659,
  870 + "units": null,
  871 + "decimals": null,
  872 + "funcBody": null,
  873 + "usePostProcessing": null,
  874 + "postFuncBody": null
  875 + },
  876 + {
  877 + "name": "target_fw_title",
  878 + "type": "timeseries",
  879 + "label": "Target FW title",
  880 + "color": "#ffc107",
  881 + "settings": {
  882 + "columnWidth": "0px",
  883 + "useCellStyleFunction": false,
  884 + "cellStyleFunction": "",
  885 + "useCellContentFunction": false,
  886 + "defaultColumnVisibility": "visible",
  887 + "columnSelectionToDisplay": "enabled"
  888 + },
  889 + "_hash": 0.9934225682766313,
  890 + "units": null,
  891 + "decimals": null,
  892 + "funcBody": null,
  893 + "usePostProcessing": null,
  894 + "postFuncBody": null
  895 + },
  896 + {
  897 + "name": "target_fw_version",
  898 + "type": "timeseries",
  899 + "label": "Target FW version",
  900 + "color": "#607d8b",
  901 + "settings": {
  902 + "columnWidth": "0px",
  903 + "useCellStyleFunction": false,
  904 + "cellStyleFunction": "",
  905 + "useCellContentFunction": false,
  906 + "cellContentFunction": "",
  907 + "defaultColumnVisibility": "visible",
  908 + "columnSelectionToDisplay": "enabled"
  909 + },
  910 + "_hash": 0.5251724416842531,
  911 + "units": null,
  912 + "decimals": null,
  913 + "funcBody": null,
  914 + "usePostProcessing": null,
  915 + "postFuncBody": null
  916 + },
  917 + {
  918 + "name": "target_fw_ts",
  919 + "type": "timeseries",
  920 + "label": "Target FW set time",
  921 + "color": "#e91e63",
  922 + "settings": {
  923 + "columnWidth": "0px",
  924 + "useCellStyleFunction": false,
  925 + "cellStyleFunction": "",
  926 + "useCellContentFunction": true,
  927 + "defaultColumnVisibility": "visible",
  928 + "columnSelectionToDisplay": "enabled",
  929 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  930 + },
  931 + "_hash": 0.31823244858578237,
  932 + "units": null,
  933 + "decimals": null,
  934 + "funcBody": null,
  935 + "usePostProcessing": null,
  936 + "postFuncBody": null
  937 + },
  938 + {
  939 + "name": "fw_state",
  940 + "type": "timeseries",
  941 + "label": "Progress",
  942 + "color": "#9c27b0",
  943 + "settings": {
  944 + "columnWidth": "30%",
  945 + "useCellStyleFunction": true,
  946 + "useCellContentFunction": true,
  947 + "defaultColumnVisibility": "visible",
  948 + "columnSelectionToDisplay": "enabled",
  949 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  950 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  951 + },
  952 + "_hash": 0.8174211757846257,
  953 + "units": null,
  954 + "decimals": null,
  955 + "funcBody": null,
  956 + "usePostProcessing": null,
  957 + "postFuncBody": null
  958 + },
  959 + {
  960 + "name": "fw_state",
  961 + "type": "timeseries",
  962 + "label": "Status",
  963 + "color": "#f44336",
  964 + "settings": {
  965 + "columnWidth": "130px",
  966 + "useCellStyleFunction": true,
  967 + "useCellContentFunction": true,
  968 + "defaultColumnVisibility": "visible",
  969 + "columnSelectionToDisplay": "enabled",
  970 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  971 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  972 + },
  973 + "_hash": 0.7764426948615217,
  974 + "units": null,
  975 + "decimals": null,
  976 + "funcBody": null,
  977 + "usePostProcessing": null,
  978 + "postFuncBody": null
  979 + },
  980 + {
  981 + "name": "fw_checksum",
  982 + "type": "attribute",
  983 + "label": "fw_checksum",
  984 + "color": "#3f51b5",
  985 + "settings": {
  986 + "columnWidth": "0px",
  987 + "useCellStyleFunction": false,
  988 + "cellStyleFunction": "",
  989 + "useCellContentFunction": false,
  990 + "defaultColumnVisibility": "hidden",
  991 + "columnSelectionToDisplay": "disabled"
  992 + },
  993 + "_hash": 0.5594087842471693,
  994 + "units": null,
  995 + "decimals": null,
  996 + "funcBody": null,
  997 + "usePostProcessing": null,
  998 + "postFuncBody": null
  999 + }
  1000 + ]
  1001 + }
  1002 + ],
  1003 + "actions": {
  1004 + "actionCellButton": [
  1005 + {
  1006 + "name": "History firmware update",
  1007 + "icon": "history",
  1008 + "type": "openDashboardState",
  1009 + "targetDashboardStateId": "device_firmware_history",
  1010 + "setEntityId": true,
  1011 + "stateEntityParamName": null,
  1012 + "openInSeparateDialog": false,
  1013 + "dialogTitle": "",
  1014 + "dialogHideDashboardToolbar": true,
  1015 + "dialogWidth": null,
  1016 + "dialogHeight": null,
  1017 + "openRightLayout": false,
  1018 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1019 + },
  1020 + {
  1021 + "name": "Edit firmware",
  1022 + "icon": "edit",
  1023 + "type": "customPretty",
  1024 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1025 + "customCss": "",
  1026 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
  1027 + "customResources": [],
  1028 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1029 + },
  1030 + {
  1031 + "name": "Download firware",
  1032 + "icon": "file_download",
  1033 + "type": "custom",
  1034 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1035 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1036 + },
  1037 + {
  1038 + "name": "Copy checksum",
  1039 + "icon": "content_copy",
  1040 + "type": "custom",
  1041 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1042 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1043 + }
  1044 + ]
  1045 + },
  1046 + "showTitleIcon": false,
  1047 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1048 + "iconSize": "24px",
  1049 + "titleTooltip": "",
  1050 + "widgetStyle": {}
  1051 + },
  1052 + "row": 0,
  1053 + "col": 0,
  1054 + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401"
  1055 + },
  1056 + "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
  1057 + "isSystemType": true,
  1058 + "bundleAlias": "cards",
  1059 + "typeAlias": "entities_table",
  1060 + "type": "latest",
  1061 + "title": "New widget",
  1062 + "image": null,
  1063 + "description": null,
  1064 + "sizeX": 7.5,
  1065 + "sizeY": 6.5,
  1066 + "config": {
  1067 + "timewindow": {
  1068 + "realtime": {
  1069 + "interval": 1000,
  1070 + "timewindowMs": 86400000
  1071 + },
  1072 + "aggregation": {
  1073 + "type": "NONE",
  1074 + "limit": 200
  1075 + }
  1076 + },
  1077 + "showTitle": true,
  1078 + "backgroundColor": "rgb(255, 255, 255)",
  1079 + "color": "rgba(0, 0, 0, 0.87)",
  1080 + "padding": "4px",
  1081 + "settings": {
  1082 + "enableSearch": true,
  1083 + "displayPagination": true,
  1084 + "defaultPageSize": 10,
  1085 + "defaultSortOrder": "entityLabel",
  1086 + "displayEntityName": false,
  1087 + "displayEntityType": false,
  1088 + "enableSelectColumnDisplay": false,
  1089 + "enableStickyHeader": true,
  1090 + "enableStickyAction": true,
  1091 + "entitiesTitle": "Devices",
  1092 + "displayEntityLabel": true,
  1093 + "entityLabelColumnTitle": "Device"
  1094 + },
  1095 + "title": "New Entities table",
  1096 + "dropShadow": true,
  1097 + "enableFullscreen": true,
  1098 + "titleStyle": {
  1099 + "fontSize": "16px",
  1100 + "fontWeight": 400,
  1101 + "padding": "5px 10px 5px 10px"
  1102 + },
  1103 + "useDashboardTimewindow": false,
  1104 + "showLegend": false,
  1105 + "datasources": [
  1106 + {
  1107 + "type": "entity",
  1108 + "name": null,
  1109 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1110 + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  1111 + "dataKeys": [
  1112 + {
  1113 + "name": "current_fw_title",
  1114 + "type": "timeseries",
  1115 + "label": "Current FW title",
  1116 + "color": "#2196f3",
  1117 + "settings": {
  1118 + "columnWidth": "0px",
  1119 + "useCellStyleFunction": false,
  1120 + "cellStyleFunction": "",
  1121 + "useCellContentFunction": false,
  1122 + "defaultColumnVisibility": "visible",
  1123 + "columnSelectionToDisplay": "enabled"
  1124 + },
  1125 + "_hash": 0.09545533885166413,
  1126 + "units": null,
  1127 + "decimals": null,
  1128 + "funcBody": null,
  1129 + "usePostProcessing": null,
  1130 + "postFuncBody": null
  1131 + },
  1132 + {
  1133 + "name": "current_fw_version",
  1134 + "type": "timeseries",
  1135 + "label": "Current FW version",
  1136 + "color": "#4caf50",
  1137 + "settings": {
  1138 + "columnWidth": "0px",
  1139 + "useCellStyleFunction": false,
  1140 + "cellStyleFunction": "",
  1141 + "useCellContentFunction": false,
  1142 + "defaultColumnVisibility": "visible",
  1143 + "columnSelectionToDisplay": "enabled"
  1144 + },
  1145 + "_hash": 0.7206056602328659,
  1146 + "units": null,
  1147 + "decimals": null,
  1148 + "funcBody": null,
  1149 + "usePostProcessing": null,
  1150 + "postFuncBody": null
  1151 + },
  1152 + {
  1153 + "name": "target_fw_title",
  1154 + "type": "timeseries",
  1155 + "label": "Target FW title",
  1156 + "color": "#ffc107",
  1157 + "settings": {
  1158 + "columnWidth": "0px",
  1159 + "useCellStyleFunction": false,
  1160 + "cellStyleFunction": "",
  1161 + "useCellContentFunction": false,
  1162 + "defaultColumnVisibility": "visible",
  1163 + "columnSelectionToDisplay": "enabled"
  1164 + },
  1165 + "_hash": 0.9934225682766313,
  1166 + "units": null,
  1167 + "decimals": null,
  1168 + "funcBody": null,
  1169 + "usePostProcessing": null,
  1170 + "postFuncBody": null
  1171 + },
  1172 + {
  1173 + "name": "target_fw_version",
  1174 + "type": "timeseries",
  1175 + "label": "Target FW version",
  1176 + "color": "#607d8b",
  1177 + "settings": {
  1178 + "columnWidth": "0px",
  1179 + "useCellStyleFunction": false,
  1180 + "cellStyleFunction": "",
  1181 + "useCellContentFunction": false,
  1182 + "cellContentFunction": "",
  1183 + "defaultColumnVisibility": "visible",
  1184 + "columnSelectionToDisplay": "enabled"
  1185 + },
  1186 + "_hash": 0.5251724416842531,
  1187 + "units": null,
  1188 + "decimals": null,
  1189 + "funcBody": null,
  1190 + "usePostProcessing": null,
  1191 + "postFuncBody": null
  1192 + },
  1193 + {
  1194 + "name": "target_fw_ts",
  1195 + "type": "timeseries",
  1196 + "label": "Target FW set time",
  1197 + "color": "#e91e63",
  1198 + "settings": {
  1199 + "columnWidth": "0px",
  1200 + "useCellStyleFunction": false,
  1201 + "cellStyleFunction": "",
  1202 + "useCellContentFunction": true,
  1203 + "defaultColumnVisibility": "visible",
  1204 + "columnSelectionToDisplay": "enabled",
  1205 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1206 + },
  1207 + "_hash": 0.31823244858578237,
  1208 + "units": null,
  1209 + "decimals": null,
  1210 + "funcBody": null,
  1211 + "usePostProcessing": null,
  1212 + "postFuncBody": null
  1213 + },
  1214 + {
  1215 + "name": "fw_state",
  1216 + "type": "timeseries",
  1217 + "label": "Progress",
  1218 + "color": "#9c27b0",
  1219 + "settings": {
  1220 + "columnWidth": "30%",
  1221 + "useCellStyleFunction": true,
  1222 + "useCellContentFunction": true,
  1223 + "defaultColumnVisibility": "visible",
  1224 + "columnSelectionToDisplay": "enabled",
  1225 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1226 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1227 + },
  1228 + "_hash": 0.8174211757846257,
  1229 + "units": null,
  1230 + "decimals": null,
  1231 + "funcBody": null,
  1232 + "usePostProcessing": null,
  1233 + "postFuncBody": null
  1234 + },
  1235 + {
  1236 + "name": "fw_state",
  1237 + "type": "timeseries",
  1238 + "label": "Status",
  1239 + "color": "#f44336",
  1240 + "settings": {
  1241 + "columnWidth": "130px",
  1242 + "useCellStyleFunction": true,
  1243 + "useCellContentFunction": true,
  1244 + "defaultColumnVisibility": "visible",
  1245 + "columnSelectionToDisplay": "enabled",
  1246 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1247 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1248 + },
  1249 + "_hash": 0.7764426948615217,
  1250 + "units": null,
  1251 + "decimals": null,
  1252 + "funcBody": null,
  1253 + "usePostProcessing": null,
  1254 + "postFuncBody": null
  1255 + },
  1256 + {
  1257 + "name": "fw_checksum",
  1258 + "type": "attribute",
  1259 + "label": "fw_checksum",
  1260 + "color": "#3f51b5",
  1261 + "settings": {
  1262 + "columnWidth": "0px",
  1263 + "useCellStyleFunction": false,
  1264 + "cellStyleFunction": "",
  1265 + "useCellContentFunction": false,
  1266 + "defaultColumnVisibility": "hidden",
  1267 + "columnSelectionToDisplay": "disabled"
  1268 + },
  1269 + "_hash": 0.5594087842471693,
  1270 + "units": null,
  1271 + "decimals": null,
  1272 + "funcBody": null,
  1273 + "usePostProcessing": null,
  1274 + "postFuncBody": null
  1275 + }
  1276 + ]
  1277 + }
  1278 + ],
  1279 + "actions": {
  1280 + "actionCellButton": [
  1281 + {
  1282 + "name": "History firmware update",
  1283 + "icon": "history",
  1284 + "type": "openDashboardState",
  1285 + "targetDashboardStateId": "device_firmware_history",
  1286 + "setEntityId": true,
  1287 + "stateEntityParamName": null,
  1288 + "openInSeparateDialog": false,
  1289 + "dialogTitle": "",
  1290 + "dialogHideDashboardToolbar": true,
  1291 + "dialogWidth": null,
  1292 + "dialogHeight": null,
  1293 + "openRightLayout": false,
  1294 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1295 + },
  1296 + {
  1297 + "name": "Edit firmware",
  1298 + "icon": "edit",
  1299 + "type": "customPretty",
  1300 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1301 + "customCss": "",
  1302 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
  1303 + "customResources": [],
  1304 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1305 + },
  1306 + {
  1307 + "name": "Download firware",
  1308 + "icon": "file_download",
  1309 + "type": "custom",
  1310 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1311 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1312 + },
  1313 + {
  1314 + "name": "Copy checksum",
  1315 + "icon": "content_copy",
  1316 + "type": "custom",
  1317 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1318 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1319 + }
  1320 + ]
  1321 + },
  1322 + "showTitleIcon": false,
  1323 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1324 + "iconSize": "24px",
  1325 + "titleTooltip": "",
  1326 + "widgetStyle": {}
  1327 + },
  1328 + "row": 0,
  1329 + "col": 0,
  1330 + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91"
  1331 + },
  1332 + "3624013b-378c-f110-5eba-ae95c25a4dcc": {
  1333 + "isSystemType": true,
  1334 + "bundleAlias": "cards",
  1335 + "typeAlias": "entities_table",
  1336 + "type": "latest",
  1337 + "title": "New widget",
  1338 + "image": null,
  1339 + "description": null,
  1340 + "sizeX": 7.5,
  1341 + "sizeY": 6.5,
  1342 + "config": {
  1343 + "timewindow": {
  1344 + "realtime": {
  1345 + "interval": 1000,
  1346 + "timewindowMs": 86400000
  1347 + },
  1348 + "aggregation": {
  1349 + "type": "NONE",
  1350 + "limit": 200
  1351 + }
  1352 + },
  1353 + "showTitle": true,
  1354 + "backgroundColor": "rgb(255, 255, 255)",
  1355 + "color": "rgba(0, 0, 0, 0.87)",
  1356 + "padding": "4px",
  1357 + "settings": {
  1358 + "enableSearch": true,
  1359 + "displayPagination": true,
  1360 + "defaultPageSize": 10,
  1361 + "defaultSortOrder": "entityLabel",
  1362 + "displayEntityName": false,
  1363 + "displayEntityType": false,
  1364 + "enableSelectColumnDisplay": false,
  1365 + "enableStickyHeader": true,
  1366 + "enableStickyAction": true,
  1367 + "entitiesTitle": "Devices",
  1368 + "displayEntityLabel": true,
  1369 + "entityLabelColumnTitle": "Device"
  1370 + },
  1371 + "title": "New Entities table",
  1372 + "dropShadow": true,
  1373 + "enableFullscreen": true,
  1374 + "titleStyle": {
  1375 + "fontSize": "16px",
  1376 + "fontWeight": 400,
  1377 + "padding": "5px 10px 5px 10px"
  1378 + },
  1379 + "useDashboardTimewindow": false,
  1380 + "showLegend": false,
  1381 + "datasources": [
  1382 + {
  1383 + "type": "entity",
  1384 + "name": null,
  1385 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1386 + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  1387 + "dataKeys": [
  1388 + {
  1389 + "name": "current_fw_title",
  1390 + "type": "timeseries",
  1391 + "label": "Current FW title",
  1392 + "color": "#2196f3",
  1393 + "settings": {
  1394 + "columnWidth": "0px",
  1395 + "useCellStyleFunction": false,
  1396 + "cellStyleFunction": "",
  1397 + "useCellContentFunction": false,
  1398 + "defaultColumnVisibility": "visible",
  1399 + "columnSelectionToDisplay": "enabled"
  1400 + },
  1401 + "_hash": 0.09545533885166413,
  1402 + "units": null,
  1403 + "decimals": null,
  1404 + "funcBody": null,
  1405 + "usePostProcessing": null,
  1406 + "postFuncBody": null
  1407 + },
  1408 + {
  1409 + "name": "current_fw_version",
  1410 + "type": "timeseries",
  1411 + "label": "Current FW version",
  1412 + "color": "#4caf50",
  1413 + "settings": {
  1414 + "columnWidth": "0px",
  1415 + "useCellStyleFunction": false,
  1416 + "cellStyleFunction": "",
  1417 + "useCellContentFunction": false,
  1418 + "defaultColumnVisibility": "visible",
  1419 + "columnSelectionToDisplay": "enabled"
  1420 + },
  1421 + "_hash": 0.7206056602328659,
  1422 + "units": null,
  1423 + "decimals": null,
  1424 + "funcBody": null,
  1425 + "usePostProcessing": null,
  1426 + "postFuncBody": null
  1427 + },
  1428 + {
  1429 + "name": "target_fw_title",
  1430 + "type": "timeseries",
  1431 + "label": "Target FW title",
  1432 + "color": "#ffc107",
  1433 + "settings": {
  1434 + "columnWidth": "0px",
  1435 + "useCellStyleFunction": false,
  1436 + "cellStyleFunction": "",
  1437 + "useCellContentFunction": false,
  1438 + "defaultColumnVisibility": "visible",
  1439 + "columnSelectionToDisplay": "enabled"
  1440 + },
  1441 + "_hash": 0.9934225682766313,
  1442 + "units": null,
  1443 + "decimals": null,
  1444 + "funcBody": null,
  1445 + "usePostProcessing": null,
  1446 + "postFuncBody": null
  1447 + },
  1448 + {
  1449 + "name": "target_fw_version",
  1450 + "type": "timeseries",
  1451 + "label": "Target FW version",
  1452 + "color": "#607d8b",
  1453 + "settings": {
  1454 + "columnWidth": "0px",
  1455 + "useCellStyleFunction": false,
  1456 + "cellStyleFunction": "",
  1457 + "useCellContentFunction": false,
  1458 + "cellContentFunction": "",
  1459 + "defaultColumnVisibility": "visible",
  1460 + "columnSelectionToDisplay": "enabled"
  1461 + },
  1462 + "_hash": 0.5251724416842531,
  1463 + "units": null,
  1464 + "decimals": null,
  1465 + "funcBody": null,
  1466 + "usePostProcessing": null,
  1467 + "postFuncBody": null
  1468 + },
  1469 + {
  1470 + "name": "target_fw_ts",
  1471 + "type": "timeseries",
  1472 + "label": "Target FW set time",
  1473 + "color": "#e91e63",
  1474 + "settings": {
  1475 + "columnWidth": "0px",
  1476 + "useCellStyleFunction": false,
  1477 + "cellStyleFunction": "",
  1478 + "useCellContentFunction": true,
  1479 + "defaultColumnVisibility": "visible",
  1480 + "columnSelectionToDisplay": "enabled",
  1481 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1482 + },
  1483 + "_hash": 0.31823244858578237,
  1484 + "units": null,
  1485 + "decimals": null,
  1486 + "funcBody": null,
  1487 + "usePostProcessing": null,
  1488 + "postFuncBody": null
  1489 + },
  1490 + {
  1491 + "name": "fw_state",
  1492 + "type": "timeseries",
  1493 + "label": "Progress",
  1494 + "color": "#9c27b0",
  1495 + "settings": {
  1496 + "columnWidth": "30%",
  1497 + "useCellStyleFunction": true,
  1498 + "useCellContentFunction": true,
  1499 + "defaultColumnVisibility": "visible",
  1500 + "columnSelectionToDisplay": "enabled",
  1501 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1502 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1503 + },
  1504 + "_hash": 0.8174211757846257,
  1505 + "units": null,
  1506 + "decimals": null,
  1507 + "funcBody": null,
  1508 + "usePostProcessing": null,
  1509 + "postFuncBody": null
  1510 + },
  1511 + {
  1512 + "name": "fw_state",
  1513 + "type": "timeseries",
  1514 + "label": "Status",
  1515 + "color": "#f44336",
  1516 + "settings": {
  1517 + "columnWidth": "130px",
  1518 + "useCellStyleFunction": true,
  1519 + "useCellContentFunction": true,
  1520 + "defaultColumnVisibility": "visible",
  1521 + "columnSelectionToDisplay": "enabled",
  1522 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1523 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1524 + },
  1525 + "_hash": 0.7764426948615217,
  1526 + "units": null,
  1527 + "decimals": null,
  1528 + "funcBody": null,
  1529 + "usePostProcessing": null,
  1530 + "postFuncBody": null
  1531 + },
  1532 + {
  1533 + "name": "fw_checksum",
  1534 + "type": "attribute",
  1535 + "label": "fw_checksum",
  1536 + "color": "#3f51b5",
  1537 + "settings": {
  1538 + "columnWidth": "0px",
  1539 + "useCellStyleFunction": false,
  1540 + "cellStyleFunction": "",
  1541 + "useCellContentFunction": false,
  1542 + "defaultColumnVisibility": "hidden",
  1543 + "columnSelectionToDisplay": "disabled"
  1544 + },
  1545 + "_hash": 0.5594087842471693,
  1546 + "units": null,
  1547 + "decimals": null,
  1548 + "funcBody": null,
  1549 + "usePostProcessing": null,
  1550 + "postFuncBody": null
  1551 + }
  1552 + ]
  1553 + }
  1554 + ],
  1555 + "actions": {
  1556 + "actionCellButton": [
  1557 + {
  1558 + "name": "History firmware update",
  1559 + "icon": "history",
  1560 + "type": "openDashboardState",
  1561 + "targetDashboardStateId": "device_firmware_history",
  1562 + "setEntityId": true,
  1563 + "stateEntityParamName": null,
  1564 + "openInSeparateDialog": false,
  1565 + "dialogTitle": "",
  1566 + "dialogHideDashboardToolbar": true,
  1567 + "dialogWidth": null,
  1568 + "dialogHeight": null,
  1569 + "openRightLayout": false,
  1570 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1571 + },
  1572 + {
  1573 + "name": "Edit firmware",
  1574 + "icon": "edit",
  1575 + "type": "customPretty",
  1576 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1577 + "customCss": "",
  1578 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
  1579 + "customResources": [],
  1580 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1581 + },
  1582 + {
  1583 + "name": "Download firware",
  1584 + "icon": "file_download",
  1585 + "type": "custom",
  1586 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1587 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1588 + },
  1589 + {
  1590 + "name": "Copy checksum",
  1591 + "icon": "content_copy",
  1592 + "type": "custom",
  1593 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1594 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1595 + }
  1596 + ]
  1597 + },
  1598 + "showTitleIcon": false,
  1599 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1600 + "iconSize": "24px",
  1601 + "titleTooltip": "",
  1602 + "widgetStyle": {}
  1603 + },
  1604 + "row": 0,
  1605 + "col": 0,
  1606 + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc"
  1607 + },
  1608 + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
  1609 + "isSystemType": true,
  1610 + "bundleAlias": "cards",
  1611 + "typeAlias": "entities_table",
  1612 + "type": "latest",
  1613 + "title": "New widget",
  1614 + "image": null,
  1615 + "description": null,
  1616 + "sizeX": 7.5,
  1617 + "sizeY": 6.5,
  1618 + "config": {
  1619 + "timewindow": {
  1620 + "realtime": {
  1621 + "interval": 1000,
  1622 + "timewindowMs": 86400000
  1623 + },
  1624 + "aggregation": {
  1625 + "type": "NONE",
  1626 + "limit": 200
  1627 + }
  1628 + },
  1629 + "showTitle": true,
  1630 + "backgroundColor": "rgb(255, 255, 255)",
  1631 + "color": "rgba(0, 0, 0, 0.87)",
  1632 + "padding": "4px",
  1633 + "settings": {
  1634 + "enableSearch": true,
  1635 + "displayPagination": true,
  1636 + "defaultPageSize": 10,
  1637 + "defaultSortOrder": "entityLabel",
  1638 + "displayEntityName": false,
  1639 + "displayEntityType": false,
  1640 + "enableSelectColumnDisplay": false,
  1641 + "enableStickyHeader": true,
  1642 + "enableStickyAction": true,
  1643 + "entitiesTitle": "Devices",
  1644 + "displayEntityLabel": true,
  1645 + "entityLabelColumnTitle": "Device"
  1646 + },
  1647 + "title": "New Entities table",
  1648 + "dropShadow": true,
  1649 + "enableFullscreen": true,
  1650 + "titleStyle": {
  1651 + "fontSize": "16px",
  1652 + "fontWeight": 400,
  1653 + "padding": "5px 10px 5px 10px"
  1654 + },
  1655 + "useDashboardTimewindow": false,
  1656 + "showLegend": false,
  1657 + "datasources": [
  1658 + {
  1659 + "type": "entity",
  1660 + "name": null,
  1661 + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  1662 + "filterId": "6044e198-df64-cd76-f339-696f220c4943",
  1663 + "dataKeys": [
  1664 + {
  1665 + "name": "current_fw_title",
  1666 + "type": "timeseries",
  1667 + "label": "Current FW title",
  1668 + "color": "#2196f3",
  1669 + "settings": {
  1670 + "columnWidth": "0px",
  1671 + "useCellStyleFunction": false,
  1672 + "cellStyleFunction": "",
  1673 + "useCellContentFunction": false,
  1674 + "defaultColumnVisibility": "visible",
  1675 + "columnSelectionToDisplay": "enabled"
  1676 + },
  1677 + "_hash": 0.09545533885166413,
  1678 + "units": null,
  1679 + "decimals": null,
  1680 + "funcBody": null,
  1681 + "usePostProcessing": null,
  1682 + "postFuncBody": null
  1683 + },
  1684 + {
  1685 + "name": "current_fw_version",
  1686 + "type": "timeseries",
  1687 + "label": "Current FW version",
  1688 + "color": "#4caf50",
  1689 + "settings": {
  1690 + "columnWidth": "0px",
  1691 + "useCellStyleFunction": false,
  1692 + "cellStyleFunction": "",
  1693 + "useCellContentFunction": false,
  1694 + "defaultColumnVisibility": "visible",
  1695 + "columnSelectionToDisplay": "enabled"
  1696 + },
  1697 + "_hash": 0.7206056602328659,
  1698 + "units": null,
  1699 + "decimals": null,
  1700 + "funcBody": null,
  1701 + "usePostProcessing": null,
  1702 + "postFuncBody": null
  1703 + },
  1704 + {
  1705 + "name": "target_fw_title",
  1706 + "type": "timeseries",
  1707 + "label": "Target FW title",
  1708 + "color": "#ffc107",
  1709 + "settings": {
  1710 + "columnWidth": "0px",
  1711 + "useCellStyleFunction": false,
  1712 + "cellStyleFunction": "",
  1713 + "useCellContentFunction": false,
  1714 + "defaultColumnVisibility": "visible",
  1715 + "columnSelectionToDisplay": "enabled"
  1716 + },
  1717 + "_hash": 0.9934225682766313,
  1718 + "units": null,
  1719 + "decimals": null,
  1720 + "funcBody": null,
  1721 + "usePostProcessing": null,
  1722 + "postFuncBody": null
  1723 + },
  1724 + {
  1725 + "name": "target_fw_version",
  1726 + "type": "timeseries",
  1727 + "label": "Target FW version",
  1728 + "color": "#607d8b",
  1729 + "settings": {
  1730 + "columnWidth": "0px",
  1731 + "useCellStyleFunction": false,
  1732 + "cellStyleFunction": "",
  1733 + "useCellContentFunction": false,
  1734 + "cellContentFunction": "",
  1735 + "defaultColumnVisibility": "visible",
  1736 + "columnSelectionToDisplay": "enabled"
  1737 + },
  1738 + "_hash": 0.5251724416842531,
  1739 + "units": null,
  1740 + "decimals": null,
  1741 + "funcBody": null,
  1742 + "usePostProcessing": null,
  1743 + "postFuncBody": null
  1744 + },
  1745 + {
  1746 + "name": "target_fw_ts",
  1747 + "type": "timeseries",
  1748 + "label": "Target FW set time",
  1749 + "color": "#e91e63",
  1750 + "settings": {
  1751 + "columnWidth": "0px",
  1752 + "useCellStyleFunction": false,
  1753 + "cellStyleFunction": "",
  1754 + "useCellContentFunction": true,
  1755 + "defaultColumnVisibility": "visible",
  1756 + "columnSelectionToDisplay": "enabled",
  1757 + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';"
  1758 + },
  1759 + "_hash": 0.31823244858578237,
  1760 + "units": null,
  1761 + "decimals": null,
  1762 + "funcBody": null,
  1763 + "usePostProcessing": null,
  1764 + "postFuncBody": null
  1765 + },
  1766 + {
  1767 + "name": "fw_state",
  1768 + "type": "timeseries",
  1769 + "label": "Progress",
  1770 + "color": "#9c27b0",
  1771 + "settings": {
  1772 + "columnWidth": "30%",
  1773 + "useCellStyleFunction": true,
  1774 + "useCellContentFunction": true,
  1775 + "defaultColumnVisibility": "visible",
  1776 + "columnSelectionToDisplay": "enabled",
  1777 + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}",
  1778 + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `<mat-progress-bar style=\"height: 8px\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" tabindex=\"-1\" mode=\"determinate\" value=\"${progress}\" class=\"mat-progress-bar ${color}\" aria-valuenow=\"${progress}\"><div aria-hidden=\"true\"><svg width=\"100%\" height=\"8\" focusable=\"false\" class=\"mat-progress-bar-background mat-progress-bar-element\"><defs><pattern x=\"4\" y=\"0\" width=\"8\" height=\"4\" patternUnits=\"userSpaceOnUse\" id=\"mat-progress-bar-0\"><circle cx=\"2\" cy=\"2\" r=\"2\"></circle></pattern></defs><rect width=\"100%\" height=\"100%\" fill=\"url(\"/components/progress-bar/overview#mat-progress-bar-0\")\"></rect></svg><div class=\"mat-progress-bar-buffer mat-progress-bar-element\"></div><div class=\"mat-progress-bar-primary mat-progress-bar-fill mat-progress-bar-element\" style=\"transform: scale3d(${progress / 100}, 1, 1);\"></div><div class=\"mat-progress-bar-secondary mat-progress-bar-fill mat-progress-bar-element\"></div></div></mat-progress-bar>`;\n}"
  1779 + },
  1780 + "_hash": 0.8174211757846257,
  1781 + "units": null,
  1782 + "decimals": null,
  1783 + "funcBody": null,
  1784 + "usePostProcessing": null,
  1785 + "postFuncBody": null
  1786 + },
  1787 + {
  1788 + "name": "fw_state",
  1789 + "type": "timeseries",
  1790 + "label": "Status",
  1791 + "color": "#f44336",
  1792 + "settings": {
  1793 + "columnWidth": "130px",
  1794 + "useCellStyleFunction": true,
  1795 + "useCellContentFunction": true,
  1796 + "defaultColumnVisibility": "visible",
  1797 + "columnSelectionToDisplay": "enabled",
  1798 + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};",
  1799 + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000;\"><svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M6,2V8H6V8L10,12L6,16V16H6V22H18V16H18V16L14,12L18,8V8H18V2H6M16,16.5V20H8V16.5L12,12.5L16,16.5M12,11.5L8,7.5V4H16V7.5L12,11.5Z\" /></svg></mat-icon>';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.74 2.1951C11.63 1.2876 10.2575 0.687598 8.75 0.537598V2.0526C9.845 2.1876 10.8425 2.6226 11.675 3.2676L12.74 2.1951ZM13.9475 7.2501H15.4625C15.3125 5.7426 14.7125 4.3701 13.805 3.2601L12.7325 4.3251C13.3775 5.1576 13.8125 6.1551 13.9475 7.2501ZM12.7325 11.6751L13.805 12.7476C14.7125 11.6376 15.3125 10.2576 15.4625 8.7576H13.9475C13.8125 9.8451 13.3775 10.8426 12.7325 11.6751ZM8.75 13.9476V15.4626C10.2575 15.3126 11.63 14.7126 12.74 13.8051L11.6675 12.7326C10.8425 13.3776 9.845 13.8126 8.75 13.9476ZM8.75 8.0001V4.2501H7.25V8.0001H4.25L8 11.7501L11.75 8.0001H8.75ZM7.25 13.9476V15.4626C3.4625 15.0876 0.5 11.8926 0.5 8.0001C0.5 4.1076 3.4625 0.912598 7.25 0.537598V2.0526C4.2875 2.4201 2 4.9401 2 8.0001C2 11.0601 4.2875 13.5801 7.25 13.9476Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\">update</mat-icon>';\n }\n if (value == 'UPDATED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #000\"><svg style=\"width:22px;height:22px\" viewBox=\"0 0 34 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M33.26 2.82L30.44 0L12.06 18.38L3.55999 9.9L0.73999 12.72L12.06 24.04L33.26 2.82Z\" fill=\"black\"/><path d=\"M31 28H3V32H31V28Z\" fill=\"black\"/></svg></mat-icon>';\n }\n if (value == 'FAILED') {\n return '<mat-icon _role=\"img\" class=\"mat-icon notranslate material-icons mat-icon-no-color\" aria-hidden=\"true\" data-mat-icon-type=\"font\" style=\"color: #D93025\">warning</mat-icon>';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '<span style=\"vertical-align: super;padding-left: 8px;\">' + capitalize(value) + '</span>';"
  1800 + },
  1801 + "_hash": 0.7764426948615217,
  1802 + "units": null,
  1803 + "decimals": null,
  1804 + "funcBody": null,
  1805 + "usePostProcessing": null,
  1806 + "postFuncBody": null
  1807 + },
  1808 + {
  1809 + "name": "fw_checksum",
  1810 + "type": "attribute",
  1811 + "label": "fw_checksum",
  1812 + "color": "#3f51b5",
  1813 + "settings": {
  1814 + "columnWidth": "0px",
  1815 + "useCellStyleFunction": false,
  1816 + "cellStyleFunction": "",
  1817 + "useCellContentFunction": false,
  1818 + "defaultColumnVisibility": "hidden",
  1819 + "columnSelectionToDisplay": "disabled"
  1820 + },
  1821 + "_hash": 0.5594087842471693,
  1822 + "units": null,
  1823 + "decimals": null,
  1824 + "funcBody": null,
  1825 + "usePostProcessing": null,
  1826 + "postFuncBody": null
  1827 + }
  1828 + ]
  1829 + }
  1830 + ],
  1831 + "actions": {
  1832 + "actionCellButton": [
  1833 + {
  1834 + "name": "History firmware update",
  1835 + "icon": "history",
  1836 + "type": "openDashboardState",
  1837 + "targetDashboardStateId": "device_firmware_history",
  1838 + "setEntityId": true,
  1839 + "stateEntityParamName": null,
  1840 + "openInSeparateDialog": false,
  1841 + "dialogTitle": "",
  1842 + "dialogHideDashboardToolbar": true,
  1843 + "dialogWidth": null,
  1844 + "dialogHeight": null,
  1845 + "openRightLayout": false,
  1846 + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b"
  1847 + },
  1848 + {
  1849 + "name": "Edit firmware",
  1850 + "icon": "edit",
  1851 + "type": "customPretty",
  1852 + "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit firmware {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n formControlName=\"firmwareId\">\n </tb-firmware-autocomplete>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n </div>\n</form>",
  1853 + "customCss": "",
  1854 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}",
  1855 + "customResources": [],
  1856 + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5"
  1857 + },
  1858 + {
  1859 + "name": "Download firware",
  1860 + "icon": "file_download",
  1861 + "type": "custom",
  1862 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}",
  1863 + "id": "12533058-42f6-e75f-620c-219c48d01ec0"
  1864 + },
  1865 + {
  1866 + "name": "Copy checksum",
  1867 + "icon": "content_copy",
  1868 + "type": "custom",
  1869 + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}",
  1870 + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7"
  1871 + }
  1872 + ]
  1873 + },
  1874 + "showTitleIcon": false,
  1875 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1876 + "iconSize": "24px",
  1877 + "titleTooltip": "",
  1878 + "widgetStyle": {}
  1879 + },
  1880 + "row": 0,
  1881 + "col": 0,
  1882 + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176"
  1883 + }
  1884 + },
  1885 + "states": {
  1886 + "default": {
  1887 + "name": "Device list",
  1888 + "root": true,
  1889 + "layouts": {
  1890 + "main": {
  1891 + "widgets": {
  1892 + "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
  1893 + "sizeX": 19,
  1894 + "sizeY": 12,
  1895 + "row": 0,
  1896 + "col": 0
  1897 + },
  1898 + "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
  1899 + "sizeX": 5,
  1900 + "sizeY": 3,
  1901 + "row": 0,
  1902 + "col": 19
  1903 + },
  1904 + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
  1905 + "sizeX": 5,
  1906 + "sizeY": 3,
  1907 + "row": 3,
  1908 + "col": 19
  1909 + },
  1910 + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
  1911 + "sizeX": 5,
  1912 + "sizeY": 3,
  1913 + "row": 9,
  1914 + "col": 19
  1915 + },
  1916 + "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
  1917 + "sizeX": 5,
  1918 + "sizeY": 3,
  1919 + "row": 6,
  1920 + "col": 19
  1921 + }
  1922 + },
  1923 + "gridSettings": {
  1924 + "backgroundColor": "#eeeeee",
  1925 + "color": "rgba(0,0,0,0.870588)",
  1926 + "columns": 24,
  1927 + "margin": 12,
  1928 + "backgroundSizeMode": "100%",
  1929 + "autoFillHeight": true,
  1930 + "backgroundImageUrl": null,
  1931 + "mobileAutoFillHeight": true,
  1932 + "mobileRowHeight": 70
  1933 + }
  1934 + }
  1935 + }
  1936 + },
  1937 + "device_firmware_history": {
  1938 + "name": "Device Firmware history",
  1939 + "root": false,
  1940 + "layouts": {
  1941 + "main": {
  1942 + "widgets": {
  1943 + "100b756c-0082-6505-3ae1-3603e6deea48": {
  1944 + "sizeX": 24,
  1945 + "sizeY": 12,
  1946 + "row": 0,
  1947 + "col": 0
  1948 + }
  1949 + },
  1950 + "gridSettings": {
  1951 + "backgroundColor": "#eeeeee",
  1952 + "color": "rgba(0,0,0,0.870588)",
  1953 + "columns": 24,
  1954 + "margin": 10,
  1955 + "backgroundSizeMode": "100%",
  1956 + "autoFillHeight": true,
  1957 + "backgroundImageUrl": null,
  1958 + "mobileAutoFillHeight": false,
  1959 + "mobileRowHeight": 70
  1960 + }
  1961 + }
  1962 + }
  1963 + },
  1964 + "device_waiting": {
  1965 + "name": "Device waiting",
  1966 + "root": false,
  1967 + "layouts": {
  1968 + "main": {
  1969 + "widgets": {
  1970 + "21be08bb-ec90-f760-ad6f-e7678f12c401": {
  1971 + "sizeX": 24,
  1972 + "sizeY": 12,
  1973 + "row": 0,
  1974 + "col": 0
  1975 + }
  1976 + },
  1977 + "gridSettings": {
  1978 + "backgroundColor": "#eeeeee",
  1979 + "color": "rgba(0,0,0,0.870588)",
  1980 + "columns": 24,
  1981 + "margin": 10,
  1982 + "backgroundSizeMode": "100%",
  1983 + "autoFillHeight": true,
  1984 + "backgroundImageUrl": null,
  1985 + "mobileAutoFillHeight": false,
  1986 + "mobileRowHeight": 70
  1987 + }
  1988 + }
  1989 + }
  1990 + },
  1991 + "device_updating": {
  1992 + "name": "Device updating",
  1993 + "root": false,
  1994 + "layouts": {
  1995 + "main": {
  1996 + "widgets": {
  1997 + "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
  1998 + "sizeX": 24,
  1999 + "sizeY": 12,
  2000 + "row": 0,
  2001 + "col": 0
  2002 + }
  2003 + },
  2004 + "gridSettings": {
  2005 + "backgroundColor": "#eeeeee",
  2006 + "color": "rgba(0,0,0,0.870588)",
  2007 + "columns": 24,
  2008 + "margin": 10,
  2009 + "backgroundSizeMode": "100%",
  2010 + "autoFillHeight": true,
  2011 + "backgroundImageUrl": null,
  2012 + "mobileAutoFillHeight": false,
  2013 + "mobileRowHeight": 70
  2014 + }
  2015 + }
  2016 + }
  2017 + },
  2018 + "device_updated": {
  2019 + "name": "Device updated",
  2020 + "root": false,
  2021 + "layouts": {
  2022 + "main": {
  2023 + "widgets": {
  2024 + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
  2025 + "sizeX": 27,
  2026 + "sizeY": 12,
  2027 + "row": 0,
  2028 + "col": 0
  2029 + }
  2030 + },
  2031 + "gridSettings": {
  2032 + "backgroundColor": "#eeeeee",
  2033 + "color": "rgba(0,0,0,0.870588)",
  2034 + "columns": 24,
  2035 + "margin": 10,
  2036 + "backgroundSizeMode": "100%",
  2037 + "autoFillHeight": true,
  2038 + "backgroundImageUrl": null,
  2039 + "mobileAutoFillHeight": false,
  2040 + "mobileRowHeight": 70
  2041 + }
  2042 + }
  2043 + }
  2044 + },
  2045 + "device_error": {
  2046 + "name": "Device failed",
  2047 + "root": false,
  2048 + "layouts": {
  2049 + "main": {
  2050 + "widgets": {
  2051 + "3624013b-378c-f110-5eba-ae95c25a4dcc": {
  2052 + "sizeX": 24,
  2053 + "sizeY": 12,
  2054 + "row": 0,
  2055 + "col": 0
  2056 + }
  2057 + },
  2058 + "gridSettings": {
  2059 + "backgroundColor": "#eeeeee",
  2060 + "color": "rgba(0,0,0,0.870588)",
  2061 + "columns": 24,
  2062 + "margin": 10,
  2063 + "backgroundSizeMode": "100%",
  2064 + "autoFillHeight": true,
  2065 + "backgroundImageUrl": null,
  2066 + "mobileAutoFillHeight": false,
  2067 + "mobileRowHeight": 70
  2068 + }
  2069 + }
  2070 + }
  2071 + }
  2072 + },
  2073 + "entityAliases": {
  2074 + "639da5b4-31f0-0151-6282-c37a3897b7e8": {
  2075 + "id": "639da5b4-31f0-0151-6282-c37a3897b7e8",
  2076 + "alias": "All devices",
  2077 + "filter": {
  2078 + "type": "entityType",
  2079 + "resolveMultiple": true,
  2080 + "entityType": "DEVICE"
  2081 + }
  2082 + },
  2083 + "19f41c21-d9af-e666-8f50-e1748778f955": {
  2084 + "id": "19f41c21-d9af-e666-8f50-e1748778f955",
  2085 + "alias": "State entity",
  2086 + "filter": {
  2087 + "type": "stateEntity",
  2088 + "resolveMultiple": false,
  2089 + "stateEntityParamName": null,
  2090 + "defaultStateEntity": null
  2091 + }
  2092 + }
  2093 + },
  2094 + "filters": {
  2095 + "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e": {
  2096 + "id": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e",
  2097 + "filter": "WaitingDevicesFilter",
  2098 + "keyFilters": [
  2099 + {
  2100 + "key": {
  2101 + "type": "TIME_SERIES",
  2102 + "key": "fw_state"
  2103 + },
  2104 + "valueType": "STRING",
  2105 + "predicates": [
  2106 + {
  2107 + "keyFilterPredicate": {
  2108 + "operation": "EQUAL",
  2109 + "value": {
  2110 + "defaultValue": "QUEUED",
  2111 + "dynamicValue": null
  2112 + },
  2113 + "ignoreCase": false,
  2114 + "type": "STRING"
  2115 + },
  2116 + "userInfo": {
  2117 + "editable": true,
  2118 + "label": "",
  2119 + "autogeneratedLabel": true,
  2120 + "order": 0
  2121 + }
  2122 + }
  2123 + ]
  2124 + }
  2125 + ],
  2126 + "editable": false
  2127 + },
  2128 + "579f0468-9ce9-7e3e-b34c-88dd3de59897": {
  2129 + "id": "579f0468-9ce9-7e3e-b34c-88dd3de59897",
  2130 + "filter": "UpdatingDevicesFilter",
  2131 + "keyFilters": [
  2132 + {
  2133 + "key": {
  2134 + "type": "TIME_SERIES",
  2135 + "key": "fw_state"
  2136 + },
  2137 + "valueType": "STRING",
  2138 + "predicates": [
  2139 + {
  2140 + "keyFilterPredicate": {
  2141 + "operation": "OR",
  2142 + "predicates": [
  2143 + {
  2144 + "keyFilterPredicate": {
  2145 + "operation": "EQUAL",
  2146 + "value": {
  2147 + "defaultValue": "INITIATED",
  2148 + "dynamicValue": null
  2149 + },
  2150 + "ignoreCase": false,
  2151 + "type": "STRING"
  2152 + },
  2153 + "userInfo": {
  2154 + "editable": false,
  2155 + "label": "fw_state equel",
  2156 + "autogeneratedLabel": true,
  2157 + "order": 0
  2158 + }
  2159 + },
  2160 + {
  2161 + "keyFilterPredicate": {
  2162 + "operation": "EQUAL",
  2163 + "value": {
  2164 + "defaultValue": "DOWNLOADING",
  2165 + "dynamicValue": null
  2166 + },
  2167 + "ignoreCase": false,
  2168 + "type": "STRING"
  2169 + },
  2170 + "userInfo": {
  2171 + "editable": false,
  2172 + "label": "fw_state equal",
  2173 + "autogeneratedLabel": true,
  2174 + "order": 0
  2175 + }
  2176 + },
  2177 + {
  2178 + "keyFilterPredicate": {
  2179 + "operation": "EQUAL",
  2180 + "value": {
  2181 + "defaultValue": "DOWNLOADED",
  2182 + "dynamicValue": null
  2183 + },
  2184 + "ignoreCase": false,
  2185 + "type": "STRING"
  2186 + },
  2187 + "userInfo": {
  2188 + "editable": false,
  2189 + "label": "fw_state equal",
  2190 + "autogeneratedLabel": true,
  2191 + "order": 0
  2192 + }
  2193 + },
  2194 + {
  2195 + "keyFilterPredicate": {
  2196 + "operation": "EQUAL",
  2197 + "value": {
  2198 + "defaultValue": "VERIFIED",
  2199 + "dynamicValue": null
  2200 + },
  2201 + "ignoreCase": false,
  2202 + "type": "STRING"
  2203 + },
  2204 + "userInfo": {
  2205 + "editable": false,
  2206 + "label": "fw_state equal",
  2207 + "autogeneratedLabel": true,
  2208 + "order": 0
  2209 + }
  2210 + },
  2211 + {
  2212 + "keyFilterPredicate": {
  2213 + "operation": "EQUAL",
  2214 + "value": {
  2215 + "defaultValue": "UPDATING",
  2216 + "dynamicValue": null
  2217 + },
  2218 + "ignoreCase": false,
  2219 + "type": "STRING"
  2220 + },
  2221 + "userInfo": {
  2222 + "editable": false,
  2223 + "label": "fw_state equal",
  2224 + "autogeneratedLabel": true,
  2225 + "order": 0
  2226 + }
  2227 + }
  2228 + ],
  2229 + "type": "COMPLEX"
  2230 + },
  2231 + "userInfo": {
  2232 + "editable": true,
  2233 + "label": "",
  2234 + "autogeneratedLabel": true,
  2235 + "order": 0
  2236 + }
  2237 + }
  2238 + ]
  2239 + }
  2240 + ],
  2241 + "editable": false
  2242 + },
  2243 + "6044e198-df64-cd76-f339-696f220c4943": {
  2244 + "id": "6044e198-df64-cd76-f339-696f220c4943",
  2245 + "filter": "UpdetedDevicesFilter",
  2246 + "keyFilters": [
  2247 + {
  2248 + "key": {
  2249 + "type": "TIME_SERIES",
  2250 + "key": "fw_state"
  2251 + },
  2252 + "valueType": "STRING",
  2253 + "predicates": [
  2254 + {
  2255 + "keyFilterPredicate": {
  2256 + "operation": "EQUAL",
  2257 + "value": {
  2258 + "defaultValue": "UPDATED",
  2259 + "dynamicValue": null
  2260 + },
  2261 + "ignoreCase": false,
  2262 + "type": "STRING"
  2263 + },
  2264 + "userInfo": {
  2265 + "editable": true,
  2266 + "label": "",
  2267 + "autogeneratedLabel": true,
  2268 + "order": 0
  2269 + }
  2270 + }
  2271 + ]
  2272 + }
  2273 + ],
  2274 + "editable": false
  2275 + },
  2276 + "bdbc6ea1-95a7-3912-341a-58dc7704a00f": {
  2277 + "id": "bdbc6ea1-95a7-3912-341a-58dc7704a00f",
  2278 + "filter": "FailedDevicesFilter",
  2279 + "keyFilters": [
  2280 + {
  2281 + "key": {
  2282 + "type": "TIME_SERIES",
  2283 + "key": "fw_state"
  2284 + },
  2285 + "valueType": "STRING",
  2286 + "predicates": [
  2287 + {
  2288 + "keyFilterPredicate": {
  2289 + "operation": "EQUAL",
  2290 + "value": {
  2291 + "defaultValue": "FAILED",
  2292 + "dynamicValue": null
  2293 + },
  2294 + "ignoreCase": false,
  2295 + "type": "STRING"
  2296 + },
  2297 + "userInfo": {
  2298 + "editable": true,
  2299 + "label": "",
  2300 + "autogeneratedLabel": true,
  2301 + "order": 0
  2302 + }
  2303 + }
  2304 + ]
  2305 + }
  2306 + ],
  2307 + "editable": false
  2308 + },
  2309 + "8fdb88d0-50ac-2232-fdb7-69c30c16544e": {
  2310 + "id": "8fdb88d0-50ac-2232-fdb7-69c30c16544e",
  2311 + "filter": "DeviceSearch",
  2312 + "keyFilters": [
  2313 + {
  2314 + "key": {
  2315 + "type": "ENTITY_FIELD",
  2316 + "key": "name"
  2317 + },
  2318 + "valueType": "STRING",
  2319 + "predicates": [
  2320 + {
  2321 + "keyFilterPredicate": {
  2322 + "operation": "CONTAINS",
  2323 + "value": {
  2324 + "defaultValue": ""
  2325 + },
  2326 + "ignoreCase": true,
  2327 + "type": "STRING"
  2328 + },
  2329 + "userInfo": {
  2330 + "editable": true,
  2331 + "label": "Device name",
  2332 + "autogeneratedLabel": false,
  2333 + "order": 0
  2334 + }
  2335 + }
  2336 + ]
  2337 + }
  2338 + ],
  2339 + "editable": true
  2340 + }
  2341 + },
  2342 + "timewindow": {
  2343 + "displayValue": "",
  2344 + "hideInterval": false,
  2345 + "hideAggregation": false,
  2346 + "hideAggInterval": false,
  2347 + "hideTimezone": false,
  2348 + "selectedTab": 0,
  2349 + "realtime": {
  2350 + "realtimeType": 0,
  2351 + "interval": 1000,
  2352 + "timewindowMs": 60000,
  2353 + "quickInterval": "CURRENT_DAY"
  2354 + },
  2355 + "history": {
  2356 + "historyType": 0,
  2357 + "interval": 1000,
  2358 + "timewindowMs": 60000,
  2359 + "fixedTimewindow": {
  2360 + "startTimeMs": 1618998609030,
  2361 + "endTimeMs": 1619085009030
  2362 + },
  2363 + "quickInterval": "CURRENT_DAY"
  2364 + },
  2365 + "aggregation": {
  2366 + "type": "AVG",
  2367 + "limit": 25000
  2368 + }
  2369 + },
  2370 + "settings": {
  2371 + "stateControllerId": "entity",
  2372 + "showTitle": false,
  2373 + "showDashboardsSelect": false,
  2374 + "showEntitiesSelect": false,
  2375 + "showDashboardTimewindow": true,
  2376 + "showDashboardExport": false,
  2377 + "toolbarAlwaysOpen": true,
  2378 + "titleColor": "rgba(0,0,0,0.870588)",
  2379 + "showFilters": true,
  2380 + "showDashboardLogo": false,
  2381 + "dashboardLogoUrl": null
  2382 + }
  2383 + },
  2384 + "name": "Firmware"
  2385 +}
\ No newline at end of file
... ...
... ... @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS firmware (
69 69 content_type varchar(255),
70 70 checksum_algorithm varchar(32),
71 71 checksum varchar(1020),
72   - data bytea,
  72 + data oid,
73 73 data_size bigint,
74 74 additional_info varchar,
75 75 search_text varchar(255),
... ...
... ... @@ -25,7 +25,6 @@ import org.springframework.stereotype.Service;
25 25 import org.thingsboard.common.util.ThingsBoardThreadFactory;
26 26 import org.thingsboard.server.actors.ActorSystemContext;
27 27 import org.thingsboard.server.actors.DefaultTbActorSystem;
28   -import org.thingsboard.server.actors.TbActorId;
29 28 import org.thingsboard.server.actors.TbActorRef;
30 29 import org.thingsboard.server.actors.TbActorSystem;
31 30 import org.thingsboard.server.actors.TbActorSystemSettings;
... ... @@ -33,14 +32,13 @@ import org.thingsboard.server.actors.app.AppActor;
33 32 import org.thingsboard.server.actors.app.AppInitMsg;
34 33 import org.thingsboard.server.actors.stats.StatsActor;
35 34 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
36   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
37 35 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
  36 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
38 37
39 38 import javax.annotation.PostConstruct;
40 39 import javax.annotation.PreDestroy;
41 40 import java.util.concurrent.ExecutorService;
42 41 import java.util.concurrent.Executors;
43   -import java.util.concurrent.ScheduledExecutorService;
44 42
45 43 @Service
46 44 @Slf4j
... ...
... ... @@ -75,7 +75,6 @@ import javax.annotation.Nullable;
75 75 import java.io.IOException;
76 76 import java.util.ArrayList;
77 77 import java.util.List;
78   -import java.util.Objects;
79 78 import java.util.stream.Collectors;
80 79
81 80 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
... ... @@ -120,12 +119,12 @@ public class DeviceController extends BaseController {
120 119 @ResponseBody
121 120 public Device saveDevice(@RequestBody Device device,
122 121 @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException {
  122 + boolean created = device.getId() == null;
123 123 try {
124 124 device.setTenantId(getCurrentUser().getTenantId());
125 125
126 126 checkEntity(device.getId(), device, Resource.DEVICE);
127 127
128   - boolean created = device.getId() == null;
129 128 Device oldDevice;
130 129 if (!created) {
131 130 oldDevice = deviceService.findDeviceById(getTenantId(), device.getId());
... ... @@ -146,7 +145,7 @@ public class DeviceController extends BaseController {
146 145
147 146 logEntityAction(savedDevice.getId(), savedDevice,
148 147 savedDevice.getCustomerId(),
149   - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  148 + created ? ActionType.ADDED : ActionType.UPDATED, null);
150 149
151 150 if (device.getId() == null) {
152 151 deviceStateService.onDeviceAdded(savedDevice);
... ... @@ -157,10 +156,9 @@ public class DeviceController extends BaseController {
157 156 firmwareStateService.update(savedDevice, oldDevice);
158 157
159 158 return savedDevice;
160   - } catch (
161   - Exception e) {
  159 + } catch (Exception e) {
162 160 logEntityAction(emptyId(EntityType.DEVICE), device,
163   - null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  161 + null, created ? ActionType.ADDED : ActionType.UPDATED, e);
164 162 throw handleException(e);
165 163 }
166 164
... ...
... ... @@ -29,8 +29,10 @@ import org.springframework.web.bind.annotation.RequestParam;
29 29 import org.springframework.web.bind.annotation.ResponseBody;
30 30 import org.springframework.web.bind.annotation.RestController;
31 31 import org.springframework.web.multipart.MultipartFile;
  32 +import org.thingsboard.server.common.data.EntityType;
32 33 import org.thingsboard.server.common.data.Firmware;
33 34 import org.thingsboard.server.common.data.FirmwareInfo;
  35 +import org.thingsboard.server.common.data.audit.ActionType;
34 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 37 import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
36 38 import org.thingsboard.server.common.data.id.FirmwareId;
... ... @@ -72,14 +74,14 @@ public class FirmwareController extends BaseController {
72 74 }
73 75 }
74 76
75   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  77 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
76 78 @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET)
77 79 @ResponseBody
78 80 public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
79 81 checkParameter(FIRMWARE_ID, strFirmwareId);
80 82 try {
81 83 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
82   - return checkFirmwareInfoId(firmwareId, Operation.READ);
  84 + return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId));
83 85 } catch (Exception e) {
84 86 throw handleException(e);
85 87 }
... ... @@ -102,11 +104,17 @@ public class FirmwareController extends BaseController {
102 104 @RequestMapping(value = "/firmware", method = RequestMethod.POST)
103 105 @ResponseBody
104 106 public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException {
105   - firmwareInfo.setTenantId(getTenantId());
106   - checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);
  107 + boolean created = firmwareInfo.getId() == null;
107 108 try {
108   - return firmwareService.saveFirmwareInfo(firmwareInfo);
  109 + firmwareInfo.setTenantId(getTenantId());
  110 + checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);
  111 + FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo);
  112 + logEntityAction(savedFirmwareInfo.getId(), savedFirmwareInfo,
  113 + null, created ? ActionType.ADDED : ActionType.UPDATED, null);
  114 + return savedFirmwareInfo;
109 115 } catch (Exception e) {
  116 + logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo,
  117 + null, created ? ActionType.ADDED : ActionType.UPDATED, e);
110 118 throw handleException(e);
111 119 }
112 120 }
... ... @@ -144,13 +152,16 @@ public class FirmwareController extends BaseController {
144 152 firmware.setContentType(file.getContentType());
145 153 firmware.setData(data);
146 154 firmware.setDataSize((long) data.capacity());
147   - return firmwareService.saveFirmware(firmware);
  155 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  156 + logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null);
  157 + return savedFirmware;
148 158 } catch (Exception e) {
  159 + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId);
149 160 throw handleException(e);
150 161 }
151 162 }
152 163
153   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  164 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
154 165 @RequestMapping(value = "/firmwares", method = RequestMethod.GET)
155 166 @ResponseBody
156 167 public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,
... ... @@ -166,7 +177,7 @@ public class FirmwareController extends BaseController {
166 177 }
167 178 }
168 179
169   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  180 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
170 181 @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET)
171 182 @ResponseBody
172 183 public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData,
... ... @@ -186,13 +197,15 @@ public class FirmwareController extends BaseController {
186 197 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
187 198 @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
188 199 @ResponseBody
189   - public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
  200 + public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
190 201 checkParameter(FIRMWARE_ID, strFirmwareId);
191 202 try {
192 203 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
193   - checkFirmwareInfoId(firmwareId, Operation.DELETE);
  204 + FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE);
194 205 firmwareService.deleteFirmware(getTenantId(), firmwareId);
  206 + logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId);
195 207 } catch (Exception e) {
  208 + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId);
196 209 throw handleException(e);
197 210 }
198 211 }
... ...
... ... @@ -28,8 +28,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
28 28 import org.springframework.web.bind.annotation.RequestParam;
29 29 import org.springframework.web.bind.annotation.ResponseBody;
30 30 import org.springframework.web.bind.annotation.RestController;
  31 +import org.thingsboard.server.common.data.EntityType;
31 32 import org.thingsboard.server.common.data.TbResource;
32 33 import org.thingsboard.server.common.data.TbResourceInfo;
  34 +import org.thingsboard.server.common.data.audit.ActionType;
33 35 import org.thingsboard.server.common.data.exception.ThingsboardException;
34 36 import org.thingsboard.server.common.data.id.TbResourceId;
35 37 import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
... ... @@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.page.PageData;
37 39 import org.thingsboard.server.common.data.page.PageLink;
38 40 import org.thingsboard.server.common.data.security.Authority;
39 41 import org.thingsboard.server.queue.util.TbCoreComponent;
40   -import org.thingsboard.server.service.resource.TbResourceService;
41 42 import org.thingsboard.server.service.security.permission.Operation;
42 43 import org.thingsboard.server.service.security.permission.Resource;
43 44
... ... @@ -103,12 +104,18 @@ public class TbResourceController extends BaseController {
103 104 @RequestMapping(value = "/resource", method = RequestMethod.POST)
104 105 @ResponseBody
105 106 public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException {
  107 + boolean created = resource.getId() == null;
106 108 try {
107   - resource.setTenantId(getTenantId());
108   - checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
109   - return addResource(resource);
110   - }
111   - catch (Exception e) {
  109 + resource.setTenantId(getTenantId());
  110 + checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
  111 + TbResource savedResource = checkNotNull(resourceService.saveResource(resource));
  112 + tbClusterService.onResourceChange(savedResource, null);
  113 + logEntityAction(savedResource.getId(), savedResource,
  114 + null, created ? ActionType.ADDED : ActionType.UPDATED, null);
  115 + return savedResource;
  116 + } catch (Exception e) {
  117 + logEntityAction(emptyId(EntityType.TB_RESOURCE), resource,
  118 + null, created ? ActionType.ADDED : ActionType.UPDATED, e);
112 119 throw handleException(e);
113 120 }
114 121 }
... ... @@ -172,15 +179,11 @@ public class TbResourceController extends BaseController {
172 179 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
173 180 resourceService.deleteResource(getTenantId(), resourceId);
174 181 tbClusterService.onResourceDeleted(tbResource, null);
  182 + logEntityAction(resourceId, tbResource, null, ActionType.DELETED, null, strResourceId);
175 183 } catch (Exception e) {
  184 + logEntityAction(emptyId(EntityType.TB_RESOURCE), null, null, ActionType.DELETED, e, strResourceId);
176 185 throw handleException(e);
177 186 }
178 187 }
179 188
180   - private TbResource addResource(TbResource resource) throws Exception {
181   - checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
182   - TbResource savedResource = checkNotNull(resourceService.saveResource(resource));
183   - tbClusterService.onResourceChange(savedResource, null);
184   - return savedResource;
185   - }
186 189 }
\ No newline at end of file
... ...
... ... @@ -56,7 +56,7 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
56 56 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
57 57 import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
58 58 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
59   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  59 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
60 60 import org.thingsboard.server.queue.discovery.PartitionService;
61 61 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
62 62 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
23 23 import org.thingsboard.server.common.msg.queue.TbCallback;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
25 25 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
26   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  26 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
27 27
28 28 public interface TbApiUsageStateService extends ApplicationListener<PartitionChangeEvent> {
29 29
... ...
... ... @@ -22,7 +22,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
22 22 import org.thingsboard.server.common.data.DataConstants;
23 23 import org.thingsboard.server.common.data.Device;
24 24 import org.thingsboard.server.common.data.DeviceProfile;
25   -import org.thingsboard.server.common.data.Firmware;
26 25 import org.thingsboard.server.common.data.FirmwareInfo;
27 26 import org.thingsboard.server.common.data.id.DeviceId;
28 27 import org.thingsboard.server.common.data.id.FirmwareId;
... ... @@ -35,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
35 34 import org.thingsboard.server.common.data.kv.TsKvEntry;
36 35 import org.thingsboard.server.common.data.page.PageData;
37 36 import org.thingsboard.server.common.data.page.PageLink;
38   -import org.thingsboard.server.common.msg.queue.TbCallback;
39 37 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
40 38 import org.thingsboard.server.dao.device.DeviceProfileService;
41 39 import org.thingsboard.server.dao.device.DeviceService;
... ... @@ -155,7 +153,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
155 153 }
156 154
157 155 if (targetFirmwareId.equals(currentFirmwareId)) {
158   - update(device, firmwareService.findFirmwareById(device.getTenantId(), targetFirmwareId), ts);
  156 + update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts);
159 157 isSuccess = true;
160 158 } else {
161 159 log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId);
... ... @@ -187,6 +185,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
187 185 List<TsKvEntry> telemetry = new ArrayList<>();
188 186 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle())));
189 187 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion())));
  188 + telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(DataConstants.TARGET_FIRMWARE_TS, ts)));
190 189 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name())));
191 190
192 191 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
... ...
... ... @@ -55,7 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
55 55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
56 56 import org.thingsboard.server.queue.TbQueueConsumer;
57 57 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
58   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  58 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
59 59 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
60 60 import org.thingsboard.server.queue.util.TbCoreComponent;
61 61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
... ...
... ... @@ -38,7 +38,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
38 38 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
39 39 import org.thingsboard.server.queue.TbQueueConsumer;
40 40 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
41   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  41 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
42 42 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
43 43 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
44 44 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
... ...
... ... @@ -16,7 +16,7 @@
16 16 package org.thingsboard.server.service.queue;
17 17
18 18 import org.springframework.context.ApplicationListener;
19   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  19 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
20 20
21 21 public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> {
22 22
... ...
... ... @@ -16,7 +16,7 @@
16 16 package org.thingsboard.server.service.queue;
17 17
18 18 import org.springframework.context.ApplicationListener;
19   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  19 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
20 20
21 21 public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> {
22 22
... ...
... ... @@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
35 35 import org.thingsboard.server.common.msg.queue.TbCallback;
36 36 import org.thingsboard.server.queue.TbQueueConsumer;
37 37 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
38   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  38 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
39 39 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
40 40 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
41 41 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
... ...
... ... @@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.Customer;
30 30 import org.thingsboard.server.common.data.Device;
31 31 import org.thingsboard.server.common.data.DeviceProfile;
32 32 import org.thingsboard.server.common.data.EntityView;
  33 +import org.thingsboard.server.common.data.FirmwareInfo;
  34 +import org.thingsboard.server.common.data.TbResourceInfo;
33 35 import org.thingsboard.server.common.data.Tenant;
34 36 import org.thingsboard.server.common.data.User;
35 37 import org.thingsboard.server.common.data.asset.Asset;
... ... @@ -44,8 +46,10 @@ import org.thingsboard.server.common.data.id.EdgeId;
44 46 import org.thingsboard.server.common.data.id.EntityId;
45 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
46 48 import org.thingsboard.server.common.data.id.EntityViewId;
  49 +import org.thingsboard.server.common.data.id.FirmwareId;
47 50 import org.thingsboard.server.common.data.id.RuleChainId;
48 51 import org.thingsboard.server.common.data.id.RuleNodeId;
  52 +import org.thingsboard.server.common.data.id.TbResourceId;
49 53 import org.thingsboard.server.common.data.id.TenantId;
50 54 import org.thingsboard.server.common.data.id.UserId;
51 55 import org.thingsboard.server.common.data.rule.RuleChain;
... ... @@ -59,6 +63,8 @@ import org.thingsboard.server.dao.device.DeviceService;
59 63 import org.thingsboard.server.dao.edge.EdgeService;
60 64 import org.thingsboard.server.dao.entityview.EntityViewService;
61 65 import org.thingsboard.server.dao.exception.IncorrectParameterException;
  66 +import org.thingsboard.server.dao.firmware.FirmwareService;
  67 +import org.thingsboard.server.dao.resource.ResourceService;
62 68 import org.thingsboard.server.dao.rule.RuleChainService;
63 69 import org.thingsboard.server.dao.tenant.TenantService;
64 70 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
... ... @@ -125,6 +131,12 @@ public class AccessValidator {
125 131 @Autowired
126 132 protected ApiUsageStateService apiUsageStateService;
127 133
  134 + @Autowired
  135 + protected ResourceService resourceService;
  136 +
  137 + @Autowired
  138 + protected FirmwareService firmwareService;
  139 +
128 140 private ExecutorService executor;
129 141
130 142 @PostConstruct
... ... @@ -217,6 +229,12 @@ public class AccessValidator {
217 229 case API_USAGE_STATE:
218 230 validateApiUsageState(currentUser, operation, entityId, callback);
219 231 return;
  232 + case TB_RESOURCE:
  233 + validateResource(currentUser, operation, entityId, callback);
  234 + return;
  235 + case FIRMWARE:
  236 + validateFirmware(currentUser, operation, entityId, callback);
  237 + return;
220 238 default:
221 239 //TODO: add support of other entities
222 240 throw new IllegalStateException("Not Implemented!");
... ... @@ -282,6 +300,40 @@ public class AccessValidator {
282 300 }
283 301 }
284 302
  303 + private void validateFirmware(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  304 + if (currentUser.isSystemAdmin()) {
  305 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  306 + } else {
  307 + FirmwareInfo firmware = firmwareService.findFirmwareInfoById(currentUser.getTenantId(), new FirmwareId(entityId.getId()));
  308 + if (firmware == null) {
  309 + callback.onSuccess(ValidationResult.entityNotFound("Firmware with requested id wasn't found!"));
  310 + } else {
  311 + try {
  312 + accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware);
  313 + } catch (ThingsboardException e) {
  314 + callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
  315 + }
  316 + callback.onSuccess(ValidationResult.ok(firmware));
  317 + }
  318 + }
  319 + }
  320 +
  321 + private void validateResource(SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  322 + ListenableFuture<TbResourceInfo> resourceFuture = resourceService.findResourceInfoByIdAsync(currentUser.getTenantId(), new TbResourceId(entityId.getId()));
  323 + Futures.addCallback(resourceFuture, getCallback(callback, resource -> {
  324 + if (resource == null) {
  325 + return ValidationResult.entityNotFound("Resource with requested id wasn't found!");
  326 + } else {
  327 + try {
  328 + accessControlService.checkPermission(currentUser, Resource.TB_RESOURCE, operation, entityId, resource);
  329 + } catch (ThingsboardException e) {
  330 + return ValidationResult.accessDenied(e.getMessage());
  331 + }
  332 + return ValidationResult.ok(resource);
  333 + }
  334 + }), executor);
  335 + }
  336 +
285 337 private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
286 338 if (currentUser.isSystemAdmin()) {
287 339 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
... ...
... ... @@ -54,7 +54,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
54 54 import org.thingsboard.server.dao.timeseries.TimeseriesService;
55 55 import org.thingsboard.common.util.JacksonUtil;
56 56 import org.thingsboard.server.gen.transport.TransportProtos;
57   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  57 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
58 58 import org.thingsboard.server.queue.discovery.PartitionService;
59 59 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
60 60 import org.thingsboard.server.queue.util.TbCoreComponent;
... ...
... ... @@ -18,7 +18,7 @@ package org.thingsboard.server.service.state;
18 18 import org.springframework.context.ApplicationListener;
19 19 import org.thingsboard.server.common.data.Device;
20 20 import org.thingsboard.server.common.data.id.DeviceId;
21   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  21 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
22 22 import org.thingsboard.server.gen.transport.TransportProtos;
23 23 import org.thingsboard.server.common.msg.queue.TbCallback;
24 24
... ...
... ... @@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdate
46 46 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto;
47 47 import org.thingsboard.server.queue.TbQueueProducer;
48 48 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
49   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  49 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
50 50 import org.thingsboard.server.queue.discovery.PartitionService;
51 51 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
52 52 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
... ...
... ... @@ -20,10 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.context.annotation.Lazy;
21 21 import org.springframework.context.event.EventListener;
22 22 import org.springframework.stereotype.Service;
23   -import org.thingsboard.common.util.ThingsBoardThreadFactory;
24 23 import org.thingsboard.server.gen.transport.TransportProtos;
25   -import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
26   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  24 +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
  25 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
27 26 import org.thingsboard.server.queue.discovery.PartitionService;
28 27 import org.thingsboard.server.common.msg.queue.ServiceType;
29 28 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
... ...
... ... @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
23 23 import org.thingsboard.server.common.data.kv.TsKvEntry;
24 24 import org.thingsboard.server.common.msg.queue.TbCallback;
25   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  25 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
26 26
27 27 import java.util.List;
28 28
... ...
... ... @@ -15,8 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.service.subscription;
17 17
18   -import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
19   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  18 +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
  19 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
20 20 import org.thingsboard.server.common.msg.queue.TbCallback;
21 21 import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
22 22 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
... ...
... ... @@ -22,35 +22,18 @@ import lombok.extern.slf4j.Slf4j;
22 22 import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.context.ApplicationListener;
24 24 import org.springframework.context.event.EventListener;
25   -import org.springframework.stereotype.Service;
26 25 import org.thingsboard.common.util.ThingsBoardThreadFactory;
27   -import org.thingsboard.server.common.data.id.EntityId;
28   -import org.thingsboard.server.common.data.id.TenantId;
29   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
30   -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
31   -import org.thingsboard.server.common.data.kv.BooleanDataEntry;
32   -import org.thingsboard.server.common.data.kv.DoubleDataEntry;
33   -import org.thingsboard.server.common.data.kv.LongDataEntry;
34   -import org.thingsboard.server.common.data.kv.StringDataEntry;
35   -import org.thingsboard.server.common.data.kv.TsKvEntry;
36 26 import org.thingsboard.server.common.msg.queue.ServiceType;
37   -import org.thingsboard.server.common.msg.queue.TbCallback;
38 27 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
39   -import org.thingsboard.server.dao.attributes.AttributesService;
40   -import org.thingsboard.server.dao.timeseries.TimeseriesService;
41   -import org.thingsboard.server.gen.transport.TransportProtos;
42   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  28 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
43 29 import org.thingsboard.server.queue.discovery.PartitionService;
44 30 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
45 31 import org.thingsboard.server.service.queue.TbClusterService;
46 32 import org.thingsboard.server.service.subscription.SubscriptionManagerService;
47   -import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
48 33
49 34 import javax.annotation.Nullable;
50 35 import javax.annotation.PostConstruct;
51 36 import javax.annotation.PreDestroy;
52   -import java.util.Collections;
53   -import java.util.List;
54 37 import java.util.Optional;
55 38 import java.util.Set;
56 39 import java.util.concurrent.ConcurrentHashMap;
... ...
... ... @@ -17,8 +17,7 @@ package org.thingsboard.server.service.telemetry;
17 17
18 18 import org.springframework.context.ApplicationListener;
19 19 import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
20   -import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
21   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  20 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
22 21
23 22 /**
24 23 * Created by ashvayka on 27.03.18.
... ...
... ... @@ -16,8 +16,7 @@
16 16 package org.thingsboard.server.service.telemetry;
17 17
18 18 import org.springframework.context.ApplicationListener;
19   -import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
20   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  19 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
21 20
22 21 /**
23 22 * Created by ashvayka on 27.03.18.
... ...
... ... @@ -23,17 +23,18 @@ import com.google.common.util.concurrent.ListenableFuture;
23 23 import com.google.common.util.concurrent.MoreExecutors;
24 24 import com.google.protobuf.ByteString;
25 25 import lombok.extern.slf4j.Slf4j;
26   -import org.springframework.cache.CacheManager;
27 26 import org.springframework.stereotype.Service;
28 27 import org.springframework.util.StringUtils;
29 28 import org.thingsboard.common.util.JacksonUtil;
30   -import org.thingsboard.server.cache.firmware.FirmwareCacheWriter;
  29 +import org.thingsboard.server.cache.firmware.FirmwareDataCache;
31 30 import org.thingsboard.server.common.data.ApiUsageState;
32 31 import org.thingsboard.server.common.data.DataConstants;
33 32 import org.thingsboard.server.common.data.Device;
34 33 import org.thingsboard.server.common.data.DeviceProfile;
  34 +import org.thingsboard.server.common.data.DeviceTransportType;
35 35 import org.thingsboard.server.common.data.EntityType;
36 36 import org.thingsboard.server.common.data.Firmware;
  37 +import org.thingsboard.server.common.data.FirmwareInfo;
37 38 import org.thingsboard.server.common.data.ResourceType;
38 39 import org.thingsboard.server.common.data.TbResource;
39 40 import org.thingsboard.server.common.data.TenantProfile;
... ... @@ -45,6 +46,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
45 46 import org.thingsboard.server.common.data.id.DeviceProfileId;
46 47 import org.thingsboard.server.common.data.id.FirmwareId;
47 48 import org.thingsboard.server.common.data.id.TenantId;
  49 +import org.thingsboard.server.common.data.page.PageData;
  50 +import org.thingsboard.server.common.data.page.PageLink;
48 51 import org.thingsboard.server.common.data.relation.EntityRelation;
49 52 import org.thingsboard.server.common.data.security.DeviceCredentials;
50 53 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
... ... @@ -61,15 +64,18 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest;
61 64 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
62 65 import org.thingsboard.server.dao.firmware.FirmwareService;
63 66 import org.thingsboard.server.dao.relation.RelationService;
64   -import org.thingsboard.server.dao.resource.ResourceService;
65 67 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
66 68 import org.thingsboard.server.gen.transport.TransportProtos;
67 69 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
  70 +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
  71 +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg;
68 72 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
69 73 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
70 74 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
71 75 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
72 76 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
  77 +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRequestMsg;
  78 +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesResponseMsg;
73 79 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
74 80 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
75 81 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
... ... @@ -92,6 +98,7 @@ import java.util.concurrent.ConcurrentHashMap;
92 98 import java.util.concurrent.ConcurrentMap;
93 99 import java.util.concurrent.locks.Lock;
94 100 import java.util.concurrent.locks.ReentrantLock;
  101 +import java.util.stream.Collectors;
95 102
96 103 /**
97 104 * Created by ashvayka on 05.10.18.
... ... @@ -116,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService {
116 123 private final DeviceProvisionService deviceProvisionService;
117 124 private final TbResourceService resourceService;
118 125 private final FirmwareService firmwareService;
119   - private final FirmwareCacheWriter firmwareCacheWriter;
  126 + private final FirmwareDataCache firmwareDataCache;
120 127
121 128 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
122 129
... ... @@ -125,7 +132,7 @@ public class DefaultTransportApiService implements TransportApiService {
125 132 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
126 133 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
127 134 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
128   - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareCacheWriter firmwareCacheWriter) {
  135 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) {
129 136 this.deviceProfileCache = deviceProfileCache;
130 137 this.tenantProfileCache = tenantProfileCache;
131 138 this.apiUsageStateService = apiUsageStateService;
... ... @@ -139,49 +146,49 @@ public class DefaultTransportApiService implements TransportApiService {
139 146 this.deviceProvisionService = deviceProvisionService;
140 147 this.resourceService = resourceService;
141 148 this.firmwareService = firmwareService;
142   - this.firmwareCacheWriter = firmwareCacheWriter;
  149 + this.firmwareDataCache = firmwareDataCache;
143 150 }
144 151
145 152 @Override
146 153 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
147 154 TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue();
  155 + ListenableFuture<TransportApiResponseMsg> result = null;
  156 +
148 157 if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
149 158 ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg();
150   - return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN),
151   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  159 + result = validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN);
152 160 } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) {
153 161 TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg();
154   - return Futures.transform(validateCredentials(msg),
155   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  162 + result = validateCredentials(msg);
156 163 } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
157 164 ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
158   - return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE),
159   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  165 + result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE);
160 166 } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
161   - return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()),
162   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  167 + result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg());
163 168 } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
164   - return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()),
165   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  169 + result = handle(transportApiRequestMsg.getEntityProfileRequestMsg());
166 170 } else if (transportApiRequestMsg.hasLwM2MRequestMsg()) {
167   - return Futures.transform(handle(transportApiRequestMsg.getLwM2MRequestMsg()),
168   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  171 + result = handle(transportApiRequestMsg.getLwM2MRequestMsg());
169 172 } else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) {
170 173 ValidateDeviceLwM2MCredentialsRequestMsg msg = transportApiRequestMsg.getValidateDeviceLwM2MCredentialsRequestMsg();
171   - return Futures.transform(validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS),
172   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  174 + result = validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS);
173 175 } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
174   - return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
175   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  176 + result = handle(transportApiRequestMsg.getProvisionDeviceRequestMsg());
176 177 } else if (transportApiRequestMsg.hasResourceRequestMsg()) {
177   - return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
178   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  178 + result = handle(transportApiRequestMsg.getResourceRequestMsg());
  179 + } else if (transportApiRequestMsg.hasSnmpDevicesRequestMsg()) {
  180 + result = handle(transportApiRequestMsg.getSnmpDevicesRequestMsg());
  181 + } else if (transportApiRequestMsg.hasDeviceRequestMsg()) {
  182 + result = handle(transportApiRequestMsg.getDeviceRequestMsg());
  183 + } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) {
  184 + result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg());
179 185 } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {
180   - return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()),
181   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  186 + result = handle(transportApiRequestMsg.getFirmwareRequestMsg());
182 187 }
183   - return Futures.transform(getEmptyTransportApiResponseFuture(),
184   - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  188 +
  189 + return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
  190 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()),
  191 + MoreExecutors.directExecutor());
185 192 }
186 193
187 194 private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
... ... @@ -375,6 +382,39 @@ public class DefaultTransportApiService implements TransportApiService {
375 382 return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build());
376 383 }
377 384
  385 + private ListenableFuture<TransportApiResponseMsg> handle(GetDeviceRequestMsg requestMsg) {
  386 + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
  387 + Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
  388 +
  389 + TransportApiResponseMsg responseMsg;
  390 + if (device != null) {
  391 + UUID deviceProfileId = device.getDeviceProfileId().getId();
  392 + responseMsg = TransportApiResponseMsg.newBuilder()
  393 + .setDeviceResponseMsg(TransportProtos.GetDeviceResponseMsg.newBuilder()
  394 + .setDeviceProfileIdMSB(deviceProfileId.getMostSignificantBits())
  395 + .setDeviceProfileIdLSB(deviceProfileId.getLeastSignificantBits())
  396 + .setDeviceTransportConfiguration(ByteString.copyFrom(
  397 + dataDecodingEncodingService.encode(device.getDeviceData().getTransportConfiguration())
  398 + )))
  399 + .build();
  400 + } else {
  401 + responseMsg = TransportApiResponseMsg.getDefaultInstance();
  402 + }
  403 +
  404 + return Futures.immediateFuture(responseMsg);
  405 + }
  406 +
  407 + private ListenableFuture<TransportApiResponseMsg> handle(GetDeviceCredentialsRequestMsg requestMsg) {
  408 + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
  409 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, deviceId);
  410 +
  411 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
  412 + .setDeviceCredentialsResponseMsg(TransportProtos.GetDeviceCredentialsResponseMsg.newBuilder()
  413 + .setDeviceCredentialsData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceCredentials))))
  414 + .build());
  415 + }
  416 +
  417 +
378 418 private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
379 419 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
380 420 ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
... ... @@ -393,6 +433,22 @@ public class DefaultTransportApiService implements TransportApiService {
393 433 return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build());
394 434 }
395 435
  436 + private ListenableFuture<TransportApiResponseMsg> handle(GetSnmpDevicesRequestMsg requestMsg) {
  437 + PageLink pageLink = new PageLink(requestMsg.getPageSize(), requestMsg.getPage());
  438 + PageData<UUID> result = deviceService.findDevicesIdsByDeviceProfileTransportType(DeviceTransportType.SNMP, pageLink);
  439 +
  440 + GetSnmpDevicesResponseMsg responseMsg = GetSnmpDevicesResponseMsg.newBuilder()
  441 + .addAllIds(result.getData().stream()
  442 + .map(UUID::toString)
  443 + .collect(Collectors.toList()))
  444 + .setHasNextPage(result.hasNext())
  445 + .build();
  446 +
  447 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
  448 + .setSnmpDevicesResponseMsg(responseMsg)
  449 + .build());
  450 + }
  451 +
396 452 private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
397 453 return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
398 454 if (device == null) {
... ... @@ -473,19 +529,22 @@ public class DefaultTransportApiService implements TransportApiService {
473 529 if (firmwareId == null) {
474 530 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
475 531 } else {
476   - Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);
  532 + FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(tenantId, firmwareId);
477 533
478   - if (firmware == null) {
  534 + if (firmwareInfo == null) {
479 535 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
480 536 } else {
481 537 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
482 538 builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());
483 539 builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits());
484   - builder.setTitle(firmware.getTitle());
485   - builder.setVersion(firmware.getVersion());
486   - builder.setFileName(firmware.getFileName());
487   - builder.setContentType(firmware.getContentType());
488   - firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array());
  540 + builder.setTitle(firmwareInfo.getTitle());
  541 + builder.setVersion(firmwareInfo.getVersion());
  542 + builder.setFileName(firmwareInfo.getFileName());
  543 + builder.setContentType(firmwareInfo.getContentType());
  544 + if (!firmwareDataCache.has(firmwareId.toString())) {
  545 + Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);
  546 + firmwareDataCache.put(firmwareId.toString(), firmware.getData().array());
  547 + }
489 548 }
490 549 }
491 550
... ...
... ... @@ -26,6 +26,7 @@
26 26 </appender>
27 27
28 28 <logger name="org.thingsboard.server" level="INFO" />
  29 + <logger name="org.thingsboard.server.transport.snmp" level="TRACE" />
29 30
30 31 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 32 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
... ...
... ... @@ -372,8 +372,8 @@ caffeine:
372 372 timeToLiveInMinutes: 20000
373 373 maxSize: 10000
374 374 firmwares:
375   - timeToLiveInMinutes: 1440
376   - maxSize: 100
  375 + timeToLiveInMinutes: 60
  376 + maxSize: 10
377 377 edges:
378 378 timeToLiveInMinutes: 1440
379 379 maxSize: 0
... ... @@ -496,6 +496,8 @@ audit-log:
496 496 "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
497 497 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
498 498 "edge": "${AUDIT_LOG_MASK_EDGE:W}"
  499 + "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}"
  500 + "firmware": "${AUDIT_LOG_MASK_FIRMWARE:W}"
499 501 sink:
500 502 # Type of external sink. possible options: none, elasticsearch
501 503 type: "${AUDIT_LOG_SINK_TYPE:none}"
... ... @@ -619,9 +621,9 @@ transport:
619 621 key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
620 622 # Key alias
621 623 key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
622   - # Skip certificate validity check for client certificates.
623   - skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
624 624 x509:
  625 + # Skip certificate validity check for client certificates.
  626 + skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
625 627 dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
626 628 dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
627 629 # Local LwM2M transport parameters
... ... @@ -684,6 +686,13 @@ transport:
684 686 alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
685 687 # Use redis for Security and Registration stores
686 688 redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
  689 + snmp:
  690 + enabled: "${SNMP_ENABLED:true}"
  691 + response_processing:
  692 + # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
  693 + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
  694 + # to configure SNMP to work over UDP or TCP
  695 + underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
687 696
688 697 # Edges parameters
689 698 edges:
... ...
common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java
... ... @@ -15,22 +15,20 @@
15 15 */
16 16 package org.thingsboard.server.cache.firmware;
17 17
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 20 import org.springframework.cache.CacheManager;
20 21 import org.springframework.stereotype.Service;
21 22
22 23 import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
23 24
24 25 @Service
25   -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')")
26   -public class CaffeineFirmwareCacheReader implements FirmwareCacheReader {
  26 +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
  27 +@RequiredArgsConstructor
  28 +public class CaffeineFirmwareCache implements FirmwareDataCache {
27 29
28 30 private final CacheManager cacheManager;
29 31
30   - public CaffeineFirmwareCacheReader(CacheManager cacheManager) {
31   - this.cacheManager = cacheManager;
32   - }
33   -
34 32 @Override
35 33 public byte[] get(String key) {
36 34 return get(key, 0, 0);
... ... @@ -57,4 +55,14 @@ public class CaffeineFirmwareCacheReader implements FirmwareCacheReader {
57 55 }
58 56 return new byte[0];
59 57 }
  58 +
  59 + @Override
  60 + public void put(String key, byte[] value) {
  61 + cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value);
  62 + }
  63 +
  64 + @Override
  65 + public void evict(String key) {
  66 + cacheManager.getCache(FIRMWARE_CACHE).evict(key);
  67 + }
60 68 }
... ...
common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java
... ... @@ -15,8 +15,18 @@
15 15 */
16 16 package org.thingsboard.server.cache.firmware;
17 17
18   -public interface FirmwareCacheReader {
  18 +public interface FirmwareDataCache {
  19 +
19 20 byte[] get(String key);
20 21
21 22 byte[] get(String key, int chunkSize, int chunk);
  23 +
  24 + void put(String key, byte[] value);
  25 +
  26 + void evict(String key);
  27 +
  28 + default boolean has(String firmwareId) {
  29 + byte[] data = get(firmwareId, 1, 0);
  30 + return data != null && data.length > 0;
  31 + }
22 32 }
... ...
common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java
... ... @@ -15,18 +15,20 @@
15 15 */
16 16 package org.thingsboard.server.cache.firmware;
17 17
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 20 import org.springframework.data.redis.connection.RedisConnection;
20 21 import org.springframework.data.redis.connection.RedisConnectionFactory;
21 22 import org.springframework.stereotype.Service;
22 23
  24 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  25 +
23 26 @Service
24   -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && '${cache.type:null}'=='redis'")
25   -public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache implements FirmwareCacheReader {
  27 +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
  28 +@RequiredArgsConstructor
  29 +public class RedisFirmwareDataCache implements FirmwareDataCache {
26 30
27   - public RedisFirmwareCacheReader(RedisConnectionFactory redisConnectionFactory) {
28   - super(redisConnectionFactory);
29   - }
  31 + private final RedisConnectionFactory redisConnectionFactory;
30 32
31 33 @Override
32 34 public byte[] get(String key) {
... ... @@ -46,4 +48,21 @@ public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache impleme
46 48 }
47 49 }
48 50
  51 + @Override
  52 + public void put(String key, byte[] value) {
  53 + try (RedisConnection connection = redisConnectionFactory.getConnection()) {
  54 + connection.set(toFirmwareCacheKey(key), value);
  55 + }
  56 + }
  57 +
  58 + @Override
  59 + public void evict(String key) {
  60 + try (RedisConnection connection = redisConnectionFactory.getConnection()) {
  61 + connection.del(toFirmwareCacheKey(key));
  62 + }
  63 + }
  64 +
  65 + private byte[] toFirmwareCacheKey(String key) {
  66 + return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes();
  67 + }
49 68 }
... ...
... ... @@ -19,11 +19,10 @@ import lombok.Getter;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.springframework.beans.factory.annotation.Autowired;
21 21 import org.springframework.beans.factory.annotation.Value;
22   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
23 22 import org.springframework.stereotype.Component;
24 23
25 24 @Slf4j
26   -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
  25 +@TbCoapServerComponent
27 26 @Component
28 27 public class CoapServerContext {
29 28
... ...
... ... @@ -23,7 +23,6 @@ import org.eclipse.californium.core.server.resources.Resource;
23 23 import org.eclipse.californium.scandium.DTLSConnector;
24 24 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
25 25 import org.springframework.beans.factory.annotation.Autowired;
26   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
27 26 import org.springframework.stereotype.Component;
28 27
29 28 import javax.annotation.PostConstruct;
... ... @@ -39,7 +38,7 @@ import java.util.concurrent.TimeUnit;
39 38
40 39 @Slf4j
41 40 @Component
42   -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
  41 +@TbCoapServerComponent
43 42 public class DefaultCoapServerService implements CoapServerService {
44 43
45 44 @Autowired
... ...
... ... @@ -39,7 +39,6 @@ import java.util.Collections;
39 39 import java.util.Optional;
40 40
41 41 @Slf4j
42   -@ConditionalOnExpression("'${transport.coap.enabled}'=='true'")
43 42 @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
44 43 @Component
45 44 public class TbCoapDtlsSettings {
... ... @@ -50,7 +49,7 @@ public class TbCoapDtlsSettings {
50 49 @Value("${transport.coap.dtls.bind_port}")
51 50 private Integer port;
52 51
53   - @Value("${transport.coap.dtls.mode}")
  52 + @Value("${transport.coap.dtls.mode:NO_AUTH}")
54 53 private String mode;
55 54
56 55 @Value("${transport.coap.dtls.key_store}")
... ... @@ -65,13 +64,13 @@ public class TbCoapDtlsSettings {
65 64 @Value("${transport.coap.dtls.key_alias}")
66 65 private String keyAlias;
67 66
68   - @Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
  67 + @Value("${transport.coap.dtls.x509.skip_validity_check_for_client_cert:false}")
69 68 private boolean skipValidityCheckForClientCert;
70 69
71   - @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
  70 + @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout:86400000}")
72 71 private long dtlsSessionInactivityTimeout;
73 72
74   - @Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
  73 + @Value("${transport.coap.dtls.x509.dtls_session_report_timeout:1800000}")
75 74 private long dtlsSessionReportTimeout;
76 75
77 76 @Autowired
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.coapserver;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +
  20 +import java.lang.annotation.Retention;
  21 +import java.lang.annotation.RetentionPolicy;
  22 +
  23 +@Retention(RetentionPolicy.RUNTIME)
  24 +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
  25 +public @interface TbCoapServerComponent {
  26 +}
... ...
... ... @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Device;
20 20 import org.thingsboard.server.common.data.DeviceInfo;
21 21 import org.thingsboard.server.common.data.DeviceProfile;
  22 +import org.thingsboard.server.common.data.DeviceTransportType;
22 23 import org.thingsboard.server.common.data.EntitySubtype;
23 24 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
24 25 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
32 33 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
33 34
34 35 import java.util.List;
  36 +import java.util.UUID;
35 37
36 38 public interface DeviceService {
37 39
... ... @@ -93,6 +95,8 @@ public interface DeviceService {
93 95
94 96 Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile);
95 97
  98 + PageData<UUID> findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink);
  99 +
96 100 Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
97 101
98 102 Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.firmware;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.thingsboard.server.common.data.Firmware;
19 20 import org.thingsboard.server.common.data.FirmwareInfo;
20 21 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -40,6 +41,8 @@ public interface FirmwareService {
40 41
41 42 FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId);
42 43
  44 + ListenableFuture<FirmwareInfo> findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId);
  45 +
43 46 PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink);
44 47
45 48 PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.resource;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.thingsboard.server.common.data.ResourceType;
19 20 import org.thingsboard.server.common.data.TbResource;
20 21 import org.thingsboard.server.common.data.TbResourceInfo;
... ... @@ -34,6 +35,8 @@ public interface ResourceService {
34 35
35 36 TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
36 37
  38 + ListenableFuture<TbResourceInfo> findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId);
  39 +
37 40 PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
38 41
39 42 PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
... ...
... ... @@ -87,6 +87,10 @@
87 87 <groupId>org.thingsboard</groupId>
88 88 <artifactId>protobuf-dynamic</artifactId>
89 89 </dependency>
  90 + <dependency>
  91 + <groupId>org.apache.commons</groupId>
  92 + <artifactId>commons-lang3</artifactId>
  93 + </dependency>
90 94 </dependencies>
91 95
92 96 <build>
... ...
... ... @@ -99,6 +99,7 @@ public class DataConstants {
99 99 public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version";
100 100 public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
101 101 public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
  102 + public static final String TARGET_FIRMWARE_TS = "target_fw_ts";
102 103 public static final String FIRMWARE_STATE = "fw_state";
103 104
104 105 //attributes
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
18 18 public enum DeviceTransportType {
19 19 DEFAULT,
20 20 MQTT,
  21 + COAP,
21 22 LWM2M,
22   - COAP
  23 + SNMP
23 24 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 19 import lombok.Data;
19 20 import lombok.EqualsAndHashCode;
20 21 import lombok.extern.slf4j.Slf4j;
... ... @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
25 26 @Slf4j
26 27 @Data
27 28 @EqualsAndHashCode(callSuper = true)
28   -public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId> implements HasTenantId {
  29 +public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId> implements HasName, HasTenantId {
29 30
30 31 private static final long serialVersionUID = 3168391583570815419L;
31 32
... ... @@ -65,4 +66,10 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId>
65 66 public String getSearchText() {
66 67 return title;
67 68 }
  69 +
  70 + @Override
  71 + @JsonIgnore
  72 + public String getName() {
  73 + return title;
  74 + }
68 75 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 19 import lombok.Data;
19 20 import lombok.EqualsAndHashCode;
20 21 import lombok.extern.slf4j.Slf4j;
... ... @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
25 26 @Slf4j
26 27 @Data
27 28 @EqualsAndHashCode(callSuper = true)
28   -public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
  29 +public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasName, HasTenantId {
29 30
30 31 private static final long serialVersionUID = 7282664529021651736L;
31 32
... ... @@ -54,6 +55,12 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
54 55 }
55 56
56 57 @Override
  58 + @JsonIgnore
  59 + public String getName() {
  60 + return title;
  61 + }
  62 +
  63 + @Override
57 64 public String getSearchText() {
58 65 return searchText != null ? searchText : title;
59 66 }
... ...
common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java
... ... @@ -13,8 +13,8 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.cache.firmware;
  16 +package org.thingsboard.server.common.data;
17 17
18   -public interface FirmwareCacheWriter {
19   - void put(String key, byte[] value);
  18 +public interface TbTransportService {
  19 + String getName();
20 20 }
... ...
... ... @@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
21 21 import com.fasterxml.jackson.annotation.JsonTypeInfo;
22 22 import org.thingsboard.server.common.data.DeviceTransportType;
23 23
  24 +import java.io.Serializable;
  25 +
24 26 @JsonIgnoreProperties(ignoreUnknown = true)
25 27 @JsonTypeInfo(
26 28 use = JsonTypeInfo.Id.NAME,
... ... @@ -29,11 +31,14 @@ import org.thingsboard.server.common.data.DeviceTransportType;
29 31 @JsonSubTypes({
30 32 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
31 33 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
  34 + @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
32 35 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"),
33   - @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP")})
34   -public interface DeviceTransportConfiguration {
35   -
  36 + @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")})
  37 +public interface DeviceTransportConfiguration extends Serializable {
36 38 @JsonIgnore
37 39 DeviceTransportType getType();
38 40
  41 + default void validate() {
  42 + }
  43 +
39 44 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.device.data;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import lombok.Data;
  20 +import lombok.ToString;
  21 +import org.apache.commons.lang3.ObjectUtils;
  22 +import org.apache.commons.lang3.StringUtils;
  23 +import org.thingsboard.server.common.data.DeviceTransportType;
  24 +import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol;
  25 +import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol;
  26 +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
  27 +
  28 +import java.util.Objects;
  29 +
  30 +@Data
  31 +@ToString(of = {"host", "port", "protocolVersion"})
  32 +public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration {
  33 + private String host;
  34 + private Integer port;
  35 + private SnmpProtocolVersion protocolVersion;
  36 +
  37 + /*
  38 + * For SNMP v1 and v2c
  39 + * */
  40 + private String community;
  41 +
  42 + /*
  43 + * For SNMP v3
  44 + * */
  45 + private String username;
  46 + private String securityName;
  47 + private String contextName;
  48 + private AuthenticationProtocol authenticationProtocol;
  49 + private String authenticationPassphrase;
  50 + private PrivacyProtocol privacyProtocol;
  51 + private String privacyPassphrase;
  52 + private String engineId;
  53 +
  54 + @Override
  55 + public DeviceTransportType getType() {
  56 + return DeviceTransportType.SNMP;
  57 + }
  58 +
  59 + @Override
  60 + public void validate() {
  61 + if (!isValid()) {
  62 + throw new IllegalArgumentException("Transport configuration is not valid");
  63 + }
  64 + }
  65 +
  66 + @JsonIgnore
  67 + private boolean isValid() {
  68 + boolean isValid = StringUtils.isNotBlank(host) && port != null && protocolVersion != null;
  69 + if (isValid) {
  70 + switch (protocolVersion) {
  71 + case V1:
  72 + case V2C:
  73 + isValid = StringUtils.isNotEmpty(community);
  74 + break;
  75 + case V3:
  76 + isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName)
  77 + && contextName != null && authenticationProtocol != null
  78 + && StringUtils.isNotBlank(authenticationPassphrase)
  79 + && privacyProtocol != null && privacyPassphrase != null && engineId != null;
  80 + break;
  81 + }
  82 + }
  83 + return isValid;
  84 + }
  85 +}
... ...
... ... @@ -29,13 +29,18 @@ import java.io.Serializable;
29 29 include = JsonTypeInfo.As.PROPERTY,
30 30 property = "type")
31 31 @JsonSubTypes({
32   - @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
33   - @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
34   - @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
35   - @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP")})
  32 + @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
  33 + @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
  34 + @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
  35 + @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"),
  36 + @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")
  37 +})
36 38 public interface DeviceProfileTransportConfiguration extends Serializable {
37 39
38 40 @JsonIgnore
39 41 DeviceTransportType getType();
40 42
  43 + default void validate() {
  44 + }
  45 +
41 46 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.device.profile;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.DeviceTransportType;
  21 +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
  22 +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
  23 +
  24 +import java.util.List;
  25 +
  26 +@Data
  27 +public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
  28 + private Integer timeoutMs;
  29 + private Integer retries;
  30 + private List<SnmpCommunicationConfig> communicationConfigs;
  31 +
  32 + @Override
  33 + public DeviceTransportType getType() {
  34 + return DeviceTransportType.SNMP;
  35 + }
  36 +
  37 + @Override
  38 + public void validate() {
  39 + if (!isValid()) {
  40 + throw new IllegalArgumentException("SNMP transport configuration is not valid");
  41 + }
  42 + }
  43 +
  44 + @JsonIgnore
  45 + private boolean isValid() {
  46 + return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0
  47 + && communicationConfigs != null
  48 + && communicationConfigs.stream().allMatch(config -> config != null && config.isValid())
  49 + && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid)
  50 + .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum();
  51 + }
  52 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +import java.util.Arrays;
  19 +import java.util.Optional;
  20 +
  21 +public enum AuthenticationProtocol {
  22 + SHA_1("1.3.6.1.6.3.10.1.1.3"),
  23 + SHA_224("1.3.6.1.6.3.10.1.1.4"),
  24 + SHA_256("1.3.6.1.6.3.10.1.1.5"),
  25 + SHA_384("1.3.6.1.6.3.10.1.1.6"),
  26 + SHA_512("1.3.6.1.6.3.10.1.1.7"),
  27 + MD5("1.3.6.1.6.3.10.1.1.2");
  28 +
  29 + // oids taken from org.snmp4j.security.SecurityProtocol implementations
  30 + private final String oid;
  31 +
  32 + AuthenticationProtocol(String oid) {
  33 + this.oid = oid;
  34 + }
  35 +
  36 + public String getOid() {
  37 + return oid;
  38 + }
  39 +
  40 + public static Optional<AuthenticationProtocol> forName(String name) {
  41 + return Arrays.stream(values())
  42 + .filter(protocol -> protocol.name().equalsIgnoreCase(name))
  43 + .findFirst();
  44 + }
  45 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +import java.util.Arrays;
  19 +import java.util.Optional;
  20 +
  21 +public enum PrivacyProtocol {
  22 + DES("1.3.6.1.6.3.10.1.2.2"),
  23 + AES_128("1.3.6.1.6.3.10.1.2.4"),
  24 + AES_192("1.3.6.1.4.1.4976.2.2.1.1.1"),
  25 + AES_256("1.3.6.1.4.1.4976.2.2.1.1.2");
  26 +
  27 + // oids taken from org.snmp4j.security.SecurityProtocol implementations
  28 + private final String oid;
  29 +
  30 + PrivacyProtocol(String oid) {
  31 + this.oid = oid;
  32 + }
  33 +
  34 + public String getOid() {
  35 + return oid;
  36 + }
  37 +
  38 + public static Optional<PrivacyProtocol> forName(String name) {
  39 + return Arrays.stream(values())
  40 + .filter(protocol -> protocol.name().equalsIgnoreCase(name))
  41 + .findFirst();
  42 + }
  43 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +public enum SnmpCommunicationSpec {
  19 + TELEMETRY_QUERYING,
  20 +
  21 + CLIENT_ATTRIBUTES_QUERYING,
  22 + SHARED_ATTRIBUTES_SETTING,
  23 +
  24 + TO_DEVICE_RPC_REQUEST,
  25 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import lombok.AllArgsConstructor;
  20 +import lombok.Data;
  21 +import lombok.NoArgsConstructor;
  22 +import org.apache.commons.lang3.StringUtils;
  23 +import org.thingsboard.server.common.data.kv.DataType;
  24 +
  25 +import java.util.regex.Pattern;
  26 +
  27 +@Data
  28 +@AllArgsConstructor
  29 +@NoArgsConstructor
  30 +public class SnmpMapping {
  31 + private String oid;
  32 + private String key;
  33 + private DataType dataType;
  34 +
  35 + private static final Pattern OID_PATTERN = Pattern.compile("^\\.?([0-2])((\\.0)|(\\.[1-9][0-9]*))*$");
  36 +
  37 + @JsonIgnore
  38 + public boolean isValid() {
  39 + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && StringUtils.isNotBlank(key);
  40 + }
  41 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +public enum SnmpMethod {
  19 + GET(-96),
  20 + SET(-93);
  21 +
  22 + // codes taken from org.snmp4j.PDU class
  23 + private final int code;
  24 +
  25 + SnmpMethod(int code) {
  26 + this.code = code;
  27 + }
  28 +
  29 + public int getCode() {
  30 + return code;
  31 + }
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp;
  17 +
  18 +public enum SnmpProtocolVersion {
  19 + V1(0),
  20 + V2C(1),
  21 + V3(3);
  22 +
  23 + private final int code;
  24 +
  25 + SnmpProtocolVersion(int code) {
  26 + this.code = code;
  27 + }
  28 +
  29 + public int getCode() {
  30 + return code;
  31 + }
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp.config;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
  20 +
  21 +import java.util.List;
  22 +
  23 +@Data
  24 +public abstract class MultipleMappingsSnmpCommunicationConfig implements SnmpCommunicationConfig {
  25 + protected List<SnmpMapping> mappings;
  26 +
  27 + @Override
  28 + public boolean isValid() {
  29 + return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid());
  30 + }
  31 +
  32 + @Override
  33 + public List<SnmpMapping> getAllMappings() {
  34 + return mappings;
  35 + }
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp.config;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
  21 +
  22 +@EqualsAndHashCode(callSuper = true)
  23 +@Data
  24 +public abstract class RepeatingQueryingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
  25 + private Long queryingFrequencyMs;
  26 +
  27 + @Override
  28 + public SnmpMethod getMethod() {
  29 + return SnmpMethod.GET;
  30 + }
  31 +
  32 + @Override
  33 + public boolean isValid() {
  34 + return queryingFrequencyMs != null && queryingFrequencyMs > 0 && super.isValid();
  35 + }
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp.config;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  20 +import com.fasterxml.jackson.annotation.JsonSubTypes;
  21 +import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
  22 +import com.fasterxml.jackson.annotation.JsonTypeInfo;
  23 +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
  24 +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
  25 +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
  26 +import org.thingsboard.server.common.data.transport.snmp.config.impl.ClientAttributesQueryingSnmpCommunicationConfig;
  27 +import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttributesSettingSnmpCommunicationConfig;
  28 +import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig;
  29 +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcRequestSnmpCommunicationConfig;
  30 +
  31 +import java.util.List;
  32 +
  33 +@JsonIgnoreProperties(ignoreUnknown = true)
  34 +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "spec")
  35 +@JsonSubTypes({
  36 + @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"),
  37 + @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"),
  38 + @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"),
  39 + @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST")
  40 +})
  41 +public interface SnmpCommunicationConfig {
  42 +
  43 + SnmpCommunicationSpec getSpec();
  44 +
  45 + @JsonIgnore
  46 + default SnmpMethod getMethod() {
  47 + return null;
  48 + }
  49 +
  50 + @JsonIgnore
  51 + List<SnmpMapping> getAllMappings();
  52 +
  53 + @JsonIgnore
  54 + boolean isValid();
  55 +
  56 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java
... ... @@ -13,26 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.cache.firmware;
  16 +package org.thingsboard.server.common.data.transport.snmp.config.impl;
17 17
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
19   -import org.springframework.data.redis.connection.RedisConnection;
20   -import org.springframework.data.redis.connection.RedisConnectionFactory;
21   -import org.springframework.stereotype.Service;
  18 +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
  19 +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
22 20
23   -@Service
24   -@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${cache.type:null}'=='redis'")
25   -public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter {
26   -
27   - public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) {
28   - super(redisConnectionFactory);
29   - }
  21 +public class ClientAttributesQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig {
30 22
31 23 @Override
32   - public void put(String key, byte[] value) {
33   - try (RedisConnection connection = redisConnectionFactory.getConnection()) {
34   - connection.set(toFirmwareCacheKey(key), value);
35   - }
  24 + public SnmpCommunicationSpec getSpec() {
  25 + return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING;
36 26 }
37 27
38 28 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp.config.impl;
  17 +
  18 +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
  19 +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
  20 +import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig;
  21 +
  22 +public class SharedAttributesSettingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
  23 +
  24 + @Override
  25 + public SnmpCommunicationSpec getSpec() {
  26 + return SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING;
  27 + }
  28 +
  29 + @Override
  30 + public SnmpMethod getMethod() {
  31 + return SnmpMethod.SET;
  32 + }
  33 +
  34 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.common.data.transport.snmp.config.impl;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
  21 +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
  22 +
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@Data
  25 +public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig {
  26 +
  27 + @Override
  28 + public SnmpCommunicationSpec getSpec() {
  29 + return SnmpCommunicationSpec.TELEMETRY_QUERYING;
  30 + }
  31 +
  32 +}
... ...
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java renamed from common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java
... ... @@ -13,26 +13,14 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.cache.firmware;
  16 +package org.thingsboard.server.common.data.transport.snmp.config.impl;
17 17
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
19   -import org.springframework.cache.CacheManager;
20   -import org.springframework.stereotype.Service;
21   -
22   -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
23   -
24   -@Service
25   -@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')")
26   -public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter {
27   -
28   - private final CacheManager cacheManager;
29   -
30   - public CaffeineFirmwareCacheWriter(CacheManager cacheManager) {
31   - this.cacheManager = cacheManager;
32   - }
  18 +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
  19 +import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig;
33 20
  21 +public class ToDeviceRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
34 22 @Override
35   - public void put(String key, byte[] value) {
36   - cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value);
  23 + public SnmpCommunicationSpec getSpec() {
  24 + return SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST;
37 25 }
38 26 }
... ...
... ... @@ -16,5 +16,5 @@
16 16 package org.thingsboard.server.common.msg.session;
17 17
18 18 public enum FeatureType {
19   - ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION
  19 + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE
20 20 }
... ...
... ... @@ -30,7 +30,9 @@ public enum SessionMsgType {
30 30
31 31 SESSION_OPEN, SESSION_CLOSE,
32 32
33   - CLAIM_REQUEST();
  33 + CLAIM_REQUEST(),
  34 +
  35 + GET_FIRMWARE_REQUEST;
34 36
35 37 private final boolean requiresRulesProcessing;
36 38
... ...
... ... @@ -19,19 +19,23 @@ import lombok.Getter;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.springframework.beans.factory.annotation.Autowired;
21 21 import org.springframework.beans.factory.annotation.Value;
  22 +import org.springframework.context.ApplicationContext;
22 23 import org.springframework.stereotype.Component;
23 24 import org.springframework.util.StringUtils;
  25 +import org.thingsboard.server.common.data.TbTransportService;
24 26 import org.thingsboard.server.common.data.id.TenantId;
25 27 import org.thingsboard.server.common.msg.queue.ServiceType;
26 28 import org.thingsboard.server.gen.transport.TransportProtos;
27 29 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
28 30 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
29 31 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
  32 +import org.thingsboard.server.queue.util.AfterContextReady;
30 33
31 34 import javax.annotation.PostConstruct;
32 35 import java.net.InetAddress;
33 36 import java.net.UnknownHostException;
34 37 import java.util.Arrays;
  38 +import java.util.Collection;
35 39 import java.util.Collections;
36 40 import java.util.List;
37 41 import java.util.Optional;
... ... @@ -56,6 +60,8 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
56 60
57 61 @Autowired(required = false)
58 62 private TbQueueRuleEngineSettings ruleEngineSettings;
  63 + @Autowired
  64 + private ApplicationContext applicationContext;
59 65
60 66 private List<ServiceType> serviceTypes;
61 67 private ServiceInfo serviceInfo;
... ... @@ -102,6 +108,19 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
102 108 serviceInfo = builder.build();
103 109 }
104 110
  111 + @AfterContextReady
  112 + public void setTransports() {
  113 + serviceInfo = ServiceInfo.newBuilder(serviceInfo)
  114 + .addAllTransports(getTransportServices().stream()
  115 + .map(TbTransportService::getName)
  116 + .collect(Collectors.toSet()))
  117 + .build();
  118 + }
  119 +
  120 + private Collection<TbTransportService> getTransportServices() {
  121 + return applicationContext.getBeansOfType(TbTransportService.class).values();
  122 + }
  123 +
105 124 @Override
106 125 public ServiceInfo getServiceInfo() {
107 126 return serviceInfo;
... ...
... ... @@ -15,26 +15,27 @@
15 15 */
16 16 package org.thingsboard.server.queue.discovery;
17 17
18   -import com.google.common.hash.HashCode;
19 18 import com.google.common.hash.HashFunction;
  19 +import com.google.common.hash.Hasher;
20 20 import com.google.common.hash.Hashing;
21   -import lombok.Getter;
22 21 import lombok.extern.slf4j.Slf4j;
23 22 import org.springframework.beans.factory.annotation.Value;
24 23 import org.springframework.context.ApplicationEventPublisher;
25 24 import org.springframework.stereotype.Service;
26 25 import org.thingsboard.server.common.data.id.EntityId;
27 26 import org.thingsboard.server.common.data.id.TenantId;
28   -import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
29 27 import org.thingsboard.server.common.msg.queue.ServiceQueue;
  28 +import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
30 29 import org.thingsboard.server.common.msg.queue.ServiceType;
31 30 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
32 31 import org.thingsboard.server.gen.transport.TransportProtos;
33 32 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
  33 +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
  34 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
  35 +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
34 36 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
35 37
36 38 import javax.annotation.PostConstruct;
37   -import java.nio.charset.StandardCharsets;
38 39 import java.util.ArrayList;
39 40 import java.util.Collections;
40 41 import java.util.Comparator;
... ... @@ -46,7 +47,6 @@ import java.util.Set;
46 47 import java.util.UUID;
47 48 import java.util.concurrent.ConcurrentHashMap;
48 49 import java.util.concurrent.ConcurrentMap;
49   -import java.util.concurrent.ConcurrentNavigableMap;
50 50 import java.util.stream.Collectors;
51 51
52 52 @Service
... ... @@ -186,6 +186,8 @@ public class HashPartitionService implements PartitionService {
186 186 applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes));
187 187 }
188 188 }
  189 +
  190 + applicationEventPublisher.publishEvent(new ServiceListChangedEvent(otherServices, currentService));
189 191 }
190 192
191 193 @Override
... ... @@ -219,6 +221,14 @@ public class HashPartitionService implements PartitionService {
219 221 }
220 222 }
221 223
  224 + @Override
  225 + public int resolvePartitionIndex(UUID entityId, int partitions) {
  226 + int hash = hashFunction.newHasher()
  227 + .putLong(entityId.getMostSignificantBits())
  228 + .putLong(entityId.getLeastSignificantBits()).hash().asInt();
  229 + return Math.abs(hash % partitions);
  230 + }
  231 +
222 232 private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) {
223 233 final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
224 234 services.forEach(serviceInfo -> {
... ...
... ... @@ -20,9 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.msg.queue.ServiceType;
21 21 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
22 22 import org.thingsboard.server.gen.transport.TransportProtos;
  23 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
23 24
24 25 import java.util.List;
25 26 import java.util.Set;
  27 +import java.util.UUID;
26 28
27 29 /**
28 30 * Once application is ready or cluster topology changes, this Service will produce {@link PartitionChangeEvent}
... ... @@ -55,4 +57,6 @@ public interface PartitionService {
55 57 * @return
56 58 */
57 59 TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId);
  60 +
  61 + int resolvePartitionIndex(UUID entityId, int partitions);
58 62 }
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.discovery;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.context.ApplicationListener;
  20 +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent;
20 21
21 22 import java.util.concurrent.locks.Lock;
22 23 import java.util.concurrent.locks.ReentrantLock;
... ...
... ... @@ -33,12 +33,14 @@ import org.apache.zookeeper.KeeperException;
33 33 import org.springframework.beans.factory.annotation.Value;
34 34 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35 35 import org.springframework.boot.context.event.ApplicationReadyEvent;
  36 +import org.springframework.context.ApplicationEventPublisher;
36 37 import org.springframework.context.event.EventListener;
37 38 import org.springframework.core.annotation.Order;
38 39 import org.springframework.stereotype.Service;
39 40 import org.springframework.util.Assert;
40 41 import org.thingsboard.common.util.ThingsBoardThreadFactory;
41 42 import org.thingsboard.server.gen.transport.TransportProtos;
  43 +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
42 44
43 45 import javax.annotation.PostConstruct;
44 46 import javax.annotation.PreDestroy;
... ... @@ -77,7 +79,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
77 79
78 80 private volatile boolean stopped = true;
79 81
80   - public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) {
  82 + public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider,
  83 + PartitionService partitionService) {
81 84 this.serviceInfoProvider = serviceInfoProvider;
82 85 this.partitionService = partitionService;
83 86 }
... ...
common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java renamed from common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java
... ... @@ -13,10 +13,9 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.queue.discovery;
  16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 18 import lombok.Getter;
19   -import org.springframework.context.ApplicationEvent;
20 19 import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
21 20
22 21 import java.util.Set;
... ...
common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java renamed from common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java
... ... @@ -13,10 +13,9 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.queue.discovery;
  16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 18 import lombok.Getter;
19   -import org.springframework.context.ApplicationEvent;
20 19 import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
21 20 import org.thingsboard.server.common.msg.queue.ServiceType;
22 21 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.queue.discovery.event;
  17 +
  18 +import lombok.Getter;
  19 +import lombok.ToString;
  20 +import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
  21 +
  22 +import java.util.List;
  23 +
  24 +@Getter
  25 +@ToString
  26 +public class ServiceListChangedEvent extends TbApplicationEvent {
  27 + private final List<ServiceInfo> otherServices;
  28 + private final ServiceInfo currentService;
  29 +
  30 + public ServiceListChangedEvent(List<ServiceInfo> otherServices, ServiceInfo currentService) {
  31 + super(otherServices);
  32 + this.otherServices = otherServices;
  33 + this.currentService = currentService;
  34 + }
  35 +}
... ...
common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java renamed from common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.queue.discovery;
  16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 18 import lombok.Getter;
19 19 import org.springframework.context.ApplicationEvent;
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.queue.util;
  17 +
  18 +import org.springframework.context.event.ContextRefreshedEvent;
  19 +import org.springframework.context.event.EventListener;
  20 +import org.springframework.core.annotation.AliasFor;
  21 +import org.springframework.core.annotation.Order;
  22 +
  23 +import java.lang.annotation.ElementType;
  24 +import java.lang.annotation.Retention;
  25 +import java.lang.annotation.RetentionPolicy;
  26 +import java.lang.annotation.Target;
  27 +
  28 +@Retention(RetentionPolicy.RUNTIME)
  29 +@Target(ElementType.METHOD)
  30 +@EventListener(ContextRefreshedEvent.class)
  31 +@Order
  32 +public @interface AfterContextReady {
  33 + @AliasFor(annotation = Order.class, attribute = "value")
  34 + int order() default Integer.MAX_VALUE;
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.queue.util;
  17 +
  18 +import org.springframework.boot.context.event.ApplicationReadyEvent;
  19 +import org.springframework.context.event.EventListener;
  20 +import org.springframework.core.annotation.AliasFor;
  21 +import org.springframework.core.annotation.Order;
  22 +
  23 +import java.lang.annotation.ElementType;
  24 +import java.lang.annotation.Retention;
  25 +import java.lang.annotation.RetentionPolicy;
  26 +import java.lang.annotation.Target;
  27 +
  28 +@Retention(RetentionPolicy.RUNTIME)
  29 +@Target(ElementType.METHOD)
  30 +@EventListener(ApplicationReadyEvent.class)
  31 +@Order
  32 +public @interface AfterStartUp {
  33 + @AliasFor(annotation = Order.class, attribute = "value")
  34 + int order() default Integer.MAX_VALUE;
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.queue.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +
  20 +import java.lang.annotation.ElementType;
  21 +import java.lang.annotation.Retention;
  22 +import java.lang.annotation.RetentionPolicy;
  23 +import java.lang.annotation.Target;
  24 +
  25 +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')")
  26 +@Retention(RetentionPolicy.RUNTIME)
  27 +@Target({ElementType.TYPE, ElementType.METHOD})
  28 +public @interface TbSnmpTransportComponent {
  29 +}
... ...
... ... @@ -34,6 +34,7 @@ message ServiceInfo {
34 34 int64 tenantIdMSB = 3;
35 35 int64 tenantIdLSB = 4;
36 36 repeated QueueInfo ruleEngineQueues = 5;
  37 + repeated string transports = 6;
37 38 }
38 39
39 40 /**
... ... @@ -246,6 +247,36 @@ message GetEntityProfileResponseMsg {
246 247 bytes apiState = 3;
247 248 }
248 249
  250 +message GetDeviceRequestMsg {
  251 + int64 deviceIdMSB = 1;
  252 + int64 deviceIdLSB = 2;
  253 +}
  254 +
  255 +message GetDeviceResponseMsg {
  256 + int64 deviceProfileIdMSB = 1;
  257 + int64 deviceProfileIdLSB = 2;
  258 + bytes deviceTransportConfiguration = 3;
  259 +}
  260 +
  261 +message GetDeviceCredentialsRequestMsg {
  262 + int64 deviceIdMSB = 1;
  263 + int64 deviceIdLSB = 2;
  264 +}
  265 +
  266 +message GetDeviceCredentialsResponseMsg {
  267 + bytes deviceCredentialsData = 1;
  268 +}
  269 +
  270 +message GetSnmpDevicesRequestMsg {
  271 + int32 page = 1;
  272 + int32 pageSize = 2;
  273 +}
  274 +
  275 +message GetSnmpDevicesResponseMsg {
  276 + repeated string ids = 1;
  277 + bool hasNextPage = 2;
  278 +}
  279 +
249 280 message EntityUpdateMsg {
250 281 string entityType = 1;
251 282 bytes data = 2;
... ... @@ -590,6 +621,9 @@ message TransportApiRequestMsg {
590 621 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
591 622 GetResourceRequestMsg resourceRequestMsg = 9;
592 623 GetFirmwareRequestMsg firmwareRequestMsg = 10;
  624 + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11;
  625 + GetDeviceRequestMsg deviceRequestMsg = 12;
  626 + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13;
593 627 }
594 628
595 629 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -598,9 +632,12 @@ message TransportApiResponseMsg {
598 632 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
599 633 GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
600 634 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
  635 + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5;
601 636 LwM2MResponseMsg lwM2MResponseMsg = 6;
602 637 GetResourceResponseMsg resourceResponseMsg = 7;
603 638 GetFirmwareResponseMsg firmwareResponseMsg = 8;
  639 + GetDeviceResponseMsg deviceResponseMsg = 9;
  640 + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10;
604 641 }
605 642
606 643 /* Messages that are handled by ThingsBoard Core Service */
... ...
... ... @@ -28,12 +28,12 @@ import org.eclipse.californium.core.observe.ObserveRelation;
28 28 import org.eclipse.californium.core.server.resources.CoapExchange;
29 29 import org.eclipse.californium.core.server.resources.Resource;
30 30 import org.eclipse.californium.core.server.resources.ResourceObserver;
31   -import org.springframework.util.StringUtils;
32 31 import org.thingsboard.server.coapserver.CoapServerService;
33 32 import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo;
34 33 import org.thingsboard.server.common.data.DataConstants;
35 34 import org.thingsboard.server.common.data.DeviceProfile;
36 35 import org.thingsboard.server.common.data.DeviceTransportType;
  36 +import org.thingsboard.server.common.data.StringUtils;
37 37 import org.thingsboard.server.common.data.TransportPayloadType;
38 38 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
39 39 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
... ... @@ -120,6 +120,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
120 120 processExchangeGetRequest(exchange, featureType.get());
121 121 } else if (featureType.get() == FeatureType.ATTRIBUTES) {
122 122 processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST);
  123 + } else if (featureType.get() == FeatureType.FIRMWARE) {
  124 + processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST);
123 125 } else {
124 126 log.trace("Invalid feature type parameter");
125 127 exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
... ... @@ -201,7 +203,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
201 203 Request request = advanced.getRequest();
202 204
203 205 String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
204   - if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
  206 + if (StringUtils.isNotEmpty(dtlsSessionIdStr)) {
205 207 if (dtlsSessionIdMap != null) {
206 208 TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
207 209 .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
... ... @@ -323,6 +325,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
323 325 coapTransportAdaptor.convertToGetAttributes(sessionId, request),
324 326 new CoapNoOpCallback(exchange));
325 327 break;
  328 + case GET_FIRMWARE_REQUEST:
  329 + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
  330 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  331 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  332 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  333 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build();
  334 + transportContext.getTransportService().process(sessionInfo, requestMsg, new FirmwareCallback(exchange));
  335 + break;
326 336 }
327 337 } catch (AdaptorException e) {
328 338 log.trace("[{}] Failed to decode message: ", sessionId, e);
... ... @@ -424,6 +434,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
424 434 }
425 435 }
426 436
  437 + private class FirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
  438 + private final CoapExchange exchange;
  439 +
  440 + FirmwareCallback(CoapExchange exchange) {
  441 + this.exchange = exchange;
  442 + }
  443 +
  444 + @Override
  445 + public void onSuccess(TransportProtos.GetFirmwareResponseMsg msg) {
  446 + String title = exchange.getQueryParameter("title");
  447 + String version = exchange.getQueryParameter("version");
  448 + if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
  449 + if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) {
  450 + String firmwareId = new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB()).toString();
  451 + String strChunkSize = exchange.getQueryParameter("size");
  452 + String strChunk = exchange.getQueryParameter("chunk");
  453 + int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize);
  454 + int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk);
  455 + exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getFirmwareDataCache().get(firmwareId, chunkSize, chunk));
  456 + } else {
  457 + exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
  458 + }
  459 + } else {
  460 + exchange.respond(CoAP.ResponseCode.NOT_FOUND);
  461 + }
  462 + }
  463 +
  464 + @Override
  465 + public void onError(Throwable e) {
  466 + log.warn("Failed to process request", e);
  467 + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
  468 + }
  469 + }
  470 +
427 471 private static class CoapSessionListener implements SessionMsgListener {
428 472
429 473 private final CoapExchange exchange;
... ...
... ... @@ -18,11 +18,12 @@ package org.thingsboard.server.transport.coap;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.CoapResource;
20 20 import org.eclipse.californium.core.CoapServer;
21   -
22 21 import org.springframework.beans.factory.annotation.Autowired;
23 22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
24 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.TbTransportService;
25 25 import org.thingsboard.server.coapserver.CoapServerService;
  26 +import org.thingsboard.server.coapserver.TbCoapServerComponent;
26 27 import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
27 28
28 29 import javax.annotation.PostConstruct;
... ... @@ -30,9 +31,9 @@ import javax.annotation.PreDestroy;
30 31 import java.net.UnknownHostException;
31 32
32 33 @Service("CoapTransportService")
33   -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
  34 +@TbCoapServerComponent
34 35 @Slf4j
35   -public class CoapTransportService {
  36 +public class CoapTransportService implements TbTransportService {
36 37
37 38 private static final String V1 = "v1";
38 39 private static final String API = "api";
... ... @@ -66,4 +67,9 @@ public class CoapTransportService {
66 67 public void shutdown() {
67 68 log.info("CoAP transport stopped!");
68 69 }
  70 +
  71 + @Override
  72 + public String getName() {
  73 + return "COAP";
  74 + }
69 75 }
... ...