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,6 +90,10 @@
90 <artifactId>lwm2m</artifactId> 90 <artifactId>lwm2m</artifactId>
91 </dependency> 91 </dependency>
92 <dependency> 92 <dependency>
  93 + <groupId>org.thingsboard.common.transport</groupId>
  94 + <artifactId>snmp</artifactId>
  95 + </dependency>
  96 + <dependency>
93 <groupId>org.thingsboard</groupId> 97 <groupId>org.thingsboard</groupId>
94 <artifactId>dao</artifactId> 98 <artifactId>dao</artifactId>
95 </dependency> 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 +}
@@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS firmware ( @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS firmware (
69 content_type varchar(255), 69 content_type varchar(255),
70 checksum_algorithm varchar(32), 70 checksum_algorithm varchar(32),
71 checksum varchar(1020), 71 checksum varchar(1020),
72 - data bytea, 72 + data oid,
73 data_size bigint, 73 data_size bigint,
74 additional_info varchar, 74 additional_info varchar,
75 search_text varchar(255), 75 search_text varchar(255),
@@ -25,7 +25,6 @@ import org.springframework.stereotype.Service; @@ -25,7 +25,6 @@ import org.springframework.stereotype.Service;
25 import org.thingsboard.common.util.ThingsBoardThreadFactory; 25 import org.thingsboard.common.util.ThingsBoardThreadFactory;
26 import org.thingsboard.server.actors.ActorSystemContext; 26 import org.thingsboard.server.actors.ActorSystemContext;
27 import org.thingsboard.server.actors.DefaultTbActorSystem; 27 import org.thingsboard.server.actors.DefaultTbActorSystem;
28 -import org.thingsboard.server.actors.TbActorId;  
29 import org.thingsboard.server.actors.TbActorRef; 28 import org.thingsboard.server.actors.TbActorRef;
30 import org.thingsboard.server.actors.TbActorSystem; 29 import org.thingsboard.server.actors.TbActorSystem;
31 import org.thingsboard.server.actors.TbActorSystemSettings; 30 import org.thingsboard.server.actors.TbActorSystemSettings;
@@ -33,14 +32,13 @@ import org.thingsboard.server.actors.app.AppActor; @@ -33,14 +32,13 @@ import org.thingsboard.server.actors.app.AppActor;
33 import org.thingsboard.server.actors.app.AppInitMsg; 32 import org.thingsboard.server.actors.app.AppInitMsg;
34 import org.thingsboard.server.actors.stats.StatsActor; 33 import org.thingsboard.server.actors.stats.StatsActor;
35 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; 34 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
36 -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;  
37 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 35 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
  36 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
38 37
39 import javax.annotation.PostConstruct; 38 import javax.annotation.PostConstruct;
40 import javax.annotation.PreDestroy; 39 import javax.annotation.PreDestroy;
41 import java.util.concurrent.ExecutorService; 40 import java.util.concurrent.ExecutorService;
42 import java.util.concurrent.Executors; 41 import java.util.concurrent.Executors;
43 -import java.util.concurrent.ScheduledExecutorService;  
44 42
45 @Service 43 @Service
46 @Slf4j 44 @Slf4j
@@ -75,7 +75,6 @@ import javax.annotation.Nullable; @@ -75,7 +75,6 @@ import javax.annotation.Nullable;
75 import java.io.IOException; 75 import java.io.IOException;
76 import java.util.ArrayList; 76 import java.util.ArrayList;
77 import java.util.List; 77 import java.util.List;
78 -import java.util.Objects;  
79 import java.util.stream.Collectors; 78 import java.util.stream.Collectors;
80 79
81 import static org.thingsboard.server.controller.EdgeController.EDGE_ID; 80 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@@ -120,12 +119,12 @@ public class DeviceController extends BaseController { @@ -120,12 +119,12 @@ public class DeviceController extends BaseController {
120 @ResponseBody 119 @ResponseBody
121 public Device saveDevice(@RequestBody Device device, 120 public Device saveDevice(@RequestBody Device device,
122 @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { 121 @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException {
  122 + boolean created = device.getId() == null;
123 try { 123 try {
124 device.setTenantId(getCurrentUser().getTenantId()); 124 device.setTenantId(getCurrentUser().getTenantId());
125 125
126 checkEntity(device.getId(), device, Resource.DEVICE); 126 checkEntity(device.getId(), device, Resource.DEVICE);
127 127
128 - boolean created = device.getId() == null;  
129 Device oldDevice; 128 Device oldDevice;
130 if (!created) { 129 if (!created) {
131 oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); 130 oldDevice = deviceService.findDeviceById(getTenantId(), device.getId());
@@ -146,7 +145,7 @@ public class DeviceController extends BaseController { @@ -146,7 +145,7 @@ public class DeviceController extends BaseController {
146 145
147 logEntityAction(savedDevice.getId(), savedDevice, 146 logEntityAction(savedDevice.getId(), savedDevice,
148 savedDevice.getCustomerId(), 147 savedDevice.getCustomerId(),
149 - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); 148 + created ? ActionType.ADDED : ActionType.UPDATED, null);
150 149
151 if (device.getId() == null) { 150 if (device.getId() == null) {
152 deviceStateService.onDeviceAdded(savedDevice); 151 deviceStateService.onDeviceAdded(savedDevice);
@@ -157,10 +156,9 @@ public class DeviceController extends BaseController { @@ -157,10 +156,9 @@ public class DeviceController extends BaseController {
157 firmwareStateService.update(savedDevice, oldDevice); 156 firmwareStateService.update(savedDevice, oldDevice);
158 157
159 return savedDevice; 158 return savedDevice;
160 - } catch (  
161 - Exception e) { 159 + } catch (Exception e) {
162 logEntityAction(emptyId(EntityType.DEVICE), device, 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 throw handleException(e); 162 throw handleException(e);
165 } 163 }
166 164
@@ -29,8 +29,10 @@ import org.springframework.web.bind.annotation.RequestParam; @@ -29,8 +29,10 @@ import org.springframework.web.bind.annotation.RequestParam;
29 import org.springframework.web.bind.annotation.ResponseBody; 29 import org.springframework.web.bind.annotation.ResponseBody;
30 import org.springframework.web.bind.annotation.RestController; 30 import org.springframework.web.bind.annotation.RestController;
31 import org.springframework.web.multipart.MultipartFile; 31 import org.springframework.web.multipart.MultipartFile;
  32 +import org.thingsboard.server.common.data.EntityType;
32 import org.thingsboard.server.common.data.Firmware; 33 import org.thingsboard.server.common.data.Firmware;
33 import org.thingsboard.server.common.data.FirmwareInfo; 34 import org.thingsboard.server.common.data.FirmwareInfo;
  35 +import org.thingsboard.server.common.data.audit.ActionType;
34 import org.thingsboard.server.common.data.exception.ThingsboardException; 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; 37 import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm;
36 import org.thingsboard.server.common.data.id.FirmwareId; 38 import org.thingsboard.server.common.data.id.FirmwareId;
@@ -72,14 +74,14 @@ public class FirmwareController extends BaseController { @@ -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 @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) 78 @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET)
77 @ResponseBody 79 @ResponseBody
78 public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { 80 public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
79 checkParameter(FIRMWARE_ID, strFirmwareId); 81 checkParameter(FIRMWARE_ID, strFirmwareId);
80 try { 82 try {
81 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); 83 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
82 - return checkFirmwareInfoId(firmwareId, Operation.READ); 84 + return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId));
83 } catch (Exception e) { 85 } catch (Exception e) {
84 throw handleException(e); 86 throw handleException(e);
85 } 87 }
@@ -102,11 +104,17 @@ public class FirmwareController extends BaseController { @@ -102,11 +104,17 @@ public class FirmwareController extends BaseController {
102 @RequestMapping(value = "/firmware", method = RequestMethod.POST) 104 @RequestMapping(value = "/firmware", method = RequestMethod.POST)
103 @ResponseBody 105 @ResponseBody
104 public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException { 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 try { 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 } catch (Exception e) { 115 } catch (Exception e) {
  116 + logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo,
  117 + null, created ? ActionType.ADDED : ActionType.UPDATED, e);
110 throw handleException(e); 118 throw handleException(e);
111 } 119 }
112 } 120 }
@@ -144,13 +152,16 @@ public class FirmwareController extends BaseController { @@ -144,13 +152,16 @@ public class FirmwareController extends BaseController {
144 firmware.setContentType(file.getContentType()); 152 firmware.setContentType(file.getContentType());
145 firmware.setData(data); 153 firmware.setData(data);
146 firmware.setDataSize((long) data.capacity()); 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 } catch (Exception e) { 158 } catch (Exception e) {
  159 + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId);
149 throw handleException(e); 160 throw handleException(e);
150 } 161 }
151 } 162 }
152 163
153 - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 164 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
154 @RequestMapping(value = "/firmwares", method = RequestMethod.GET) 165 @RequestMapping(value = "/firmwares", method = RequestMethod.GET)
155 @ResponseBody 166 @ResponseBody
156 public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize, 167 public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,
@@ -166,7 +177,7 @@ public class FirmwareController extends BaseController { @@ -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 @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET) 181 @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET)
171 @ResponseBody 182 @ResponseBody
172 public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData, 183 public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData,
@@ -186,13 +197,15 @@ public class FirmwareController extends BaseController { @@ -186,13 +197,15 @@ public class FirmwareController extends BaseController {
186 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 197 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
187 @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) 198 @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
188 @ResponseBody 199 @ResponseBody
189 - public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException { 200 + public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
190 checkParameter(FIRMWARE_ID, strFirmwareId); 201 checkParameter(FIRMWARE_ID, strFirmwareId);
191 try { 202 try {
192 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); 203 FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
193 - checkFirmwareInfoId(firmwareId, Operation.DELETE); 204 + FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE);
194 firmwareService.deleteFirmware(getTenantId(), firmwareId); 205 firmwareService.deleteFirmware(getTenantId(), firmwareId);
  206 + logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId);
195 } catch (Exception e) { 207 } catch (Exception e) {
  208 + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId);
196 throw handleException(e); 209 throw handleException(e);
197 } 210 }
198 } 211 }
@@ -28,8 +28,10 @@ import org.springframework.web.bind.annotation.RequestMethod; @@ -28,8 +28,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
28 import org.springframework.web.bind.annotation.RequestParam; 28 import org.springframework.web.bind.annotation.RequestParam;
29 import org.springframework.web.bind.annotation.ResponseBody; 29 import org.springframework.web.bind.annotation.ResponseBody;
30 import org.springframework.web.bind.annotation.RestController; 30 import org.springframework.web.bind.annotation.RestController;
  31 +import org.thingsboard.server.common.data.EntityType;
31 import org.thingsboard.server.common.data.TbResource; 32 import org.thingsboard.server.common.data.TbResource;
32 import org.thingsboard.server.common.data.TbResourceInfo; 33 import org.thingsboard.server.common.data.TbResourceInfo;
  34 +import org.thingsboard.server.common.data.audit.ActionType;
33 import org.thingsboard.server.common.data.exception.ThingsboardException; 35 import org.thingsboard.server.common.data.exception.ThingsboardException;
34 import org.thingsboard.server.common.data.id.TbResourceId; 36 import org.thingsboard.server.common.data.id.TbResourceId;
35 import org.thingsboard.server.common.data.lwm2m.LwM2mObject; 37 import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
@@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.page.PageData; @@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.page.PageData;
37 import org.thingsboard.server.common.data.page.PageLink; 39 import org.thingsboard.server.common.data.page.PageLink;
38 import org.thingsboard.server.common.data.security.Authority; 40 import org.thingsboard.server.common.data.security.Authority;
39 import org.thingsboard.server.queue.util.TbCoreComponent; 41 import org.thingsboard.server.queue.util.TbCoreComponent;
40 -import org.thingsboard.server.service.resource.TbResourceService;  
41 import org.thingsboard.server.service.security.permission.Operation; 42 import org.thingsboard.server.service.security.permission.Operation;
42 import org.thingsboard.server.service.security.permission.Resource; 43 import org.thingsboard.server.service.security.permission.Resource;
43 44
@@ -103,12 +104,18 @@ public class TbResourceController extends BaseController { @@ -103,12 +104,18 @@ public class TbResourceController extends BaseController {
103 @RequestMapping(value = "/resource", method = RequestMethod.POST) 104 @RequestMapping(value = "/resource", method = RequestMethod.POST)
104 @ResponseBody 105 @ResponseBody
105 public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException { 106 public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException {
  107 + boolean created = resource.getId() == null;
106 try { 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 throw handleException(e); 119 throw handleException(e);
113 } 120 }
114 } 121 }
@@ -172,15 +179,11 @@ public class TbResourceController extends BaseController { @@ -172,15 +179,11 @@ public class TbResourceController extends BaseController {
172 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); 179 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
173 resourceService.deleteResource(getTenantId(), resourceId); 180 resourceService.deleteResource(getTenantId(), resourceId);
174 tbClusterService.onResourceDeleted(tbResource, null); 181 tbClusterService.onResourceDeleted(tbResource, null);
  182 + logEntityAction(resourceId, tbResource, null, ActionType.DELETED, null, strResourceId);
175 } catch (Exception e) { 183 } catch (Exception e) {
  184 + logEntityAction(emptyId(EntityType.TB_RESOURCE), null, null, ActionType.DELETED, e, strResourceId);
176 throw handleException(e); 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 }
@@ -56,7 +56,7 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -56,7 +56,7 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
56 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; 56 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
57 import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; 57 import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
58 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 import org.thingsboard.server.queue.discovery.PartitionService; 60 import org.thingsboard.server.queue.discovery.PartitionService;
61 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 61 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
62 import org.thingsboard.server.queue.scheduler.SchedulerComponent; 62 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
@@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
23 import org.thingsboard.server.common.msg.queue.TbCallback; 23 import org.thingsboard.server.common.msg.queue.TbCallback;
24 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; 24 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
25 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 public interface TbApiUsageStateService extends ApplicationListener<PartitionChangeEvent> { 28 public interface TbApiUsageStateService extends ApplicationListener<PartitionChangeEvent> {
29 29
@@ -22,7 +22,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; @@ -22,7 +22,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
22 import org.thingsboard.server.common.data.DataConstants; 22 import org.thingsboard.server.common.data.DataConstants;
23 import org.thingsboard.server.common.data.Device; 23 import org.thingsboard.server.common.data.Device;
24 import org.thingsboard.server.common.data.DeviceProfile; 24 import org.thingsboard.server.common.data.DeviceProfile;
25 -import org.thingsboard.server.common.data.Firmware;  
26 import org.thingsboard.server.common.data.FirmwareInfo; 25 import org.thingsboard.server.common.data.FirmwareInfo;
27 import org.thingsboard.server.common.data.id.DeviceId; 26 import org.thingsboard.server.common.data.id.DeviceId;
28 import org.thingsboard.server.common.data.id.FirmwareId; 27 import org.thingsboard.server.common.data.id.FirmwareId;
@@ -35,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -35,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
35 import org.thingsboard.server.common.data.kv.TsKvEntry; 34 import org.thingsboard.server.common.data.kv.TsKvEntry;
36 import org.thingsboard.server.common.data.page.PageData; 35 import org.thingsboard.server.common.data.page.PageData;
37 import org.thingsboard.server.common.data.page.PageLink; 36 import org.thingsboard.server.common.data.page.PageLink;
38 -import org.thingsboard.server.common.msg.queue.TbCallback;  
39 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 37 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
40 import org.thingsboard.server.dao.device.DeviceProfileService; 38 import org.thingsboard.server.dao.device.DeviceProfileService;
41 import org.thingsboard.server.dao.device.DeviceService; 39 import org.thingsboard.server.dao.device.DeviceService;
@@ -155,7 +153,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -155,7 +153,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
155 } 153 }
156 154
157 if (targetFirmwareId.equals(currentFirmwareId)) { 155 if (targetFirmwareId.equals(currentFirmwareId)) {
158 - update(device, firmwareService.findFirmwareById(device.getTenantId(), targetFirmwareId), ts); 156 + update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts);
159 isSuccess = true; 157 isSuccess = true;
160 } else { 158 } else {
161 log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); 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,6 +185,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
187 List<TsKvEntry> telemetry = new ArrayList<>(); 185 List<TsKvEntry> telemetry = new ArrayList<>();
188 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); 186 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle())));
189 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); 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 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); 189 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name())));
191 190
192 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { 191 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
@@ -55,7 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM @@ -55,7 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; 55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
56 import org.thingsboard.server.queue.TbQueueConsumer; 56 import org.thingsboard.server.queue.TbQueueConsumer;
57 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 import org.thingsboard.server.queue.provider.TbCoreQueueFactory; 59 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
60 import org.thingsboard.server.queue.util.TbCoreComponent; 60 import org.thingsboard.server.queue.util.TbCoreComponent;
61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService; 61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
@@ -38,7 +38,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -38,7 +38,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
38 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; 38 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
39 import org.thingsboard.server.queue.TbQueueConsumer; 39 import org.thingsboard.server.queue.TbQueueConsumer;
40 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; 42 import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
43 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; 43 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
44 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; 44 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 package org.thingsboard.server.service.queue; 16 package org.thingsboard.server.service.queue;
17 17
18 import org.springframework.context.ApplicationListener; 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 public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> { 21 public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> {
22 22
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 package org.thingsboard.server.service.queue; 16 package org.thingsboard.server.service.queue;
17 17
18 import org.springframework.context.ApplicationListener; 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 public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> { 21 public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> {
22 22
@@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; @@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
35 import org.thingsboard.server.common.msg.queue.TbCallback; 35 import org.thingsboard.server.common.msg.queue.TbCallback;
36 import org.thingsboard.server.queue.TbQueueConsumer; 36 import org.thingsboard.server.queue.TbQueueConsumer;
37 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; 39 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
40 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 40 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
41 import org.thingsboard.server.service.apiusage.TbApiUsageStateService; 41 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
@@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.Customer; @@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.Customer;
30 import org.thingsboard.server.common.data.Device; 30 import org.thingsboard.server.common.data.Device;
31 import org.thingsboard.server.common.data.DeviceProfile; 31 import org.thingsboard.server.common.data.DeviceProfile;
32 import org.thingsboard.server.common.data.EntityView; 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 import org.thingsboard.server.common.data.Tenant; 35 import org.thingsboard.server.common.data.Tenant;
34 import org.thingsboard.server.common.data.User; 36 import org.thingsboard.server.common.data.User;
35 import org.thingsboard.server.common.data.asset.Asset; 37 import org.thingsboard.server.common.data.asset.Asset;
@@ -44,8 +46,10 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -44,8 +46,10 @@ import org.thingsboard.server.common.data.id.EdgeId;
44 import org.thingsboard.server.common.data.id.EntityId; 46 import org.thingsboard.server.common.data.id.EntityId;
45 import org.thingsboard.server.common.data.id.EntityIdFactory; 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
46 import org.thingsboard.server.common.data.id.EntityViewId; 48 import org.thingsboard.server.common.data.id.EntityViewId;
  49 +import org.thingsboard.server.common.data.id.FirmwareId;
47 import org.thingsboard.server.common.data.id.RuleChainId; 50 import org.thingsboard.server.common.data.id.RuleChainId;
48 import org.thingsboard.server.common.data.id.RuleNodeId; 51 import org.thingsboard.server.common.data.id.RuleNodeId;
  52 +import org.thingsboard.server.common.data.id.TbResourceId;
49 import org.thingsboard.server.common.data.id.TenantId; 53 import org.thingsboard.server.common.data.id.TenantId;
50 import org.thingsboard.server.common.data.id.UserId; 54 import org.thingsboard.server.common.data.id.UserId;
51 import org.thingsboard.server.common.data.rule.RuleChain; 55 import org.thingsboard.server.common.data.rule.RuleChain;
@@ -59,6 +63,8 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -59,6 +63,8 @@ import org.thingsboard.server.dao.device.DeviceService;
59 import org.thingsboard.server.dao.edge.EdgeService; 63 import org.thingsboard.server.dao.edge.EdgeService;
60 import org.thingsboard.server.dao.entityview.EntityViewService; 64 import org.thingsboard.server.dao.entityview.EntityViewService;
61 import org.thingsboard.server.dao.exception.IncorrectParameterException; 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 import org.thingsboard.server.dao.rule.RuleChainService; 68 import org.thingsboard.server.dao.rule.RuleChainService;
63 import org.thingsboard.server.dao.tenant.TenantService; 69 import org.thingsboard.server.dao.tenant.TenantService;
64 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; 70 import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
@@ -125,6 +131,12 @@ public class AccessValidator { @@ -125,6 +131,12 @@ public class AccessValidator {
125 @Autowired 131 @Autowired
126 protected ApiUsageStateService apiUsageStateService; 132 protected ApiUsageStateService apiUsageStateService;
127 133
  134 + @Autowired
  135 + protected ResourceService resourceService;
  136 +
  137 + @Autowired
  138 + protected FirmwareService firmwareService;
  139 +
128 private ExecutorService executor; 140 private ExecutorService executor;
129 141
130 @PostConstruct 142 @PostConstruct
@@ -217,6 +229,12 @@ public class AccessValidator { @@ -217,6 +229,12 @@ public class AccessValidator {
217 case API_USAGE_STATE: 229 case API_USAGE_STATE:
218 validateApiUsageState(currentUser, operation, entityId, callback); 230 validateApiUsageState(currentUser, operation, entityId, callback);
219 return; 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 default: 238 default:
221 //TODO: add support of other entities 239 //TODO: add support of other entities
222 throw new IllegalStateException("Not Implemented!"); 240 throw new IllegalStateException("Not Implemented!");
@@ -282,6 +300,40 @@ public class AccessValidator { @@ -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 private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { 337 private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
286 if (currentUser.isSystemAdmin()) { 338 if (currentUser.isSystemAdmin()) {
287 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); 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,7 +54,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
54 import org.thingsboard.server.dao.timeseries.TimeseriesService; 54 import org.thingsboard.server.dao.timeseries.TimeseriesService;
55 import org.thingsboard.common.util.JacksonUtil; 55 import org.thingsboard.common.util.JacksonUtil;
56 import org.thingsboard.server.gen.transport.TransportProtos; 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 import org.thingsboard.server.queue.discovery.PartitionService; 58 import org.thingsboard.server.queue.discovery.PartitionService;
59 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 59 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
60 import org.thingsboard.server.queue.util.TbCoreComponent; 60 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -18,7 +18,7 @@ package org.thingsboard.server.service.state; @@ -18,7 +18,7 @@ package org.thingsboard.server.service.state;
18 import org.springframework.context.ApplicationListener; 18 import org.springframework.context.ApplicationListener;
19 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
20 import org.thingsboard.server.common.data.id.DeviceId; 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 import org.thingsboard.server.gen.transport.TransportProtos; 22 import org.thingsboard.server.gen.transport.TransportProtos;
23 import org.thingsboard.server.common.msg.queue.TbCallback; 23 import org.thingsboard.server.common.msg.queue.TbCallback;
24 24
@@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdate @@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdate
46 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto; 46 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto;
47 import org.thingsboard.server.queue.TbQueueProducer; 47 import org.thingsboard.server.queue.TbQueueProducer;
48 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 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 import org.thingsboard.server.queue.discovery.PartitionService; 50 import org.thingsboard.server.queue.discovery.PartitionService;
51 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 51 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
52 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; 52 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@@ -20,10 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -20,10 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.context.annotation.Lazy; 20 import org.springframework.context.annotation.Lazy;
21 import org.springframework.context.event.EventListener; 21 import org.springframework.context.event.EventListener;
22 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
23 -import org.thingsboard.common.util.ThingsBoardThreadFactory;  
24 import org.thingsboard.server.gen.transport.TransportProtos; 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 import org.thingsboard.server.queue.discovery.PartitionService; 26 import org.thingsboard.server.queue.discovery.PartitionService;
28 import org.thingsboard.server.common.msg.queue.ServiceType; 27 import org.thingsboard.server.common.msg.queue.ServiceType;
29 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 28 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId;
22 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
23 import org.thingsboard.server.common.data.kv.TsKvEntry; 23 import org.thingsboard.server.common.data.kv.TsKvEntry;
24 import org.thingsboard.server.common.msg.queue.TbCallback; 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 import java.util.List; 27 import java.util.List;
28 28
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 */ 15 */
16 package org.thingsboard.server.service.subscription; 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 import org.thingsboard.server.common.msg.queue.TbCallback; 20 import org.thingsboard.server.common.msg.queue.TbCallback;
21 import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; 21 import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
22 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; 22 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@@ -22,35 +22,18 @@ import lombok.extern.slf4j.Slf4j; @@ -22,35 +22,18 @@ import lombok.extern.slf4j.Slf4j;
22 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.context.ApplicationListener; 23 import org.springframework.context.ApplicationListener;
24 import org.springframework.context.event.EventListener; 24 import org.springframework.context.event.EventListener;
25 -import org.springframework.stereotype.Service;  
26 import org.thingsboard.common.util.ThingsBoardThreadFactory; 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 import org.thingsboard.server.common.msg.queue.ServiceType; 26 import org.thingsboard.server.common.msg.queue.ServiceType;
37 -import org.thingsboard.server.common.msg.queue.TbCallback;  
38 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 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 import org.thingsboard.server.queue.discovery.PartitionService; 29 import org.thingsboard.server.queue.discovery.PartitionService;
44 import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 30 import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
45 import org.thingsboard.server.service.queue.TbClusterService; 31 import org.thingsboard.server.service.queue.TbClusterService;
46 import org.thingsboard.server.service.subscription.SubscriptionManagerService; 32 import org.thingsboard.server.service.subscription.SubscriptionManagerService;
47 -import org.thingsboard.server.service.subscription.TbSubscriptionUtils;  
48 33
49 import javax.annotation.Nullable; 34 import javax.annotation.Nullable;
50 import javax.annotation.PostConstruct; 35 import javax.annotation.PostConstruct;
51 import javax.annotation.PreDestroy; 36 import javax.annotation.PreDestroy;
52 -import java.util.Collections;  
53 -import java.util.List;  
54 import java.util.Optional; 37 import java.util.Optional;
55 import java.util.Set; 38 import java.util.Set;
56 import java.util.concurrent.ConcurrentHashMap; 39 import java.util.concurrent.ConcurrentHashMap;
@@ -17,8 +17,7 @@ package org.thingsboard.server.service.telemetry; @@ -17,8 +17,7 @@ package org.thingsboard.server.service.telemetry;
17 17
18 import org.springframework.context.ApplicationListener; 18 import org.springframework.context.ApplicationListener;
19 import org.thingsboard.rule.engine.api.RuleEngineAlarmService; 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 * Created by ashvayka on 27.03.18. 23 * Created by ashvayka on 27.03.18.
@@ -16,8 +16,7 @@ @@ -16,8 +16,7 @@
16 package org.thingsboard.server.service.telemetry; 16 package org.thingsboard.server.service.telemetry;
17 17
18 import org.springframework.context.ApplicationListener; 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 * Created by ashvayka on 27.03.18. 22 * Created by ashvayka on 27.03.18.
@@ -23,17 +23,18 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -23,17 +23,18 @@ import com.google.common.util.concurrent.ListenableFuture;
23 import com.google.common.util.concurrent.MoreExecutors; 23 import com.google.common.util.concurrent.MoreExecutors;
24 import com.google.protobuf.ByteString; 24 import com.google.protobuf.ByteString;
25 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
26 -import org.springframework.cache.CacheManager;  
27 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
28 import org.springframework.util.StringUtils; 27 import org.springframework.util.StringUtils;
29 import org.thingsboard.common.util.JacksonUtil; 28 import org.thingsboard.common.util.JacksonUtil;
30 -import org.thingsboard.server.cache.firmware.FirmwareCacheWriter; 29 +import org.thingsboard.server.cache.firmware.FirmwareDataCache;
31 import org.thingsboard.server.common.data.ApiUsageState; 30 import org.thingsboard.server.common.data.ApiUsageState;
32 import org.thingsboard.server.common.data.DataConstants; 31 import org.thingsboard.server.common.data.DataConstants;
33 import org.thingsboard.server.common.data.Device; 32 import org.thingsboard.server.common.data.Device;
34 import org.thingsboard.server.common.data.DeviceProfile; 33 import org.thingsboard.server.common.data.DeviceProfile;
  34 +import org.thingsboard.server.common.data.DeviceTransportType;
35 import org.thingsboard.server.common.data.EntityType; 35 import org.thingsboard.server.common.data.EntityType;
36 import org.thingsboard.server.common.data.Firmware; 36 import org.thingsboard.server.common.data.Firmware;
  37 +import org.thingsboard.server.common.data.FirmwareInfo;
37 import org.thingsboard.server.common.data.ResourceType; 38 import org.thingsboard.server.common.data.ResourceType;
38 import org.thingsboard.server.common.data.TbResource; 39 import org.thingsboard.server.common.data.TbResource;
39 import org.thingsboard.server.common.data.TenantProfile; 40 import org.thingsboard.server.common.data.TenantProfile;
@@ -45,6 +46,8 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -45,6 +46,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
45 import org.thingsboard.server.common.data.id.DeviceProfileId; 46 import org.thingsboard.server.common.data.id.DeviceProfileId;
46 import org.thingsboard.server.common.data.id.FirmwareId; 47 import org.thingsboard.server.common.data.id.FirmwareId;
47 import org.thingsboard.server.common.data.id.TenantId; 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 import org.thingsboard.server.common.data.relation.EntityRelation; 51 import org.thingsboard.server.common.data.relation.EntityRelation;
49 import org.thingsboard.server.common.data.security.DeviceCredentials; 52 import org.thingsboard.server.common.data.security.DeviceCredentials;
50 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 53 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@@ -61,15 +64,18 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest; @@ -61,15 +64,18 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest;
61 import org.thingsboard.server.dao.device.provision.ProvisionResponse; 64 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
62 import org.thingsboard.server.dao.firmware.FirmwareService; 65 import org.thingsboard.server.dao.firmware.FirmwareService;
63 import org.thingsboard.server.dao.relation.RelationService; 66 import org.thingsboard.server.dao.relation.RelationService;
64 -import org.thingsboard.server.dao.resource.ResourceService;  
65 import org.thingsboard.server.dao.tenant.TbTenantProfileCache; 67 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
66 import org.thingsboard.server.gen.transport.TransportProtos; 68 import org.thingsboard.server.gen.transport.TransportProtos;
67 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; 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 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; 72 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
69 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; 73 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
70 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; 74 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
71 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; 75 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
72 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; 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 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; 79 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
74 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; 80 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
75 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; 81 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
@@ -92,6 +98,7 @@ import java.util.concurrent.ConcurrentHashMap; @@ -92,6 +98,7 @@ import java.util.concurrent.ConcurrentHashMap;
92 import java.util.concurrent.ConcurrentMap; 98 import java.util.concurrent.ConcurrentMap;
93 import java.util.concurrent.locks.Lock; 99 import java.util.concurrent.locks.Lock;
94 import java.util.concurrent.locks.ReentrantLock; 100 import java.util.concurrent.locks.ReentrantLock;
  101 +import java.util.stream.Collectors;
95 102
96 /** 103 /**
97 * Created by ashvayka on 05.10.18. 104 * Created by ashvayka on 05.10.18.
@@ -116,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -116,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService {
116 private final DeviceProvisionService deviceProvisionService; 123 private final DeviceProvisionService deviceProvisionService;
117 private final TbResourceService resourceService; 124 private final TbResourceService resourceService;
118 private final FirmwareService firmwareService; 125 private final FirmwareService firmwareService;
119 - private final FirmwareCacheWriter firmwareCacheWriter; 126 + private final FirmwareDataCache firmwareDataCache;
120 127
121 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); 128 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
122 129
@@ -125,7 +132,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -125,7 +132,7 @@ public class DefaultTransportApiService implements TransportApiService {
125 RelationService relationService, DeviceCredentialsService deviceCredentialsService, 132 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
126 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, 133 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
127 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, 134 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
128 - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareCacheWriter firmwareCacheWriter) { 135 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) {
129 this.deviceProfileCache = deviceProfileCache; 136 this.deviceProfileCache = deviceProfileCache;
130 this.tenantProfileCache = tenantProfileCache; 137 this.tenantProfileCache = tenantProfileCache;
131 this.apiUsageStateService = apiUsageStateService; 138 this.apiUsageStateService = apiUsageStateService;
@@ -139,49 +146,49 @@ public class DefaultTransportApiService implements TransportApiService { @@ -139,49 +146,49 @@ public class DefaultTransportApiService implements TransportApiService {
139 this.deviceProvisionService = deviceProvisionService; 146 this.deviceProvisionService = deviceProvisionService;
140 this.resourceService = resourceService; 147 this.resourceService = resourceService;
141 this.firmwareService = firmwareService; 148 this.firmwareService = firmwareService;
142 - this.firmwareCacheWriter = firmwareCacheWriter; 149 + this.firmwareDataCache = firmwareDataCache;
143 } 150 }
144 151
145 @Override 152 @Override
146 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) { 153 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
147 TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue(); 154 TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue();
  155 + ListenableFuture<TransportApiResponseMsg> result = null;
  156 +
148 if (transportApiRequestMsg.hasValidateTokenRequestMsg()) { 157 if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
149 ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); 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 } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) { 160 } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) {
153 TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg(); 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 } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { 163 } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
157 ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); 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 } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { 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 } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { 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 } else if (transportApiRequestMsg.hasLwM2MRequestMsg()) { 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 } else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) { 172 } else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) {
170 ValidateDeviceLwM2MCredentialsRequestMsg msg = transportApiRequestMsg.getValidateDeviceLwM2MCredentialsRequestMsg(); 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 } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { 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 } else if (transportApiRequestMsg.hasResourceRequestMsg()) { 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 } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) { 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 private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) { 194 private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
@@ -375,6 +382,39 @@ public class DefaultTransportApiService implements TransportApiService { @@ -375,6 +382,39 @@ public class DefaultTransportApiService implements TransportApiService {
375 return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build()); 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 private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) { 418 private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
379 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); 419 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
380 ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType()); 420 ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
@@ -393,6 +433,22 @@ public class DefaultTransportApiService implements TransportApiService { @@ -393,6 +433,22 @@ public class DefaultTransportApiService implements TransportApiService {
393 return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build()); 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 private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { 452 private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
397 return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { 453 return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
398 if (device == null) { 454 if (device == null) {
@@ -473,19 +529,22 @@ public class DefaultTransportApiService implements TransportApiService { @@ -473,19 +529,22 @@ public class DefaultTransportApiService implements TransportApiService {
473 if (firmwareId == null) { 529 if (firmwareId == null) {
474 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); 530 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
475 } else { 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 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); 535 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
480 } else { 536 } else {
481 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); 537 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
482 builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()); 538 builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());
483 builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()); 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,6 +26,7 @@
26 </appender> 26 </appender>
27 27
28 <logger name="org.thingsboard.server" level="INFO" /> 28 <logger name="org.thingsboard.server" level="INFO" />
  29 + <logger name="org.thingsboard.server.transport.snmp" level="TRACE" />
29 30
30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> 31 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> 32 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
@@ -372,8 +372,8 @@ caffeine: @@ -372,8 +372,8 @@ caffeine:
372 timeToLiveInMinutes: 20000 372 timeToLiveInMinutes: 20000
373 maxSize: 10000 373 maxSize: 10000
374 firmwares: 374 firmwares:
375 - timeToLiveInMinutes: 1440  
376 - maxSize: 100 375 + timeToLiveInMinutes: 60
  376 + maxSize: 10
377 edges: 377 edges:
378 timeToLiveInMinutes: 1440 378 timeToLiveInMinutes: 1440
379 maxSize: 0 379 maxSize: 0
@@ -496,6 +496,8 @@ audit-log: @@ -496,6 +496,8 @@ audit-log:
496 "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" 496 "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
497 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" 497 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
498 "edge": "${AUDIT_LOG_MASK_EDGE:W}" 498 "edge": "${AUDIT_LOG_MASK_EDGE:W}"
  499 + "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}"
  500 + "firmware": "${AUDIT_LOG_MASK_FIRMWARE:W}"
499 sink: 501 sink:
500 # Type of external sink. possible options: none, elasticsearch 502 # Type of external sink. possible options: none, elasticsearch
501 type: "${AUDIT_LOG_SINK_TYPE:none}" 503 type: "${AUDIT_LOG_SINK_TYPE:none}"
@@ -619,9 +621,9 @@ transport: @@ -619,9 +621,9 @@ transport:
619 key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" 621 key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
620 # Key alias 622 # Key alias
621 key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" 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 x509: 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 dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}" 627 dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
626 dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}" 628 dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
627 # Local LwM2M transport parameters 629 # Local LwM2M transport parameters
@@ -684,6 +686,13 @@ transport: @@ -684,6 +686,13 @@ transport:
684 alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}" 686 alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
685 # Use redis for Security and Registration stores 687 # Use redis for Security and Registration stores
686 redis.enabled: "${LWM2M_REDIS_ENABLED:false}" 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 # Edges parameters 697 # Edges parameters
689 edges: 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,22 +15,20 @@
15 */ 15 */
16 package org.thingsboard.server.cache.firmware; 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 import org.springframework.cache.CacheManager; 20 import org.springframework.cache.CacheManager;
20 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
21 22
22 import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; 23 import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
23 24
24 @Service 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 private final CacheManager cacheManager; 30 private final CacheManager cacheManager;
29 31
30 - public CaffeineFirmwareCacheReader(CacheManager cacheManager) {  
31 - this.cacheManager = cacheManager;  
32 - }  
33 -  
34 @Override 32 @Override
35 public byte[] get(String key) { 33 public byte[] get(String key) {
36 return get(key, 0, 0); 34 return get(key, 0, 0);
@@ -57,4 +55,14 @@ public class CaffeineFirmwareCacheReader implements FirmwareCacheReader { @@ -57,4 +55,14 @@ public class CaffeineFirmwareCacheReader implements FirmwareCacheReader {
57 } 55 }
58 return new byte[0]; 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,8 +15,18 @@
15 */ 15 */
16 package org.thingsboard.server.cache.firmware; 16 package org.thingsboard.server.cache.firmware;
17 17
18 -public interface FirmwareCacheReader { 18 +public interface FirmwareDataCache {
  19 +
19 byte[] get(String key); 20 byte[] get(String key);
20 21
21 byte[] get(String key, int chunkSize, int chunk); 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,18 +15,20 @@
15 */ 15 */
16 package org.thingsboard.server.cache.firmware; 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 import org.springframework.data.redis.connection.RedisConnection; 20 import org.springframework.data.redis.connection.RedisConnection;
20 import org.springframework.data.redis.connection.RedisConnectionFactory; 21 import org.springframework.data.redis.connection.RedisConnectionFactory;
21 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
22 23
  24 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  25 +
23 @Service 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 @Override 33 @Override
32 public byte[] get(String key) { 34 public byte[] get(String key) {
@@ -46,4 +48,21 @@ public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache impleme @@ -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,11 +19,10 @@ import lombok.Getter;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.beans.factory.annotation.Autowired; 20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.beans.factory.annotation.Value; 21 import org.springframework.beans.factory.annotation.Value;
22 -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;  
23 import org.springframework.stereotype.Component; 22 import org.springframework.stereotype.Component;
24 23
25 @Slf4j 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 @Component 26 @Component
28 public class CoapServerContext { 27 public class CoapServerContext {
29 28
@@ -23,7 +23,6 @@ import org.eclipse.californium.core.server.resources.Resource; @@ -23,7 +23,6 @@ import org.eclipse.californium.core.server.resources.Resource;
23 import org.eclipse.californium.scandium.DTLSConnector; 23 import org.eclipse.californium.scandium.DTLSConnector;
24 import org.eclipse.californium.scandium.config.DtlsConnectorConfig; 24 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
25 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
26 -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;  
27 import org.springframework.stereotype.Component; 26 import org.springframework.stereotype.Component;
28 27
29 import javax.annotation.PostConstruct; 28 import javax.annotation.PostConstruct;
@@ -39,7 +38,7 @@ import java.util.concurrent.TimeUnit; @@ -39,7 +38,7 @@ import java.util.concurrent.TimeUnit;
39 38
40 @Slf4j 39 @Slf4j
41 @Component 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 public class DefaultCoapServerService implements CoapServerService { 42 public class DefaultCoapServerService implements CoapServerService {
44 43
45 @Autowired 44 @Autowired
@@ -39,7 +39,6 @@ import java.util.Collections; @@ -39,7 +39,6 @@ import java.util.Collections;
39 import java.util.Optional; 39 import java.util.Optional;
40 40
41 @Slf4j 41 @Slf4j
42 -@ConditionalOnExpression("'${transport.coap.enabled}'=='true'")  
43 @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) 42 @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
44 @Component 43 @Component
45 public class TbCoapDtlsSettings { 44 public class TbCoapDtlsSettings {
@@ -50,7 +49,7 @@ public class TbCoapDtlsSettings { @@ -50,7 +49,7 @@ public class TbCoapDtlsSettings {
50 @Value("${transport.coap.dtls.bind_port}") 49 @Value("${transport.coap.dtls.bind_port}")
51 private Integer port; 50 private Integer port;
52 51
53 - @Value("${transport.coap.dtls.mode}") 52 + @Value("${transport.coap.dtls.mode:NO_AUTH}")
54 private String mode; 53 private String mode;
55 54
56 @Value("${transport.coap.dtls.key_store}") 55 @Value("${transport.coap.dtls.key_store}")
@@ -65,13 +64,13 @@ public class TbCoapDtlsSettings { @@ -65,13 +64,13 @@ public class TbCoapDtlsSettings {
65 @Value("${transport.coap.dtls.key_alias}") 64 @Value("${transport.coap.dtls.key_alias}")
66 private String keyAlias; 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 private boolean skipValidityCheckForClientCert; 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 private long dtlsSessionInactivityTimeout; 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 private long dtlsSessionReportTimeout; 74 private long dtlsSessionReportTimeout;
76 75
77 @Autowired 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,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
20 import org.thingsboard.server.common.data.DeviceInfo; 20 import org.thingsboard.server.common.data.DeviceInfo;
21 import org.thingsboard.server.common.data.DeviceProfile; 21 import org.thingsboard.server.common.data.DeviceProfile;
  22 +import org.thingsboard.server.common.data.DeviceTransportType;
22 import org.thingsboard.server.common.data.EntitySubtype; 23 import org.thingsboard.server.common.data.EntitySubtype;
23 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 24 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
24 import org.thingsboard.server.common.data.id.CustomerId; 25 import org.thingsboard.server.common.data.id.CustomerId;
@@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
32 import org.thingsboard.server.dao.device.provision.ProvisionRequest; 33 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
33 34
34 import java.util.List; 35 import java.util.List;
  36 +import java.util.UUID;
35 37
36 public interface DeviceService { 38 public interface DeviceService {
37 39
@@ -93,6 +95,8 @@ public interface DeviceService { @@ -93,6 +95,8 @@ public interface DeviceService {
93 95
94 Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); 96 Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile);
95 97
  98 + PageData<UUID> findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink);
  99 +
96 Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); 100 Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
97 101
98 Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); 102 Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.firmware; 16 package org.thingsboard.server.dao.firmware;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.Firmware; 19 import org.thingsboard.server.common.data.Firmware;
19 import org.thingsboard.server.common.data.FirmwareInfo; 20 import org.thingsboard.server.common.data.FirmwareInfo;
20 import org.thingsboard.server.common.data.exception.ThingsboardException; 21 import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -40,6 +41,8 @@ public interface FirmwareService { @@ -40,6 +41,8 @@ public interface FirmwareService {
40 41
41 FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); 42 FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId);
42 43
  44 + ListenableFuture<FirmwareInfo> findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId);
  45 +
43 PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); 46 PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink);
44 47
45 PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); 48 PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.resource; 16 package org.thingsboard.server.dao.resource;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.ResourceType; 19 import org.thingsboard.server.common.data.ResourceType;
19 import org.thingsboard.server.common.data.TbResource; 20 import org.thingsboard.server.common.data.TbResource;
20 import org.thingsboard.server.common.data.TbResourceInfo; 21 import org.thingsboard.server.common.data.TbResourceInfo;
@@ -34,6 +35,8 @@ public interface ResourceService { @@ -34,6 +35,8 @@ public interface ResourceService {
34 35
35 TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); 36 TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
36 37
  38 + ListenableFuture<TbResourceInfo> findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId);
  39 +
37 PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); 40 PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
38 41
39 PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); 42 PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
@@ -87,6 +87,10 @@ @@ -87,6 +87,10 @@
87 <groupId>org.thingsboard</groupId> 87 <groupId>org.thingsboard</groupId>
88 <artifactId>protobuf-dynamic</artifactId> 88 <artifactId>protobuf-dynamic</artifactId>
89 </dependency> 89 </dependency>
  90 + <dependency>
  91 + <groupId>org.apache.commons</groupId>
  92 + <artifactId>commons-lang3</artifactId>
  93 + </dependency>
90 </dependencies> 94 </dependencies>
91 95
92 <build> 96 <build>
@@ -99,6 +99,7 @@ public class DataConstants { @@ -99,6 +99,7 @@ public class DataConstants {
99 public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; 99 public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version";
100 public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; 100 public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
101 public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; 101 public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
  102 + public static final String TARGET_FIRMWARE_TS = "target_fw_ts";
102 public static final String FIRMWARE_STATE = "fw_state"; 103 public static final String FIRMWARE_STATE = "fw_state";
103 104
104 //attributes 105 //attributes
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
18 public enum DeviceTransportType { 18 public enum DeviceTransportType {
19 DEFAULT, 19 DEFAULT,
20 MQTT, 20 MQTT,
  21 + COAP,
21 LWM2M, 22 LWM2M,
22 - COAP 23 + SNMP
23 } 24 }
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.common.data; 16 package org.thingsboard.server.common.data;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 import lombok.Data; 19 import lombok.Data;
19 import lombok.EqualsAndHashCode; 20 import lombok.EqualsAndHashCode;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
@@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
25 @Slf4j 26 @Slf4j
26 @Data 27 @Data
27 @EqualsAndHashCode(callSuper = true) 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 private static final long serialVersionUID = 3168391583570815419L; 31 private static final long serialVersionUID = 3168391583570815419L;
31 32
@@ -65,4 +66,10 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId> @@ -65,4 +66,10 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId>
65 public String getSearchText() { 66 public String getSearchText() {
66 return title; 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,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.common.data; 16 package org.thingsboard.server.common.data;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 import lombok.Data; 19 import lombok.Data;
19 import lombok.EqualsAndHashCode; 20 import lombok.EqualsAndHashCode;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
@@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
25 @Slf4j 26 @Slf4j
26 @Data 27 @Data
27 @EqualsAndHashCode(callSuper = true) 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 private static final long serialVersionUID = 7282664529021651736L; 31 private static final long serialVersionUID = 7282664529021651736L;
31 32
@@ -54,6 +55,12 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has @@ -54,6 +55,12 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
54 } 55 }
55 56
56 @Override 57 @Override
  58 + @JsonIgnore
  59 + public String getName() {
  60 + return title;
  61 + }
  62 +
  63 + @Override
57 public String getSearchText() { 64 public String getSearchText() {
58 return searchText != null ? searchText : title; 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,8 +13,8 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.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,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
21 import com.fasterxml.jackson.annotation.JsonTypeInfo; 21 import com.fasterxml.jackson.annotation.JsonTypeInfo;
22 import org.thingsboard.server.common.data.DeviceTransportType; 22 import org.thingsboard.server.common.data.DeviceTransportType;
23 23
  24 +import java.io.Serializable;
  25 +
24 @JsonIgnoreProperties(ignoreUnknown = true) 26 @JsonIgnoreProperties(ignoreUnknown = true)
25 @JsonTypeInfo( 27 @JsonTypeInfo(
26 use = JsonTypeInfo.Id.NAME, 28 use = JsonTypeInfo.Id.NAME,
@@ -29,11 +31,14 @@ import org.thingsboard.server.common.data.DeviceTransportType; @@ -29,11 +31,14 @@ import org.thingsboard.server.common.data.DeviceTransportType;
29 @JsonSubTypes({ 31 @JsonSubTypes({
30 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), 32 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
31 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), 33 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
  34 + @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
32 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), 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 @JsonIgnore 38 @JsonIgnore
37 DeviceTransportType getType(); 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,13 +29,18 @@ import java.io.Serializable;
29 include = JsonTypeInfo.As.PROPERTY, 29 include = JsonTypeInfo.As.PROPERTY,
30 property = "type") 30 property = "type")
31 @JsonSubTypes({ 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 public interface DeviceProfileTransportConfiguration extends Serializable { 38 public interface DeviceProfileTransportConfiguration extends Serializable {
37 39
38 @JsonIgnore 40 @JsonIgnore
39 DeviceTransportType getType(); 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,26 +13,16 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.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 @Override 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,26 +13,14 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.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 @Override 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,5 +16,5 @@
16 package org.thingsboard.server.common.msg.session; 16 package org.thingsboard.server.common.msg.session;
17 17
18 public enum FeatureType { 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,7 +30,9 @@ public enum SessionMsgType {
30 30
31 SESSION_OPEN, SESSION_CLOSE, 31 SESSION_OPEN, SESSION_CLOSE,
32 32
33 - CLAIM_REQUEST(); 33 + CLAIM_REQUEST(),
  34 +
  35 + GET_FIRMWARE_REQUEST;
34 36
35 private final boolean requiresRulesProcessing; 37 private final boolean requiresRulesProcessing;
36 38
@@ -19,19 +19,23 @@ import lombok.Getter; @@ -19,19 +19,23 @@ import lombok.Getter;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.beans.factory.annotation.Autowired; 20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.beans.factory.annotation.Value; 21 import org.springframework.beans.factory.annotation.Value;
  22 +import org.springframework.context.ApplicationContext;
22 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
23 import org.springframework.util.StringUtils; 24 import org.springframework.util.StringUtils;
  25 +import org.thingsboard.server.common.data.TbTransportService;
24 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.common.msg.queue.ServiceType; 27 import org.thingsboard.server.common.msg.queue.ServiceType;
26 import org.thingsboard.server.gen.transport.TransportProtos; 28 import org.thingsboard.server.gen.transport.TransportProtos;
27 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; 29 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
28 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; 30 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
29 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; 31 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
  32 +import org.thingsboard.server.queue.util.AfterContextReady;
30 33
31 import javax.annotation.PostConstruct; 34 import javax.annotation.PostConstruct;
32 import java.net.InetAddress; 35 import java.net.InetAddress;
33 import java.net.UnknownHostException; 36 import java.net.UnknownHostException;
34 import java.util.Arrays; 37 import java.util.Arrays;
  38 +import java.util.Collection;
35 import java.util.Collections; 39 import java.util.Collections;
36 import java.util.List; 40 import java.util.List;
37 import java.util.Optional; 41 import java.util.Optional;
@@ -56,6 +60,8 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { @@ -56,6 +60,8 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
56 60
57 @Autowired(required = false) 61 @Autowired(required = false)
58 private TbQueueRuleEngineSettings ruleEngineSettings; 62 private TbQueueRuleEngineSettings ruleEngineSettings;
  63 + @Autowired
  64 + private ApplicationContext applicationContext;
59 65
60 private List<ServiceType> serviceTypes; 66 private List<ServiceType> serviceTypes;
61 private ServiceInfo serviceInfo; 67 private ServiceInfo serviceInfo;
@@ -102,6 +108,19 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { @@ -102,6 +108,19 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
102 serviceInfo = builder.build(); 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 @Override 124 @Override
106 public ServiceInfo getServiceInfo() { 125 public ServiceInfo getServiceInfo() {
107 return serviceInfo; 126 return serviceInfo;
@@ -15,26 +15,27 @@ @@ -15,26 +15,27 @@
15 */ 15 */
16 package org.thingsboard.server.queue.discovery; 16 package org.thingsboard.server.queue.discovery;
17 17
18 -import com.google.common.hash.HashCode;  
19 import com.google.common.hash.HashFunction; 18 import com.google.common.hash.HashFunction;
  19 +import com.google.common.hash.Hasher;
20 import com.google.common.hash.Hashing; 20 import com.google.common.hash.Hashing;
21 -import lombok.Getter;  
22 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Value; 22 import org.springframework.beans.factory.annotation.Value;
24 import org.springframework.context.ApplicationEventPublisher; 23 import org.springframework.context.ApplicationEventPublisher;
25 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
26 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
27 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
28 -import org.thingsboard.server.common.msg.queue.ServiceQueueKey;  
29 import org.thingsboard.server.common.msg.queue.ServiceQueue; 27 import org.thingsboard.server.common.msg.queue.ServiceQueue;
  28 +import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
30 import org.thingsboard.server.common.msg.queue.ServiceType; 29 import org.thingsboard.server.common.msg.queue.ServiceType;
31 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 30 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
32 import org.thingsboard.server.gen.transport.TransportProtos; 31 import org.thingsboard.server.gen.transport.TransportProtos;
33 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; 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 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; 36 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
35 37
36 import javax.annotation.PostConstruct; 38 import javax.annotation.PostConstruct;
37 -import java.nio.charset.StandardCharsets;  
38 import java.util.ArrayList; 39 import java.util.ArrayList;
39 import java.util.Collections; 40 import java.util.Collections;
40 import java.util.Comparator; 41 import java.util.Comparator;
@@ -46,7 +47,6 @@ import java.util.Set; @@ -46,7 +47,6 @@ import java.util.Set;
46 import java.util.UUID; 47 import java.util.UUID;
47 import java.util.concurrent.ConcurrentHashMap; 48 import java.util.concurrent.ConcurrentHashMap;
48 import java.util.concurrent.ConcurrentMap; 49 import java.util.concurrent.ConcurrentMap;
49 -import java.util.concurrent.ConcurrentNavigableMap;  
50 import java.util.stream.Collectors; 50 import java.util.stream.Collectors;
51 51
52 @Service 52 @Service
@@ -186,6 +186,8 @@ public class HashPartitionService implements PartitionService { @@ -186,6 +186,8 @@ public class HashPartitionService implements PartitionService {
186 applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes)); 186 applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes));
187 } 187 }
188 } 188 }
  189 +
  190 + applicationEventPublisher.publishEvent(new ServiceListChangedEvent(otherServices, currentService));
189 } 191 }
190 192
191 @Override 193 @Override
@@ -219,6 +221,14 @@ public class HashPartitionService implements PartitionService { @@ -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 private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) { 232 private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) {
223 final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>(); 233 final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
224 services.forEach(serviceInfo -> { 234 services.forEach(serviceInfo -> {
@@ -20,9 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -20,9 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
20 import org.thingsboard.server.common.msg.queue.ServiceType; 20 import org.thingsboard.server.common.msg.queue.ServiceType;
21 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 21 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
22 import org.thingsboard.server.gen.transport.TransportProtos; 22 import org.thingsboard.server.gen.transport.TransportProtos;
  23 +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
23 24
24 import java.util.List; 25 import java.util.List;
25 import java.util.Set; 26 import java.util.Set;
  27 +import java.util.UUID;
26 28
27 /** 29 /**
28 * Once application is ready or cluster topology changes, this Service will produce {@link PartitionChangeEvent} 30 * Once application is ready or cluster topology changes, this Service will produce {@link PartitionChangeEvent}
@@ -55,4 +57,6 @@ public interface PartitionService { @@ -55,4 +57,6 @@ public interface PartitionService {
55 * @return 57 * @return
56 */ 58 */
57 TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId); 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,6 +17,7 @@ package org.thingsboard.server.queue.discovery;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.context.ApplicationListener; 19 import org.springframework.context.ApplicationListener;
  20 +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent;
20 21
21 import java.util.concurrent.locks.Lock; 22 import java.util.concurrent.locks.Lock;
22 import java.util.concurrent.locks.ReentrantLock; 23 import java.util.concurrent.locks.ReentrantLock;
@@ -33,12 +33,14 @@ import org.apache.zookeeper.KeeperException; @@ -33,12 +33,14 @@ import org.apache.zookeeper.KeeperException;
33 import org.springframework.beans.factory.annotation.Value; 33 import org.springframework.beans.factory.annotation.Value;
34 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 34 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35 import org.springframework.boot.context.event.ApplicationReadyEvent; 35 import org.springframework.boot.context.event.ApplicationReadyEvent;
  36 +import org.springframework.context.ApplicationEventPublisher;
36 import org.springframework.context.event.EventListener; 37 import org.springframework.context.event.EventListener;
37 import org.springframework.core.annotation.Order; 38 import org.springframework.core.annotation.Order;
38 import org.springframework.stereotype.Service; 39 import org.springframework.stereotype.Service;
39 import org.springframework.util.Assert; 40 import org.springframework.util.Assert;
40 import org.thingsboard.common.util.ThingsBoardThreadFactory; 41 import org.thingsboard.common.util.ThingsBoardThreadFactory;
41 import org.thingsboard.server.gen.transport.TransportProtos; 42 import org.thingsboard.server.gen.transport.TransportProtos;
  43 +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
42 44
43 import javax.annotation.PostConstruct; 45 import javax.annotation.PostConstruct;
44 import javax.annotation.PreDestroy; 46 import javax.annotation.PreDestroy;
@@ -77,7 +79,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @@ -77,7 +79,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
77 79
78 private volatile boolean stopped = true; 80 private volatile boolean stopped = true;
79 81
80 - public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) { 82 + public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider,
  83 + PartitionService partitionService) {
81 this.serviceInfoProvider = serviceInfoProvider; 84 this.serviceInfoProvider = serviceInfoProvider;
82 this.partitionService = partitionService; 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,10 +13,9 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.queue.discovery; 16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
19 -import org.springframework.context.ApplicationEvent;  
20 import org.thingsboard.server.common.msg.queue.ServiceQueueKey; 19 import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
21 20
22 import java.util.Set; 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,10 +13,9 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.queue.discovery; 16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
19 -import org.springframework.context.ApplicationEvent;  
20 import org.thingsboard.server.common.msg.queue.ServiceQueueKey; 19 import org.thingsboard.server.common.msg.queue.ServiceQueueKey;
21 import org.thingsboard.server.common.msg.queue.ServiceType; 20 import org.thingsboard.server.common.msg.queue.ServiceType;
22 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 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,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.queue.discovery; 16 +package org.thingsboard.server.queue.discovery.event;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
19 import org.springframework.context.ApplicationEvent; 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,6 +34,7 @@ message ServiceInfo {
34 int64 tenantIdMSB = 3; 34 int64 tenantIdMSB = 3;
35 int64 tenantIdLSB = 4; 35 int64 tenantIdLSB = 4;
36 repeated QueueInfo ruleEngineQueues = 5; 36 repeated QueueInfo ruleEngineQueues = 5;
  37 + repeated string transports = 6;
37 } 38 }
38 39
39 /** 40 /**
@@ -246,6 +247,36 @@ message GetEntityProfileResponseMsg { @@ -246,6 +247,36 @@ message GetEntityProfileResponseMsg {
246 bytes apiState = 3; 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 message EntityUpdateMsg { 280 message EntityUpdateMsg {
250 string entityType = 1; 281 string entityType = 1;
251 bytes data = 2; 282 bytes data = 2;
@@ -590,6 +621,9 @@ message TransportApiRequestMsg { @@ -590,6 +621,9 @@ message TransportApiRequestMsg {
590 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; 621 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
591 GetResourceRequestMsg resourceRequestMsg = 9; 622 GetResourceRequestMsg resourceRequestMsg = 9;
592 GetFirmwareRequestMsg firmwareRequestMsg = 10; 623 GetFirmwareRequestMsg firmwareRequestMsg = 10;
  624 + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11;
  625 + GetDeviceRequestMsg deviceRequestMsg = 12;
  626 + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13;
593 } 627 }
594 628
595 /* Response from ThingsBoard Core Service to Transport Service */ 629 /* Response from ThingsBoard Core Service to Transport Service */
@@ -598,9 +632,12 @@ message TransportApiResponseMsg { @@ -598,9 +632,12 @@ message TransportApiResponseMsg {
598 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; 632 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
599 GetEntityProfileResponseMsg entityProfileResponseMsg = 3; 633 GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
600 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; 634 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
  635 + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5;
601 LwM2MResponseMsg lwM2MResponseMsg = 6; 636 LwM2MResponseMsg lwM2MResponseMsg = 6;
602 GetResourceResponseMsg resourceResponseMsg = 7; 637 GetResourceResponseMsg resourceResponseMsg = 7;
603 GetFirmwareResponseMsg firmwareResponseMsg = 8; 638 GetFirmwareResponseMsg firmwareResponseMsg = 8;
  639 + GetDeviceResponseMsg deviceResponseMsg = 9;
  640 + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10;
604 } 641 }
605 642
606 /* Messages that are handled by ThingsBoard Core Service */ 643 /* Messages that are handled by ThingsBoard Core Service */
@@ -28,12 +28,12 @@ import org.eclipse.californium.core.observe.ObserveRelation; @@ -28,12 +28,12 @@ import org.eclipse.californium.core.observe.ObserveRelation;
28 import org.eclipse.californium.core.server.resources.CoapExchange; 28 import org.eclipse.californium.core.server.resources.CoapExchange;
29 import org.eclipse.californium.core.server.resources.Resource; 29 import org.eclipse.californium.core.server.resources.Resource;
30 import org.eclipse.californium.core.server.resources.ResourceObserver; 30 import org.eclipse.californium.core.server.resources.ResourceObserver;
31 -import org.springframework.util.StringUtils;  
32 import org.thingsboard.server.coapserver.CoapServerService; 31 import org.thingsboard.server.coapserver.CoapServerService;
33 import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; 32 import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo;
34 import org.thingsboard.server.common.data.DataConstants; 33 import org.thingsboard.server.common.data.DataConstants;
35 import org.thingsboard.server.common.data.DeviceProfile; 34 import org.thingsboard.server.common.data.DeviceProfile;
36 import org.thingsboard.server.common.data.DeviceTransportType; 35 import org.thingsboard.server.common.data.DeviceTransportType;
  36 +import org.thingsboard.server.common.data.StringUtils;
37 import org.thingsboard.server.common.data.TransportPayloadType; 37 import org.thingsboard.server.common.data.TransportPayloadType;
38 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; 38 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
39 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; 39 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
@@ -120,6 +120,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -120,6 +120,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
120 processExchangeGetRequest(exchange, featureType.get()); 120 processExchangeGetRequest(exchange, featureType.get());
121 } else if (featureType.get() == FeatureType.ATTRIBUTES) { 121 } else if (featureType.get() == FeatureType.ATTRIBUTES) {
122 processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); 122 processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST);
  123 + } else if (featureType.get() == FeatureType.FIRMWARE) {
  124 + processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST);
123 } else { 125 } else {
124 log.trace("Invalid feature type parameter"); 126 log.trace("Invalid feature type parameter");
125 exchange.respond(CoAP.ResponseCode.BAD_REQUEST); 127 exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
@@ -201,7 +203,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -201,7 +203,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
201 Request request = advanced.getRequest(); 203 Request request = advanced.getRequest();
202 204
203 String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY); 205 String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
204 - if (!StringUtils.isEmpty(dtlsSessionIdStr)) { 206 + if (StringUtils.isNotEmpty(dtlsSessionIdStr)) {
205 if (dtlsSessionIdMap != null) { 207 if (dtlsSessionIdMap != null) {
206 TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap 208 TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
207 .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> { 209 .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
@@ -323,6 +325,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -323,6 +325,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
323 coapTransportAdaptor.convertToGetAttributes(sessionId, request), 325 coapTransportAdaptor.convertToGetAttributes(sessionId, request),
324 new CoapNoOpCallback(exchange)); 326 new CoapNoOpCallback(exchange));
325 break; 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 } catch (AdaptorException e) { 337 } catch (AdaptorException e) {
328 log.trace("[{}] Failed to decode message: ", sessionId, e); 338 log.trace("[{}] Failed to decode message: ", sessionId, e);
@@ -424,6 +434,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -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 private static class CoapSessionListener implements SessionMsgListener { 471 private static class CoapSessionListener implements SessionMsgListener {
428 472
429 private final CoapExchange exchange; 473 private final CoapExchange exchange;
@@ -18,11 +18,12 @@ package org.thingsboard.server.transport.coap; @@ -18,11 +18,12 @@ package org.thingsboard.server.transport.coap;
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.eclipse.californium.core.CoapResource; 19 import org.eclipse.californium.core.CoapResource;
20 import org.eclipse.californium.core.CoapServer; 20 import org.eclipse.californium.core.CoapServer;
21 -  
22 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
24 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.TbTransportService;
25 import org.thingsboard.server.coapserver.CoapServerService; 25 import org.thingsboard.server.coapserver.CoapServerService;
  26 +import org.thingsboard.server.coapserver.TbCoapServerComponent;
26 import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; 27 import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
27 28
28 import javax.annotation.PostConstruct; 29 import javax.annotation.PostConstruct;
@@ -30,9 +31,9 @@ import javax.annotation.PreDestroy; @@ -30,9 +31,9 @@ import javax.annotation.PreDestroy;
30 import java.net.UnknownHostException; 31 import java.net.UnknownHostException;
31 32
32 @Service("CoapTransportService") 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 @Slf4j 35 @Slf4j
35 -public class CoapTransportService { 36 +public class CoapTransportService implements TbTransportService {
36 37
37 private static final String V1 = "v1"; 38 private static final String V1 = "v1";
38 private static final String API = "api"; 39 private static final String API = "api";
@@ -66,4 +67,9 @@ public class CoapTransportService { @@ -66,4 +67,9 @@ public class CoapTransportService {
66 public void shutdown() { 67 public void shutdown() {
67 log.info("CoAP transport stopped!"); 68 log.info("CoAP transport stopped!");
68 } 69 }
  70 +
  71 + @Override
  72 + public String getName() {
  73 + return "COAP";
  74 + }
69 } 75 }