Commit 831ddc43bc2c5d4eaf98b8ab393657b3afcefa50

Authored by Volodymyr Babak
2 parents ea8e55db b978f0f7

Merge remote-tracking branch 'upstream/master' into edge-3.3

Showing 53 changed files with 2662 additions and 538 deletions

Too many changes to show.

To preserve performance only 53 of 293 files are displayed.

@@ -146,6 +146,10 @@ @@ -146,6 +146,10 @@
146 <scope>runtime</scope> 146 <scope>runtime</scope>
147 </dependency> 147 </dependency>
148 <dependency> 148 <dependency>
  149 + <groupId>org.springframework.integration</groupId>
  150 + <artifactId>spring-integration-redis</artifactId>
  151 + </dependency>
  152 + <dependency>
149 <groupId>org.springframework.boot</groupId> 153 <groupId>org.springframework.boot</groupId>
150 <artifactId>spring-boot-starter-security</artifactId> 154 <artifactId>spring-boot-starter-security</artifactId>
151 </dependency> 155 </dependency>
1 { 1 {
2 "title": "Firmware", 2 "title": "Firmware",
  3 + "image": null,
3 "configuration": { 4 "configuration": {
4 "description": "", 5 "description": "",
5 "widgets": { 6 "widgets": {
@@ -247,7 +248,7 @@ @@ -247,7 +248,7 @@
247 "name": "Edit firmware", 248 "name": "Edit firmware",
248 "icon": "edit", 249 "icon": "edit",
249 "type": "customPretty", 250 "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-firmware-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\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 + "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-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 "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 "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 "customResources": [],
@@ -257,7 +258,7 @@ @@ -257,7 +258,7 @@
257 "name": "Download firware", 258 "name": "Download firware",
258 "icon": "file_download", 259 "icon": "file_download",
259 "type": "custom", 260 "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 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\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 otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(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 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
262 }, 263 },
263 { 264 {
@@ -1021,7 +1022,7 @@ @@ -1021,7 +1022,7 @@
1021 "name": "Edit firmware", 1022 "name": "Edit firmware",
1022 "icon": "edit", 1023 "icon": "edit",
1023 "type": "customPretty", 1024 "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 + "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-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 "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 "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 "customResources": [],
@@ -1031,7 +1032,7 @@ @@ -1031,7 +1032,7 @@
1031 "name": "Download firware", 1032 "name": "Download firware",
1032 "icon": "file_download", 1033 "icon": "file_download",
1033 "type": "custom", 1034 "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 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\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 otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(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 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1036 }, 1037 },
1037 { 1038 {
@@ -1297,7 +1298,7 @@ @@ -1297,7 +1298,7 @@
1297 "name": "Edit firmware", 1298 "name": "Edit firmware",
1298 "icon": "edit", 1299 "icon": "edit",
1299 "type": "customPretty", 1300 "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 + "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-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 "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 "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 "customResources": [],
@@ -1307,7 +1308,7 @@ @@ -1307,7 +1308,7 @@
1307 "name": "Download firware", 1308 "name": "Download firware",
1308 "icon": "file_download", 1309 "icon": "file_download",
1309 "type": "custom", 1310 "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 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\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 otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(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 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1312 }, 1313 },
1313 { 1314 {
@@ -1573,7 +1574,7 @@ @@ -1573,7 +1574,7 @@
1573 "name": "Edit firmware", 1574 "name": "Edit firmware",
1574 "icon": "edit", 1575 "icon": "edit",
1575 "type": "customPretty", 1576 "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 + "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-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 "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 "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 "customResources": [],
@@ -1583,7 +1584,7 @@ @@ -1583,7 +1584,7 @@
1583 "name": "Download firware", 1584 "name": "Download firware",
1584 "icon": "file_download", 1585 "icon": "file_download",
1585 "type": "custom", 1586 "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 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\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 otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(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 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1588 }, 1589 },
1589 { 1590 {
@@ -1849,7 +1850,7 @@ @@ -1849,7 +1850,7 @@
1849 "name": "Edit firmware", 1850 "name": "Edit firmware",
1850 "icon": "edit", 1851 "icon": "edit",
1851 "type": "customPretty", 1852 "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 + "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 *ngIf=\"entity.deviceProfileId\" mat-dialog-content fxLayout=\"column\">\n <tb-ota-package-autocomplete\n [useFullEntityId]=\"true\"\n [deviceProfileId]=\"entity.deviceProfileId.id\"\n formControlName=\"firmwareId\">\n </tb-ota-package-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 "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 "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 "customResources": [],
@@ -1859,7 +1860,7 @@ @@ -1859,7 +1860,7 @@
1859 "name": "Download firware", 1860 "name": "Download firware",
1860 "icon": "file_download", 1861 "icon": "file_download",
1861 "type": "custom", 1862 "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 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\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 otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(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 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1864 }, 1865 },
1865 { 1866 {
@@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
36 "resources": [], 36 "resources": [],
37 "templateHtml": "", 37 "templateHtml": "",
38 "templateCss": "", 38 "templateCss": "",
39 - "controllerScript": "self.onInit = function() {\n\n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n cardHtml = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n self.ctx.$container.html(cardHtml);\n\n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", 39 + "controllerScript": "self.onInit = function() {\n\n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml + self.ctx.widget.id));\n cardHtml = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n self.ctx.$container.html(cardHtml);\n\n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
40 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", 40 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}",
41 "dataKeySettingsSchema": "{}\n", 41 "dataKeySettingsSchema": "{}\n",
42 "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}" 42 "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", 55 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
56 "templateCss": "", 56 "templateCss": "",
57 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", 57 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
58 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", 58 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
59 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", 59 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}",
60 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" 60 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
61 } 61 }
@@ -72,7 +72,7 @@ @@ -72,7 +72,7 @@
72 "resources": [], 72 "resources": [],
73 "templateHtml": "", 73 "templateHtml": "",
74 "templateCss": "", 74 "templateCss": "",
75 - "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n singleEntity: true,\n dataKeysOptional: true\n };\n}\n\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get(self.ctx.servicesMap.get('utils'));\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{i18n:[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n", 75 + "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml + self.ctx.widget.id));\n self.ctx.html = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n singleEntity: true,\n dataKeysOptional: true\n };\n}\n\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get(self.ctx.servicesMap.get('utils'));\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{i18n:[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n",
76 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", 76 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}",
77 "dataKeySettingsSchema": "{}\n", 77 "dataKeySettingsSchema": "{}\n",
78 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 78 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -18,8 +18,8 @@ @@ -18,8 +18,8 @@
18 "resources": [], 18 "resources": [],
19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", 19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
20 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", 20 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n",
21 - "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n",  
22 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", 21 + "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var utils = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('utils'));\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = utils.guid();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n \nself.onDestroy = function() {\n}",
  22 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\"\n ]\n}",
23 "dataKeySettingsSchema": "{}\n", 23 "dataKeySettingsSchema": "{}\n",
24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 24 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
25 } 25 }
@@ -43,7 +43,7 @@ BEGIN @@ -43,7 +43,7 @@ BEGIN
43 into max_customer_ttl; 43 into max_customer_ttl;
44 max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl); 44 max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl);
45 if max_ttl IS NOT NULL AND max_ttl > 0 THEN 45 if max_ttl IS NOT NULL AND max_ttl > 0 THEN
46 - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - (max_ttl / 1000)); 46 + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl);
47 partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); 47 partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date);
48 RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; 48 RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date;
49 IF partition_by_max_ttl_date IS NOT NULL THEN 49 IF partition_by_max_ttl_date IS NOT NULL THEN
@@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS resource ( @@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS resource (
59 CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) 59 CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
60 ); 60 );
61 61
62 -CREATE TABLE IF NOT EXISTS firmware (  
63 - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, 62 +CREATE TABLE IF NOT EXISTS ota_package (
  63 + id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY,
64 created_time bigint NOT NULL, 64 created_time bigint NOT NULL,
65 tenant_id uuid NOT NULL, 65 tenant_id uuid NOT NULL,
66 device_profile_id uuid, 66 device_profile_id uuid,
@@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS firmware ( @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS firmware (
75 data_size bigint, 75 data_size bigint,
76 additional_info varchar, 76 additional_info varchar,
77 search_text varchar(255), 77 search_text varchar(255),
78 - CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) 78 + CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
79 ); 79 );
80 80
81 ALTER TABLE dashboard 81 ALTER TABLE dashboard
@@ -101,13 +101,13 @@ DO $$ @@ -101,13 +101,13 @@ DO $$
101 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN 101 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
102 ALTER TABLE device_profile 102 ALTER TABLE device_profile
103 ADD CONSTRAINT fk_firmware_device_profile 103 ADD CONSTRAINT fk_firmware_device_profile
104 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 104 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
105 END IF; 105 END IF;
106 106
107 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN 107 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN
108 ALTER TABLE device_profile 108 ALTER TABLE device_profile
109 ADD CONSTRAINT fk_software_device_profile 109 ADD CONSTRAINT fk_software_device_profile
110 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 110 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
111 END IF; 111 END IF;
112 112
113 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN 113 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN
@@ -119,13 +119,13 @@ DO $$ @@ -119,13 +119,13 @@ DO $$
119 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN 119 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
120 ALTER TABLE device 120 ALTER TABLE device
121 ADD CONSTRAINT fk_firmware_device 121 ADD CONSTRAINT fk_firmware_device
122 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 122 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
123 END IF; 123 END IF;
124 124
125 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN 125 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN
126 ALTER TABLE device 126 ALTER TABLE device
127 ADD CONSTRAINT fk_software_device 127 ADD CONSTRAINT fk_software_device
128 - FOREIGN KEY (firmware_id) REFERENCES firmware(id); 128 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
129 END IF; 129 END IF;
130 END; 130 END;
131 $$; 131 $$;
@@ -25,8 +25,8 @@ import lombok.extern.slf4j.Slf4j; @@ -25,8 +25,8 @@ import lombok.extern.slf4j.Slf4j;
25 import org.apache.commons.collections.CollectionUtils; 25 import org.apache.commons.collections.CollectionUtils;
26 import org.thingsboard.rule.engine.api.RpcError; 26 import org.thingsboard.rule.engine.api.RpcError;
27 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; 27 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
28 -import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;  
29 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; 28 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
  29 +import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
30 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; 30 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
31 import org.thingsboard.server.actors.ActorSystemContext; 31 import org.thingsboard.server.actors.ActorSystemContext;
32 import org.thingsboard.server.actors.TbActorCtx; 32 import org.thingsboard.server.actors.TbActorCtx;
@@ -73,7 +73,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM @@ -73,7 +73,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM
73 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; 73 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
74 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; 74 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
75 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; 75 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
76 -import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;  
77 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; 76 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
78 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; 77 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
79 import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; 78 import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
@@ -164,8 +163,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -164,8 +163,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
164 void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) { 163 void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
165 ToDeviceRpcRequest request = msg.getMsg(); 164 ToDeviceRpcRequest request = msg.getMsg();
166 ToDeviceRpcRequestBody body = request.getBody(); 165 ToDeviceRpcRequestBody body = request.getBody();
167 - ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(  
168 - rpcSeq++).setMethodName(body.getMethod()).setParams(body.getParams()).build(); 166 + ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
  167 + .setRequestId(rpcSeq++)
  168 + .setMethodName(body.getMethod())
  169 + .setParams(body.getParams())
  170 + .setExpirationTime(request.getExpirationTime())
  171 + .setRequestIdMSB(request.getId().getMostSignificantBits())
  172 + .setRequestIdLSB(request.getId().getLeastSignificantBits())
  173 + .build();
169 174
170 long timeout = request.getExpirationTime() - System.currentTimeMillis(); 175 long timeout = request.getExpirationTime() - System.currentTimeMillis();
171 if (timeout <= 0) { 176 if (timeout <= 0) {
@@ -258,8 +263,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -258,8 +263,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
258 sentOneWayIds.add(entry.getKey()); 263 sentOneWayIds.add(entry.getKey());
259 systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null)); 264 systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
260 } 265 }
261 - ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(  
262 - entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build(); 266 + ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
  267 + .setRequestId(entry.getKey())
  268 + .setMethodName(body.getMethod())
  269 + .setParams(body.getParams())
  270 + .setExpirationTime(request.getExpirationTime())
  271 + .setRequestIdMSB(request.getId().getMostSignificantBits())
  272 + .setRequestIdLSB(request.getId().getLeastSignificantBits())
  273 + .build();
263 sendToTransport(rpcRequest, sessionId, nodeId); 274 sendToTransport(rpcRequest, sessionId, nodeId);
264 }; 275 };
265 } 276 }
@@ -306,25 +317,48 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -306,25 +317,48 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
306 317
307 private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { 318 private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
308 int requestId = request.getRequestId(); 319 int requestId = request.getRequestId();
309 - Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<List<List<AttributeKvEntry>>>() {  
310 - @Override  
311 - public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {  
312 - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()  
313 - .setRequestId(requestId)  
314 - .addAllClientAttributeList(toTsKvProtos(result.get(0)))  
315 - .addAllSharedAttributeList(toTsKvProtos(result.get(1)))  
316 - .build();  
317 - sendToTransport(responseMsg, sessionInfo);  
318 - } 320 + if (request.getOnlyShared()) {
  321 + Futures.addCallback(findAllAttributesByScope(DataConstants.SHARED_SCOPE), new FutureCallback<>() {
  322 + @Override
  323 + public void onSuccess(@Nullable List<AttributeKvEntry> result) {
  324 + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
  325 + .setRequestId(requestId)
  326 + .setSharedStateMsg(true)
  327 + .addAllSharedAttributeList(toTsKvProtos(result))
  328 + .build();
  329 + sendToTransport(responseMsg, sessionInfo);
  330 + }
319 331
320 - @Override  
321 - public void onFailure(Throwable t) {  
322 - GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()  
323 - .setError(t.getMessage())  
324 - .build();  
325 - sendToTransport(responseMsg, sessionInfo);  
326 - }  
327 - }, MoreExecutors.directExecutor()); 332 + @Override
  333 + public void onFailure(Throwable t) {
  334 + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
  335 + .setError(t.getMessage())
  336 + .setSharedStateMsg(true)
  337 + .build();
  338 + sendToTransport(responseMsg, sessionInfo);
  339 + }
  340 + }, MoreExecutors.directExecutor());
  341 + } else {
  342 + Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<>() {
  343 + @Override
  344 + public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
  345 + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
  346 + .setRequestId(requestId)
  347 + .addAllClientAttributeList(toTsKvProtos(result.get(0)))
  348 + .addAllSharedAttributeList(toTsKvProtos(result.get(1)))
  349 + .build();
  350 + sendToTransport(responseMsg, sessionInfo);
  351 + }
  352 +
  353 + @Override
  354 + public void onFailure(Throwable t) {
  355 + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
  356 + .setError(t.getMessage())
  357 + .build();
  358 + sendToTransport(responseMsg, sessionInfo);
  359 + }
  360 + }, MoreExecutors.directExecutor());
  361 + }
328 } 362 }
329 363
330 private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) { 364 private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {
@@ -392,9 +426,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -392,9 +426,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
392 } 426 }
393 if (hasNotificationData) { 427 if (hasNotificationData) {
394 AttributeUpdateNotificationMsg finalNotification = notification.build(); 428 AttributeUpdateNotificationMsg finalNotification = notification.build();
395 - attributeSubscriptions.entrySet().forEach(sub -> {  
396 - sendToTransport(finalNotification, sub.getKey(), sub.getValue().getNodeId());  
397 - }); 429 + attributeSubscriptions.forEach((key, value) -> sendToTransport(finalNotification, key, value.getNodeId()));
398 } 430 }
399 } else { 431 } else {
400 log.debug("[{}] No registered attributes subscriptions to process!", deviceId); 432 log.debug("[{}] No registered attributes subscriptions to process!", deviceId);
@@ -464,7 +496,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -464,7 +496,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
464 if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { 496 if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
465 UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); 497 UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
466 if (sessionIdToRemove != null) { 498 if (sessionIdToRemove != null) {
467 - notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove)); 499 + notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove), "max concurrent sessions limit reached per device!");
468 } 500 }
469 } 501 }
470 sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); 502 sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId())));
@@ -510,7 +542,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -510,7 +542,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
510 notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); 542 notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
511 }); 543 });
512 } else { 544 } else {
513 - sessions.forEach(this::notifyTransportAboutClosedSession); 545 + sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!"));
514 attributeSubscriptions.clear(); 546 attributeSubscriptions.clear();
515 rpcSubscriptions.clear(); 547 rpcSubscriptions.clear();
516 dumpSessions(); 548 dumpSessions();
@@ -518,11 +550,15 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -518,11 +550,15 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
518 } 550 }
519 } 551 }
520 552
521 - private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) { 553 + private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
  554 + SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
  555 + .newBuilder()
  556 + .setMessage(message).build();
522 ToTransportMsg msg = ToTransportMsg.newBuilder() 557 ToTransportMsg msg = ToTransportMsg.newBuilder()
523 .setSessionIdMSB(sessionId.getMostSignificantBits()) 558 .setSessionIdMSB(sessionId.getMostSignificantBits())
524 .setSessionIdLSB(sessionId.getLeastSignificantBits()) 559 .setSessionIdLSB(sessionId.getLeastSignificantBits())
525 - .setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build(); 560 + .setSessionCloseNotification(sessionCloseNotificationProto)
  561 + .build();
526 systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg); 562 systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
527 } 563 }
528 564
@@ -730,7 +766,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { @@ -730,7 +766,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
730 sessions.remove(sessionId); 766 sessions.remove(sessionId);
731 rpcSubscriptions.remove(sessionId); 767 rpcSubscriptions.remove(sessionId);
732 attributeSubscriptions.remove(sessionId); 768 attributeSubscriptions.remove(sessionId);
733 - notifyTransportAboutClosedSession(sessionId, sessionMD); 769 + notifyTransportAboutClosedSession(sessionId, sessionMD, "session timeout!");
734 }); 770 });
735 if (!sessionsToRemove.isEmpty()) { 771 if (!sessionsToRemove.isEmpty()) {
736 dumpSessions(); 772 dumpSessions();
@@ -39,8 +39,8 @@ import org.thingsboard.server.common.data.EdgeUtils; @@ -39,8 +39,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
39 import org.thingsboard.server.common.data.EntityType; 39 import org.thingsboard.server.common.data.EntityType;
40 import org.thingsboard.server.common.data.EntityView; 40 import org.thingsboard.server.common.data.EntityView;
41 import org.thingsboard.server.common.data.EntityViewInfo; 41 import org.thingsboard.server.common.data.EntityViewInfo;
42 -import org.thingsboard.server.common.data.Firmware;  
43 -import org.thingsboard.server.common.data.FirmwareInfo; 42 +import org.thingsboard.server.common.data.OtaPackage;
  43 +import org.thingsboard.server.common.data.OtaPackageInfo;
44 import org.thingsboard.server.common.data.HasName; 44 import org.thingsboard.server.common.data.HasName;
45 import org.thingsboard.server.common.data.HasTenantId; 45 import org.thingsboard.server.common.data.HasTenantId;
46 import org.thingsboard.server.common.data.TbResource; 46 import org.thingsboard.server.common.data.TbResource;
@@ -70,7 +70,8 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -70,7 +70,8 @@ import org.thingsboard.server.common.data.id.EdgeId;
70 import org.thingsboard.server.common.data.id.EntityId; 70 import org.thingsboard.server.common.data.id.EntityId;
71 import org.thingsboard.server.common.data.id.EntityIdFactory; 71 import org.thingsboard.server.common.data.id.EntityIdFactory;
72 import org.thingsboard.server.common.data.id.EntityViewId; 72 import org.thingsboard.server.common.data.id.EntityViewId;
73 -import org.thingsboard.server.common.data.id.FirmwareId; 73 +import org.thingsboard.server.common.data.id.OtaPackageId;
  74 +import org.thingsboard.server.common.data.id.TbResourceId;
74 import org.thingsboard.server.common.data.id.RuleChainId; 75 import org.thingsboard.server.common.data.id.RuleChainId;
75 import org.thingsboard.server.common.data.id.RuleNodeId; 76 import org.thingsboard.server.common.data.id.RuleNodeId;
76 import org.thingsboard.server.common.data.id.TbResourceId; 77 import org.thingsboard.server.common.data.id.TbResourceId;
@@ -111,7 +112,7 @@ import org.thingsboard.server.dao.edge.EdgeService; @@ -111,7 +112,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
111 import org.thingsboard.server.dao.entityview.EntityViewService; 112 import org.thingsboard.server.dao.entityview.EntityViewService;
112 import org.thingsboard.server.dao.exception.DataValidationException; 113 import org.thingsboard.server.dao.exception.DataValidationException;
113 import org.thingsboard.server.dao.exception.IncorrectParameterException; 114 import org.thingsboard.server.dao.exception.IncorrectParameterException;
114 -import org.thingsboard.server.dao.firmware.FirmwareService; 115 +import org.thingsboard.server.dao.ota.OtaPackageService;
115 import org.thingsboard.server.dao.model.ModelConstants; 116 import org.thingsboard.server.dao.model.ModelConstants;
116 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; 117 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
117 import org.thingsboard.server.dao.oauth2.OAuth2Service; 118 import org.thingsboard.server.dao.oauth2.OAuth2Service;
@@ -129,9 +130,10 @@ import org.thingsboard.server.queue.discovery.PartitionService; @@ -129,9 +130,10 @@ import org.thingsboard.server.queue.discovery.PartitionService;
129 import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 130 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
130 import org.thingsboard.server.queue.util.TbCoreComponent; 131 import org.thingsboard.server.queue.util.TbCoreComponent;
131 import org.thingsboard.server.service.component.ComponentDiscoveryService; 132 import org.thingsboard.server.service.component.ComponentDiscoveryService;
132 -import org.thingsboard.server.service.edge.EdgeNotificationService;  
133 import org.thingsboard.server.service.edge.rpc.EdgeRpcService; 133 import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
134 -import org.thingsboard.server.service.firmware.FirmwareStateService; 134 +import org.thingsboard.server.service.ota.OtaPackageStateService;
  135 +import org.thingsboard.server.service.edge.EdgeNotificationService;
  136 +import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
135 import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; 137 import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
136 import org.thingsboard.server.service.profile.TbDeviceProfileCache; 138 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
137 import org.thingsboard.server.service.queue.TbClusterService; 139 import org.thingsboard.server.service.queue.TbClusterService;
@@ -162,8 +164,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @@ -162,8 +164,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
162 public abstract class BaseController { 164 public abstract class BaseController {
163 165
164 public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; 166 public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
165 - public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";  
166 -  
167 protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; 167 protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
168 protected static final String HOME_DASHBOARD = "homeDashboardId"; 168 protected static final String HOME_DASHBOARD = "homeDashboardId";
169 169
@@ -256,10 +256,10 @@ public abstract class BaseController { @@ -256,10 +256,10 @@ public abstract class BaseController {
256 protected TbResourceService resourceService; 256 protected TbResourceService resourceService;
257 257
258 @Autowired 258 @Autowired
259 - protected FirmwareService firmwareService; 259 + protected OtaPackageService otaPackageService;
260 260
261 @Autowired 261 @Autowired
262 - protected FirmwareStateService firmwareStateService; 262 + protected OtaPackageStateService otaPackageStateService;
263 263
264 @Autowired 264 @Autowired
265 protected TbQueueProducerProvider producerProvider; 265 protected TbQueueProducerProvider producerProvider;
@@ -514,8 +514,8 @@ public abstract class BaseController { @@ -514,8 +514,8 @@ public abstract class BaseController {
514 case TB_RESOURCE: 514 case TB_RESOURCE:
515 checkResourceId(new TbResourceId(entityId.getId()), operation); 515 checkResourceId(new TbResourceId(entityId.getId()), operation);
516 return; 516 return;
517 - case FIRMWARE:  
518 - checkFirmwareId(new FirmwareId(entityId.getId()), operation); 517 + case OTA_PACKAGE:
  518 + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);
519 return; 519 return;
520 default: 520 default:
521 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); 521 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
@@ -772,25 +772,25 @@ public abstract class BaseController { @@ -772,25 +772,25 @@ public abstract class BaseController {
772 } 772 }
773 } 773 }
774 774
775 - Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { 775 + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
776 try { 776 try {
777 - validateId(firmwareId, "Incorrect firmwareId " + firmwareId);  
778 - Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId);  
779 - checkNotNull(firmware);  
780 - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware);  
781 - return firmware; 777 + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
  778 + OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId);
  779 + checkNotNull(otaPackage);
  780 + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage);
  781 + return otaPackage;
782 } catch (Exception e) { 782 } catch (Exception e) {
783 throw handleException(e, false); 783 throw handleException(e, false);
784 } 784 }
785 } 785 }
786 786
787 - FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { 787 + OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
788 try { 788 try {
789 - validateId(firmwareId, "Incorrect firmwareId " + firmwareId);  
790 - FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId);  
791 - checkNotNull(firmwareInfo);  
792 - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo);  
793 - return firmwareInfo; 789 + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
  790 + OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId);
  791 + checkNotNull(otaPackageIn);
  792 + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn);
  793 + return otaPackageIn;
794 } catch (Exception e) { 794 } catch (Exception e) {
795 throw handleException(e, false); 795 throw handleException(e, false);
796 } 796 }
@@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
53 import org.thingsboard.server.common.data.id.DeviceProfileId; 53 import org.thingsboard.server.common.data.id.DeviceProfileId;
54 import org.thingsboard.server.common.data.id.EdgeId; 54 import org.thingsboard.server.common.data.id.EdgeId;
55 import org.thingsboard.server.common.data.id.TenantId; 55 import org.thingsboard.server.common.data.id.TenantId;
  56 +import org.thingsboard.server.common.data.ota.OtaPackageType;
56 import org.thingsboard.server.common.data.page.PageData; 57 import org.thingsboard.server.common.data.page.PageData;
57 import org.thingsboard.server.common.data.page.PageLink; 58 import org.thingsboard.server.common.data.page.PageLink;
58 import org.thingsboard.server.common.data.page.TimePageLink; 59 import org.thingsboard.server.common.data.page.TimePageLink;
@@ -75,6 +76,7 @@ import javax.annotation.Nullable; @@ -75,6 +76,7 @@ import javax.annotation.Nullable;
75 import java.io.IOException; 76 import java.io.IOException;
76 import java.util.ArrayList; 77 import java.util.ArrayList;
77 import java.util.List; 78 import java.util.List;
  79 +import java.util.UUID;
78 import java.util.stream.Collectors; 80 import java.util.stream.Collectors;
79 81
80 import static org.thingsboard.server.controller.EdgeController.EDGE_ID; 82 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@@ -153,7 +155,7 @@ public class DeviceController extends BaseController { @@ -153,7 +155,7 @@ public class DeviceController extends BaseController {
153 deviceStateService.onDeviceUpdated(savedDevice); 155 deviceStateService.onDeviceUpdated(savedDevice);
154 } 156 }
155 157
156 - firmwareStateService.update(savedDevice, oldDevice); 158 + otaPackageStateService.update(savedDevice, oldDevice);
157 159
158 return savedDevice; 160 return savedDevice;
159 } catch (Exception e) { 161 } catch (Exception e) {
@@ -778,4 +780,19 @@ public class DeviceController extends BaseController { @@ -778,4 +780,19 @@ public class DeviceController extends BaseController {
778 throw handleException(e); 780 throw handleException(e);
779 } 781 }
780 } 782 }
  783 +
  784 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  785 + @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET)
  786 + @ResponseBody
  787 + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
  788 + @RequestParam String deviceProfileId) throws ThingsboardException {
  789 + checkParameter("OtaPackageType", otaPackageType);
  790 + checkParameter("DeviceProfileId", deviceProfileId);
  791 + try {
  792 + return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(
  793 + getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType));
  794 + } catch (Exception e) {
  795 + throw handleException(e);
  796 + }
  797 + }
781 } 798 }
@@ -168,7 +168,7 @@ public class DeviceProfileController extends BaseController { @@ -168,7 +168,7 @@ public class DeviceProfileController extends BaseController {
168 null, 168 null,
169 created ? ActionType.ADDED : ActionType.UPDATED, null); 169 created ? ActionType.ADDED : ActionType.UPDATED, null);
170 170
171 - firmwareStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); 171 + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged);
172 172
173 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(), 173 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),
174 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); 174 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
@@ -17,6 +17,7 @@ package org.thingsboard.server.controller; @@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
17 17
18 import com.fasterxml.jackson.databind.ObjectMapper; 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.SecurityMode;
20 import org.springframework.security.access.prepost.PreAuthorize; 21 import org.springframework.security.access.prepost.PreAuthorize;
21 import org.springframework.web.bind.annotation.PathVariable; 22 import org.springframework.web.bind.annotation.PathVariable;
22 import org.springframework.web.bind.annotation.RequestBody; 23 import org.springframework.web.bind.annotation.RequestBody;
@@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController { @@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController {
46 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 47 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
47 @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) 48 @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
48 @ResponseBody 49 @ResponseBody
49 - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, 50 + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode,
50 @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { 51 @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException {
  52 + checkNotNull(strSecurityMode);
51 try { 53 try {
  54 + SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode);
52 return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); 55 return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer);
53 } catch (Exception e) { 56 } catch (Exception e) {
54 throw handleException(e); 57 throw handleException(e);
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java renamed from application/src/main/java/org/thingsboard/server/controller/FirmwareController.java
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 -import com.google.common.hash.Hashing;  
19 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
20 import org.apache.commons.lang3.StringUtils; 19 import org.apache.commons.lang3.StringUtils;
21 import org.springframework.core.io.ByteArrayResource; 20 import org.springframework.core.io.ByteArrayResource;
@@ -31,13 +30,14 @@ import org.springframework.web.bind.annotation.ResponseBody; @@ -31,13 +30,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
31 import org.springframework.web.bind.annotation.RestController; 30 import org.springframework.web.bind.annotation.RestController;
32 import org.springframework.web.multipart.MultipartFile; 31 import org.springframework.web.multipart.MultipartFile;
33 import org.thingsboard.server.common.data.EntityType; 32 import org.thingsboard.server.common.data.EntityType;
34 -import org.thingsboard.server.common.data.Firmware;  
35 -import org.thingsboard.server.common.data.FirmwareInfo; 33 +import org.thingsboard.server.common.data.OtaPackage;
  34 +import org.thingsboard.server.common.data.OtaPackageInfo;
36 import org.thingsboard.server.common.data.audit.ActionType; 35 import org.thingsboard.server.common.data.audit.ActionType;
37 import org.thingsboard.server.common.data.exception.ThingsboardException; 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
38 -import org.thingsboard.server.common.data.firmware.FirmwareType; 37 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  38 +import org.thingsboard.server.common.data.ota.OtaPackageType;
39 import org.thingsboard.server.common.data.id.DeviceProfileId; 39 import org.thingsboard.server.common.data.id.DeviceProfileId;
40 -import org.thingsboard.server.common.data.id.FirmwareId; 40 +import org.thingsboard.server.common.data.id.OtaPackageId;
41 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
42 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
43 import org.thingsboard.server.queue.util.TbCoreComponent; 43 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -50,25 +50,26 @@ import java.nio.ByteBuffer; @@ -50,25 +50,26 @@ import java.nio.ByteBuffer;
50 @RestController 50 @RestController
51 @TbCoreComponent 51 @TbCoreComponent
52 @RequestMapping("/api") 52 @RequestMapping("/api")
53 -public class FirmwareController extends BaseController { 53 +public class OtaPackageController extends BaseController {
54 54
55 - public static final String FIRMWARE_ID = "firmwareId"; 55 + public static final String OTA_PACKAGE_ID = "otaPackageId";
  56 + public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm";
56 57
57 @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") 58 @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
58 - @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) 59 + @RequestMapping(value = "/otaPackage/{otaPackageId}/download", method = RequestMethod.GET)
59 @ResponseBody 60 @ResponseBody
60 - public ResponseEntity<org.springframework.core.io.Resource> downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
61 - checkParameter(FIRMWARE_ID, strFirmwareId); 61 + public ResponseEntity<org.springframework.core.io.Resource> downloadOtaPackage(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  62 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
62 try { 63 try {
63 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
64 - Firmware firmware = checkFirmwareId(firmwareId, Operation.READ); 64 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  65 + OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ);
65 66
66 - ByteArrayResource resource = new ByteArrayResource(firmware.getData().array()); 67 + ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array());
67 return ResponseEntity.ok() 68 return ResponseEntity.ok()
68 - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName())  
69 - .header("x-filename", firmware.getFileName()) 69 + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName())
  70 + .header("x-filename", otaPackage.getFileName())
70 .contentLength(resource.contentLength()) 71 .contentLength(resource.contentLength())
71 - .contentType(parseMediaType(firmware.getContentType())) 72 + .contentType(parseMediaType(otaPackage.getContentType()))
72 .body(resource); 73 .body(resource);
73 } catch (Exception e) { 74 } catch (Exception e) {
74 throw handleException(e); 75 throw handleException(e);
@@ -76,142 +77,144 @@ public class FirmwareController extends BaseController { @@ -76,142 +77,144 @@ public class FirmwareController extends BaseController {
76 } 77 }
77 78
78 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 79 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
79 - @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) 80 + @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET)
80 @ResponseBody 81 @ResponseBody
81 - public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
82 - checkParameter(FIRMWARE_ID, strFirmwareId); 82 + public OtaPackageInfo getOtaPackageInfoById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  83 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
83 try { 84 try {
84 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
85 - return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId)); 85 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  86 + return checkNotNull(otaPackageService.findOtaPackageInfoById(getTenantId(), otaPackageId));
86 } catch (Exception e) { 87 } catch (Exception e) {
87 throw handleException(e); 88 throw handleException(e);
88 } 89 }
89 } 90 }
90 91
91 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 92 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
92 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET) 93 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
93 @ResponseBody 94 @ResponseBody
94 - public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {  
95 - checkParameter(FIRMWARE_ID, strFirmwareId); 95 + public OtaPackage getOtaPackageById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException {
  96 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
96 try { 97 try {
97 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
98 - return checkFirmwareId(firmwareId, Operation.READ); 98 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  99 + return checkOtaPackageId(otaPackageId, Operation.READ);
99 } catch (Exception e) { 100 } catch (Exception e) {
100 throw handleException(e); 101 throw handleException(e);
101 } 102 }
102 } 103 }
103 104
104 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 105 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
105 - @RequestMapping(value = "/firmware", method = RequestMethod.POST) 106 + @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
106 @ResponseBody 107 @ResponseBody
107 - public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException {  
108 - boolean created = firmwareInfo.getId() == null; 108 + public OtaPackageInfo saveOtaPackageInfo(@RequestBody OtaPackageInfo otaPackageInfo) throws ThingsboardException {
  109 + boolean created = otaPackageInfo.getId() == null;
109 try { 110 try {
110 - firmwareInfo.setTenantId(getTenantId());  
111 - checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);  
112 - FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo);  
113 - logEntityAction(savedFirmwareInfo.getId(), savedFirmwareInfo, 111 + otaPackageInfo.setTenantId(getTenantId());
  112 + checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE);
  113 + OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(otaPackageInfo);
  114 + logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo,
114 null, created ? ActionType.ADDED : ActionType.UPDATED, null); 115 null, created ? ActionType.ADDED : ActionType.UPDATED, null);
115 - return savedFirmwareInfo; 116 + return savedOtaPackageInfo;
116 } catch (Exception e) { 117 } catch (Exception e) {
117 - logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo, 118 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), otaPackageInfo,
118 null, created ? ActionType.ADDED : ActionType.UPDATED, e); 119 null, created ? ActionType.ADDED : ActionType.UPDATED, e);
119 throw handleException(e); 120 throw handleException(e);
120 } 121 }
121 } 122 }
122 123
123 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 124 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
124 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) 125 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST)
125 @ResponseBody 126 @ResponseBody
126 - public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId,  
127 - @RequestParam(required = false) String checksum,  
128 - @RequestParam(required = false) String checksumAlgorithm,  
129 - @RequestBody MultipartFile file) throws ThingsboardException {  
130 - checkParameter(FIRMWARE_ID, strFirmwareId); 127 + public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId,
  128 + @RequestParam(required = false) String checksum,
  129 + @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,
  130 + @RequestBody MultipartFile file) throws ThingsboardException {
  131 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
  132 + checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr);
131 try { 133 try {
132 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
133 - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ);  
134 -  
135 - Firmware firmware = new Firmware(firmwareId);  
136 - firmware.setCreatedTime(info.getCreatedTime());  
137 - firmware.setTenantId(getTenantId());  
138 - firmware.setDeviceProfileId(info.getDeviceProfileId());  
139 - firmware.setType(info.getType());  
140 - firmware.setTitle(info.getTitle());  
141 - firmware.setVersion(info.getVersion());  
142 - firmware.setAdditionalInfo(info.getAdditionalInfo());  
143 -  
144 - byte[] data = file.getBytes();  
145 - if (StringUtils.isEmpty(checksumAlgorithm)) {  
146 - checksumAlgorithm = "sha256";  
147 - checksum = Hashing.sha256().hashBytes(data).toString(); 134 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  135 + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.READ);
  136 +
  137 + OtaPackage otaPackage = new OtaPackage(otaPackageId);
  138 + otaPackage.setCreatedTime(info.getCreatedTime());
  139 + otaPackage.setTenantId(getTenantId());
  140 + otaPackage.setDeviceProfileId(info.getDeviceProfileId());
  141 + otaPackage.setType(info.getType());
  142 + otaPackage.setTitle(info.getTitle());
  143 + otaPackage.setVersion(info.getVersion());
  144 + otaPackage.setAdditionalInfo(info.getAdditionalInfo());
  145 +
  146 + ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());
  147 +
  148 + byte[] bytes = file.getBytes();
  149 + if (StringUtils.isEmpty(checksum)) {
  150 + checksum = otaPackageService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes));
148 } 151 }
149 152
150 - firmware.setChecksumAlgorithm(checksumAlgorithm);  
151 - firmware.setChecksum(checksum);  
152 - firmware.setFileName(file.getOriginalFilename());  
153 - firmware.setContentType(file.getContentType());  
154 - firmware.setData(ByteBuffer.wrap(data));  
155 - firmware.setDataSize((long) data.length);  
156 - Firmware savedFirmware = firmwareService.saveFirmware(firmware);  
157 - logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null);  
158 - return savedFirmware; 153 + otaPackage.setChecksumAlgorithm(checksumAlgorithm);
  154 + otaPackage.setChecksum(checksum);
  155 + otaPackage.setFileName(file.getOriginalFilename());
  156 + otaPackage.setContentType(file.getContentType());
  157 + otaPackage.setData(ByteBuffer.wrap(bytes));
  158 + otaPackage.setDataSize((long) bytes.length);
  159 + OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
  160 + logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null);
  161 + return savedOtaPackage;
159 } catch (Exception e) { 162 } catch (Exception e) {
160 - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId); 163 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.UPDATED, e, strOtaPackageId);
161 throw handleException(e); 164 throw handleException(e);
162 } 165 }
163 } 166 }
164 167
165 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 168 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
166 - @RequestMapping(value = "/firmwares", method = RequestMethod.GET) 169 + @RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
167 @ResponseBody 170 @ResponseBody
168 - public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,  
169 - @RequestParam int page,  
170 - @RequestParam(required = false) String textSearch,  
171 - @RequestParam(required = false) String sortProperty,  
172 - @RequestParam(required = false) String sortOrder) throws ThingsboardException { 171 + public PageData<OtaPackageInfo> getOtaPackages(@RequestParam int pageSize,
  172 + @RequestParam int page,
  173 + @RequestParam(required = false) String textSearch,
  174 + @RequestParam(required = false) String sortProperty,
  175 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
173 try { 176 try {
174 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); 177 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
175 - return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink)); 178 + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink));
176 } catch (Exception e) { 179 } catch (Exception e) {
177 throw handleException(e); 180 throw handleException(e);
178 } 181 }
179 } 182 }
180 183
181 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 184 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
182 - @RequestMapping(value = "/firmwares/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET) 185 + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET)
183 @ResponseBody 186 @ResponseBody
184 - public PageData<FirmwareInfo> getFirmwares(@PathVariable("deviceProfileId") String strDeviceProfileId,  
185 - @PathVariable("type") String strType,  
186 - @PathVariable("hasData") boolean hasData,  
187 - @RequestParam int pageSize,  
188 - @RequestParam int page,  
189 - @RequestParam(required = false) String textSearch,  
190 - @RequestParam(required = false) String sortProperty,  
191 - @RequestParam(required = false) String sortOrder) throws ThingsboardException { 187 + public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId,
  188 + @PathVariable("type") String strType,
  189 + @PathVariable("hasData") boolean hasData,
  190 + @RequestParam int pageSize,
  191 + @RequestParam int page,
  192 + @RequestParam(required = false) String textSearch,
  193 + @RequestParam(required = false) String sortProperty,
  194 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
192 checkParameter("deviceProfileId", strDeviceProfileId); 195 checkParameter("deviceProfileId", strDeviceProfileId);
193 checkParameter("type", strType); 196 checkParameter("type", strType);
194 try { 197 try {
195 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); 198 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
196 - return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),  
197 - new DeviceProfileId(toUUID(strDeviceProfileId)), FirmwareType.valueOf(strType), hasData, pageLink)); 199 + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),
  200 + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), hasData, pageLink));
198 } catch (Exception e) { 201 } catch (Exception e) {
199 throw handleException(e); 202 throw handleException(e);
200 } 203 }
201 } 204 }
202 205
203 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 206 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
204 - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) 207 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
205 @ResponseBody 208 @ResponseBody
206 - public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {  
207 - checkParameter(FIRMWARE_ID, strFirmwareId); 209 + public void deleteOtaPackage(@PathVariable("otaPackageId") String strOtaPackageId) throws ThingsboardException {
  210 + checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
208 try { 211 try {
209 - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));  
210 - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE);  
211 - firmwareService.deleteFirmware(getTenantId(), firmwareId);  
212 - logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId); 212 + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
  213 + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.DELETE);
  214 + otaPackageService.deleteOtaPackage(getTenantId(), otaPackageId);
  215 + logEntityAction(otaPackageId, info, null, ActionType.DELETED, null, strOtaPackageId);
213 } catch (Exception e) { 216 } catch (Exception e) {
214 - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId); 217 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.DELETED, e, strOtaPackageId);
215 throw handleException(e); 218 throw handleException(e);
216 } 219 }
217 } 220 }
@@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.id.DeviceId;
40 import org.thingsboard.server.common.data.id.EntityId; 40 import org.thingsboard.server.common.data.id.EntityId;
41 import org.thingsboard.server.common.data.id.TenantId; 41 import org.thingsboard.server.common.data.id.TenantId;
42 import org.thingsboard.server.common.data.id.UUIDBased; 42 import org.thingsboard.server.common.data.id.UUIDBased;
43 -import org.thingsboard.server.common.data.rpc.RpcRequest;  
44 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; 43 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
45 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; 44 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
46 import org.thingsboard.server.queue.util.TbCoreComponent; 45 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -97,22 +96,17 @@ public class RpcController extends BaseController { @@ -97,22 +96,17 @@ public class RpcController extends BaseController {
97 private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { 96 private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
98 try { 97 try {
99 JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); 98 JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
100 - RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),  
101 - jsonMapper.writeValueAsString(rpcRequestBody.get("params")));  
102 -  
103 - if (rpcRequestBody.has("timeout")) {  
104 - cmd.setTimeout(rpcRequestBody.get("timeout").asLong());  
105 - } 99 + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
106 SecurityUser currentUser = getCurrentUser(); 100 SecurityUser currentUser = getCurrentUser();
107 TenantId tenantId = currentUser.getTenantId(); 101 TenantId tenantId = currentUser.getTenantId();
108 final DeferredResult<ResponseEntity> response = new DeferredResult<>(); 102 final DeferredResult<ResponseEntity> response = new DeferredResult<>();
109 - long timeout = cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout; 103 + long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout;
110 long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout); 104 long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
111 - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); 105 + UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
112 accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() { 106 accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
113 @Override 107 @Override
114 public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) { 108 public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
115 - ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), 109 + ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID,
116 tenantId, 110 tenantId,
117 deviceId, 111 deviceId,
118 oneWay, 112 oneWay,
@@ -193,6 +193,9 @@ public class ThingsboardInstallService { @@ -193,6 +193,9 @@ public class ThingsboardInstallService {
193 databaseEntitiesUpgradeService.upgradeDatabase("3.2.1"); 193 databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
194 case "3.2.2": 194 case "3.2.2":
195 log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ..."); 195 log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
  196 + if (databaseTsUpgradeService != null) {
  197 + databaseTsUpgradeService.upgradeDatabase("3.2.2");
  198 + }
196 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); 199 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
197 200
198 dataUpdateService.updateData("3.2.2"); 201 dataUpdateService.updateData("3.2.2");
@@ -65,7 +65,6 @@ import java.util.Collections; @@ -65,7 +65,6 @@ import java.util.Collections;
65 import java.util.List; 65 import java.util.List;
66 import java.util.Optional; 66 import java.util.Optional;
67 import java.util.concurrent.ExecutionException; 67 import java.util.concurrent.ExecutionException;
68 -import java.util.concurrent.locks.ReentrantLock;  
69 68
70 69
71 @Service 70 @Service
@@ -78,8 +77,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @@ -78,8 +77,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
78 private static final String DEVICE_PROVISION_STATE = "provisionState"; 77 private static final String DEVICE_PROVISION_STATE = "provisionState";
79 private static final String PROVISIONED_STATE = "provisioned"; 78 private static final String PROVISIONED_STATE = "provisioned";
80 79
81 - private final ReentrantLock deviceCreationLock = new ReentrantLock();  
82 -  
83 @Autowired 80 @Autowired
84 DeviceDao deviceDao; 81 DeviceDao deviceDao;
85 82
@@ -177,12 +174,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @@ -177,12 +174,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
177 } 174 }
178 175
179 private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { 176 private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
180 - deviceCreationLock.lock();  
181 - try {  
182 - return processCreateDevice(provisionRequest, profile);  
183 - } finally {  
184 - deviceCreationLock.unlock();  
185 - } 177 + return processCreateDevice(provisionRequest, profile);
186 } 178 }
187 179
188 private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { 180 private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
@@ -191,28 +183,26 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @@ -191,28 +183,26 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
191 } 183 }
192 184
193 private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { 185 private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
194 - Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());  
195 try { 186 try {
196 - if (device == null) {  
197 - if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {  
198 - String newDeviceName = RandomStringUtils.randomAlphanumeric(20);  
199 - log.info("Device name not found in provision request. Generated name is: {}", newDeviceName);  
200 - provisionRequest.setDeviceName(newDeviceName);  
201 - }  
202 - Device savedDevice = deviceService.saveDevice(provisionRequest, profile);  
203 -  
204 - deviceStateService.onDeviceAdded(savedDevice);  
205 - saveProvisionStateAttribute(savedDevice).get();  
206 - pushDeviceCreatedEventToRuleEngine(savedDevice);  
207 - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);  
208 -  
209 - return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS);  
210 - } else {  
211 - log.warn("[{}] The device is already provisioned!", device.getName()); 187 + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {
  188 + String newDeviceName = RandomStringUtils.randomAlphanumeric(20);
  189 + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName);
  190 + provisionRequest.setDeviceName(newDeviceName);
  191 + }
  192 + Device savedDevice = deviceService.saveDevice(provisionRequest, profile);
  193 +
  194 + deviceStateService.onDeviceAdded(savedDevice);
  195 + saveProvisionStateAttribute(savedDevice).get();
  196 + pushDeviceCreatedEventToRuleEngine(savedDevice);
  197 + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  198 +
  199 + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS);
  200 + } catch (Exception e) {
  201 + log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e);
  202 + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
  203 + if (device != null) {
212 notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); 204 notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
213 - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());  
214 } 205 }
215 - } catch (InterruptedException | ExecutionException e) {  
216 throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); 206 throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
217 } 207 }
218 } 208 }
@@ -51,6 +51,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase @@ -51,6 +51,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
51 case "2.5.0": 51 case "2.5.0":
52 case "3.1.1": 52 case "3.1.1":
53 case "3.2.1": 53 case "3.2.1":
  54 + case "3.2.2":
54 break; 55 break;
55 default: 56 default:
56 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 57 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
@@ -209,6 +209,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -209,6 +209,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
209 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);"); 209 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);");
210 } 210 }
211 break; 211 break;
  212 + case "3.2.2":
  213 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  214 + log.info("Load Drop Partitions functions ...");
  215 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
  216 + }
  217 + break;
212 default: 218 default:
213 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 219 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
214 } 220 }
@@ -184,6 +184,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -184,6 +184,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
184 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1"); 184 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1");
185 } 185 }
186 break; 186 break;
  187 + case "3.2.2":
  188 + break;
187 default: 189 default:
188 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 190 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
189 } 191 }
@@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m; @@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m;
18 18
19 import lombok.RequiredArgsConstructor; 19 import lombok.RequiredArgsConstructor;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.leshan.core.SecurityMode;
21 import org.eclipse.leshan.core.util.Hex; 22 import org.eclipse.leshan.core.util.Hex;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
23 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
@@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
25 import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; 26 import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
26 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; 27 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
27 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; 28 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
28 -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;  
29 29
30 import java.math.BigInteger; 30 import java.math.BigInteger;
31 import java.security.AlgorithmParameters; 31 import java.security.AlgorithmParameters;
@@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository { @@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository {
55 * @param bootstrapServer 55 * @param bootstrapServer
56 * @return ServerSecurityConfig more value is default: Important - port, host, publicKey 56 * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
57 */ 57 */
58 - public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) {  
59 - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase());  
60 - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode); 58 + public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) {
  59 + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode);
61 result.setBootstrapServerIs(bootstrapServer); 60 result.setBootstrapServerIs(bootstrapServer);
62 return result; 61 return result;
63 } 62 }
64 63
65 - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) { 64 + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) {
66 ServerSecurityConfig bsServ = new ServerSecurityConfig(); 65 ServerSecurityConfig bsServ = new ServerSecurityConfig();
67 bsServ.setServerId(serverConfig.getId()); 66 bsServ.setServerId(serverConfig.getId());
68 - switch (mode) { 67 + switch (securityMode) {
69 case NO_SEC: 68 case NO_SEC:
70 bsServ.setHost(serverConfig.getHost()); 69 bsServ.setHost(serverConfig.getHost());
71 bsServ.setPort(serverConfig.getPort()); 70 bsServ.setPort(serverConfig.getPort());
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java renamed from application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.service.firmware; 16 +package org.thingsboard.server.service.ota;
17 17
18 import com.google.common.util.concurrent.FutureCallback; 18 import com.google.common.util.concurrent.FutureCallback;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
@@ -23,12 +23,9 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; @@ -23,12 +23,9 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
23 import org.thingsboard.server.common.data.DataConstants; 23 import org.thingsboard.server.common.data.DataConstants;
24 import org.thingsboard.server.common.data.Device; 24 import org.thingsboard.server.common.data.Device;
25 import org.thingsboard.server.common.data.DeviceProfile; 25 import org.thingsboard.server.common.data.DeviceProfile;
26 -import org.thingsboard.server.common.data.FirmwareInfo;  
27 -import org.thingsboard.server.common.data.firmware.FirmwareType;  
28 -import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;  
29 -import org.thingsboard.server.common.data.firmware.FirmwareUtil; 26 +import org.thingsboard.server.common.data.OtaPackageInfo;
30 import org.thingsboard.server.common.data.id.DeviceId; 27 import org.thingsboard.server.common.data.id.DeviceId;
31 -import org.thingsboard.server.common.data.id.FirmwareId; 28 +import org.thingsboard.server.common.data.id.OtaPackageId;
32 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
33 import org.thingsboard.server.common.data.kv.AttributeKey; 30 import org.thingsboard.server.common.data.kv.AttributeKey;
34 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 31 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@@ -37,13 +34,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -37,13 +34,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
37 import org.thingsboard.server.common.data.kv.LongDataEntry; 34 import org.thingsboard.server.common.data.kv.LongDataEntry;
38 import org.thingsboard.server.common.data.kv.StringDataEntry; 35 import org.thingsboard.server.common.data.kv.StringDataEntry;
39 import org.thingsboard.server.common.data.kv.TsKvEntry; 36 import org.thingsboard.server.common.data.kv.TsKvEntry;
  37 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  38 +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
  39 +import org.thingsboard.server.common.data.ota.OtaPackageUtil;
40 import org.thingsboard.server.common.data.page.PageData; 40 import org.thingsboard.server.common.data.page.PageData;
41 import org.thingsboard.server.common.data.page.PageLink; 41 import org.thingsboard.server.common.data.page.PageLink;
42 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 42 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
43 import org.thingsboard.server.dao.device.DeviceProfileService; 43 import org.thingsboard.server.dao.device.DeviceProfileService;
44 import org.thingsboard.server.dao.device.DeviceService; 44 import org.thingsboard.server.dao.device.DeviceService;
45 -import org.thingsboard.server.dao.firmware.FirmwareService;  
46 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 45 +import org.thingsboard.server.dao.ota.OtaPackageService;
  46 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
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.provider.TbCoreQueueFactory; 49 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
@@ -58,44 +58,43 @@ import java.util.List; @@ -58,44 +58,43 @@ import java.util.List;
58 import java.util.Set; 58 import java.util.Set;
59 import java.util.UUID; 59 import java.util.UUID;
60 import java.util.function.Consumer; 60 import java.util.function.Consumer;
61 -import java.util.function.Function;  
62 -  
63 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM;  
64 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM_ALGORITHM;  
65 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.SIZE;  
66 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;  
67 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE;  
68 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS;  
69 -import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION;  
70 -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;  
71 -import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;  
72 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;  
73 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey;  
74 -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey; 61 +
  62 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM;
  63 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM;
  64 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
  65 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
  66 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
  67 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
  68 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION;
  69 +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
  70 +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
  71 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey;
  72 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTargetTelemetryKey;
  73 +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTelemetryKey;
75 74
76 @Slf4j 75 @Slf4j
77 @Service 76 @Service
78 @TbCoreComponent 77 @TbCoreComponent
79 -public class DefaultFirmwareStateService implements FirmwareStateService { 78 +public class DefaultOtaPackageStateService implements OtaPackageStateService {
80 79
81 private final TbClusterService tbClusterService; 80 private final TbClusterService tbClusterService;
82 - private final FirmwareService firmwareService; 81 + private final OtaPackageService otaPackageService;
83 private final DeviceService deviceService; 82 private final DeviceService deviceService;
84 private final DeviceProfileService deviceProfileService; 83 private final DeviceProfileService deviceProfileService;
85 private final RuleEngineTelemetryService telemetryService; 84 private final RuleEngineTelemetryService telemetryService;
86 - private final TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> fwStateMsgProducer; 85 + private final TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> otaPackageStateMsgProducer;
87 86
88 - public DefaultFirmwareStateService(TbClusterService tbClusterService, FirmwareService firmwareService,  
89 - DeviceService deviceService,  
90 - DeviceProfileService deviceProfileService,  
91 - RuleEngineTelemetryService telemetryService,  
92 - TbCoreQueueFactory coreQueueFactory) { 87 + public DefaultOtaPackageStateService(TbClusterService tbClusterService, OtaPackageService otaPackageService,
  88 + DeviceService deviceService,
  89 + DeviceProfileService deviceProfileService,
  90 + RuleEngineTelemetryService telemetryService,
  91 + TbCoreQueueFactory coreQueueFactory) {
93 this.tbClusterService = tbClusterService; 92 this.tbClusterService = tbClusterService;
94 - this.firmwareService = firmwareService; 93 + this.otaPackageService = otaPackageService;
95 this.deviceService = deviceService; 94 this.deviceService = deviceService;
96 this.deviceProfileService = deviceProfileService; 95 this.deviceProfileService = deviceProfileService;
97 this.telemetryService = telemetryService; 96 this.telemetryService = telemetryService;
98 - this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer(); 97 + this.otaPackageStateMsgProducer = coreQueueFactory.createToOtaPackageStateServiceMsgProducer();
99 } 98 }
100 99
101 @Override 100 @Override
@@ -105,14 +104,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -105,14 +104,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
105 } 104 }
106 105
107 private void updateFirmware(Device device, Device oldDevice) { 106 private void updateFirmware(Device device, Device oldDevice) {
108 - FirmwareId newFirmwareId = device.getFirmwareId(); 107 + OtaPackageId newFirmwareId = device.getFirmwareId();
109 if (newFirmwareId == null) { 108 if (newFirmwareId == null) {
110 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); 109 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
111 newFirmwareId = newDeviceProfile.getFirmwareId(); 110 newFirmwareId = newDeviceProfile.getFirmwareId();
112 } 111 }
113 if (oldDevice != null) { 112 if (oldDevice != null) {
114 if (newFirmwareId != null) { 113 if (newFirmwareId != null) {
115 - FirmwareId oldFirmwareId = oldDevice.getFirmwareId(); 114 + OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
116 if (oldFirmwareId == null) { 115 if (oldFirmwareId == null) {
117 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); 116 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
118 oldFirmwareId = oldDeviceProfile.getFirmwareId(); 117 oldFirmwareId = oldDeviceProfile.getFirmwareId();
@@ -132,14 +131,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -132,14 +131,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
132 } 131 }
133 132
134 private void updateSoftware(Device device, Device oldDevice) { 133 private void updateSoftware(Device device, Device oldDevice) {
135 - FirmwareId newSoftwareId = device.getSoftwareId(); 134 + OtaPackageId newSoftwareId = device.getSoftwareId();
136 if (newSoftwareId == null) { 135 if (newSoftwareId == null) {
137 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); 136 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
138 newSoftwareId = newDeviceProfile.getSoftwareId(); 137 newSoftwareId = newDeviceProfile.getSoftwareId();
139 } 138 }
140 if (oldDevice != null) { 139 if (oldDevice != null) {
141 if (newSoftwareId != null) { 140 if (newSoftwareId != null) {
142 - FirmwareId oldSoftwareId = oldDevice.getSoftwareId(); 141 + OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
143 if (oldSoftwareId == null) { 142 if (oldSoftwareId == null) {
144 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); 143 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
145 oldSoftwareId = oldDeviceProfile.getSoftwareId(); 144 oldSoftwareId = oldDeviceProfile.getSoftwareId();
@@ -170,33 +169,20 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -170,33 +169,20 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
170 } 169 }
171 } 170 }
172 171
173 - private void update(TenantId tenantId, DeviceProfile deviceProfile, FirmwareType firmwareType) {  
174 - Function<PageLink, PageData<Device>> getDevicesFunction; 172 + private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) {
175 Consumer<Device> updateConsumer; 173 Consumer<Device> updateConsumer;
176 174
177 - switch (firmwareType) {  
178 - case FIRMWARE:  
179 - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pl);  
180 - break;  
181 - case SOFTWARE:  
182 - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId, deviceProfile.getName(), pl);  
183 - break;  
184 - default:  
185 - log.warn("Unsupported firmware type: [{}]", firmwareType);  
186 - return;  
187 - }  
188 -  
189 if (deviceProfile.getFirmwareId() != null) { 175 if (deviceProfile.getFirmwareId() != null) {
190 long ts = System.currentTimeMillis(); 176 long ts = System.currentTimeMillis();
191 - updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, firmwareType); 177 + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType);
192 } else { 178 } else {
193 - updateConsumer = d -> remove(d, firmwareType); 179 + updateConsumer = d -> remove(d, otaPackageType);
194 } 180 }
195 181
196 PageLink pageLink = new PageLink(100); 182 PageLink pageLink = new PageLink(100);
197 PageData<Device> pageData; 183 PageData<Device> pageData;
198 do { 184 do {
199 - pageData = getDevicesFunction.apply(pageLink); 185 + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId, deviceProfile.getId(), otaPackageType, pageLink);
200 pageData.getData().forEach(updateConsumer); 186 pageData.getData().forEach(updateConsumer);
201 187
202 if (pageData.hasNext()) { 188 if (pageData.hasNext()) {
@@ -206,60 +192,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -206,60 +192,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
206 } 192 }
207 193
208 @Override 194 @Override
209 - public boolean process(ToFirmwareStateServiceMsg msg) { 195 + public boolean process(ToOtaPackageStateServiceMsg msg) {
210 boolean isSuccess = false; 196 boolean isSuccess = false;
211 - FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB())); 197 + OtaPackageId targetOtaPackageId = new OtaPackageId(new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()));
212 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); 198 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
213 TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); 199 TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
214 - FirmwareType firmwareType = FirmwareType.valueOf(msg.getType()); 200 + OtaPackageType firmwareType = OtaPackageType.valueOf(msg.getType());
215 long ts = msg.getTs(); 201 long ts = msg.getTs();
216 202
217 Device device = deviceService.findDeviceById(tenantId, deviceId); 203 Device device = deviceService.findDeviceById(tenantId, deviceId);
218 if (device == null) { 204 if (device == null) {
219 log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); 205 log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId);
220 } else { 206 } else {
221 - FirmwareId currentFirmwareId = FirmwareUtil.getFirmwareId(device, firmwareType);  
222 - if (currentFirmwareId == null) { 207 + OtaPackageId currentOtaPackageId = OtaPackageUtil.getOtaPackageId(device, firmwareType);
  208 + if (currentOtaPackageId == null) {
223 DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()); 209 DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId());
224 - currentFirmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); 210 + currentOtaPackageId = OtaPackageUtil.getOtaPackageId(deviceProfile, firmwareType);
225 } 211 }
226 212
227 - if (targetFirmwareId.equals(currentFirmwareId)) {  
228 - update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts); 213 + if (targetOtaPackageId.equals(currentOtaPackageId)) {
  214 + update(device, otaPackageService.findOtaPackageInfoById(device.getTenantId(), targetOtaPackageId), ts);
229 isSuccess = true; 215 isSuccess = true;
230 } else { 216 } else {
231 - log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); 217 + log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetOtaPackageId, currentOtaPackageId);
232 } 218 }
233 } 219 }
234 return isSuccess; 220 return isSuccess;
235 } 221 }
236 222
237 - private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts, FirmwareType firmwareType) {  
238 - ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder() 223 + private void send(TenantId tenantId, DeviceId deviceId, OtaPackageId firmwareId, long ts, OtaPackageType firmwareType) {
  224 + ToOtaPackageStateServiceMsg msg = ToOtaPackageStateServiceMsg.newBuilder()
239 .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) 225 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
240 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) 226 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
241 .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) 227 .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
242 .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) 228 .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
243 - .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits())  
244 - .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()) 229 + .setOtaPackageIdMSB(firmwareId.getId().getMostSignificantBits())
  230 + .setOtaPackageIdLSB(firmwareId.getId().getLeastSignificantBits())
245 .setType(firmwareType.name()) 231 .setType(firmwareType.name())
246 .setTs(ts) 232 .setTs(ts)
247 .build(); 233 .build();
248 234
249 - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId); 235 + OtaPackageInfo firmware = otaPackageService.findOtaPackageInfoById(tenantId, firmwareId);
250 if (firmware == null) { 236 if (firmware == null) {
251 log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId); 237 log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId);
252 return; 238 return;
253 } 239 }
254 240
255 - TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false);  
256 - fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); 241 + TopicPartitionInfo tpi = new TopicPartitionInfo(otaPackageStateMsgProducer.getDefaultTopic(), null, null, false);
  242 + otaPackageStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
257 243
258 List<TsKvEntry> telemetry = new ArrayList<>(); 244 List<TsKvEntry> telemetry = new ArrayList<>();
259 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle()))); 245 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
260 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion()))); 246 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
261 telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); 247 telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
262 - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.QUEUED.name()))); 248 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
263 249
264 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { 250 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
265 @Override 251 @Override
@@ -275,11 +261,11 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -275,11 +261,11 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
275 } 261 }
276 262
277 263
278 - private void update(Device device, FirmwareInfo firmware, long ts) { 264 + private void update(Device device, OtaPackageInfo firmware, long ts) {
279 TenantId tenantId = device.getTenantId(); 265 TenantId tenantId = device.getTenantId();
280 DeviceId deviceId = device.getId(); 266 DeviceId deviceId = device.getId();
281 267
282 - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.INITIATED.name())); 268 + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.INITIATED.name()));
283 269
284 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { 270 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
285 @Override 271 @Override
@@ -297,7 +283,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -297,7 +283,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
297 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle()))); 283 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
298 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion()))); 284 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
299 attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize()))); 285 attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));
300 - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm()))); 286 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));
301 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum()))); 287 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
302 288
303 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { 289 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@@ -313,14 +299,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @@ -313,14 +299,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
313 }); 299 });
314 } 300 }
315 301
316 - private void remove(Device device, FirmwareType firmwareType) {  
317 - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareUtil.getAttributeKeys(firmwareType), 302 + private void remove(Device device, OtaPackageType firmwareType) {
  303 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, OtaPackageUtil.getAttributeKeys(firmwareType),
318 new FutureCallback<>() { 304 new FutureCallback<>() {
319 @Override 305 @Override
320 public void onSuccess(@Nullable Void tmp) { 306 public void onSuccess(@Nullable Void tmp) {
321 log.trace("[{}] Success remove target firmware attributes!", device.getId()); 307 log.trace("[{}] Success remove target firmware attributes!", device.getId());
322 Set<AttributeKey> keysToNotify = new HashSet<>(); 308 Set<AttributeKey> keysToNotify = new HashSet<>();
323 - FirmwareUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); 309 + OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
324 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); 310 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
325 } 311 }
326 312
application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java renamed from application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java
@@ -13,18 +13,18 @@ @@ -13,18 +13,18 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.service.firmware; 16 +package org.thingsboard.server.service.ota;
17 17
18 import org.thingsboard.server.common.data.Device; 18 import org.thingsboard.server.common.data.Device;
19 import org.thingsboard.server.common.data.DeviceProfile; 19 import org.thingsboard.server.common.data.DeviceProfile;
20 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 20 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
21 21
22 -public interface FirmwareStateService { 22 +public interface OtaPackageStateService {
23 23
24 void update(Device device, Device oldDevice); 24 void update(Device device, Device oldDevice);
25 25
26 void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged); 26 void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged);
27 27
28 - boolean process(ToFirmwareStateServiceMsg msg); 28 + boolean process(ToOtaPackageStateServiceMsg msg);
29 29
30 } 30 }
@@ -50,7 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP @@ -50,7 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP
50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; 50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; 51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
52 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; 52 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
53 -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; 53 +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; 54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
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;
@@ -60,9 +60,10 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; @@ -60,9 +60,10 @@ 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;
62 import org.thingsboard.server.service.edge.EdgeNotificationService; 62 import org.thingsboard.server.service.edge.EdgeNotificationService;
63 -import org.thingsboard.server.service.firmware.FirmwareStateService; 63 +import org.thingsboard.server.service.ota.OtaPackageStateService;
64 import org.thingsboard.server.service.profile.TbDeviceProfileCache; 64 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService; 65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
  66 +import org.thingsboard.server.service.queue.processing.IdMsgPair;
66 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; 67 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
67 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; 68 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
68 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; 69 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
@@ -99,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -99,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
99 @Value("${queue.core.stats.enabled:false}") 100 @Value("${queue.core.stats.enabled:false}")
100 private boolean statsEnabled; 101 private boolean statsEnabled;
101 102
102 - @Value("${queue.core.firmware.pack-interval-ms:60000}") 103 + @Value("${queue.core.ota.pack-interval-ms:60000}")
103 private long firmwarePackInterval; 104 private long firmwarePackInterval;
104 - @Value("${queue.core.firmware.pack-size:100}") 105 + @Value("${queue.core.ota.pack-size:100}")
105 private int firmwarePackSize; 106 private int firmwarePackSize;
106 107
107 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer; 108 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer;
@@ -111,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -111,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
111 private final SubscriptionManagerService subscriptionManagerService; 112 private final SubscriptionManagerService subscriptionManagerService;
112 private final TbCoreDeviceRpcService tbCoreDeviceRpcService; 113 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
113 private final EdgeNotificationService edgeNotificationService; 114 private final EdgeNotificationService edgeNotificationService;
114 - private final FirmwareStateService firmwareStateService; 115 + private final OtaPackageStateService firmwareStateService;
115 private final TbCoreConsumerStats stats; 116 private final TbCoreConsumerStats stats;
116 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer; 117 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
117 - private final TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> firmwareStatesConsumer; 118 + private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
118 119
119 protected volatile ExecutorService usageStatsExecutor; 120 protected volatile ExecutorService usageStatsExecutor;
120 121
@@ -133,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -133,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
133 TbTenantProfileCache tenantProfileCache, 134 TbTenantProfileCache tenantProfileCache,
134 TbApiUsageStateService apiUsageStateService, 135 TbApiUsageStateService apiUsageStateService,
135 EdgeNotificationService edgeNotificationService, 136 EdgeNotificationService edgeNotificationService,
136 - FirmwareStateService firmwareStateService) { 137 + OtaPackageStateService firmwareStateService) {
137 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); 138 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
138 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); 139 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
139 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); 140 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
140 - this.firmwareStatesConsumer = tbCoreQueueFactory.createToFirmwareStateServiceMsgConsumer(); 141 + this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
141 this.stateService = stateService; 142 this.stateService = stateService;
142 this.localSubscriptionService = localSubscriptionService; 143 this.localSubscriptionService = localSubscriptionService;
143 this.subscriptionManagerService = subscriptionManagerService; 144 this.subscriptionManagerService = subscriptionManagerService;
@@ -171,7 +172,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -171,7 +172,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
171 public void onApplicationEvent(ApplicationReadyEvent event) { 172 public void onApplicationEvent(ApplicationReadyEvent event) {
172 super.onApplicationEvent(event); 173 super.onApplicationEvent(event);
173 launchUsageStatsConsumer(); 174 launchUsageStatsConsumer();
174 - launchFirmwareUpdateNotificationConsumer(); 175 + launchOtaPackageUpdateNotificationConsumer();
175 } 176 }
176 177
177 @Override 178 @Override
@@ -198,14 +199,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -198,14 +199,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
198 if (msgs.isEmpty()) { 199 if (msgs.isEmpty()) {
199 continue; 200 continue;
200 } 201 }
201 - ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect(  
202 - Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); 202 + List<IdMsgPair<ToCoreMsg>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
  203 + ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = orderedMsgList.stream().collect(
  204 + Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
203 CountDownLatch processingTimeoutLatch = new CountDownLatch(1); 205 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
204 TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>( 206 TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>(
205 processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); 207 processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
206 PendingMsgHolder pendingMsgHolder = new PendingMsgHolder(); 208 PendingMsgHolder pendingMsgHolder = new PendingMsgHolder();
207 Future<?> packSubmitFuture = consumersExecutor.submit(() -> { 209 Future<?> packSubmitFuture = consumersExecutor.submit(() -> {
208 - pendingMap.forEach((id, msg) -> { 210 + orderedMsgList.forEach((element) -> {
  211 + UUID id = element.getUuid();
  212 + TbProtoQueueMsg<ToCoreMsg> msg = element.getMsg();
209 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); 213 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
210 TbCallback callback = new TbPackCallback<>(id, ctx); 214 TbCallback callback = new TbPackCallback<>(id, ctx);
211 try { 215 try {
@@ -223,7 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -223,7 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
223 } else if (toCoreMsg.hasEdgeNotificationMsg()) { 227 } else if (toCoreMsg.hasEdgeNotificationMsg()) {
224 log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg()); 228 log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg());
225 forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback); 229 forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback);
226 - } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { 230 + } else if (!toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
227 Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); 231 Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray());
228 if (actorMsg.isPresent()) { 232 if (actorMsg.isPresent()) {
229 TbActorMsg tbActorMsg = actorMsg.get(); 233 TbActorMsg tbActorMsg = actorMsg.get();
@@ -356,20 +360,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -356,20 +360,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
356 }); 360 });
357 } 361 }
358 362
359 - private void launchFirmwareUpdateNotificationConsumer() { 363 + private void launchOtaPackageUpdateNotificationConsumer() {
360 long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize; 364 long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize;
361 firmwareStatesExecutor.submit(() -> { 365 firmwareStatesExecutor.submit(() -> {
362 while (!stopped) { 366 while (!stopped) {
363 try { 367 try {
364 - List<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); 368 + List<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration());
365 if (msgs.isEmpty()) { 369 if (msgs.isEmpty()) {
366 continue; 370 continue;
367 } 371 }
368 long timeToSleep = maxProcessingTimeoutPerRecord; 372 long timeToSleep = maxProcessingTimeoutPerRecord;
369 - for (TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg : msgs) { 373 + for (TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg : msgs) {
370 try { 374 try {
371 long startTime = System.currentTimeMillis(); 375 long startTime = System.currentTimeMillis();
372 - boolean isSuccessUpdate = handleFirmwareUpdates(msg); 376 + boolean isSuccessUpdate = handleOtaPackageUpdates(msg);
373 long endTime = System.currentTimeMillis(); 377 long endTime = System.currentTimeMillis();
374 long spentTime = endTime - startTime; 378 long spentTime = endTime - startTime;
375 timeToSleep = timeToSleep - spentTime; 379 timeToSleep = timeToSleep - spentTime;
@@ -397,7 +401,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -397,7 +401,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
397 } 401 }
398 } 402 }
399 } 403 }
400 - log.info("TB Firmware States Consumer stopped."); 404 + log.info("TB Ota Package States Consumer stopped.");
401 }); 405 });
402 } 406 }
403 407
@@ -405,7 +409,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore @@ -405,7 +409,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
405 statsService.process(msg, callback); 409 statsService.process(msg, callback);
406 } 410 }
407 411
408 - private boolean handleFirmwareUpdates(TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg) { 412 + private boolean handleOtaPackageUpdates(TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg) {
409 return firmwareStateService.process(msg.getValue()); 413 return firmwareStateService.process(msg.getValue());
410 } 414 }
411 415
@@ -27,7 +27,7 @@ import java.util.stream.Collectors; @@ -27,7 +27,7 @@ import java.util.stream.Collectors;
27 public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy { 27 public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy {
28 28
29 protected final String queueName; 29 protected final String queueName;
30 - protected List<IdMsgPair> orderedMsgList; 30 + protected List<IdMsgPair<TransportProtos.ToRuleEngineMsg>> orderedMsgList;
31 private volatile boolean stopped; 31 private volatile boolean stopped;
32 32
33 public AbstractTbRuleEngineSubmitStrategy(String queueName) { 33 public AbstractTbRuleEngineSubmitStrategy(String queueName) {
@@ -38,7 +38,7 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine @@ -38,7 +38,7 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
38 38
39 @Override 39 @Override
40 public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) { 40 public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) {
41 - orderedMsgList = msgs.stream().map(msg -> new IdMsgPair(UUID.randomUUID(), msg)).collect(Collectors.toList()); 41 + orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
42 } 42 }
43 43
44 @Override 44 @Override
@@ -48,8 +48,8 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine @@ -48,8 +48,8 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
48 48
49 @Override 49 @Override
50 public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { 50 public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) {
51 - List<IdMsgPair> newOrderedMsgList = new ArrayList<>(reprocessMap.size());  
52 - for (IdMsgPair pair : orderedMsgList) { 51 + List<IdMsgPair<TransportProtos.ToRuleEngineMsg>> newOrderedMsgList = new ArrayList<>(reprocessMap.size());
  52 + for (IdMsgPair<TransportProtos.ToRuleEngineMsg> pair : orderedMsgList) {
53 if (reprocessMap.containsKey(pair.uuid)) { 53 if (reprocessMap.containsKey(pair.uuid)) {
54 newOrderedMsgList.add(pair); 54 newOrderedMsgList.add(pair);
55 } 55 }
@@ -15,16 +15,18 @@ @@ -15,16 +15,18 @@
15 */ 15 */
16 package org.thingsboard.server.service.queue.processing; 16 package org.thingsboard.server.service.queue.processing;
17 17
18 -import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 18 +import lombok.Getter;
19 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 19 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
20 20
21 import java.util.UUID; 21 import java.util.UUID;
22 22
23 -public class IdMsgPair { 23 +public class IdMsgPair<T extends com.google.protobuf.GeneratedMessageV3> {
  24 + @Getter
24 final UUID uuid; 25 final UUID uuid;
25 - final TbProtoQueueMsg<ToRuleEngineMsg> msg; 26 + @Getter
  27 + final TbProtoQueueMsg<T> msg;
26 28
27 - public IdMsgPair(UUID uuid, TbProtoQueueMsg<ToRuleEngineMsg> msg) { 29 + public IdMsgPair(UUID uuid, TbProtoQueueMsg<T> msg) {
28 this.uuid = uuid; 30 this.uuid = uuid;
29 this.msg = msg; 31 this.msg = msg;
30 } 32 }
@@ -33,7 +33,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs @@ -33,7 +33,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
33 33
34 private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer; 34 private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer;
35 private volatile ConcurrentMap<UUID, EntityId> msgToEntityIdMap = new ConcurrentHashMap<>(); 35 private volatile ConcurrentMap<UUID, EntityId> msgToEntityIdMap = new ConcurrentHashMap<>();
36 - private volatile ConcurrentMap<EntityId, Queue<IdMsgPair>> entityIdToListMap = new ConcurrentHashMap<>(); 36 + private volatile ConcurrentMap<EntityId, Queue<IdMsgPair<TransportProtos.ToRuleEngineMsg>>> entityIdToListMap = new ConcurrentHashMap<>();
37 37
38 public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) { 38 public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) {
39 super(queueName); 39 super(queueName);
@@ -66,7 +66,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs @@ -66,7 +66,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
66 protected void doOnSuccess(UUID id) { 66 protected void doOnSuccess(UUID id) {
67 EntityId entityId = msgToEntityIdMap.get(id); 67 EntityId entityId = msgToEntityIdMap.get(id);
68 if (entityId != null) { 68 if (entityId != null) {
69 - Queue<IdMsgPair> queue = entityIdToListMap.get(entityId); 69 + Queue<IdMsgPair<TransportProtos.ToRuleEngineMsg>> queue = entityIdToListMap.get(entityId);
70 if (queue != null) { 70 if (queue != null) {
71 IdMsgPair next = null; 71 IdMsgPair next = null;
72 synchronized (queue) { 72 synchronized (queue) {
@@ -86,7 +86,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs @@ -86,7 +86,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
86 private void initMaps() { 86 private void initMaps() {
87 msgToEntityIdMap.clear(); 87 msgToEntityIdMap.clear();
88 entityIdToListMap.clear(); 88 entityIdToListMap.clear();
89 - for (IdMsgPair pair : orderedMsgList) { 89 + for (IdMsgPair<TransportProtos.ToRuleEngineMsg> pair : orderedMsgList) {
90 EntityId entityId = getEntityId(pair.msg.getValue()); 90 EntityId entityId = getEntityId(pair.msg.getValue());
91 if (entityId != null) { 91 if (entityId != null) {
92 msgToEntityIdMap.put(pair.uuid, entityId); 92 msgToEntityIdMap.put(pair.uuid, entityId);
@@ -89,7 +89,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -89,7 +89,7 @@ public class DefaultTbResourceService implements TbResourceService {
89 } catch (InvalidDDFFileException | IOException e) { 89 } catch (InvalidDDFFileException | IOException e) {
90 throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); 90 throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL);
91 } 91 }
92 - if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) { 92 + if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) {
93 throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); 93 throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
94 } 94 }
95 } else { 95 } else {
@@ -131,7 +131,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -131,7 +131,7 @@ public class DefaultTbResourceService implements TbResourceService {
131 List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL, 131 List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
132 objectIds); 132 objectIds);
133 return resources.stream() 133 return resources.stream()
134 - .flatMap(s -> Stream.ofNullable(toLwM2mObject(s))) 134 + .flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false)))
135 .sorted(getComparator(sortProperty, sortOrder)) 135 .sorted(getComparator(sortProperty, sortOrder))
136 .collect(Collectors.toList()); 136 .collect(Collectors.toList());
137 } 137 }
@@ -142,7 +142,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -142,7 +142,7 @@ public class DefaultTbResourceService implements TbResourceService {
142 validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 142 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
143 PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink); 143 PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
144 return resourcePageData.getData().stream() 144 return resourcePageData.getData().stream()
145 - .flatMap(s -> Stream.ofNullable(toLwM2mObject(s))) 145 + .flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false)))
146 .sorted(getComparator(sortProperty, sortOrder)) 146 .sorted(getComparator(sortProperty, sortOrder))
147 .collect(Collectors.toList()); 147 .collect(Collectors.toList());
148 } 148 }
@@ -167,7 +167,7 @@ public class DefaultTbResourceService implements TbResourceService { @@ -167,7 +167,7 @@ public class DefaultTbResourceService implements TbResourceService {
167 return "DESC".equals(sortOrder) ? comparator.reversed() : comparator; 167 return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
168 } 168 }
169 169
170 - private LwM2mObject toLwM2mObject(TbResource resource) { 170 + private LwM2mObject toLwM2mObject(TbResource resource, boolean isSave) {
171 try { 171 try {
172 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); 172 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
173 List<ObjectModel> objectModels = 173 List<ObjectModel> objectModels =
@@ -186,12 +186,16 @@ public class DefaultTbResourceService implements TbResourceService { @@ -186,12 +186,16 @@ public class DefaultTbResourceService implements TbResourceService {
186 instance.setId(0); 186 instance.setId(0);
187 List<LwM2mResourceObserve> resources = new ArrayList<>(); 187 List<LwM2mResourceObserve> resources = new ArrayList<>();
188 obj.resources.forEach((k, v) -> { 188 obj.resources.forEach((k, v) -> {
189 - if (v.operations.isReadable()) { 189 + if (isSave) {
  190 + LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
  191 + resources.add(lwM2MResourceObserve);
  192 + }
  193 + else if (v.operations.isReadable()) {
190 LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); 194 LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
191 resources.add(lwM2MResourceObserve); 195 resources.add(lwM2MResourceObserve);
192 } 196 }
193 }); 197 });
194 - if (resources.size() > 0) { 198 + if (isSave || resources.size() > 0) {
195 instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); 199 instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
196 lwM2mObject.setInstances(new LwM2mInstance[]{instance}); 200 lwM2mObject.setInstances(new LwM2mInstance[]{instance});
197 return lwM2mObject; 201 return lwM2mObject;
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
26 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 26 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
27 import org.springframework.scheduling.annotation.Scheduled; 27 import org.springframework.scheduling.annotation.Scheduled;
28 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
  29 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
29 import org.thingsboard.server.gen.js.JsInvokeProtos; 30 import org.thingsboard.server.gen.js.JsInvokeProtos;
30 import org.thingsboard.server.queue.TbQueueRequestTemplate; 31 import org.thingsboard.server.queue.TbQueueRequestTemplate;
31 import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; 32 import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
@@ -39,6 +40,8 @@ import javax.annotation.PreDestroy; @@ -39,6 +40,8 @@ import javax.annotation.PreDestroy;
39 import java.util.Map; 40 import java.util.Map;
40 import java.util.UUID; 41 import java.util.UUID;
41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.ConcurrentHashMap;
  43 +import java.util.concurrent.ExecutorService;
  44 +import java.util.concurrent.Executors;
42 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException; 46 import java.util.concurrent.TimeoutException;
44 import java.util.concurrent.atomic.AtomicInteger; 47 import java.util.concurrent.atomic.AtomicInteger;
@@ -69,6 +72,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @@ -69,6 +72,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
69 private final AtomicInteger queueEvalMsgs = new AtomicInteger(0); 72 private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
70 private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); 73 private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
71 private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); 74 private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
  75 + private final ExecutorService callbackExecutor = Executors.newFixedThreadPool(
  76 + Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback"));
72 77
73 public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { 78 public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) {
74 super(apiUsageStateService, apiUsageClient); 79 super(apiUsageStateService, apiUsageClient);
@@ -139,7 +144,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @@ -139,7 +144,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
139 } 144 }
140 queueFailedMsgs.incrementAndGet(); 145 queueFailedMsgs.incrementAndGet();
141 } 146 }
142 - }, MoreExecutors.directExecutor()); 147 + }, callbackExecutor);
143 return Futures.transform(future, response -> { 148 return Futures.transform(future, response -> {
144 JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse(); 149 JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse();
145 UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); 150 UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
@@ -151,7 +156,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @@ -151,7 +156,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
151 log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); 156 log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
152 throw new RuntimeException(compilationResult.getErrorDetails()); 157 throw new RuntimeException(compilationResult.getErrorDetails());
153 } 158 }
154 - }, MoreExecutors.directExecutor()); 159 + }, callbackExecutor);
155 } 160 }
156 161
157 @Override 162 @Override
@@ -194,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @@ -194,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
194 } 199 }
195 queueFailedMsgs.incrementAndGet(); 200 queueFailedMsgs.incrementAndGet();
196 } 201 }
197 - }, MoreExecutors.directExecutor()); 202 + }, callbackExecutor);
198 return Futures.transform(future, response -> { 203 return Futures.transform(future, response -> {
199 JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); 204 JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse();
200 if (invokeResult.getSuccess()) { 205 if (invokeResult.getSuccess()) {
@@ -204,7 +209,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @@ -204,7 +209,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
204 log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); 209 log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
205 throw new RuntimeException(invokeResult.getErrorDetails()); 210 throw new RuntimeException(invokeResult.getErrorDetails());
206 } 211 }
207 - }, MoreExecutors.directExecutor()); 212 + }, callbackExecutor);
208 } 213 }
209 214
210 @Override 215 @Override
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Customer; @@ -30,7 +30,7 @@ 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; 33 +import org.thingsboard.server.common.data.OtaPackageInfo;
34 import org.thingsboard.server.common.data.TbResourceInfo; 34 import org.thingsboard.server.common.data.TbResourceInfo;
35 import org.thingsboard.server.common.data.Tenant; 35 import org.thingsboard.server.common.data.Tenant;
36 import org.thingsboard.server.common.data.User; 36 import org.thingsboard.server.common.data.User;
@@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
46 import org.thingsboard.server.common.data.id.EntityId; 46 import org.thingsboard.server.common.data.id.EntityId;
47 import org.thingsboard.server.common.data.id.EntityIdFactory; 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
48 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; 49 +import org.thingsboard.server.common.data.id.OtaPackageId;
50 import org.thingsboard.server.common.data.id.RuleChainId; 50 import org.thingsboard.server.common.data.id.RuleChainId;
51 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; 52 import org.thingsboard.server.common.data.id.TbResourceId;
@@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService;
63 import org.thingsboard.server.dao.edge.EdgeService; 63 import org.thingsboard.server.dao.edge.EdgeService;
64 import org.thingsboard.server.dao.entityview.EntityViewService; 64 import org.thingsboard.server.dao.entityview.EntityViewService;
65 import org.thingsboard.server.dao.exception.IncorrectParameterException; 65 import org.thingsboard.server.dao.exception.IncorrectParameterException;
66 -import org.thingsboard.server.dao.firmware.FirmwareService; 66 +import org.thingsboard.server.dao.ota.OtaPackageService;
67 import org.thingsboard.server.dao.resource.ResourceService; 67 import org.thingsboard.server.dao.resource.ResourceService;
68 import org.thingsboard.server.dao.rule.RuleChainService; 68 import org.thingsboard.server.dao.rule.RuleChainService;
69 import org.thingsboard.server.dao.tenant.TenantService; 69 import org.thingsboard.server.dao.tenant.TenantService;
@@ -135,7 +135,7 @@ public class AccessValidator { @@ -135,7 +135,7 @@ public class AccessValidator {
135 protected ResourceService resourceService; 135 protected ResourceService resourceService;
136 136
137 @Autowired 137 @Autowired
138 - protected FirmwareService firmwareService; 138 + protected OtaPackageService otaPackageService;
139 139
140 private ExecutorService executor; 140 private ExecutorService executor;
141 141
@@ -232,8 +232,8 @@ public class AccessValidator { @@ -232,8 +232,8 @@ public class AccessValidator {
232 case TB_RESOURCE: 232 case TB_RESOURCE:
233 validateResource(currentUser, operation, entityId, callback); 233 validateResource(currentUser, operation, entityId, callback);
234 return; 234 return;
235 - case FIRMWARE:  
236 - validateFirmware(currentUser, operation, entityId, callback); 235 + case OTA_PACKAGE:
  236 + validateOtaPackage(currentUser, operation, entityId, callback);
237 return; 237 return;
238 default: 238 default:
239 //TODO: add support of other entities 239 //TODO: add support of other entities
@@ -300,20 +300,20 @@ public class AccessValidator { @@ -300,20 +300,20 @@ public class AccessValidator {
300 } 300 }
301 } 301 }
302 302
303 - private void validateFirmware(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { 303 + private void validateOtaPackage(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
304 if (currentUser.isSystemAdmin()) { 304 if (currentUser.isSystemAdmin()) {
305 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); 305 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
306 } else { 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!")); 307 + OtaPackageInfo otaPackage = otaPackageService.findOtaPackageInfoById(currentUser.getTenantId(), new OtaPackageId(entityId.getId()));
  308 + if (otaPackage == null) {
  309 + callback.onSuccess(ValidationResult.entityNotFound("OtaPackage with requested id wasn't found!"));
310 } else { 310 } else {
311 try { 311 try {
312 - accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware); 312 + accessControlService.checkPermission(currentUser, Resource.OTA_PACKAGE, operation, entityId, otaPackage);
313 } catch (ThingsboardException e) { 313 } catch (ThingsboardException e) {
314 callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); 314 callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
315 } 315 }
316 - callback.onSuccess(ValidationResult.ok(firmware)); 316 + callback.onSuccess(ValidationResult.ok(otaPackage));
317 } 317 }
318 } 318 }
319 } 319 }
@@ -38,7 +38,7 @@ public enum Resource { @@ -38,7 +38,7 @@ public enum Resource {
38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE), 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
39 API_USAGE_STATE(EntityType.API_USAGE_STATE), 39 API_USAGE_STATE(EntityType.API_USAGE_STATE),
40 TB_RESOURCE(EntityType.TB_RESOURCE), 40 TB_RESOURCE(EntityType.TB_RESOURCE),
41 - FIRMWARE(EntityType.FIRMWARE), 41 + OTA_PACKAGE(EntityType.OTA_PACKAGE),
42 EDGE(EntityType.EDGE); 42 EDGE(EntityType.EDGE);
43 43
44 private final EntityType entityType; 44 private final EntityType entityType;
@@ -42,7 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions { @@ -42,7 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); 42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); 43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker); 44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
45 - put(Resource.FIRMWARE, tenantEntityPermissionChecker); 45 + put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker);
46 put(Resource.EDGE, tenantEntityPermissionChecker); 46 put(Resource.EDGE, tenantEntityPermissionChecker);
47 } 47 }
48 48
@@ -26,27 +26,27 @@ import lombok.extern.slf4j.Slf4j; @@ -26,27 +26,27 @@ import lombok.extern.slf4j.Slf4j;
26 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
27 import org.springframework.util.StringUtils; 27 import org.springframework.util.StringUtils;
28 import org.thingsboard.common.util.JacksonUtil; 28 import org.thingsboard.common.util.JacksonUtil;
29 -import org.thingsboard.server.cache.firmware.FirmwareDataCache; 29 +import org.thingsboard.server.cache.ota.OtaPackageDataCache;
30 import org.thingsboard.server.common.data.ApiUsageState; 30 import org.thingsboard.server.common.data.ApiUsageState;
31 import org.thingsboard.server.common.data.DataConstants; 31 import org.thingsboard.server.common.data.DataConstants;
32 import org.thingsboard.server.common.data.Device; 32 import org.thingsboard.server.common.data.Device;
33 import org.thingsboard.server.common.data.DeviceProfile; 33 import org.thingsboard.server.common.data.DeviceProfile;
34 import org.thingsboard.server.common.data.DeviceTransportType; 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;  
37 -import org.thingsboard.server.common.data.FirmwareInfo; 36 +import org.thingsboard.server.common.data.OtaPackage;
  37 +import org.thingsboard.server.common.data.OtaPackageInfo;
38 import org.thingsboard.server.common.data.ResourceType; 38 import org.thingsboard.server.common.data.ResourceType;
39 import org.thingsboard.server.common.data.TbResource; 39 import org.thingsboard.server.common.data.TbResource;
40 import org.thingsboard.server.common.data.TenantProfile; 40 import org.thingsboard.server.common.data.TenantProfile;
41 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 41 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
42 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; 42 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
43 import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; 43 import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
44 -import org.thingsboard.server.common.data.firmware.FirmwareType;  
45 -import org.thingsboard.server.common.data.firmware.FirmwareUtil; 44 +import org.thingsboard.server.common.data.ota.OtaPackageType;
  45 +import org.thingsboard.server.common.data.ota.OtaPackageUtil;
46 import org.thingsboard.server.common.data.id.CustomerId; 46 import org.thingsboard.server.common.data.id.CustomerId;
47 import org.thingsboard.server.common.data.id.DeviceId; 47 import org.thingsboard.server.common.data.id.DeviceId;
48 import org.thingsboard.server.common.data.id.DeviceProfileId; 48 import org.thingsboard.server.common.data.id.DeviceProfileId;
49 -import org.thingsboard.server.common.data.id.FirmwareId; 49 +import org.thingsboard.server.common.data.id.OtaPackageId;
50 import org.thingsboard.server.common.data.id.TenantId; 50 import org.thingsboard.server.common.data.id.TenantId;
51 import org.thingsboard.server.common.data.page.PageData; 51 import org.thingsboard.server.common.data.page.PageData;
52 import org.thingsboard.server.common.data.page.PageLink; 52 import org.thingsboard.server.common.data.page.PageLink;
@@ -64,7 +64,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -64,7 +64,7 @@ import org.thingsboard.server.dao.device.DeviceService;
64 import org.thingsboard.server.dao.device.provision.ProvisionFailedException; 64 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
65 import org.thingsboard.server.dao.device.provision.ProvisionRequest; 65 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
66 import org.thingsboard.server.dao.device.provision.ProvisionResponse; 66 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
67 -import org.thingsboard.server.dao.firmware.FirmwareService; 67 +import org.thingsboard.server.dao.ota.OtaPackageService;
68 import org.thingsboard.server.dao.relation.RelationService; 68 import org.thingsboard.server.dao.relation.RelationService;
69 import org.thingsboard.server.dao.tenant.TbTenantProfileCache; 69 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
70 import org.thingsboard.server.gen.transport.TransportProtos; 70 import org.thingsboard.server.gen.transport.TransportProtos;
@@ -124,8 +124,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -124,8 +124,8 @@ public class DefaultTransportApiService implements TransportApiService {
124 private final DataDecodingEncodingService dataDecodingEncodingService; 124 private final DataDecodingEncodingService dataDecodingEncodingService;
125 private final DeviceProvisionService deviceProvisionService; 125 private final DeviceProvisionService deviceProvisionService;
126 private final TbResourceService resourceService; 126 private final TbResourceService resourceService;
127 - private final FirmwareService firmwareService;  
128 - private final FirmwareDataCache firmwareDataCache; 127 + private final OtaPackageService otaPackageService;
  128 + private final OtaPackageDataCache otaPackageDataCache;
129 129
130 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); 130 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
131 131
@@ -134,7 +134,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -134,7 +134,7 @@ public class DefaultTransportApiService implements TransportApiService {
134 RelationService relationService, DeviceCredentialsService deviceCredentialsService, 134 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
135 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, 135 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
136 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, 136 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
137 - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) { 137 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, OtaPackageService otaPackageService, OtaPackageDataCache otaPackageDataCache) {
138 this.deviceProfileCache = deviceProfileCache; 138 this.deviceProfileCache = deviceProfileCache;
139 this.tenantProfileCache = tenantProfileCache; 139 this.tenantProfileCache = tenantProfileCache;
140 this.apiUsageStateService = apiUsageStateService; 140 this.apiUsageStateService = apiUsageStateService;
@@ -147,8 +147,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -147,8 +147,8 @@ public class DefaultTransportApiService implements TransportApiService {
147 this.dataDecodingEncodingService = dataDecodingEncodingService; 147 this.dataDecodingEncodingService = dataDecodingEncodingService;
148 this.deviceProvisionService = deviceProvisionService; 148 this.deviceProvisionService = deviceProvisionService;
149 this.resourceService = resourceService; 149 this.resourceService = resourceService;
150 - this.firmwareService = firmwareService;  
151 - this.firmwareDataCache = firmwareDataCache; 150 + this.otaPackageService = otaPackageService;
  151 + this.otaPackageDataCache = otaPackageDataCache;
152 } 152 }
153 153
154 @Override 154 @Override
@@ -184,8 +184,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -184,8 +184,8 @@ public class DefaultTransportApiService implements TransportApiService {
184 result = handle(transportApiRequestMsg.getDeviceRequestMsg()); 184 result = handle(transportApiRequestMsg.getDeviceRequestMsg());
185 } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) { 185 } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) {
186 result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg()); 186 result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg());
187 - } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {  
188 - result = handle(transportApiRequestMsg.getFirmwareRequestMsg()); 187 + } else if (transportApiRequestMsg.hasOtaPackageRequestMsg()) {
  188 + result = handle(transportApiRequestMsg.getOtaPackageRequestMsg());
189 } 189 }
190 190
191 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture), 191 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
@@ -511,50 +511,50 @@ public class DefaultTransportApiService implements TransportApiService { @@ -511,50 +511,50 @@ public class DefaultTransportApiService implements TransportApiService {
511 } 511 }
512 } 512 }
513 513
514 - private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetFirmwareRequestMsg requestMsg) { 514 + private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetOtaPackageRequestMsg requestMsg) {
515 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); 515 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
516 DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); 516 DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
517 - FirmwareType firmwareType = FirmwareType.valueOf(requestMsg.getType()); 517 + OtaPackageType otaPackageType = OtaPackageType.valueOf(requestMsg.getType());
518 Device device = deviceService.findDeviceById(tenantId, deviceId); 518 Device device = deviceService.findDeviceById(tenantId, deviceId);
519 519
520 if (device == null) { 520 if (device == null) {
521 return getEmptyTransportApiResponseFuture(); 521 return getEmptyTransportApiResponseFuture();
522 } 522 }
523 523
524 - FirmwareId firmwareId = FirmwareUtil.getFirmwareId(device, firmwareType);  
525 - if (firmwareId == null) { 524 + OtaPackageId otaPackageId = OtaPackageUtil.getOtaPackageId(device, otaPackageType);
  525 + if (otaPackageId == null) {
526 DeviceProfile deviceProfile = deviceProfileCache.find(device.getDeviceProfileId()); 526 DeviceProfile deviceProfile = deviceProfileCache.find(device.getDeviceProfileId());
527 - firmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); 527 + otaPackageId = OtaPackageUtil.getOtaPackageId(deviceProfile, otaPackageType);
528 } 528 }
529 529
530 - TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder(); 530 + TransportProtos.GetOtaPackageResponseMsg.Builder builder = TransportProtos.GetOtaPackageResponseMsg.newBuilder();
531 531
532 - if (firmwareId == null) { 532 + if (otaPackageId == null) {
533 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); 533 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
534 } else { 534 } else {
535 - FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(tenantId, firmwareId); 535 + OtaPackageInfo otaPackageInfo = otaPackageService.findOtaPackageInfoById(tenantId, otaPackageId);
536 536
537 - if (firmwareInfo == null) { 537 + if (otaPackageInfo == null) {
538 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); 538 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
539 } else { 539 } else {
540 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); 540 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
541 - builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());  
542 - builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits());  
543 - builder.setType(firmwareInfo.getType().name());  
544 - builder.setTitle(firmwareInfo.getTitle());  
545 - builder.setVersion(firmwareInfo.getVersion());  
546 - builder.setFileName(firmwareInfo.getFileName());  
547 - builder.setContentType(firmwareInfo.getContentType());  
548 - if (!firmwareDataCache.has(firmwareId.toString())) {  
549 - Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);  
550 - firmwareDataCache.put(firmwareId.toString(), firmware.getData().array()); 541 + builder.setOtaPackageIdMSB(otaPackageId.getId().getMostSignificantBits());
  542 + builder.setOtaPackageIdLSB(otaPackageId.getId().getLeastSignificantBits());
  543 + builder.setType(otaPackageInfo.getType().name());
  544 + builder.setTitle(otaPackageInfo.getTitle());
  545 + builder.setVersion(otaPackageInfo.getVersion());
  546 + builder.setFileName(otaPackageInfo.getFileName());
  547 + builder.setContentType(otaPackageInfo.getContentType());
  548 + if (!otaPackageDataCache.has(otaPackageId.toString())) {
  549 + OtaPackage otaPackage = otaPackageService.findOtaPackageById(tenantId, otaPackageId);
  550 + otaPackageDataCache.put(otaPackageId.toString(), otaPackage.getData().array());
551 } 551 }
552 } 552 }
553 } 553 }
554 554
555 return Futures.immediateFuture( 555 return Futures.immediateFuture(
556 TransportApiResponseMsg.newBuilder() 556 TransportApiResponseMsg.newBuilder()
557 - .setFirmwareResponseMsg(builder.build()) 557 + .setOtaPackageResponseMsg(builder.build())
558 .build()); 558 .build());
559 } 559 }
560 560
@@ -371,7 +371,10 @@ caffeine: @@ -371,7 +371,10 @@ caffeine:
371 tokensOutdatageTime: 371 tokensOutdatageTime:
372 timeToLiveInMinutes: 20000 372 timeToLiveInMinutes: 20000
373 maxSize: 10000 373 maxSize: 10000
374 - firmwares: 374 + otaPackages:
  375 + timeToLiveInMinutes: 60
  376 + maxSize: 10
  377 + otaPackagesData:
375 timeToLiveInMinutes: 60 378 timeToLiveInMinutes: 60
376 maxSize: 10 379 maxSize: 10
377 edges: 380 edges:
@@ -497,7 +500,7 @@ audit-log: @@ -497,7 +500,7 @@ audit-log:
497 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" 500 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
498 "edge": "${AUDIT_LOG_MASK_EDGE:W}" 501 "edge": "${AUDIT_LOG_MASK_EDGE:W}"
499 "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}" 502 "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}"
500 - "firmware": "${AUDIT_LOG_MASK_FIRMWARE:W}" 503 + "ota_package": "${AUDIT_LOG_MASK_OTA_PACKAGE:W}"
501 sink: 504 sink:
502 # Type of external sink. possible options: none, elasticsearch 505 # Type of external sink. possible options: none, elasticsearch
503 type: "${AUDIT_LOG_SINK_TYPE:none}" 506 type: "${AUDIT_LOG_SINK_TYPE:none}"
@@ -643,6 +646,7 @@ transport: @@ -643,6 +646,7 @@ transport:
643 private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" 646 private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
644 # Only Certificate_x509: 647 # Only Certificate_x509:
645 alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" 648 alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
  649 + skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
646 bootstrap: 650 bootstrap:
647 enable: "${LWM2M_ENABLED_BS:true}" 651 enable: "${LWM2M_ENABLED_BS:true}"
648 id: "${LWM2M_SERVER_ID_BS:111}" 652 id: "${LWM2M_SERVER_ID_BS:111}"
@@ -748,7 +752,7 @@ queue: @@ -748,7 +752,7 @@ queue:
748 sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" 752 sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
749 security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" 753 security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
750 consumer-properties-per-topic: 754 consumer-properties-per-topic:
751 - tb_firmware: 755 + tb_ota_package:
752 - key: max.poll.records 756 - key: max.poll.records
753 value: 10 757 value: 10
754 other: 758 other:
@@ -758,7 +762,7 @@ queue: @@ -758,7 +762,7 @@ queue:
758 transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" 762 transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
759 notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" 763 notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
760 js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" 764 js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}"
761 - fw-updates: "${TB_QUEUE_KAFKA_FW_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" 765 + ota-updates: "${TB_QUEUE_KAFKA_OTA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
762 consumer-stats: 766 consumer-stats:
763 enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" 767 enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
764 print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" 768 print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
@@ -829,10 +833,10 @@ queue: @@ -829,10 +833,10 @@ queue:
829 poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" 833 poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
830 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" 834 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
831 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" 835 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}"
832 - firmware:  
833 - topic: "${TB_QUEUE_CORE_FW_TOPIC:tb_firmware}"  
834 - pack-interval-ms: "${TB_QUEUE_CORE_FW_PACK_INTERVAL_MS:60000}"  
835 - pack-size: "${TB_QUEUE_CORE_FW_PACK_SIZE:100}" 836 + ota:
  837 + topic: "${TB_QUEUE_CORE_OTA_TOPIC:tb_ota_package}"
  838 + pack-interval-ms: "${TB_QUEUE_CORE_OTA_PACK_INTERVAL_MS:60000}"
  839 + pack-size: "${TB_QUEUE_CORE_OTA_PACK_SIZE:100}"
836 usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" 840 usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
837 stats: 841 stats:
838 enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" 842 enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
@@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims; @@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims;
22 import io.jsonwebtoken.Header; 22 import io.jsonwebtoken.Header;
23 import io.jsonwebtoken.Jwt; 23 import io.jsonwebtoken.Jwt;
24 import io.jsonwebtoken.Jwts; 24 import io.jsonwebtoken.Jwts;
  25 +import lombok.Getter;
25 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
26 import org.apache.commons.lang3.RandomStringUtils; 27 import org.apache.commons.lang3.RandomStringUtils;
27 import org.apache.commons.lang3.StringUtils; 28 import org.apache.commons.lang3.StringUtils;
@@ -120,7 +121,7 @@ public abstract class AbstractWebTest { @@ -120,7 +121,7 @@ public abstract class AbstractWebTest {
120 protected String refreshToken; 121 protected String refreshToken;
121 protected String username; 122 protected String username;
122 123
123 - private TenantId tenantId; 124 + protected TenantId tenantId;
124 125
125 @SuppressWarnings("rawtypes") 126 @SuppressWarnings("rawtypes")
126 private HttpMessageConverter mappingJackson2HttpMessageConverter; 127 private HttpMessageConverter mappingJackson2HttpMessageConverter;
application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java renamed from application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java
@@ -25,11 +25,10 @@ import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequ @@ -25,11 +25,10 @@ import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequ
25 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 25 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
26 import org.thingsboard.common.util.JacksonUtil; 26 import org.thingsboard.common.util.JacksonUtil;
27 import org.thingsboard.server.common.data.DeviceProfile; 27 import org.thingsboard.server.common.data.DeviceProfile;
28 -import org.thingsboard.server.common.data.Firmware;  
29 -import org.thingsboard.server.common.data.FirmwareInfo; 28 +import org.thingsboard.server.common.data.OtaPackage;
  29 +import org.thingsboard.server.common.data.OtaPackageInfo;
30 import org.thingsboard.server.common.data.Tenant; 30 import org.thingsboard.server.common.data.Tenant;
31 import org.thingsboard.server.common.data.User; 31 import org.thingsboard.server.common.data.User;
32 -import org.thingsboard.server.common.data.firmware.FirmwareType;  
33 import org.thingsboard.server.common.data.id.DeviceProfileId; 32 import org.thingsboard.server.common.data.id.DeviceProfileId;
34 import org.thingsboard.server.common.data.page.PageData; 33 import org.thingsboard.server.common.data.page.PageData;
35 import org.thingsboard.server.common.data.page.PageLink; 34 import org.thingsboard.server.common.data.page.PageLink;
@@ -41,11 +40,11 @@ import java.util.Collections; @@ -41,11 +40,11 @@ import java.util.Collections;
41 import java.util.List; 40 import java.util.List;
42 41
43 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 42 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
44 -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; 43 +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
45 44
46 -public abstract class BaseFirmwareControllerTest extends AbstractControllerTest { 45 +public abstract class BaseOtaPackageControllerTest extends AbstractControllerTest {
47 46
48 - private IdComparator<FirmwareInfo> idComparator = new IdComparator<>(); 47 + private IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
49 48
50 public static final String TITLE = "My firmware"; 49 public static final String TITLE = "My firmware";
51 private static final String FILE_NAME = "filename.txt"; 50 private static final String FILE_NAME = "filename.txt";
@@ -93,13 +92,13 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -93,13 +92,13 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
93 92
94 @Test 93 @Test
95 public void testSaveFirmware() throws Exception { 94 public void testSaveFirmware() throws Exception {
96 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 95 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
97 firmwareInfo.setDeviceProfileId(deviceProfileId); 96 firmwareInfo.setDeviceProfileId(deviceProfileId);
98 firmwareInfo.setType(FIRMWARE); 97 firmwareInfo.setType(FIRMWARE);
99 firmwareInfo.setTitle(TITLE); 98 firmwareInfo.setTitle(TITLE);
100 firmwareInfo.setVersion(VERSION); 99 firmwareInfo.setVersion(VERSION);
101 100
102 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 101 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
103 102
104 Assert.assertNotNull(savedFirmwareInfo); 103 Assert.assertNotNull(savedFirmwareInfo);
105 Assert.assertNotNull(savedFirmwareInfo.getId()); 104 Assert.assertNotNull(savedFirmwareInfo.getId());
@@ -112,19 +111,19 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -112,19 +111,19 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
112 111
113 save(savedFirmwareInfo); 112 save(savedFirmwareInfo);
114 113
115 - FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); 114 + OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class);
116 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); 115 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
117 } 116 }
118 117
119 @Test 118 @Test
120 public void testSaveFirmwareData() throws Exception { 119 public void testSaveFirmwareData() throws Exception {
121 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 120 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
122 firmwareInfo.setDeviceProfileId(deviceProfileId); 121 firmwareInfo.setDeviceProfileId(deviceProfileId);
123 firmwareInfo.setType(FIRMWARE); 122 firmwareInfo.setType(FIRMWARE);
124 firmwareInfo.setTitle(TITLE); 123 firmwareInfo.setTitle(TITLE);
125 firmwareInfo.setVersion(VERSION); 124 firmwareInfo.setVersion(VERSION);
126 125
127 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 126 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
128 127
129 Assert.assertNotNull(savedFirmwareInfo); 128 Assert.assertNotNull(savedFirmwareInfo);
130 Assert.assertNotNull(savedFirmwareInfo.getId()); 129 Assert.assertNotNull(savedFirmwareInfo.getId());
@@ -137,12 +136,12 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -137,12 +136,12 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
137 136
138 save(savedFirmwareInfo); 137 save(savedFirmwareInfo);
139 138
140 - FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); 139 + OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class);
141 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); 140 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
142 141
143 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); 142 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
144 143
145 - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); 144 + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
146 145
147 Assert.assertEquals(FILE_NAME, savedFirmware.getFileName()); 146 Assert.assertEquals(FILE_NAME, savedFirmware.getFileName());
148 Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType()); 147 Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType());
@@ -150,97 +149,97 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -150,97 +149,97 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
150 149
151 @Test 150 @Test
152 public void testUpdateFirmwareFromDifferentTenant() throws Exception { 151 public void testUpdateFirmwareFromDifferentTenant() throws Exception {
153 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 152 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
154 firmwareInfo.setDeviceProfileId(deviceProfileId); 153 firmwareInfo.setDeviceProfileId(deviceProfileId);
155 firmwareInfo.setType(FIRMWARE); 154 firmwareInfo.setType(FIRMWARE);
156 firmwareInfo.setTitle(TITLE); 155 firmwareInfo.setTitle(TITLE);
157 firmwareInfo.setVersion(VERSION); 156 firmwareInfo.setVersion(VERSION);
158 157
159 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 158 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
160 159
161 loginDifferentTenant(); 160 loginDifferentTenant();
162 - doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden()); 161 + doPost("/api/otaPackage", savedFirmwareInfo, OtaPackageInfo.class, status().isForbidden());
163 deleteDifferentTenant(); 162 deleteDifferentTenant();
164 } 163 }
165 164
166 @Test 165 @Test
167 public void testFindFirmwareInfoById() throws Exception { 166 public void testFindFirmwareInfoById() throws Exception {
168 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 167 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
169 firmwareInfo.setDeviceProfileId(deviceProfileId); 168 firmwareInfo.setDeviceProfileId(deviceProfileId);
170 firmwareInfo.setType(FIRMWARE); 169 firmwareInfo.setType(FIRMWARE);
171 firmwareInfo.setTitle(TITLE); 170 firmwareInfo.setTitle(TITLE);
172 firmwareInfo.setVersion(VERSION); 171 firmwareInfo.setVersion(VERSION);
173 172
174 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 173 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
175 174
176 - FirmwareInfo foundFirmware = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); 175 + OtaPackageInfo foundFirmware = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class);
177 Assert.assertNotNull(foundFirmware); 176 Assert.assertNotNull(foundFirmware);
178 Assert.assertEquals(savedFirmwareInfo, foundFirmware); 177 Assert.assertEquals(savedFirmwareInfo, foundFirmware);
179 } 178 }
180 179
181 @Test 180 @Test
182 public void testFindFirmwareById() throws Exception { 181 public void testFindFirmwareById() throws Exception {
183 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 182 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
184 firmwareInfo.setDeviceProfileId(deviceProfileId); 183 firmwareInfo.setDeviceProfileId(deviceProfileId);
185 firmwareInfo.setType(FIRMWARE); 184 firmwareInfo.setType(FIRMWARE);
186 firmwareInfo.setTitle(TITLE); 185 firmwareInfo.setTitle(TITLE);
187 firmwareInfo.setVersion(VERSION); 186 firmwareInfo.setVersion(VERSION);
188 187
189 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 188 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
190 189
191 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); 190 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
192 191
193 - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); 192 + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
194 193
195 - Firmware foundFirmware = doGet("/api/firmware/" + savedFirmwareInfo.getId().getId().toString(), Firmware.class); 194 + OtaPackage foundFirmware = doGet("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString(), OtaPackage.class);
196 Assert.assertNotNull(foundFirmware); 195 Assert.assertNotNull(foundFirmware);
197 Assert.assertEquals(savedFirmware, foundFirmware); 196 Assert.assertEquals(savedFirmware, foundFirmware);
198 } 197 }
199 198
200 @Test 199 @Test
201 public void testDeleteFirmware() throws Exception { 200 public void testDeleteFirmware() throws Exception {
202 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 201 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
203 firmwareInfo.setDeviceProfileId(deviceProfileId); 202 firmwareInfo.setDeviceProfileId(deviceProfileId);
204 firmwareInfo.setType(FIRMWARE); 203 firmwareInfo.setType(FIRMWARE);
205 firmwareInfo.setTitle(TITLE); 204 firmwareInfo.setTitle(TITLE);
206 firmwareInfo.setVersion(VERSION); 205 firmwareInfo.setVersion(VERSION);
207 206
208 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 207 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
209 208
210 - doDelete("/api/firmware/" + savedFirmwareInfo.getId().getId().toString()) 209 + doDelete("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString())
211 .andExpect(status().isOk()); 210 .andExpect(status().isOk());
212 211
213 - doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString()) 212 + doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString())
214 .andExpect(status().isNotFound()); 213 .andExpect(status().isNotFound());
215 } 214 }
216 215
217 @Test 216 @Test
218 public void testFindTenantFirmwares() throws Exception { 217 public void testFindTenantFirmwares() throws Exception {
219 - List<FirmwareInfo> firmwares = new ArrayList<>(); 218 + List<OtaPackageInfo> otaPackages = new ArrayList<>();
220 for (int i = 0; i < 165; i++) { 219 for (int i = 0; i < 165; i++) {
221 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 220 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
222 firmwareInfo.setDeviceProfileId(deviceProfileId); 221 firmwareInfo.setDeviceProfileId(deviceProfileId);
223 firmwareInfo.setType(FIRMWARE); 222 firmwareInfo.setType(FIRMWARE);
224 firmwareInfo.setTitle(TITLE); 223 firmwareInfo.setTitle(TITLE);
225 firmwareInfo.setVersion(VERSION + i); 224 firmwareInfo.setVersion(VERSION + i);
226 225
227 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 226 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
228 227
229 if (i > 100) { 228 if (i > 100) {
230 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); 229 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
231 230
232 - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);  
233 - firmwares.add(new FirmwareInfo(savedFirmware)); 231 + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  232 + otaPackages.add(new OtaPackageInfo(savedFirmware));
234 } else { 233 } else {
235 - firmwares.add(savedFirmwareInfo); 234 + otaPackages.add(savedFirmwareInfo);
236 } 235 }
237 } 236 }
238 237
239 - List<FirmwareInfo> loadedFirmwares = new ArrayList<>(); 238 + List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
240 PageLink pageLink = new PageLink(24); 239 PageLink pageLink = new PageLink(24);
241 - PageData<FirmwareInfo> pageData; 240 + PageData<OtaPackageInfo> pageData;
242 do { 241 do {
243 - pageData = doGetTypedWithPageLink("/api/firmwares?", 242 + pageData = doGetTypedWithPageLink("/api/otaPackages?",
244 new TypeReference<>() { 243 new TypeReference<>() {
245 }, pageLink); 244 }, pageLink);
246 loadedFirmwares.addAll(pageData.getData()); 245 loadedFirmwares.addAll(pageData.getData());
@@ -249,41 +248,41 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -249,41 +248,41 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
249 } 248 }
250 } while (pageData.hasNext()); 249 } while (pageData.hasNext());
251 250
252 - Collections.sort(firmwares, idComparator); 251 + Collections.sort(otaPackages, idComparator);
253 Collections.sort(loadedFirmwares, idComparator); 252 Collections.sort(loadedFirmwares, idComparator);
254 253
255 - Assert.assertEquals(firmwares, loadedFirmwares); 254 + Assert.assertEquals(otaPackages, loadedFirmwares);
256 } 255 }
257 256
258 @Test 257 @Test
259 public void testFindTenantFirmwaresByHasData() throws Exception { 258 public void testFindTenantFirmwaresByHasData() throws Exception {
260 - List<FirmwareInfo> firmwaresWithData = new ArrayList<>();  
261 - List<FirmwareInfo> firmwaresWithoutData = new ArrayList<>(); 259 + List<OtaPackageInfo> otaPackagesWithData = new ArrayList<>();
  260 + List<OtaPackageInfo> otaPackagesWithoutData = new ArrayList<>();
262 261
263 for (int i = 0; i < 165; i++) { 262 for (int i = 0; i < 165; i++) {
264 - FirmwareInfo firmwareInfo = new FirmwareInfo(); 263 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
265 firmwareInfo.setDeviceProfileId(deviceProfileId); 264 firmwareInfo.setDeviceProfileId(deviceProfileId);
266 firmwareInfo.setType(FIRMWARE); 265 firmwareInfo.setType(FIRMWARE);
267 firmwareInfo.setTitle(TITLE); 266 firmwareInfo.setTitle(TITLE);
268 firmwareInfo.setVersion(VERSION + i); 267 firmwareInfo.setVersion(VERSION + i);
269 268
270 - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); 269 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
271 270
272 if (i > 100) { 271 if (i > 100) {
273 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); 272 MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
274 273
275 - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);  
276 - firmwaresWithData.add(new FirmwareInfo(savedFirmware)); 274 + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  275 + otaPackagesWithData.add(new OtaPackageInfo(savedFirmware));
277 } else { 276 } else {
278 - firmwaresWithoutData.add(savedFirmwareInfo); 277 + otaPackagesWithoutData.add(savedFirmwareInfo);
279 } 278 }
280 } 279 }
281 280
282 - List<FirmwareInfo> loadedFirmwaresWithData = new ArrayList<>(); 281 + List<OtaPackageInfo> loadedFirmwaresWithData = new ArrayList<>();
283 PageLink pageLink = new PageLink(24); 282 PageLink pageLink = new PageLink(24);
284 - PageData<FirmwareInfo> pageData; 283 + PageData<OtaPackageInfo> pageData;
285 do { 284 do {
286 - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/true?", 285 + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/true?",
287 new TypeReference<>() { 286 new TypeReference<>() {
288 }, pageLink); 287 }, pageLink);
289 loadedFirmwaresWithData.addAll(pageData.getData()); 288 loadedFirmwaresWithData.addAll(pageData.getData());
@@ -292,10 +291,10 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -292,10 +291,10 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
292 } 291 }
293 } while (pageData.hasNext()); 292 } while (pageData.hasNext());
294 293
295 - List<FirmwareInfo> loadedFirmwaresWithoutData = new ArrayList<>(); 294 + List<OtaPackageInfo> loadedFirmwaresWithoutData = new ArrayList<>();
296 pageLink = new PageLink(24); 295 pageLink = new PageLink(24);
297 do { 296 do {
298 - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/false?", 297 + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/false?",
299 new TypeReference<>() { 298 new TypeReference<>() {
300 }, pageLink); 299 }, pageLink);
301 loadedFirmwaresWithoutData.addAll(pageData.getData()); 300 loadedFirmwaresWithoutData.addAll(pageData.getData());
@@ -304,25 +303,25 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @@ -304,25 +303,25 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
304 } 303 }
305 } while (pageData.hasNext()); 304 } while (pageData.hasNext());
306 305
307 - Collections.sort(firmwaresWithData, idComparator);  
308 - Collections.sort(firmwaresWithoutData, idComparator); 306 + Collections.sort(otaPackagesWithData, idComparator);
  307 + Collections.sort(otaPackagesWithoutData, idComparator);
309 Collections.sort(loadedFirmwaresWithData, idComparator); 308 Collections.sort(loadedFirmwaresWithData, idComparator);
310 Collections.sort(loadedFirmwaresWithoutData, idComparator); 309 Collections.sort(loadedFirmwaresWithoutData, idComparator);
311 310
312 - Assert.assertEquals(firmwaresWithData, loadedFirmwaresWithData);  
313 - Assert.assertEquals(firmwaresWithoutData, loadedFirmwaresWithoutData); 311 + Assert.assertEquals(otaPackagesWithData, loadedFirmwaresWithData);
  312 + Assert.assertEquals(otaPackagesWithoutData, loadedFirmwaresWithoutData);
314 } 313 }
315 314
316 315
317 - private FirmwareInfo save(FirmwareInfo firmwareInfo) throws Exception {  
318 - return doPost("/api/firmware", firmwareInfo, FirmwareInfo.class); 316 + private OtaPackageInfo save(OtaPackageInfo firmwareInfo) throws Exception {
  317 + return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
319 } 318 }
320 319
321 - protected Firmware savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { 320 + protected OtaPackage savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
322 MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); 321 MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
323 postRequest.file(content); 322 postRequest.file(content);
324 setJwtToken(postRequest); 323 setJwtToken(postRequest);
325 - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), Firmware.class); 324 + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackage.class);
326 } 325 }
327 326
328 } 327 }
application/src/test/java/org/thingsboard/server/controller/sql/OtaPackageControllerSqlTest.java renamed from application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java
@@ -15,9 +15,9 @@ @@ -15,9 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.controller.sql; 16 package org.thingsboard.server.controller.sql;
17 17
18 -import org.thingsboard.server.controller.BaseFirmwareControllerTest; 18 +import org.thingsboard.server.controller.BaseOtaPackageControllerTest;
19 import org.thingsboard.server.dao.service.DaoSqlTest; 19 import org.thingsboard.server.dao.service.DaoSqlTest;
20 20
21 @DaoSqlTest 21 @DaoSqlTest
22 -public class FirmwareControllerSqlTest extends BaseFirmwareControllerTest { 22 +public class OtaPackageControllerSqlTest extends BaseOtaPackageControllerTest {
23 } 23 }
@@ -32,7 +32,8 @@ import java.util.Arrays; @@ -32,7 +32,8 @@ import java.util.Arrays;
32 "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", 32 "org.thingsboard.server.transport.*.attributes.updates.sql.*Test",
33 "org.thingsboard.server.transport.*.attributes.request.sql.*Test", 33 "org.thingsboard.server.transport.*.attributes.request.sql.*Test",
34 "org.thingsboard.server.transport.*.claim.sql.*Test", 34 "org.thingsboard.server.transport.*.claim.sql.*Test",
35 - "org.thingsboard.server.transport.*.provision.sql.*Test" 35 + "org.thingsboard.server.transport.*.provision.sql.*Test",
  36 + "org.thingsboard.server.transport.lwm2m.*Test"
36 }) 37 })
37 public class TransportSqlTestSuite { 38 public class TransportSqlTestSuite {
38 39
@@ -44,6 +44,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr @@ -44,6 +44,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
44 44
45 private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; 45 private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
46 46
  47 + protected static final String POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"attribute1\":\"value\",\"attribute2\":false,\"attribute3\":41.0,\"attribute4\":72," +
  48 + "\"attribute5\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}";
  49 +
47 @Before 50 @Before
48 public void beforeTest() throws Exception { 51 public void beforeTest() throws Exception {
49 processBeforeTest("Test Subscribe to attribute updates", null, null); 52 processBeforeTest("Test Subscribe to attribute updates", null, null);
@@ -56,50 +59,85 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr @@ -56,50 +59,85 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
56 59
57 @Test 60 @Test
58 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { 61 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
59 - processTestSubscribeToAttributesUpdates(); 62 + processTestSubscribeToAttributesUpdates(false);
60 } 63 }
61 64
62 - protected void processTestSubscribeToAttributesUpdates() throws Exception { 65 + @Test
  66 + public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
  67 + processTestSubscribeToAttributesUpdates(true);
  68 + }
63 69
  70 + protected void processTestSubscribeToAttributesUpdates(boolean emptyCurrentStateNotification) throws Exception {
  71 + if (!emptyCurrentStateNotification) {
  72 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk());
  73 + }
64 CoapClient client = getCoapClient(FeatureType.ATTRIBUTES); 74 CoapClient client = getCoapClient(FeatureType.ATTRIBUTES);
65 75
66 CountDownLatch latch = new CountDownLatch(1); 76 CountDownLatch latch = new CountDownLatch(1);
67 - TestCoapCallback testCoapCallback = new TestCoapCallback(latch); 77 + TestCoapCallback callback = new TestCoapCallback(latch);
68 78
69 Request request = Request.newGet().setObserve(); 79 Request request = Request.newGet().setObserve();
70 request.setType(CoAP.Type.CON); 80 request.setType(CoAP.Type.CON);
71 - CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); 81 + CoapObserveRelation observeRelation = client.observe(request, callback);
72 82
73 - Thread.sleep(1000); 83 + latch.await(3, TimeUnit.SECONDS);
  84 +
  85 + if (emptyCurrentStateNotification) {
  86 + validateEmptyCurrentStateAttributesResponse(callback);
  87 + } else {
  88 + validateCurrentStateAttributesResponse(callback);
  89 + }
  90 +
  91 + latch = new CountDownLatch(1);
74 92
75 doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); 93 doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
76 latch.await(3, TimeUnit.SECONDS); 94 latch.await(3, TimeUnit.SECONDS);
77 95
78 - validateUpdateAttributesResponse(testCoapCallback); 96 + validateUpdateAttributesResponse(callback);
79 97
80 latch = new CountDownLatch(1); 98 latch = new CountDownLatch(1);
81 99
82 doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); 100 doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
83 latch.await(3, TimeUnit.SECONDS); 101 latch.await(3, TimeUnit.SECONDS);
84 102
85 - validateDeleteAttributesResponse(testCoapCallback); 103 + validateDeleteAttributesResponse(callback);
86 104
87 observeRelation.proactiveCancel(); 105 observeRelation.proactiveCancel();
88 assertTrue(observeRelation.isCanceled()); 106 assertTrue(observeRelation.isCanceled());
89 } 107 }
90 108
91 - protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { 109 + protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
  110 + assertNotNull(callback.getPayloadBytes());
  111 + assertNotNull(callback.getObserve());
  112 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  113 + assertEquals(0, callback.getObserve().intValue());
  114 + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  115 + assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION), JacksonUtil.toJsonNode(response));
  116 + }
  117 +
  118 + protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
92 assertNotNull(callback.getPayloadBytes()); 119 assertNotNull(callback.getPayloadBytes());
93 assertNotNull(callback.getObserve()); 120 assertNotNull(callback.getObserve());
  121 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
94 assertEquals(0, callback.getObserve().intValue()); 122 assertEquals(0, callback.getObserve().intValue());
95 String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); 123 String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  124 + assertEquals("{}", response);
  125 + }
  126 +
  127 + protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
  128 + assertNotNull(callback.getPayloadBytes());
  129 + assertNotNull(callback.getObserve());
  130 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  131 + assertEquals(1, callback.getObserve().intValue());
  132 + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
96 assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response)); 133 assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
97 } 134 }
98 135
99 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { 136 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
100 assertNotNull(callback.getPayloadBytes()); 137 assertNotNull(callback.getPayloadBytes());
101 assertNotNull(callback.getObserve()); 138 assertNotNull(callback.getObserve());
102 - assertEquals(1, callback.getObserve().intValue()); 139 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  140 + assertEquals(2, callback.getObserve().intValue());
103 String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); 141 String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
104 assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response)); 142 assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
105 } 143 }
@@ -110,13 +148,18 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr @@ -110,13 +148,18 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
110 148
111 private Integer observe; 149 private Integer observe;
112 private byte[] payloadBytes; 150 private byte[] payloadBytes;
  151 + private CoAP.ResponseCode responseCode;
  152 +
  153 + public Integer getObserve() {
  154 + return observe;
  155 + }
113 156
114 public byte[] getPayloadBytes() { 157 public byte[] getPayloadBytes() {
115 return payloadBytes; 158 return payloadBytes;
116 } 159 }
117 160
118 - public Integer getObserve() {  
119 - return observe; 161 + public CoAP.ResponseCode getResponseCode() {
  162 + return responseCode;
120 } 163 }
121 164
122 private TestCoapCallback(CountDownLatch latch) { 165 private TestCoapCallback(CountDownLatch latch) {
@@ -125,10 +168,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr @@ -125,10 +168,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
125 168
126 @Override 169 @Override
127 public void onLoad(CoapResponse response) { 170 public void onLoad(CoapResponse response) {
128 - assertNotNull(response.getPayload());  
129 - assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT);  
130 observe = response.getOptions().getObserve(); 171 observe = response.getOptions().getObserve();
131 payloadBytes = response.getPayload(); 172 payloadBytes = response.getPayload();
  173 + responseCode = response.getCode();
132 latch.countDown(); 174 latch.countDown();
133 } 175 }
134 176
@@ -39,4 +39,9 @@ public abstract class AbstractCoapAttributesUpdatesJsonIntegrationTest extends A @@ -39,4 +39,9 @@ public abstract class AbstractCoapAttributesUpdatesJsonIntegrationTest extends A
39 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { 39 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
40 super.testSubscribeToAttributesUpdatesFromTheServer(); 40 super.testSubscribeToAttributesUpdatesFromTheServer();
41 } 41 }
  42 +
  43 + @Test
  44 + public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
  45 + super.testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification();
  46 + }
42 } 47 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.coap.attributes.updates; @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.coap.attributes.updates;
17 17
18 import com.google.protobuf.InvalidProtocolBufferException; 18 import com.google.protobuf.InvalidProtocolBufferException;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.coap.CoAP;
20 import org.junit.After; 21 import org.junit.After;
21 import org.junit.Before; 22 import org.junit.Before;
22 import org.junit.Test; 23 import org.junit.Test;
@@ -24,11 +25,15 @@ import org.thingsboard.server.common.data.CoapDeviceType; @@ -24,11 +25,15 @@ import org.thingsboard.server.common.data.CoapDeviceType;
24 import org.thingsboard.server.common.data.TransportPayloadType; 25 import org.thingsboard.server.common.data.TransportPayloadType;
25 import org.thingsboard.server.gen.transport.TransportProtos; 26 import org.thingsboard.server.gen.transport.TransportProtos;
26 27
  28 +import java.nio.charset.StandardCharsets;
  29 +import java.util.ArrayList;
  30 +import java.util.Collections;
27 import java.util.List; 31 import java.util.List;
28 import java.util.stream.Collectors; 32 import java.util.stream.Collectors;
29 33
30 import static org.junit.Assert.assertEquals; 34 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertNotNull; 35 import static org.junit.Assert.assertNotNull;
  36 +import static org.junit.Assert.assertNull;
32 import static org.junit.Assert.assertTrue; 37 import static org.junit.Assert.assertTrue;
33 38
34 @Slf4j 39 @Slf4j
@@ -46,11 +51,54 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends @@ -46,11 +51,54 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
46 51
47 @Test 52 @Test
48 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { 53 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
49 - processTestSubscribeToAttributesUpdates(); 54 + processTestSubscribeToAttributesUpdates(false);
  55 + }
  56 +
  57 + @Test
  58 + public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
  59 + processTestSubscribeToAttributesUpdates(true);
  60 + }
  61 +
  62 + protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
  63 + assertNotNull(callback.getPayloadBytes());
  64 + assertNotNull(callback.getObserve());
  65 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  66 + assertEquals(0, callback.getObserve().intValue());
  67 + TransportProtos.AttributeUpdateNotificationMsg.Builder expectedCurrentStateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
  68 + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value", TransportProtos.KeyValueType.STRING_V);
  69 + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "false", TransportProtos.KeyValueType.BOOLEAN_V);
  70 + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "41.0", TransportProtos.KeyValueType.DOUBLE_V);
  71 + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "72", TransportProtos.KeyValueType.LONG_V);
  72 + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
  73 + List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
  74 + tsKvProtoList.add(tsKvProtoAttribute1);
  75 + tsKvProtoList.add(tsKvProtoAttribute2);
  76 + tsKvProtoList.add(tsKvProtoAttribute3);
  77 + tsKvProtoList.add(tsKvProtoAttribute4);
  78 + tsKvProtoList.add(tsKvProtoAttribute5);
  79 + TransportProtos.AttributeUpdateNotificationMsg expectedCurrentStateNotificationMsg = expectedCurrentStateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList).build();
  80 + TransportProtos.AttributeUpdateNotificationMsg actualCurrentStateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
  81 +
  82 + List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  83 + List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  84 +
  85 + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
  86 + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
  87 +
  88 + }
  89 +
  90 + protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
  91 + assertNull(callback.getPayloadBytes());
  92 + assertNotNull(callback.getObserve());
  93 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  94 + assertEquals(0, callback.getObserve().intValue());
50 } 95 }
51 96
52 protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { 97 protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
53 assertNotNull(callback.getPayloadBytes()); 98 assertNotNull(callback.getPayloadBytes());
  99 + assertNotNull(callback.getObserve());
  100 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  101 + assertEquals(1, callback.getObserve().intValue());
54 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); 102 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
55 List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList(); 103 List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
56 attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); 104 attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
@@ -68,6 +116,9 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends @@ -68,6 +116,9 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
68 116
69 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { 117 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
70 assertNotNull(callback.getPayloadBytes()); 118 assertNotNull(callback.getPayloadBytes());
  119 + assertNotNull(callback.getObserve());
  120 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  121 + assertEquals(2, callback.getObserve().intValue());
71 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); 122 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
72 attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); 123 attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
73 124
@@ -83,7 +83,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab @@ -83,7 +83,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
83 83
84 @Test 84 @Test
85 public void testServerCoapTwoWayRpc() throws Exception { 85 public void testServerCoapTwoWayRpc() throws Exception {
86 - processTwoWayRpcTest(); 86 + processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}");
87 } 87 }
88 88
89 } 89 }
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.transport.coap.rpc; 16 package org.thingsboard.server.transport.coap.rpc;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
18 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
19 import org.apache.commons.lang3.StringUtils; 20 import org.apache.commons.lang3.StringUtils;
20 import org.eclipse.californium.core.CoapClient; 21 import org.eclipse.californium.core.CoapClient;
@@ -24,17 +25,18 @@ import org.eclipse.californium.core.CoapResponse; @@ -24,17 +25,18 @@ import org.eclipse.californium.core.CoapResponse;
24 import org.eclipse.californium.core.coap.CoAP; 25 import org.eclipse.californium.core.coap.CoAP;
25 import org.eclipse.californium.core.coap.MediaTypeRegistry; 26 import org.eclipse.californium.core.coap.MediaTypeRegistry;
26 import org.eclipse.californium.core.coap.Request; 27 import org.eclipse.californium.core.coap.Request;
27 -import org.junit.Assert;  
28 -import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; 28 +import org.thingsboard.common.util.JacksonUtil;
29 import org.thingsboard.server.common.data.CoapDeviceType; 29 import org.thingsboard.server.common.data.CoapDeviceType;
30 import org.thingsboard.server.common.data.TransportPayloadType; 30 import org.thingsboard.server.common.data.TransportPayloadType;
31 import org.thingsboard.server.common.msg.session.FeatureType; 31 import org.thingsboard.server.common.msg.session.FeatureType;
  32 +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
32 33
33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.CountDownLatch;
34 import java.util.concurrent.TimeUnit; 35 import java.util.concurrent.TimeUnit;
35 36
36 import static org.junit.Assert.assertEquals; 37 import static org.junit.Assert.assertEquals;
37 import static org.junit.Assert.assertNotNull; 38 import static org.junit.Assert.assertNotNull;
  39 +import static org.junit.Assert.assertNull;
38 import static org.junit.Assert.assertTrue; 40 import static org.junit.Assert.assertTrue;
39 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 41 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
40 42
@@ -55,57 +57,66 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC @@ -55,57 +57,66 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
55 client.useCONs(); 57 client.useCONs();
56 58
57 CountDownLatch latch = new CountDownLatch(1); 59 CountDownLatch latch = new CountDownLatch(1);
58 - TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, true); 60 + TestCoapCallback callback = new TestCoapCallback(client, latch, true);
59 61
60 Request request = Request.newGet().setObserve(); 62 Request request = Request.newGet().setObserve();
61 - CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); 63 + CoapObserveRelation observeRelation = client.observe(request, callback);
  64 +
  65 + latch.await(3, TimeUnit.SECONDS);
  66 +
  67 + validateCurrentStateNotification(callback);
  68 +
  69 + latch = new CountDownLatch(1);
62 70
63 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; 71 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
64 String deviceId = savedDevice.getId().getId().toString(); 72 String deviceId = savedDevice.getId().getId().toString();
65 String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); 73 String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
66 - Assert.assertTrue(StringUtils.isEmpty(result)); 74 +
67 latch.await(3, TimeUnit.SECONDS); 75 latch.await(3, TimeUnit.SECONDS);
68 - assertEquals(0, testCoapCallback.getObserve().intValue()); 76 +
  77 + validateOneWayStateChangedNotification(callback, result);
  78 +
69 observeRelation.proactiveCancel(); 79 observeRelation.proactiveCancel();
70 assertTrue(observeRelation.isCanceled()); 80 assertTrue(observeRelation.isCanceled());
71 } 81 }
72 82
73 - protected void processTwoWayRpcTest() throws Exception { 83 + protected void processTwoWayRpcTest(String expectedResponseResult) throws Exception {
74 CoapClient client = getCoapClient(FeatureType.RPC); 84 CoapClient client = getCoapClient(FeatureType.RPC);
75 client.useCONs(); 85 client.useCONs();
76 86
77 CountDownLatch latch = new CountDownLatch(1); 87 CountDownLatch latch = new CountDownLatch(1);
78 - TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, false); 88 + TestCoapCallback callback = new TestCoapCallback(client, latch, false);
79 89
80 Request request = Request.newGet().setObserve(); 90 Request request = Request.newGet().setObserve();
81 request.setType(CoAP.Type.CON); 91 request.setType(CoAP.Type.CON);
82 - CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); 92 + CoapObserveRelation observeRelation = client.observe(request, callback);
  93 +
  94 + latch.await(3, TimeUnit.SECONDS);
  95 +
  96 + validateCurrentStateNotification(callback);
83 97
84 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; 98 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
85 String deviceId = savedDevice.getId().getId().toString(); 99 String deviceId = savedDevice.getId().getId().toString();
86 100
87 - String expected = "{\"value1\":\"A\",\"value2\":\"B\"}"; 101 + String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
  102 + latch.await(3, TimeUnit.SECONDS);
  103 +
  104 + validateTwoWayStateChangedNotification(callback, 1, expectedResponseResult, actualResult);
88 105
89 - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); 106 + latch = new CountDownLatch(1);
  107 +
  108 + actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
90 latch.await(3, TimeUnit.SECONDS); 109 latch.await(3, TimeUnit.SECONDS);
91 110
92 - assertEquals(expected, result);  
93 - assertEquals(0, testCoapCallback.getObserve().intValue()); 111 + validateTwoWayStateChangedNotification(callback, 2, expectedResponseResult, actualResult);
  112 +
94 observeRelation.proactiveCancel(); 113 observeRelation.proactiveCancel();
95 assertTrue(observeRelation.isCanceled()); 114 assertTrue(observeRelation.isCanceled());
96 -  
97 -// // TODO: 3/11/21 Fix test to validate next RPC  
98 -// latch = new CountDownLatch(1);  
99 -//  
100 -// result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());  
101 -// latch.await(3, TimeUnit.SECONDS);  
102 -//  
103 -// assertEquals(expected, result);  
104 -// assertEquals(1, testCoapCallback.getObserve().intValue());  
105 } 115 }
106 116
107 protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { 117 protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) {
108 - client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe)); 118 + JsonNode responseJson = JacksonUtil.fromBytes(response.getPayload());
  119 + client.setURI(getRpcResponseFeatureTokenUrl(accessToken, responseJson.get("id").asInt()));
109 client.post(new CoapHandler() { 120 client.post(new CoapHandler() {
110 @Override 121 @Override
111 public void onLoad(CoapResponse response) { 122 public void onLoad(CoapResponse response) {
@@ -130,11 +141,21 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC @@ -130,11 +141,21 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
130 private final CountDownLatch latch; 141 private final CountDownLatch latch;
131 private final boolean isOneWayRpc; 142 private final boolean isOneWayRpc;
132 143
  144 + private Integer observe;
  145 + private byte[] payloadBytes;
  146 + private CoAP.ResponseCode responseCode;
  147 +
133 public Integer getObserve() { 148 public Integer getObserve() {
134 return observe; 149 return observe;
135 } 150 }
136 151
137 - private Integer observe; 152 + public byte[] getPayloadBytes() {
  153 + return payloadBytes;
  154 + }
  155 +
  156 + public CoAP.ResponseCode getResponseCode() {
  157 + return responseCode;
  158 + }
138 159
139 TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) { 160 TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) {
140 this.client = client; 161 this.client = client;
@@ -144,14 +165,15 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC @@ -144,14 +165,15 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
144 165
145 @Override 166 @Override
146 public void onLoad(CoapResponse response) { 167 public void onLoad(CoapResponse response) {
147 - log.warn("coap response: {}, {}", response.getResponseText(), response.getCode());  
148 - assertNotNull(response.getPayload());  
149 - assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT); 168 + payloadBytes = response.getPayload();
  169 + responseCode = response.getCode();
150 observe = response.getOptions().getObserve(); 170 observe = response.getOptions().getObserve();
151 - if (!isOneWayRpc) {  
152 - processOnLoadResponse(response, client, observe, latch);  
153 - } else {  
154 - latch.countDown(); 171 + if (observe != null) {
  172 + if (!isOneWayRpc && observe > 0) {
  173 + processOnLoadResponse(response, client, observe, latch);
  174 + } else {
  175 + latch.countDown();
  176 + }
155 } 177 }
156 } 178 }
157 179
@@ -162,4 +184,28 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC @@ -162,4 +184,28 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
162 184
163 } 185 }
164 186
  187 + private void validateCurrentStateNotification(TestCoapCallback callback) {
  188 + assertNull(callback.getPayloadBytes());
  189 + assertNotNull(callback.getObserve());
  190 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode.VALID);
  191 + assertEquals(0, callback.getObserve().intValue());
  192 + }
  193 +
  194 + private void validateOneWayStateChangedNotification(TestCoapCallback callback, String result) {
  195 + assertTrue(StringUtils.isEmpty(result));
  196 + assertNotNull(callback.getPayloadBytes());
  197 + assertNotNull(callback.getObserve());
  198 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  199 + assertEquals(1, callback.getObserve().intValue());
  200 + }
  201 +
  202 + private void validateTwoWayStateChangedNotification(TestCoapCallback callback, int expectedObserveNumber, String expectedResult, String actualResult) {
  203 + assertEquals(expectedResult, actualResult);
  204 + assertNotNull(callback.getPayloadBytes());
  205 + assertNotNull(callback.getObserve());
  206 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  207 + assertEquals(expectedObserveNumber, callback.getObserve().intValue());
  208 + }
  209 +
  210 +
165 } 211 }
@@ -42,7 +42,7 @@ public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends Abstr @@ -42,7 +42,7 @@ public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends Abstr
42 42
43 @Test 43 @Test
44 public void testServerCoapTwoWayRpc() throws Exception { 44 public void testServerCoapTwoWayRpc() throws Exception {
45 - processTwoWayRpcTest(); 45 + processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}");
46 } 46 }
47 47
48 } 48 }
@@ -85,37 +85,11 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst @@ -85,37 +85,11 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst
85 85
86 @Test 86 @Test
87 public void testServerCoapTwoWayRpc() throws Exception { 87 public void testServerCoapTwoWayRpc() throws Exception {
88 - processTwoWayRpcTest();  
89 - }  
90 -  
91 - protected void processTwoWayRpcTest() throws Exception {  
92 - CoapClient client = getCoapClient(FeatureType.RPC);  
93 - client.useCONs();  
94 -  
95 - CountDownLatch latch = new CountDownLatch(1);  
96 - TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, false);  
97 -  
98 - Request request = Request.newGet().setObserve();  
99 - request.setType(CoAP.Type.CON);  
100 - CoapObserveRelation observeRelation = client.observe(request, testCoapCallback);  
101 -  
102 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";  
103 - String deviceId = savedDevice.getId().getId().toString();  
104 -  
105 - String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";  
106 -  
107 - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());  
108 - latch.await(3, TimeUnit.SECONDS);  
109 -  
110 - assertEquals(expected, result);  
111 - assertEquals(0, testCoapCallback.getObserve().intValue());  
112 - observeRelation.proactiveCancel();  
113 - assertTrue(observeRelation.isCanceled()); 88 + processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}");
114 } 89 }
115 90
116 @Override 91 @Override
117 protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { 92 protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) {
118 - client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe));  
119 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); 93 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
120 ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); 94 ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
121 DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); 95 DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
@@ -123,25 +97,22 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst @@ -123,25 +97,22 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst
123 byte[] requestPayload = response.getPayload(); 97 byte[] requestPayload = response.getPayload();
124 DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); 98 DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
125 Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType(); 99 Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
126 - assertNotNull(rpcRequestMsgDescriptor);  
127 try { 100 try {
128 DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); 101 DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload);
129 - List<Descriptors.FieldDescriptor> fields = rpcRequestMsgDescriptor.getFields();  
130 - for (Descriptors.FieldDescriptor fieldDescriptor: fields) {  
131 - assertTrue(dynamicMessage.hasField(fieldDescriptor));  
132 - } 102 + Descriptors.FieldDescriptor requestIdDescriptor = rpcRequestMsgDescriptor.findFieldByName("requestId");
  103 + int requestId = (int) dynamicMessage.getField(requestIdDescriptor);
133 ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA); 104 ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
134 DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); 105 DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
135 DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); 106 DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
136 Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); 107 Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
137 - assertNotNull(rpcResponseMsgDescriptor);  
138 DynamicMessage rpcResponseMsg = rpcResponseBuilder 108 DynamicMessage rpcResponseMsg = rpcResponseBuilder
139 .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) 109 .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
140 .build(); 110 .build();
  111 + client.setURI(getRpcResponseFeatureTokenUrl(accessToken, requestId));
141 client.post(new CoapHandler() { 112 client.post(new CoapHandler() {
142 @Override 113 @Override
143 public void onLoad(CoapResponse response) { 114 public void onLoad(CoapResponse response) {
144 - log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); 115 + log.warn("Command Response Ack: {}", response.getCode());
145 latch.countDown(); 116 latch.countDown();
146 } 117 }
147 118
  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.transport.lwm2m;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.io.IOUtils;
  20 +import org.eclipse.leshan.core.util.Hex;
  21 +import org.junit.After;
  22 +import org.junit.Assert;
  23 +import org.junit.Before;
  24 +import org.thingsboard.common.util.JacksonUtil;
  25 +import org.thingsboard.server.common.data.DeviceProfile;
  26 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  27 +import org.thingsboard.server.common.data.DeviceProfileType;
  28 +import org.thingsboard.server.common.data.DeviceTransportType;
  29 +import org.thingsboard.server.common.data.ResourceType;
  30 +import org.thingsboard.server.common.data.TbResource;
  31 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  32 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  33 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
  34 +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
  35 +import org.thingsboard.server.controller.AbstractWebsocketTest;
  36 +import org.thingsboard.server.controller.TbTestWebSocketClient;
  37 +import org.thingsboard.server.dao.service.DaoSqlTest;
  38 +
  39 +import java.io.IOException;
  40 +import java.io.InputStream;
  41 +import java.math.BigInteger;
  42 +import java.security.AlgorithmParameters;
  43 +import java.security.GeneralSecurityException;
  44 +import java.security.KeyFactory;
  45 +import java.security.KeyStore;
  46 +import java.security.PrivateKey;
  47 +import java.security.PublicKey;
  48 +import java.security.cert.Certificate;
  49 +import java.security.cert.X509Certificate;
  50 +import java.security.spec.ECGenParameterSpec;
  51 +import java.security.spec.ECParameterSpec;
  52 +import java.security.spec.ECPoint;
  53 +import java.security.spec.ECPrivateKeySpec;
  54 +import java.security.spec.ECPublicKeySpec;
  55 +import java.security.spec.KeySpec;
  56 +import java.util.Base64;
  57 +import java.util.concurrent.Executors;
  58 +import java.util.concurrent.ScheduledExecutorService;
  59 +
  60 +@DaoSqlTest
  61 +public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
  62 +
  63 + protected DeviceProfile deviceProfile;
  64 + protected ScheduledExecutorService executor;
  65 + protected TbTestWebSocketClient wsClient;
  66 +
  67 + protected final PublicKey clientPublicKey; // client public key used for RPK
  68 + protected final PrivateKey clientPrivateKey; // client private key used for RPK
  69 + protected final PublicKey serverPublicKey; // server public key used for RPK
  70 + protected final PrivateKey serverPrivateKey; // server private key used for RPK
  71 +
  72 + // client private key used for X509
  73 + protected final PrivateKey clientPrivateKeyFromCert;
  74 + // server private key used for X509
  75 + protected final PrivateKey serverPrivateKeyFromCert;
  76 + // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
  77 + protected final X509Certificate clientX509Cert;
  78 + // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
  79 + protected final X509Certificate clientX509CertWithBadCN;
  80 + // client certificate self-signed with a good CN (CN start by leshan_integration_test)
  81 + protected final X509Certificate clientX509CertSelfSigned;
  82 + // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
  83 + protected final X509Certificate clientX509CertNotTrusted;
  84 + // server certificate signed by rootCA
  85 + protected final X509Certificate serverX509Cert;
  86 + // self-signed server certificate
  87 + protected final X509Certificate serverX509CertSelfSigned;
  88 + // rootCA used by the server
  89 + protected final X509Certificate rootCAX509Cert;
  90 + // certificates trustedby the server (should contain rootCA)
  91 + protected final Certificate[] trustedCertificates = new Certificate[1];
  92 +
  93 + public AbstractLwM2MIntegrationTest() {
  94 +// create client credentials
  95 + try {
  96 + // Get point values
  97 + byte[] publicX = Hex
  98 + .decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray());
  99 + byte[] publicY = Hex
  100 + .decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray());
  101 + byte[] privateS = Hex
  102 + .decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray());
  103 +
  104 + // Get Elliptic Curve Parameter spec for secp256r1
  105 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  106 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  107 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  108 +
  109 + // Create key specs
  110 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  111 + parameterSpec);
  112 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  113 +
  114 + // Get keys
  115 + clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  116 + clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  117 +
  118 + // Get certificates from key store
  119 + char[] clientKeyStorePwd = "client".toCharArray();
  120 + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  121 + try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) {
  122 + clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
  123 + }
  124 +
  125 + clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd);
  126 + clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client");
  127 + clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn");
  128 + clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed");
  129 + clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted");
  130 + } catch (GeneralSecurityException | IOException e) {
  131 + throw new RuntimeException(e);
  132 + }
  133 +
  134 + // create server credentials
  135 + try {
  136 + // Get point values
  137 + byte[] publicX = Hex
  138 + .decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray());
  139 + byte[] publicY = Hex
  140 + .decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray());
  141 + byte[] privateS = Hex
  142 + .decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray());
  143 +
  144 + // Get Elliptic Curve Parameter spec for secp256r1
  145 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  146 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  147 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  148 +
  149 + // Create key specs
  150 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  151 + parameterSpec);
  152 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  153 +
  154 + // Get keys
  155 + serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  156 + serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  157 +
  158 + // Get certificates from key store
  159 + char[] serverKeyStorePwd = "server".toCharArray();
  160 + KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  161 + try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) {
  162 + serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd);
  163 + }
  164 +
  165 + serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd);
  166 + rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA");
  167 + serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server");
  168 + serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed");
  169 + trustedCertificates[0] = rootCAX509Cert;
  170 + } catch (GeneralSecurityException | IOException e) {
  171 + throw new RuntimeException(e);
  172 + }
  173 + }
  174 +
  175 + @Before
  176 + public void beforeTest() throws Exception {
  177 + executor = Executors.newScheduledThreadPool(10);
  178 + loginTenantAdmin();
  179 +
  180 + String[] resources = new String[]{"1.xml", "2.xml", "3.xml"};
  181 + for (String resourceName : resources) {
  182 + TbResource lwModel = new TbResource();
  183 + lwModel.setResourceType(ResourceType.LWM2M_MODEL);
  184 + lwModel.setTitle(resourceName);
  185 + lwModel.setFileName(resourceName);
  186 + lwModel.setTenantId(tenantId);
  187 + byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName));
  188 + lwModel.setData(Base64.getEncoder().encodeToString(bytes));
  189 + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() {
  190 + });
  191 + Assert.assertNotNull(lwModel);
  192 + }
  193 + wsClient = buildAndConnectWebSocketClient();
  194 + }
  195 +
  196 + protected void createDeviceProfile(String transportConfiguration) throws Exception {
  197 + deviceProfile = new DeviceProfile();
  198 +
  199 + deviceProfile.setName("LwM2M");
  200 + deviceProfile.setType(DeviceProfileType.DEFAULT);
  201 + deviceProfile.setTenantId(tenantId);
  202 + deviceProfile.setTransportType(DeviceTransportType.LWM2M);
  203 + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
  204 + deviceProfile.setDescription(deviceProfile.getName());
  205 +
  206 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  207 + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
  208 + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
  209 + deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class));
  210 + deviceProfile.setProfileData(deviceProfileData);
  211 +
  212 + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  213 + Assert.assertNotNull(deviceProfile);
  214 + }
  215 +
  216 + @After
  217 + public void after() {
  218 + executor.shutdownNow();
  219 + wsClient.close();
  220 + }
  221 +
  222 +}
  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.transport.lwm2m;
  17 +
  18 +import org.eclipse.californium.core.network.config.NetworkConfig;
  19 +import org.eclipse.leshan.client.object.Security;
  20 +import org.jetbrains.annotations.NotNull;
  21 +import org.junit.Assert;
  22 +import org.junit.Test;
  23 +import org.thingsboard.common.util.JacksonUtil;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.query.EntityData;
  26 +import org.thingsboard.server.common.data.query.EntityDataPageLink;
  27 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  28 +import org.thingsboard.server.common.data.query.EntityKey;
  29 +import org.thingsboard.server.common.data.query.EntityKeyType;
  30 +import org.thingsboard.server.common.data.query.SingleEntityFilter;
  31 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  32 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  33 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
  34 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  35 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  36 +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
  37 +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
  38 +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
  39 +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials;
  40 +
  41 +import java.util.Collections;
  42 +import java.util.List;
  43 +
  44 +import static org.eclipse.leshan.client.object.Security.noSec;
  45 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  46 +
  47 +public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
  48 +
  49 + protected final String TRANSPORT_CONFIGURATION = "{\n" +
  50 + " \"type\": \"LWM2M\",\n" +
  51 + " \"observeAttr\": {\n" +
  52 + " \"keyName\": {\n" +
  53 + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
  54 + " },\n" +
  55 + " \"observe\": [],\n" +
  56 + " \"attribute\": [\n" +
  57 + " ],\n" +
  58 + " \"telemetry\": [\n" +
  59 + " \"/3_1.0/0/9\"\n" +
  60 + " ],\n" +
  61 + " \"attributeLwm2m\": {}\n" +
  62 + " },\n" +
  63 + " \"bootstrap\": {\n" +
  64 + " \"servers\": {\n" +
  65 + " \"binding\": \"UQ\",\n" +
  66 + " \"shortId\": 123,\n" +
  67 + " \"lifetime\": 300,\n" +
  68 + " \"notifIfDisabled\": true,\n" +
  69 + " \"defaultMinPeriod\": 1\n" +
  70 + " },\n" +
  71 + " \"lwm2mServer\": {\n" +
  72 + " \"host\": \"localhost\",\n" +
  73 + " \"port\": 5685,\n" +
  74 + " \"serverId\": 123,\n" +
  75 + " \"securityMode\": \"NO_SEC\",\n" +
  76 + " \"serverPublicKey\": \"\",\n" +
  77 + " \"bootstrapServerIs\": false,\n" +
  78 + " \"clientHoldOffTime\": 1,\n" +
  79 + " \"bootstrapServerAccountTimeout\": 0\n" +
  80 + " },\n" +
  81 + " \"bootstrapServer\": {\n" +
  82 + " \"host\": \"localhost\",\n" +
  83 + " \"port\": 5687,\n" +
  84 + " \"serverId\": 111,\n" +
  85 + " \"securityMode\": \"NO_SEC\",\n" +
  86 + " \"serverPublicKey\": \"\",\n" +
  87 + " \"bootstrapServerIs\": true,\n" +
  88 + " \"clientHoldOffTime\": 1,\n" +
  89 + " \"bootstrapServerAccountTimeout\": 0\n" +
  90 + " }\n" +
  91 + " },\n" +
  92 + " \"clientLwM2mSettings\": {\n" +
  93 + " \"clientOnlyObserveAfterConnect\": 1\n" +
  94 + " }\n" +
  95 + "}";
  96 +
  97 + private final int port = 5685;
  98 + private final Security security = noSec("coap://localhost:" + port, 123);
  99 + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port));
  100 +
  101 + @NotNull
  102 + private Device createDevice(String deviceAEndpoint) throws Exception {
  103 + Device device = new Device();
  104 + device.setName("Device A");
  105 + device.setDeviceProfileId(deviceProfile.getId());
  106 + device.setTenantId(tenantId);
  107 + device = doPost("/api/device", device, Device.class);
  108 + Assert.assertNotNull(device);
  109 +
  110 + DeviceCredentials deviceCredentials =
  111 + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  112 + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
  113 + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
  114 +
  115 + LwM2MCredentials noSecCredentials = new LwM2MCredentials();
  116 + NoSecClientCredentials clientCredentials = new NoSecClientCredentials();
  117 + clientCredentials.setEndpoint(deviceAEndpoint);
  118 + noSecCredentials.setClient(clientCredentials);
  119 + deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials));
  120 + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
  121 + return device;
  122 + }
  123 +
  124 + @Test
  125 + public void testConnectAndObserveTelemetry() throws Exception {
  126 + createDeviceProfile(TRANSPORT_CONFIGURATION);
  127 +
  128 + String deviceAEndpoint = "deviceAEndpoint";
  129 +
  130 + Device device = createDevice(deviceAEndpoint);
  131 +
  132 + SingleEntityFilter sef = new SingleEntityFilter();
  133 + sef.setSingleEntity(device.getId());
  134 + LatestValueCmd latestCmd = new LatestValueCmd();
  135 + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
  136 + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
  137 + Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  138 +
  139 + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
  140 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  141 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  142 +
  143 + wsClient.send(mapper.writeValueAsString(wrapper));
  144 + wsClient.waitForReply();
  145 +
  146 + wsClient.registerWaitForUpdate();
  147 + LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint);
  148 + client.init(security, coapConfig);
  149 + String msg = wsClient.waitForUpdate();
  150 +
  151 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  152 + Assert.assertEquals(1, update.getCmdId());
  153 + List<EntityData> eData = update.getUpdate();
  154 + Assert.assertNotNull(eData);
  155 + Assert.assertEquals(1, eData.size());
  156 + Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
  157 + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
  158 + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
  159 + Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
  160 + client.destroy();
  161 + }
  162 +
  163 +}
  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.transport.lwm2m;
  17 +
  18 +import org.eclipse.californium.core.network.config.NetworkConfig;
  19 +import org.eclipse.leshan.client.object.Security;
  20 +import org.jetbrains.annotations.NotNull;
  21 +import org.junit.Assert;
  22 +import org.junit.Test;
  23 +import org.thingsboard.common.util.JacksonUtil;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials;
  26 +import org.thingsboard.server.common.data.query.EntityData;
  27 +import org.thingsboard.server.common.data.query.EntityDataPageLink;
  28 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  29 +import org.thingsboard.server.common.data.query.EntityKey;
  30 +import org.thingsboard.server.common.data.query.EntityKeyType;
  31 +import org.thingsboard.server.common.data.query.SingleEntityFilter;
  32 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  33 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  34 +import org.thingsboard.server.common.transport.util.SslUtil;
  35 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
  36 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  37 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  38 +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
  39 +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
  40 +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
  41 +
  42 +import java.util.Collections;
  43 +import java.util.List;
  44 +
  45 +import static org.eclipse.leshan.client.object.Security.x509;
  46 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  47 +
  48 +public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
  49 +
  50 + protected final String TRANSPORT_CONFIGURATION = "{\n" +
  51 + " \"type\": \"LWM2M\",\n" +
  52 + " \"observeAttr\": {\n" +
  53 + " \"keyName\": {\n" +
  54 + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
  55 + " },\n" +
  56 + " \"observe\": [],\n" +
  57 + " \"attribute\": [\n" +
  58 + " ],\n" +
  59 + " \"telemetry\": [\n" +
  60 + " \"/3_1.0/0/9\"\n" +
  61 + " ],\n" +
  62 + " \"attributeLwm2m\": {}\n" +
  63 + " },\n" +
  64 + " \"bootstrap\": {\n" +
  65 + " \"servers\": {\n" +
  66 + " \"binding\": \"UQ\",\n" +
  67 + " \"shortId\": 123,\n" +
  68 + " \"lifetime\": 300,\n" +
  69 + " \"notifIfDisabled\": true,\n" +
  70 + " \"defaultMinPeriod\": 1\n" +
  71 + " },\n" +
  72 + " \"lwm2mServer\": {\n" +
  73 + " \"host\": \"localhost\",\n" +
  74 + " \"port\": 5686,\n" +
  75 + " \"serverId\": 123,\n" +
  76 + " \"serverPublicKey\": \"\",\n" +
  77 + " \"bootstrapServerIs\": false,\n" +
  78 + " \"clientHoldOffTime\": 1,\n" +
  79 + " \"bootstrapServerAccountTimeout\": 0\n" +
  80 + " },\n" +
  81 + " \"bootstrapServer\": {\n" +
  82 + " \"host\": \"localhost\",\n" +
  83 + " \"port\": 5687,\n" +
  84 + " \"serverId\": 111,\n" +
  85 + " \"securityMode\": \"NO_SEC\",\n" +
  86 + " \"serverPublicKey\": \"\",\n" +
  87 + " \"bootstrapServerIs\": true,\n" +
  88 + " \"clientHoldOffTime\": 1,\n" +
  89 + " \"bootstrapServerAccountTimeout\": 0\n" +
  90 + " }\n" +
  91 + " },\n" +
  92 + " \"clientLwM2mSettings\": {\n" +
  93 + " \"clientOnlyObserveAfterConnect\": 1\n" +
  94 + " }\n" +
  95 + "}";
  96 +
  97 +
  98 + private final int port = 5686;
  99 + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port));
  100 + private final String endpoint = "deviceAEndpoint";
  101 + private final String serverUri = "coaps://localhost:" + port;
  102 +
  103 + @NotNull
  104 + private Device createDevice(X509ClientCredentials clientCredentials) throws Exception {
  105 + Device device = new Device();
  106 + device.setName("Device A");
  107 + device.setDeviceProfileId(deviceProfile.getId());
  108 + device.setTenantId(tenantId);
  109 + device = doPost("/api/device", device, Device.class);
  110 + Assert.assertNotNull(device);
  111 +
  112 + DeviceCredentials deviceCredentials =
  113 + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  114 + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
  115 + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
  116 +
  117 + LwM2MCredentials credentials = new LwM2MCredentials();
  118 +
  119 + credentials.setClient(clientCredentials);
  120 +
  121 + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
  122 + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
  123 + return device;
  124 + }
  125 +
  126 + @Test
  127 + public void testConnectAndObserveTelemetry() throws Exception {
  128 + createDeviceProfile(TRANSPORT_CONFIGURATION);
  129 + X509ClientCredentials credentials = new X509ClientCredentials();
  130 + credentials.setEndpoint(endpoint);
  131 + Device device = createDevice(credentials);
  132 +
  133 + SingleEntityFilter sef = new SingleEntityFilter();
  134 + sef.setSingleEntity(device.getId());
  135 + LatestValueCmd latestCmd = new LatestValueCmd();
  136 + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
  137 + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
  138 + Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  139 +
  140 + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
  141 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  142 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  143 +
  144 + wsClient.send(mapper.writeValueAsString(wrapper));
  145 + wsClient.waitForReply();
  146 +
  147 + wsClient.registerWaitForUpdate();
  148 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
  149 + Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
  150 + client.init(security, coapConfig);
  151 + String msg = wsClient.waitForUpdate();
  152 +
  153 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  154 + Assert.assertEquals(1, update.getCmdId());
  155 + List<EntityData> eData = update.getUpdate();
  156 + Assert.assertNotNull(eData);
  157 + Assert.assertEquals(1, eData.size());
  158 + Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
  159 + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
  160 + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
  161 + Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
  162 + client.destroy();
  163 + }
  164 +
  165 + @Test
  166 + public void testConnectWithCertAndObserveTelemetry() throws Exception {
  167 + createDeviceProfile(TRANSPORT_CONFIGURATION);
  168 + X509ClientCredentials credentials = new X509ClientCredentials();
  169 + credentials.setEndpoint(endpoint);
  170 + credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted));
  171 + Device device = createDevice(credentials);
  172 +
  173 + SingleEntityFilter sef = new SingleEntityFilter();
  174 + sef.setSingleEntity(device.getId());
  175 + LatestValueCmd latestCmd = new LatestValueCmd();
  176 + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
  177 + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
  178 + Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  179 +
  180 + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
  181 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  182 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  183 +
  184 + wsClient.send(mapper.writeValueAsString(wrapper));
  185 + wsClient.waitForReply();
  186 +
  187 + wsClient.registerWaitForUpdate();
  188 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
  189 +
  190 + Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
  191 +
  192 + client.init(security, coapConfig);
  193 + String msg = wsClient.waitForUpdate();
  194 +
  195 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  196 + Assert.assertEquals(1, update.getCmdId());
  197 + List<EntityData> eData = update.getUpdate();
  198 + Assert.assertNotNull(eData);
  199 + Assert.assertEquals(1, eData.size());
  200 + Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
  201 + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
  202 + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
  203 + Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
  204 + client.destroy();
  205 + }
  206 +
  207 +}
  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.transport.lwm2m.client;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.network.config.NetworkConfig;
  21 +import org.eclipse.californium.elements.Connector;
  22 +import org.eclipse.californium.scandium.DTLSConnector;
  23 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  24 +import org.eclipse.californium.scandium.dtls.ClientHandshaker;
  25 +import org.eclipse.californium.scandium.dtls.DTLSSession;
  26 +import org.eclipse.californium.scandium.dtls.HandshakeException;
  27 +import org.eclipse.californium.scandium.dtls.Handshaker;
  28 +import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker;
  29 +import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker;
  30 +import org.eclipse.californium.scandium.dtls.ServerHandshaker;
  31 +import org.eclipse.californium.scandium.dtls.SessionAdapter;
  32 +import org.eclipse.leshan.client.californium.LeshanClient;
  33 +import org.eclipse.leshan.client.californium.LeshanClientBuilder;
  34 +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
  35 +import org.eclipse.leshan.client.object.Security;
  36 +import org.eclipse.leshan.client.object.Server;
  37 +import org.eclipse.leshan.client.observer.LwM2mClientObserver;
  38 +import org.eclipse.leshan.client.resource.ObjectsInitializer;
  39 +import org.eclipse.leshan.client.servers.ServerIdentity;
  40 +import org.eclipse.leshan.core.ResponseCode;
  41 +import org.eclipse.leshan.core.californium.DefaultEndpointFactory;
  42 +import org.eclipse.leshan.core.model.LwM2mModel;
  43 +import org.eclipse.leshan.core.model.ObjectLoader;
  44 +import org.eclipse.leshan.core.model.ObjectModel;
  45 +import org.eclipse.leshan.core.model.StaticModel;
  46 +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
  47 +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
  48 +import org.eclipse.leshan.core.request.BindingMode;
  49 +import org.eclipse.leshan.core.request.BootstrapRequest;
  50 +import org.eclipse.leshan.core.request.DeregisterRequest;
  51 +import org.eclipse.leshan.core.request.RegisterRequest;
  52 +import org.eclipse.leshan.core.request.UpdateRequest;
  53 +
  54 +import java.util.ArrayList;
  55 +import java.util.List;
  56 +import java.util.concurrent.ScheduledExecutorService;
  57 +
  58 +import static org.eclipse.leshan.core.LwM2mId.DEVICE;
  59 +import static org.eclipse.leshan.core.LwM2mId.SECURITY;
  60 +import static org.eclipse.leshan.core.LwM2mId.SERVER;
  61 +
  62 +@Slf4j
  63 +@Data
  64 +public class LwM2MTestClient {
  65 +
  66 + private final ScheduledExecutorService executor;
  67 + private final String endpoint;
  68 + private LeshanClient client;
  69 +
  70 + public void init(Security security, NetworkConfig coapConfig) {
  71 + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"};
  72 + List<ObjectModel> models = new ArrayList<>();
  73 + for (String resourceName : resources) {
  74 + models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
  75 + }
  76 + LwM2mModel model = new StaticModel(models);
  77 + ObjectsInitializer initializer = new ObjectsInitializer(model);
  78 + initializer.setInstancesForObject(SECURITY, security);
  79 + initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false));
  80 + initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
  81 +
  82 + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
  83 + dtlsConfig.setRecommendedCipherSuitesOnly(true);
  84 +
  85 + DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
  86 + engineFactory.setReconnectOnUpdate(false);
  87 + engineFactory.setResumeOnConnect(true);
  88 +
  89 + DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) {
  90 + @Override
  91 + protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) {
  92 +
  93 + return new DTLSConnector(dtlsConfig) {
  94 + @Override
  95 + protected void onInitializeHandshaker(Handshaker handshaker) {
  96 + handshaker.addSessionListener(new SessionAdapter() {
  97 +
  98 + @Override
  99 + public void handshakeStarted(Handshaker handshaker) throws HandshakeException {
  100 + if (handshaker instanceof ServerHandshaker) {
  101 + log.info("DTLS Full Handshake initiated by server : STARTED ...");
  102 + } else if (handshaker instanceof ResumingServerHandshaker) {
  103 + log.info("DTLS abbreviated Handshake initiated by server : STARTED ...");
  104 + } else if (handshaker instanceof ClientHandshaker) {
  105 + log.info("DTLS Full Handshake initiated by client : STARTED ...");
  106 + } else if (handshaker instanceof ResumingClientHandshaker) {
  107 + log.info("DTLS abbreviated Handshake initiated by client : STARTED ...");
  108 + }
  109 + }
  110 +
  111 + @Override
  112 + public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession)
  113 + throws HandshakeException {
  114 + if (handshaker instanceof ServerHandshaker) {
  115 + log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
  116 + } else if (handshaker instanceof ResumingServerHandshaker) {
  117 + log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker);
  118 + } else if (handshaker instanceof ClientHandshaker) {
  119 + log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
  120 + } else if (handshaker instanceof ResumingClientHandshaker) {
  121 + log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker);
  122 + }
  123 + }
  124 +
  125 + @Override
  126 + public void handshakeFailed(Handshaker handshaker, Throwable error) {
  127 + /** get cause */
  128 + String cause;
  129 + if (error != null) {
  130 + if (error.getMessage() != null) {
  131 + cause = error.getMessage();
  132 + } else {
  133 + cause = error.getClass().getName();
  134 + }
  135 + } else {
  136 + cause = "unknown cause";
  137 + }
  138 +
  139 + if (handshaker instanceof ServerHandshaker) {
  140 + log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause);
  141 + } else if (handshaker instanceof ResumingServerHandshaker) {
  142 + log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause);
  143 + } else if (handshaker instanceof ClientHandshaker) {
  144 + log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause);
  145 + } else if (handshaker instanceof ResumingClientHandshaker) {
  146 + log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause);
  147 + }
  148 + }
  149 + });
  150 + }
  151 + };
  152 + }
  153 + };
  154 +
  155 + LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
  156 + builder.setLocalAddress("0.0.0.0", 11000);
  157 + builder.setObjects(initializer.createAll());
  158 + builder.setCoapConfig(coapConfig);
  159 + builder.setDtlsConfig(dtlsConfig);
  160 + builder.setRegistrationEngineFactory(engineFactory);
  161 + builder.setEndpointFactory(endpointFactory);
  162 + builder.setSharedExecutor(executor);
  163 + builder.setDecoder(new DefaultLwM2mNodeDecoder(true));
  164 + builder.setEncoder(new DefaultLwM2mNodeEncoder(true));
  165 + client = builder.build();
  166 +
  167 + LwM2mClientObserver observer = new LwM2mClientObserver() {
  168 + @Override
  169 + public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
  170 + log.info("ClientObserver -> onBootstrapStarted...");
  171 + }
  172 +
  173 + @Override
  174 + public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
  175 + log.info("ClientObserver -> onBootstrapSuccess...");
  176 + }
  177 +
  178 + @Override
  179 + public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
  180 + log.info("ClientObserver -> onBootstrapFailure...");
  181 + }
  182 +
  183 + @Override
  184 + public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
  185 + log.info("ClientObserver -> onBootstrapTimeout...");
  186 + }
  187 +
  188 + @Override
  189 + public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
  190 +// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName());
  191 + }
  192 +
  193 + @Override
  194 + public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
  195 + log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID);
  196 + }
  197 +
  198 + @Override
  199 + public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
  200 + log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server);
  201 + }
  202 +
  203 + @Override
  204 + public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
  205 + log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request);
  206 + }
  207 +
  208 + @Override
  209 + public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
  210 +// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request);
  211 + }
  212 +
  213 + @Override
  214 + public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
  215 +// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request);
  216 + }
  217 +
  218 + @Override
  219 + public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
  220 +
  221 + }
  222 +
  223 + @Override
  224 + public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
  225 +
  226 + }
  227 +
  228 + @Override
  229 + public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
  230 + log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId());
  231 +
  232 + }
  233 +
  234 + @Override
  235 + public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
  236 + log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId());
  237 +
  238 + }
  239 +
  240 + @Override
  241 + public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
  242 + log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
  243 + }
  244 +
  245 + @Override
  246 + public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
  247 + log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
  248 + }
  249 + };
  250 + this.client.addObserver(observer);
  251 +
  252 + client.start();
  253 + }
  254 +
  255 + public void destroy() {
  256 + client.destroy(true);
  257 + }
  258 +
  259 +}
  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.transport.lwm2m.client;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
  20 +import org.eclipse.leshan.client.servers.ServerIdentity;
  21 +import org.eclipse.leshan.core.model.ObjectModel;
  22 +import org.eclipse.leshan.core.model.ResourceModel;
  23 +import org.eclipse.leshan.core.node.LwM2mResource;
  24 +import org.eclipse.leshan.core.response.ExecuteResponse;
  25 +import org.eclipse.leshan.core.response.ReadResponse;
  26 +import org.eclipse.leshan.core.response.WriteResponse;
  27 +
  28 +import javax.security.auth.Destroyable;
  29 +import java.text.SimpleDateFormat;
  30 +import java.util.Arrays;
  31 +import java.util.Calendar;
  32 +import java.util.HashMap;
  33 +import java.util.List;
  34 +import java.util.Map;
  35 +import java.util.Random;
  36 +import java.util.TimeZone;
  37 +
  38 +@Slf4j
  39 +public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
  40 +
  41 +
  42 + private static final Random RANDOM = new Random();
  43 + private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3
  44 +// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21
  45 + );
  46 +
  47 + @Override
  48 + public ReadResponse read(ServerIdentity identity, int resourceid) {
  49 + if (!identity.isSystem())
  50 + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
  51 + switch (resourceid) {
  52 + case 0:
  53 + return ReadResponse.success(resourceid, getManufacturer());
  54 + case 1:
  55 + return ReadResponse.success(resourceid, getModelNumber());
  56 + case 2:
  57 + return ReadResponse.success(resourceid, getSerialNumber());
  58 + case 3:
  59 + return ReadResponse.success(resourceid, getFirmwareVersion());
  60 + case 9:
  61 + return ReadResponse.success(resourceid, getBatteryLevel());
  62 + case 10:
  63 + return ReadResponse.success(resourceid, getMemoryFree());
  64 + case 11:
  65 + Map<Integer, Long> errorCodes = new HashMap<>();
  66 + errorCodes.put(0, getErrorCode());
  67 + return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER);
  68 + case 14:
  69 + return ReadResponse.success(resourceid, getUtcOffset());
  70 + case 15:
  71 + return ReadResponse.success(resourceid, getTimezone());
  72 + case 16:
  73 + return ReadResponse.success(resourceid, getSupportedBinding());
  74 + case 17:
  75 + return ReadResponse.success(resourceid, getDeviceType());
  76 + case 18:
  77 + return ReadResponse.success(resourceid, getHardwareVersion());
  78 + case 19:
  79 + return ReadResponse.success(resourceid, getSoftwareVersion());
  80 + case 20:
  81 + return ReadResponse.success(resourceid, getBatteryStatus());
  82 + case 21:
  83 + return ReadResponse.success(resourceid, getMemoryTotal());
  84 + default:
  85 + return super.read(identity, resourceid);
  86 + }
  87 + }
  88 +
  89 + @Override
  90 + public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) {
  91 + String withParams = null;
  92 + if (params != null && params.length() != 0) {
  93 + withParams = " with params " + params;
  94 + }
  95 + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceid, withParams != null ? withParams : "");
  96 + return ExecuteResponse.success();
  97 + }
  98 +
  99 + @Override
  100 + public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) {
  101 + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid);
  102 +
  103 + switch (resourceid) {
  104 + case 13:
  105 + return WriteResponse.notFound();
  106 + case 14:
  107 + setUtcOffset((String) value.getValue());
  108 + fireResourcesChange(resourceid);
  109 + return WriteResponse.success();
  110 + case 15:
  111 + setTimezone((String) value.getValue());
  112 + fireResourcesChange(resourceid);
  113 + return WriteResponse.success();
  114 + default:
  115 + return super.write(identity, resourceid, value);
  116 + }
  117 + }
  118 +
  119 + private String getManufacturer() {
  120 + return "Leshan Demo Device";
  121 + }
  122 +
  123 + private String getModelNumber() {
  124 + return "Model 500";
  125 + }
  126 +
  127 + private String getSerialNumber() {
  128 + return "LT-500-000-0001";
  129 + }
  130 +
  131 + private String getFirmwareVersion() {
  132 + return "1.0.0";
  133 + }
  134 +
  135 + private long getErrorCode() {
  136 + return 0;
  137 + }
  138 +
  139 + private int getBatteryLevel() {
  140 + return 42;
  141 + }
  142 +
  143 + private long getMemoryFree() {
  144 + return Runtime.getRuntime().freeMemory() / 1024;
  145 + }
  146 +
  147 + private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime());
  148 +
  149 + private String getUtcOffset() {
  150 + return utcOffset;
  151 + }
  152 +
  153 + private void setUtcOffset(String t) {
  154 + utcOffset = t;
  155 + }
  156 +
  157 + private String timeZone = TimeZone.getDefault().getID();
  158 +
  159 + private String getTimezone() {
  160 + return timeZone;
  161 + }
  162 +
  163 + private void setTimezone(String t) {
  164 + timeZone = t;
  165 + }
  166 +
  167 + private String getSupportedBinding() {
  168 + return "U";
  169 + }
  170 +
  171 + private String getDeviceType() {
  172 + return "Demo";
  173 + }
  174 +
  175 + private String getHardwareVersion() {
  176 + return "1.0.1";
  177 + }
  178 +
  179 + private String getSoftwareVersion() {
  180 + return "1.0.2";
  181 + }
  182 +
  183 + private int getBatteryStatus() {
  184 + return RANDOM.nextInt(7);
  185 + }
  186 +
  187 + private long getMemoryTotal() {
  188 + return Runtime.getRuntime().totalMemory() / 1024;
  189 + }
  190 +
  191 + @Override
  192 + public List<Integer> getAvailableResourceIds(ObjectModel model) {
  193 + return supportedResources;
  194 + }
  195 +
  196 + @Override
  197 + public void destroy() {
  198 + }
  199 +}
  1 +transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks
  2 +transport.lwm2m.security.key_store_password=server
  3 +edges.enabled=true
  4 +transport.lwm2m.bootstrap.security.alias=server
@@ -14,6 +14,8 @@ @@ -14,6 +14,8 @@
14 <logger name="org.springframework.boot.test" level="WARN"/> 14 <logger name="org.springframework.boot.test" level="WARN"/>
15 <logger name="org.apache.cassandra" level="WARN"/> 15 <logger name="org.apache.cassandra" level="WARN"/>
16 <logger name="org.cassandraunit" level="INFO"/> 16 <logger name="org.cassandraunit" level="INFO"/>
  17 + <logger name="org.eclipse.leshan" level="TRACE"/>
  18 +
17 19
18 <root level="WARN"> 20 <root level="WARN">
19 <appender-ref ref="console"/> 21 <appender-ref ref="console"/>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +
  3 +<!--
  4 +FILE INFORMATION
  5 +
  6 +OMA Permanent Document
  7 + File: OMA-SUP-XML_0-V1_2-20201110-A.xml
  8 + Path: http://www.openmobilealliance.org/release/ObjLwM2M_Security/
  9 +
  10 +OMNA LwM2M Registry
  11 + Path: https://github.com/OpenMobileAlliance/lwm2m-registry
  12 + Name: 0.xml
  13 +
  14 +NORMATIVE INFORMATION
  15 +
  16 + Information about this file can be found in the latest revision of
  17 +
  18 + OMA-TS-LightweightM2M_Core-V1_2
  19 +
  20 + This is available at http://www.openmobilealliance.org/release/LightweightM2M/
  21 +
  22 + Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
  23 +
  24 +LEGAL DISCLAIMER
  25 +
  26 + Copyright 2020 Open Mobile Alliance.
  27 +
  28 + Redistribution and use in source and binary forms, with or without
  29 + modification, are permitted provided that the following conditions
  30 + are met:
  31 +
  32 + 1. Redistributions of source code must retain the above copyright
  33 + notice, this list of conditions and the following disclaimer.
  34 + 2. Redistributions in binary form must reproduce the above copyright
  35 + notice, this list of conditions and the following disclaimer in the
  36 + documentation and/or other materials provided with the distribution.
  37 + 3. Neither the name of the copyright holder nor the names of its
  38 + contributors may be used to endorse or promote products derived
  39 + from this software without specific prior written permission.
  40 +
  41 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  42 + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  43 + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  44 + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  45 + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  46 + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  47 + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  48 + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  49 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  50 + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  51 + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  52 + POSSIBILITY OF SUCH DAMAGE.
  53 +
  54 + The above license is used as a license under copyright only. Please
  55 + reference the OMA IPR Policy for patent licensing terms:
  56 + https://www.omaspecworks.org/about/intellectual-property-rights/
  57 +
  58 +-->
  59 +
  60 +<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
  61 + <Object ObjectType="MODefinition">
  62 + <Name>LWM2M Security</Name>
  63 + <Description1><![CDATA[This LwM2M Object provides the keying material of a LwM2M Client appropriate to access a specified LwM2M Server. One Object Instance SHOULD address a LwM2M Bootstrap-Server.
  64 +These LwM2M Object Resources MUST only be changed by a LwM2M Bootstrap-Server or Bootstrap from Smartcard and MUST NOT be accessible by any other LwM2M Server.]]></Description1>
  65 + <ObjectID>0</ObjectID>
  66 + <ObjectURN>urn:oma:lwm2m:oma:0:1.2</ObjectURN>
  67 + <LWM2MVersion>1.1</LWM2MVersion>
  68 + <ObjectVersion>1.2</ObjectVersion>
  69 + <MultipleInstances>Multiple</MultipleInstances>
  70 + <Mandatory>Mandatory</Mandatory>
  71 + <Resources>
  72 + <Item ID="0">
  73 + <Name>LWM2M Server URI</Name>
  74 + <Operations></Operations>
  75 + <MultipleInstances>Single</MultipleInstances>
  76 + <Mandatory>Mandatory</Mandatory>
  77 + <Type>String</Type>
  78 + <RangeEnumeration>0..255</RangeEnumeration>
  79 + <Units></Units>
  80 + <Description><![CDATA[Uniquely identifies the LwM2M Server or LwM2M Bootstrap-Server. The format of the CoAP URI is defined in Section 6 of RFC 7252.]]></Description>
  81 + </Item>
  82 + <Item ID="1">
  83 + <Name>Bootstrap-Server</Name>
  84 + <Operations></Operations>
  85 + <MultipleInstances>Single</MultipleInstances>
  86 + <Mandatory>Mandatory</Mandatory>
  87 + <Type>Boolean</Type>
  88 + <RangeEnumeration></RangeEnumeration>
  89 + <Units></Units>
  90 + <Description><![CDATA[Determines if the current instance concerns a LwM2M Bootstrap-Server (true) or a standard LwM2M Server (false)]]></Description>
  91 + </Item>
  92 + <Item ID="2">
  93 + <Name>Security Mode</Name>
  94 + <Operations></Operations>
  95 + <MultipleInstances>Single</MultipleInstances>
  96 + <Mandatory>Mandatory</Mandatory>
  97 + <Type>Integer</Type>
  98 + <RangeEnumeration>0..4</RangeEnumeration>
  99 + <Units></Units>
  100 + <Description><![CDATA[Determines which security mode is used
  101 +0: Pre-Shared Key mode
  102 +1: Raw Public Key mode
  103 +2: Certificate mode
  104 +3: NoSec mode
  105 +4: Certificate mode with EST]]></Description>
  106 + </Item>
  107 + <Item ID="3">
  108 + <Name>Public Key or Identity</Name>
  109 + <Operations></Operations>
  110 + <MultipleInstances>Single</MultipleInstances>
  111 + <Mandatory>Mandatory</Mandatory>
  112 + <Type>Opaque</Type>
  113 + <RangeEnumeration></RangeEnumeration>
  114 + <Units></Units>
  115 + <Description><![CDATA[Stores the LwM2M Client's certificate, public key (RPK mode) or PSK Identity (PSK mode).]]></Description>
  116 + </Item>
  117 + <Item ID="4">
  118 + <Name>Server Public Key</Name>
  119 + <Operations></Operations>
  120 + <MultipleInstances>Single</MultipleInstances>
  121 + <Mandatory>Mandatory</Mandatory>
  122 + <Type>Opaque</Type>
  123 + <RangeEnumeration></RangeEnumeration>
  124 + <Units></Units>
  125 + <Description><![CDATA[Stores the LwM2M Server's, respectively LwM2M Bootstrap-Server's, certificate, public key (RPK mode) or trust anchor. The Certificate Mode Resource determines the content of this resource.]]></Description>
  126 + </Item>
  127 + <Item ID="5">
  128 + <Name>Secret Key</Name>
  129 + <Operations></Operations>
  130 + <MultipleInstances>Single</MultipleInstances>
  131 + <Mandatory>Mandatory</Mandatory>
  132 + <Type>Opaque</Type>
  133 + <RangeEnumeration></RangeEnumeration>
  134 + <Units></Units>
  135 + <Description><![CDATA[Stores the secret key (PSK mode) or private key (RPK or certificate mode).]]></Description>
  136 + </Item>
  137 + <Item ID="6">
  138 + <Name>SMS Security Mode</Name>
  139 + <Operations></Operations>
  140 + <MultipleInstances>Single</MultipleInstances>
  141 + <Mandatory>Optional</Mandatory>
  142 + <Type>Integer</Type>
  143 + <RangeEnumeration>0..255</RangeEnumeration>
  144 + <Units></Units>
  145 + <Description><![CDATA[Determines which SMS security mode is used:
  146 +0: Reserved for future use
  147 +1: DTLS mode (Device terminated) PSK mode assumed
  148 +2: Secure Packet Structure mode (Smartcard terminated)
  149 +3: NoSec mode
  150 +4: Reserved mode (DTLS mode with multiplexing Security Association support)
  151 +5-203 : Reserved for future use
  152 +204-255: Proprietary modes]]></Description>
  153 + </Item>
  154 + <Item ID="7">
  155 + <Name>SMS Binding Key Parameters</Name>
  156 + <Operations></Operations>
  157 + <MultipleInstances>Single</MultipleInstances>
  158 + <Mandatory>Optional</Mandatory>
  159 + <Type>Opaque</Type>
  160 + <RangeEnumeration>6</RangeEnumeration>
  161 + <Units></Units>
  162 + <Description><![CDATA[Stores the KIc, KID, SPI and TAR.]]></Description>
  163 + </Item>
  164 + <Item ID="8">
  165 + <Name>SMS Binding Secret Key(s)</Name>
  166 + <Operations></Operations>
  167 + <MultipleInstances>Single</MultipleInstances>
  168 + <Mandatory>Optional</Mandatory>
  169 + <Type>Opaque</Type>
  170 + <RangeEnumeration>16,32,48</RangeEnumeration>
  171 + <Units></Units>
  172 + <Description><![CDATA[Stores the values of the key(s) for the SMS binding.]]></Description>
  173 + </Item>
  174 + <Item ID="9">
  175 + <Name>LwM2M Server SMS Number</Name>
  176 + <Operations></Operations>
  177 + <MultipleInstances>Single</MultipleInstances>
  178 + <Mandatory>Optional</Mandatory>
  179 + <Type>String</Type>
  180 + <RangeEnumeration></RangeEnumeration>
  181 + <Units></Units>
  182 + <Description><![CDATA[MSISDN used by the LwM2M Client to send messages to the LwM2M Server via the SMS binding.]]></Description>
  183 + </Item>
  184 + <Item ID="10">
  185 + <Name>Short Server ID</Name>
  186 + <Operations></Operations>
  187 + <MultipleInstances>Single</MultipleInstances>
  188 + <Mandatory>Optional</Mandatory>
  189 + <Type>Integer</Type>
  190 + <RangeEnumeration>1..65534</RangeEnumeration>
  191 + <Units></Units>
  192 + <Description><![CDATA[This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client.
  193 +This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'.
  194 +The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.]]></Description>
  195 + </Item>
  196 + <Item ID="11">
  197 + <Name>Client Hold Off Time</Name>
  198 + <Operations></Operations>
  199 + <MultipleInstances>Single</MultipleInstances>
  200 + <Mandatory>Optional</Mandatory>
  201 + <Type>Integer</Type>
  202 + <RangeEnumeration></RangeEnumeration>
  203 + <Units>s</Units>
  204 + <Description><![CDATA[The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode.
  205 +In case client initiated bootstrap is supported by the LwM2M Client, this resource MUST be supported. This information is relevant for use with a Bootstrap-Server only.]]></Description>
  206 + </Item>
  207 + <Item ID="12">
  208 + <Name>Bootstrap-Server Account Timeout</Name>
  209 + <Operations></Operations>
  210 + <MultipleInstances>Single</MultipleInstances>
  211 + <Mandatory>Optional</Mandatory>
  212 + <Type>Integer</Type>
  213 + <RangeEnumeration></RangeEnumeration>
  214 + <Units>s</Units>
  215 + <Description><![CDATA[The LwM2M Client MUST purge the LwM2M Bootstrap-Server Account after the timeout value given by this resource. The lowest timeout value is 1.
  216 +If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.]]></Description>
  217 + </Item>
  218 + <Item ID="13">
  219 + <Name>Matching Type</Name>
  220 + <Operations></Operations>
  221 + <MultipleInstances>Single</MultipleInstances>
  222 + <Mandatory>Optional</Mandatory>
  223 + <Type>Integer</Type>
  224 + <RangeEnumeration>0..3</RangeEnumeration>
  225 + <Units></Units>
  226 + <Description><![CDATA[The Matching Type Resource specifies how the certificate or raw public key in in the Server Public Key is presented. Four values are currently defined:
  227 + 0: Exact match. This is the default value and also corresponds to the functionality of LwM2M v1.0. Hence, if this resource is not present then the content of the Server Public Key Resource corresponds to this value.
  228 + 1: SHA-256 hash [RFC6234]
  229 + 2: SHA-384 hash [RFC6234]
  230 + 3: SHA-512 hash [RFC6234]]]></Description>
  231 + </Item>
  232 + <Item ID="14">
  233 + <Name>SNI</Name>
  234 + <Operations></Operations>
  235 + <MultipleInstances>Single</MultipleInstances>
  236 + <Mandatory>Optional</Mandatory>
  237 + <Type>String</Type>
  238 + <RangeEnumeration></RangeEnumeration>
  239 + <Units></Units>
  240 + <Description><![CDATA[This resource holds the value of the Server Name Indication (SNI) value to be used during the TLS handshake. When this resource is present then the LwM2M Server URI acts as the address of the service while the SNI value is used for matching a presented certificate, or PSK identity.]]></Description>
  241 + </Item>
  242 + <Item ID="15">
  243 + <Name>Certificate Usage</Name>
  244 + <Operations></Operations>
  245 + <MultipleInstances>Single</MultipleInstances>
  246 + <Mandatory>Optional</Mandatory>
  247 + <Type>Integer</Type>
  248 + <RangeEnumeration>0..3</RangeEnumeration>
  249 + <Units></Units>
  250 + <Description><![CDATA[The Certificate Usage Resource specifies the semantic of the certificate or
  251 + raw public key stored in the Server Public Key Resource, which is used to match
  252 + the certificate presented in the TLS/DTLS handshake. The currently defined values are
  253 + 0 for "CA constraint", 1 for "service certificate constraint", 2 for "trust anchor
  254 + assertion", and 3 for "domain-issued certificate". When this resource is absent,
  255 + value (3) for domain issued certificate mode is assumed. More details about the
  256 + semantic of each value can be found in the security consideration section of the
  257 + LwM2M specification.]]></Description>
  258 + </Item>
  259 + <Item ID="16">
  260 + <Name>DTLS/TLS Ciphersuite</Name>
  261 + <Operations></Operations>
  262 + <MultipleInstances>Multiple</MultipleInstances>
  263 + <Mandatory>Optional</Mandatory>
  264 + <Type>Integer</Type>
  265 + <RangeEnumeration></RangeEnumeration>
  266 + <Units></Units>
  267 + <Description><![CDATA[When this resource is present it instructs the TLS/DTLS client to propose the indicated ciphersuite(s) in the ClientHello of the handshake. A ciphersuite is indicated as a 32-bit integer value. The IANA TLS ciphersuite registry is maintained at https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml. As an example, the TLS_PSK_WITH_AES_128_CCM_8 ciphersuite is represented with the following string "0xC0,0xA8". To form an integer value the two values are concatenated. In this example, the value is 0xc0a8 or 49320.]]></Description>
  268 + </Item>
  269 + <Item ID="17"><Name>OSCORE Security Mode</Name>
  270 + <Operations></Operations>
  271 + <MultipleInstances>Single</MultipleInstances>
  272 + <Mandatory>Optional</Mandatory>
  273 + <Type>Objlnk</Type>
  274 + <RangeEnumeration></RangeEnumeration>
  275 + <Units></Units>
  276 + <Description><![CDATA[If this resource is defined, it provides a link to the OSCORE Object Instance and OSCORE MUST be used by the LwM2M Client with the linked OSCORE Object Instance.]]></Description>
  277 + </Item>
  278 + <Item ID="18">
  279 + <Name>Groups To Use by Client</Name>
  280 + <Operations></Operations>
  281 + <MultipleInstances>Multiple</MultipleInstances>
  282 + <Mandatory>Optional</Mandatory>
  283 + <Type>Integer</Type>
  284 + <RangeEnumeration>0..65535</RangeEnumeration>
  285 + <Units></Units>
  286 + <Description><![CDATA[If this resource is defined, it indicates what groups the LwM2M Client should use with a LwM2M Server/LwM2M Bootstrap-Server (ordered from most preferred to least preferred). Resource instance 0 indicates the most preferred group. The values are taken from Section 4.2.7 of RFC 8446. An example is secp256r1 (0x0017).]]></Description>
  287 + </Item>
  288 + <Item ID="19">
  289 + <Name>Signature Algorithms Supported by Server</Name>
  290 + <Operations></Operations>
  291 + <MultipleInstances>Multiple</MultipleInstances>
  292 + <Mandatory>Optional</Mandatory>
  293 + <Type>Integer</Type>
  294 + <RangeEnumeration>0..65535</RangeEnumeration>
  295 + <Units></Units>
  296 + <Description><![CDATA[If this resource is defined, it indicates what signature algorithms the LwM2M Server/LwM2M Bootstrap-Server supports. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
  297 + </Item>
  298 + <Item ID="20"><Name>Signature Algorithms To Use by Client</Name>
  299 + <Operations></Operations>
  300 + <MultipleInstances>Multiple</MultipleInstances>
  301 + <Mandatory>Optional</Mandatory>
  302 + <Type>Integer</Type>
  303 + <RangeEnumeration>0..65535</RangeEnumeration>
  304 + <Units></Units>
  305 + <Description><![CDATA[If this resource is defined, it indicates what signature algorithms the LwM2M Client should use with a LwM2M Server/LwM2M Bootstrap-Server (ordered from most preferred to least preferred). Resource instance 0 indicates the most preferred group. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
  306 + </Item>
  307 + <Item ID="21">
  308 + <Name>Signature Algorithm Certs Supported by Server</Name>
  309 + <Operations></Operations>
  310 + <MultipleInstances>Multiple</MultipleInstances>
  311 + <Mandatory>Optional</Mandatory>
  312 + <Type>Integer</Type>
  313 + <RangeEnumeration>0..65535</RangeEnumeration>
  314 + <Units></Units>
  315 + <Description><![CDATA[If this resource is defined, it indicates what certificate-specific signature algorithms the the LwM2M Server/LwM2M Bootstrap-Server supports. The values are taken from Section 4.2.3 of RFC 8446. An example is ecdsa_secp256r1_sha256(0x0403).]]></Description>
  316 + </Item>
  317 + <Item ID="22">
  318 + <Name>TLS 1.3 Features To Use by Client</Name>
  319 + <Operations></Operations>
  320 + <MultipleInstances>Single</MultipleInstances>
  321 + <Mandatory>Optional</Mandatory>
  322 + <Type>Integer</Type>
  323 + <RangeEnumeration>0..65535</RangeEnumeration>
  324 + <Units></Units>
  325 + <Description><![CDATA[If this resource is defined, it indicates which features the LwM2M Client should use with the respective LwM2M Server/LwM2M Bootstrap-Server. The bitmask values listed below are defined. A bit value of '0' means the feature should not be used. bit(0) - PSK Plain, bit(1) - 0-RTT, bit(2) - PSK with PFS, bit(3) - Certificate-based Authentication. Bit(4) to bit(31) are reserved.]]></Description>
  326 + </Item>
  327 + <Item ID="23">
  328 + <Name>TLS Extensions Supported by Server</Name>
  329 + <Operations></Operations>
  330 + <MultipleInstances>Single</MultipleInstances>
  331 + <Mandatory>Optional</Mandatory>
  332 + <Type>Integer</Type>
  333 + <RangeEnumeration>0..65535</RangeEnumeration>
  334 + <Units></Units>
  335 + <Description><![CDATA[If this resource is defined, it indicates what extensions the LwM2M Server/LwM2M Bootstrap-Server supports in form of a bitmap. The following values are defined: bit(0) - Server Name Indication (RFC 6066), bit (1) - Max Fragment Length (RFC 6066), bit (2) - Status Request (RFC 6066), bit (3) - Heartbeat (RFC 6520), bit (4) - Application Layer Protocol Negotiation (RFC 7301), bit (5) - Signed Certificate Timestamp (RFC 6962), bit (6) - Certificate Compression (draft-ietf-tls-certificate-compression), bit (7) - Record Size Limit (RFC 8449), bit (8) - Ticket Pinning (draft-ietf-tls-pinning-ticket), bit (9) - Certificate Authorities (RFC 8446), bit (10) - OID Filters (RFC 8446), bit (11) - Post Handshake Auth (RFC 8446), bit (12) - Connection ID (draft-ietf-tls-dtls-connection-id/draft-ietf-tls-dtls13). Bit(13) to bit(31) are reserved. ]]></Description>
  336 + </Item>
  337 + <Item ID="24">
  338 + <Name>TLS Extensions To Use by Client</Name>
  339 + <Operations></Operations>
  340 + <MultipleInstances>Single</MultipleInstances>
  341 + <Mandatory>Optional</Mandatory>
  342 + <Type>Integer</Type>
  343 + <RangeEnumeration>0..65535</RangeEnumeration>
  344 + <Units></Units>
  345 + <Description><![CDATA[If this resource is defined, it indicates what extensions the LwM2M Client should use with the LwM2M Server/LwM2M Bootstrap-Server in form of a bitmap. The following values are defined: bit(0) - Server Name Indication (RFC 6066), bit (1) - Max Fragment Length (RFC 6066), bit (2) - Status Request (RFC 6066), bit (3) - Heartbeat (RFC 6520), bit (4) - Application Layer Protocol Negotiation (RFC 7301), bit (5) - Signed Certificate Timestamp (RFC 6962), bit (6) - Certificate Compression (draft-ietf-tls-certificate-compression), bit (7) - Record Size Limit (RFC 8449), bit (8) - Ticket Pinning (draft-ietf-tls-pinning-ticket), bit (9) - Certificate Authorities (RFC 8446), bit (10) - OID Filters (RFC 8446), bit (11) - Post Handshake Auth (RFC 8446), bit (12) - Connection ID (draft-ietf-tls-dtls-connection-id/draft-ietf-tls-dtls13). Bit(13) to bit(31) are reserved. ]]></Description>
  346 + </Item>
  347 + <Item ID="25">
  348 + <Name>Secondary LwM2M Server URI</Name>
  349 + <Operations></Operations>
  350 + <MultipleInstances>Multiple</MultipleInstances>
  351 + <Mandatory>Optional</Mandatory>
  352 + <Type>String</Type>
  353 + <RangeEnumeration>0..255</RangeEnumeration>
  354 + <Units></Units>
  355 + <Description><![CDATA[If this resource is present then the LwM2M Server URI in the Security Object, Resource ID 0, is augmented with information about further LwM2M Server URIs that can be used with the same security information found in the LwM2M Security Object. This is useful when a LwM2M Server is reachable via two different transport bindings (i.e. URIs). For example when the same server is reachable with two different URIs, such as a "coaps" and a "coaps+tcp" URI scheme.]]></Description>
  356 + </Item>
  357 + <Item ID="26"><Name>MQTT Server</Name>
  358 + <Operations></Operations>
  359 + <MultipleInstances>Single</MultipleInstances>
  360 + <Mandatory>Optional</Mandatory>
  361 + <Type>Objlnk</Type>
  362 + <RangeEnumeration></RangeEnumeration>
  363 + <Units></Units>
  364 + <Description><![CDATA[If this resource is defined, it provides a link to a MQTT Server Object Instance, which offers additional configuration information for use with this MQTT server. This Resource is used only when the URI scheme in the LwM2M Server URI Resource indicates the use of MQTT.]]></Description>
  365 + </Item>
  366 + <Item ID="27"><Name>LwM2M COSE Security</Name>
  367 + <Operations></Operations>
  368 + <MultipleInstances>Multiple</MultipleInstances>
  369 + <Mandatory>Optional</Mandatory>
  370 + <Type>Objlnk</Type>
  371 + <RangeEnumeration></RangeEnumeration>
  372 + <Units></Units>
  373 + <Description><![CDATA[If this resource is defined, it provides a links to LwM2M COSE Object Instances, which contain security-relevant configuration information for use with COSE.]]></Description>
  374 + </Item>
  375 + <Item ID="28"><Name>RDS Destination Port</Name>
  376 + <Operations></Operations>
  377 + <MultipleInstances>Single</MultipleInstances>
  378 + <Mandatory>Optional</Mandatory>
  379 + <Type>Integer</Type>
  380 + <RangeEnumeration>0..15</RangeEnumeration>
  381 + <Units></Units>
  382 + <Description><![CDATA[This resource provides the default RDS Destination Port Number (as defined in 3GPP TS 24.250) to use for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
  383 + </Item>
  384 + <Item ID="29"><Name>RDS Source Port</Name>
  385 + <Operations></Operations>
  386 + <MultipleInstances>Single</MultipleInstances>
  387 + <Mandatory>Optional</Mandatory>
  388 + <Type>Integer</Type>
  389 + <RangeEnumeration>0..15</RangeEnumeration>
  390 + <Units></Units>
  391 + <Description><![CDATA[This resource provides the default RDS Source Port Number (as defined in 3GPP TS 24.250) to use for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
  392 + </Item>
  393 + <Item ID="30"><Name>RDS Application ID</Name>
  394 + <Operations></Operations>
  395 + <MultipleInstances>Single</MultipleInstances>
  396 + <Mandatory>Optional</Mandatory>
  397 + <Type>String</Type>
  398 + <RangeEnumeration></RangeEnumeration>
  399 + <Units></Units>
  400 + <Description><![CDATA[This resource provides the Application ID (as defined in 3GPP TS 24.250) to use for querying the SCEF for the source and destination port numbers for contacting the LwM2M or Bootstrap Server when communicating through the SCEF across the Non-IP binding.]]></Description>
  401 + </Item>
  402 + </Resources>
  403 + <Description2><![CDATA[]]></Description2>
  404 + </Object>
  405 +</LWM2M>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +
  3 +<!--
  4 +FILE INFORMATION
  5 +
  6 +OMA Permanent Document
  7 + File: OMA-SUP-XML_1-V1_2-20201110-A.xml
  8 + Path: http://www.openmobilealliance.org/release/ObjLwM2M_Server/
  9 +
  10 +OMNA LwM2M Registry
  11 + Path: https://github.com/OpenMobileAlliance/lwm2m-registry
  12 + Name: 1.xml
  13 +
  14 +NORMATIVE INFORMATION
  15 +
  16 + Information about this file can be found in the latest revision of
  17 +
  18 + OMA-TS-LightweightM2M_Core-V1_2
  19 +
  20 + This is available at http://www.openmobilealliance.org/release/LightweightM2M/
  21 +
  22 + Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
  23 +
  24 +LEGAL DISCLAIMER
  25 +
  26 + Copyright 2020 Open Mobile Alliance.
  27 +
  28 + Redistribution and use in source and binary forms, with or without
  29 + modification, are permitted provided that the following conditions
  30 + are met:
  31 +
  32 + 1. Redistributions of source code must retain the above copyright
  33 + notice, this list of conditions and the following disclaimer.
  34 + 2. Redistributions in binary form must reproduce the above copyright
  35 + notice, this list of conditions and the following disclaimer in the
  36 + documentation and/or other materials provided with the distribution.
  37 + 3. Neither the name of the copyright holder nor the names of its
  38 + contributors may be used to endorse or promote products derived
  39 + from this software without specific prior written permission.
  40 +
  41 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  42 + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  43 + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  44 + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  45 + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  46 + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  47 + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  48 + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  49 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  50 + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  51 + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  52 + POSSIBILITY OF SUCH DAMAGE.
  53 +
  54 + The above license is used as a license under copyright only. Please
  55 + reference the OMA IPR Policy for patent licensing terms:
  56 + https://www.omaspecworks.org/about/intellectual-property-rights/
  57 +
  58 +-->
  59 +
  60 +<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
  61 + <Object ObjectType="MODefinition">
  62 + <Name>LwM2M Server</Name>
  63 + <Description1><![CDATA[This LwM2M Objects provides the data related to a LwM2M Server. A Bootstrap-Server has no such an Object Instance associated to it.]]></Description1>
  64 + <ObjectID>1</ObjectID>
  65 + <ObjectURN>urn:oma:lwm2m:oma:1:1.2</ObjectURN>
  66 + <LWM2MVersion>1.2</LWM2MVersion>
  67 + <ObjectVersion>1.2</ObjectVersion>
  68 + <MultipleInstances>Multiple</MultipleInstances>
  69 + <Mandatory>Mandatory</Mandatory>
  70 + <Resources>
  71 + <Item ID="0">
  72 + <Name>Short Server ID</Name>
  73 + <Operations>R</Operations>
  74 + <MultipleInstances>Single</MultipleInstances>
  75 + <Mandatory>Mandatory</Mandatory>
  76 + <Type>Integer</Type>
  77 + <RangeEnumeration>1..65534</RangeEnumeration>
  78 + <Units></Units>
  79 + <Description><![CDATA[Used as link to associate server Object Instance.]]></Description>
  80 + </Item>
  81 + <Item ID="1">
  82 + <Name>Lifetime</Name>
  83 + <Operations>RW</Operations>
  84 + <MultipleInstances>Single</MultipleInstances>
  85 + <Mandatory>Mandatory</Mandatory>
  86 + <Type>Integer</Type>
  87 + <RangeEnumeration></RangeEnumeration>
  88 + <Units>s</Units>
  89 + <Description><![CDATA[Specify the lifetime of the registration in seconds (see Client Registration Interface). If the value is set to 0, the lifetime is infinite.]]></Description>
  90 + </Item>
  91 + <Item ID="2">
  92 + <Name>Default Minimum Period</Name>
  93 + <Operations>RW</Operations>
  94 + <MultipleInstances>Single</MultipleInstances>
  95 + <Mandatory>Optional</Mandatory>
  96 + <Type>Integer</Type>
  97 + <RangeEnumeration></RangeEnumeration>
  98 + <Units>s</Units>
  99 + <Description><![CDATA[The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation.
  100 +If this Resource doesn’t exist, the default value is 0.]]></Description>
  101 + </Item>
  102 + <Item ID="3">
  103 + <Name>Default Maximum Period</Name>
  104 + <Operations>RW</Operations>
  105 + <MultipleInstances>Single</MultipleInstances>
  106 + <Mandatory>Optional</Mandatory>
  107 + <Type>Integer</Type>
  108 + <RangeEnumeration></RangeEnumeration>
  109 + <Units>s</Units>
  110 + <Description><![CDATA[The default value the LwM2M Client should use for the Maximum Period of an Observation in the absence of this parameter being included in an Observation.]]></Description>
  111 + </Item>
  112 + <Item ID="4">
  113 + <Name>Disable</Name>
  114 + <Operations>E</Operations>
  115 + <MultipleInstances>Single</MultipleInstances>
  116 + <Mandatory>Optional</Mandatory>
  117 + <Type></Type>
  118 + <RangeEnumeration></RangeEnumeration>
  119 + <Units></Units>
  120 + <Description><![CDATA[If this Resource is executed, this LwM2M Server Object is disabled for a certain period defined in the Disabled Timeout Resource. After receiving "Execute" operation, LwM2M Client MUST send response of the operation and perform de-registration process, and underlying network connection between the Client and Server MUST be disconnected to disable the LwM2M Server account.
  121 +After the above process, the LwM2M Client MUST NOT send any message to the Server and ignore all the messages from the LwM2M Server for the period.]]></Description>
  122 + </Item>
  123 + <Item ID="5">
  124 + <Name>Disable Timeout</Name>
  125 + <Operations>RW</Operations>
  126 + <MultipleInstances>Single</MultipleInstances>
  127 + <Mandatory>Optional</Mandatory>
  128 + <Type>Integer</Type>
  129 + <RangeEnumeration></RangeEnumeration>
  130 + <Units>s</Units>
  131 + <Description><![CDATA[A period to disable the Server. After this period, the LwM2M Client MUST perform registration process to the Server. If this Resource is not set, a default timeout value is 86400 (1 day).]]></Description>
  132 + </Item>
  133 + <Item ID="6">
  134 + <Name>Notification Storing When Disabled or Offline</Name>
  135 + <Operations>RW</Operations>
  136 + <MultipleInstances>Single</MultipleInstances>
  137 + <Mandatory>Mandatory</Mandatory>
  138 + <Type>Boolean</Type>
  139 + <RangeEnumeration></RangeEnumeration>
  140 + <Units></Units>
  141 + <Description><![CDATA[If true, the LwM2M Client stores "Notify" operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored "Notify" operations to the Server.
  142 +If false, the LwM2M Client discards all the "Notify" operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline.
  143 +The default value is true.
  144 +The maximum number of storing Notifications per Server is up to the implementation.]]></Description>
  145 + </Item>
  146 + <Item ID="7">
  147 + <Name>Binding</Name>
  148 + <Operations>RW</Operations>
  149 + <MultipleInstances>Single</MultipleInstances>
  150 + <Mandatory>Mandatory</Mandatory>
  151 + <Type>String</Type>
  152 + <RangeEnumeration></RangeEnumeration>
  153 + <Units></Units>
  154 + <Description><![CDATA[The possible values are those listed in the LwM2M Core Specification. This Resource defines the transport binding configured for the LwM2M Client.
  155 +If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.]]></Description>
  156 + </Item>
  157 + <Item ID="8">
  158 + <Name>Registration Update Trigger</Name>
  159 + <Operations>E</Operations>
  160 + <MultipleInstances>Single</MultipleInstances>
  161 + <Mandatory>Mandatory</Mandatory>
  162 + <Type></Type>
  163 + <RangeEnumeration></RangeEnumeration>
  164 + <Units></Units>
  165 + <Description><![CDATA[If this Resource is executed the LwM2M Client MUST perform an "Update" operation with this LwM2M Server. The LwM2M Client can use a transport binding supported in the Current Binding Mode, Preferred Transport resource or the transport specified as an argument in the Registration Update Trigger.]]></Description>
  166 + </Item>
  167 + <Item ID="9">
  168 + <Name>Bootstrap-Request Trigger</Name>
  169 + <Operations>E</Operations>
  170 + <MultipleInstances>Single</MultipleInstances>
  171 + <Mandatory>Optional</Mandatory>
  172 + <Type></Type>
  173 + <RangeEnumeration></RangeEnumeration>
  174 + <Units></Units>
  175 + <Description><![CDATA[When this Resource is executed the LwM2M Client MUST initiate a "Client Initiated Bootstrap" procedure in using the LwM2M Bootstrap-Server Account.]]></Description>
  176 + </Item>
  177 + <Item ID="10">
  178 + <Name>APN Link</Name>
  179 + <Operations>RW</Operations>
  180 + <MultipleInstances>Single</MultipleInstances>
  181 + <Mandatory>Optional</Mandatory>
  182 + <Type>Objlnk</Type>
  183 + <RangeEnumeration></RangeEnumeration>
  184 + <Units></Units>
  185 + <Description><![CDATA[If this resource is defined, it provides a link to the APN connection profile Object Instance (OMNA registered Object ID:11) to be used to communicate with this server.]]></Description>
  186 + </Item>
  187 + <Item ID="11">
  188 + <Name>TLS-DTLS Alert Code</Name>
  189 + <Operations>R</Operations>
  190 + <MultipleInstances>Single</MultipleInstances>
  191 + <Mandatory>Optional</Mandatory>
  192 + <Type>Integer</Type>
  193 + <RangeEnumeration>0..255</RangeEnumeration>
  194 + <Units></Units>
  195 + <Description><![CDATA[If this resource is defined, it contains the most recent TLS / DTLS alert message received from the LwM2M Server respective represented by the AlertDescription defined in Section 7.2 of RFC 5246. This resource set by the LwM2M Client may help the LwM2M Bootstrap-Server to determine the cause of TLS/DTLS connection failure with the respective LwM2M Server.]]></Description>
  196 + </Item>
  197 + <Item ID="12">
  198 + <Name>Last Bootstrapped</Name>
  199 + <Operations>R</Operations>
  200 + <MultipleInstances>Single</MultipleInstances>
  201 + <Mandatory>Optional</Mandatory>
  202 + <Type>Time</Type>
  203 + <RangeEnumeration></RangeEnumeration>
  204 + <Units></Units>
  205 + <Description><![CDATA[If this resource is defined, it represents the last time that the bootstrap server updated this LwM2M Server Account. The LwM2M Client is responsible for updating this value. When the Bootstrap Server detects that this LwM2M Server Account is "out-of-date", the Bootstrap Server can update the LwM2M Server Account as represented by the LwM2M Server object instance.]]></Description>
  206 + </Item>
  207 + <Item ID="13">
  208 + <Name>Registration Priority Order</Name>
  209 + <Operations>R</Operations>
  210 + <MultipleInstances>Single</MultipleInstances>
  211 + <Mandatory>Optional</Mandatory>
  212 + <Type>Integer</Type>
  213 + <RangeEnumeration></RangeEnumeration>
  214 + <Units></Units>
  215 + <Description><![CDATA[The LwM2M Client sequences the LwM2M Server registrations in increasing order of this value. If this value is not defined, registration attempts to this server are not impacted by other server registrations.]]></Description>
  216 + </Item>
  217 + <Item ID="14">
  218 + <Name>Initial Registration Delay Timer</Name>
  219 + <Operations>RW</Operations>
  220 + <MultipleInstances>Single</MultipleInstances>
  221 + <Mandatory>Optional</Mandatory>
  222 + <Type>Integer</Type>
  223 + <RangeEnumeration></RangeEnumeration>
  224 + <Units>s</Units>
  225 + <Description><![CDATA[The delay, in seconds, before registration is attempted for this LwM2M Server based upon the completion of registration of the previous LwM2M Server in the registration order. This is only applied until the first successful registration after a successful bootstrapping sequence.]]></Description>
  226 + </Item>
  227 + <Item ID="15">
  228 + <Name>Registration Failure Block</Name>
  229 + <Operations>R</Operations>
  230 + <MultipleInstances>Single</MultipleInstances>
  231 + <Mandatory>Optional</Mandatory>
  232 + <Type>Boolean</Type>
  233 + <RangeEnumeration></RangeEnumeration>
  234 + <Units></Units>
  235 + <Description><![CDATA[When set to true and registration to this LwM2M server fails, the LwM2M Client blocks registration to other servers in the order. When set to false, the LwM2M Client proceeds with registration to the next server in the order.]]></Description>
  236 + </Item>
  237 + <Item ID="16">
  238 + <Name>Bootstrap on Registration Failure</Name>
  239 + <Operations>R</Operations>
  240 + <MultipleInstances>Single</MultipleInstances>
  241 + <Mandatory>Optional</Mandatory>
  242 + <Type>Boolean</Type>
  243 + <RangeEnumeration></RangeEnumeration>
  244 + <Units></Units>
  245 + <Description><![CDATA[If set to true, this indicates that the LwM2M Client should re-bootstrap when either registration is explicitly rejected by the LwM2M Server or registration is considered as failing as dictated by the other resource settings. If set to false, the LwM2M Client will continue with the registration attempts as dictated by the other resource settings.]]></Description>
  246 + </Item>
  247 + <Item ID="17">
  248 + <Name>Communication Retry Count</Name>
  249 + <Operations>RW</Operations>
  250 + <MultipleInstances>Single</MultipleInstances>
  251 + <Mandatory>Optional</Mandatory>
  252 + <Type>Integer</Type>
  253 + <RangeEnumeration></RangeEnumeration>
  254 + <Units></Units>
  255 + <Description><![CDATA[The number of successive communication attempts before which a communication sequence is considered as failed.]]></Description>
  256 + </Item>
  257 + <Item ID="18">
  258 + <Name>Communication Retry Timer</Name>
  259 + <Operations>RW</Operations>
  260 + <MultipleInstances>Single</MultipleInstances>
  261 + <Mandatory>Optional</Mandatory>
  262 + <Type>Integer</Type>
  263 + <RangeEnumeration></RangeEnumeration>
  264 + <Units>s</Units>
  265 + <Description><![CDATA[The delay, in seconds, between successive communication attempts in a communication sequence. This value is multiplied by two to the power of the communication retry attempt minus one (2**(retry attempt-1)) to create an exponential back-off.]]></Description>
  266 + </Item>
  267 + <Item ID="19">
  268 + <Name>Communication Sequence Delay Timer</Name>
  269 + <Operations>RW</Operations>
  270 + <MultipleInstances>Single</MultipleInstances>
  271 + <Mandatory>Optional</Mandatory>
  272 + <Type>Integer</Type>
  273 + <RangeEnumeration></RangeEnumeration>
  274 + <Units>s</Units>
  275 + <Description><![CDATA[The delay, in seconds, between successive communication sequences. A communication sequence is defined as the exhaustion of the Communication Retry Count and Communication Retry Timer values. A communication sequence can be applied to server registrations or bootstrapping attempts. MAX_VALUE means do not perform another communication sequence.]]></Description>
  276 + </Item>
  277 + <Item ID="20">
  278 + <Name>Communication Sequence Retry Count</Name>
  279 + <Operations>RW</Operations>
  280 + <MultipleInstances>Single</MultipleInstances>
  281 + <Mandatory>Optional</Mandatory>
  282 + <Type>Integer</Type>
  283 + <RangeEnumeration></RangeEnumeration>
  284 + <Units></Units>
  285 + <Description><![CDATA[The number of successive communication sequences before which a registration attempt is considered as failed.]]></Description>
  286 + </Item>
  287 + <Item ID="21">
  288 + <Name>Trigger</Name>
  289 + <Operations>RW</Operations>
  290 + <MultipleInstances>Single</MultipleInstances>
  291 + <Mandatory>Optional</Mandatory>
  292 + <Type>Boolean</Type>
  293 + <RangeEnumeration></RangeEnumeration>
  294 + <Units></Units>
  295 + <Description><![CDATA[Using the Trigger Resource a LwM2M Client can indicate whether it is reachable over SMS (value set to 'true') or not (value set to 'false'). The default value (resource not present) is 'false'. When set to 'true' the LwM2M Server MAY, for example, request the LwM2M Client to perform operations, such as the "Update" operation by sending an "Execute" operation on "Registration Update Trigger" Resource via SMS. No SMS response is expected for such a message.]]></Description>
  296 + </Item>
  297 + <Item ID="22">
  298 + <Name>Preferred Transport</Name>
  299 + <Operations>RW</Operations>
  300 + <MultipleInstances>Single</MultipleInstances>
  301 + <Mandatory>Optional</Mandatory>
  302 + <Type>String</Type>
  303 + <RangeEnumeration>The possible values are those listed in the LwM2M Core Specification</RangeEnumeration>
  304 + <Units></Units>
  305 + <Description><![CDATA[Only a single transport binding SHALL be present. When the LwM2M client supports multiple transports, it MAY use this transport to initiate a connection. This resource can also be used to switch between multiple transports e.g. a non-IP device can switch to UDP transport to perform firmware updates.]]></Description>
  306 + </Item>
  307 + <Item ID="23"><Name>Mute Send</Name>
  308 + <Operations>RW</Operations>
  309 + <MultipleInstances>Single</MultipleInstances>
  310 + <Mandatory>Optional</Mandatory>
  311 + <Type>Boolean</Type>
  312 + <RangeEnumeration></RangeEnumeration>
  313 + <Units></Units>
  314 + <Description><![CDATA[If true or the Resource is not present, the LwM2M Client Send command capability is de-activated.
  315 +If false, the LwM2M Client Send Command capability is activated.]]></Description>
  316 + </Item>
  317 + <Item ID="24">
  318 + <Name>Alternate APN Links</Name>
  319 + <Operations>RW</Operations>
  320 + <MultipleInstances>Multiple</MultipleInstances>
  321 + <Mandatory>Optional</Mandatory>
  322 + <Type>Objlnk</Type>
  323 + <RangeEnumeration></RangeEnumeration>
  324 + <Units></Units>
  325 + <Description><![CDATA[If this resource is defined, it provides links to alternate APN connection profile Object Instance (OMNA registered Object ID:11) to be used to communicate with this server if Resource 10 has configuration conflicts.]]></Description>
  326 + </Item>
  327 + <Item ID="25">
  328 + <Name>Supported Server Versions</Name>
  329 + <Operations>RW</Operations>
  330 + <MultipleInstances>Multiple</MultipleInstances>
  331 + <Mandatory>Optional</Mandatory>
  332 + <Type>String</Type>
  333 + <RangeEnumeration></RangeEnumeration>
  334 + <Units></Units>
  335 + <Description><![CDATA[This resource provides the supported enabler versions of the server to the client as a set of strings. Format for each string is 1*DIGIT"."1*DIGIT"."1*DIGIT where the third DIGIT is optional.]]></Description>
  336 + </Item>
  337 + <Item ID="26">
  338 + <Name>Default Notification Mode</Name>
  339 + <Operations>RW</Operations>
  340 + <MultipleInstances>Single</MultipleInstances>
  341 + <Mandatory>Optional</Mandatory>
  342 + <Type>Integer</Type>
  343 + <RangeEnumeration>0..1</RangeEnumeration>
  344 + <Units></Units>
  345 + <Description><![CDATA[This resource indicates the default mode for observations to be sent: 0 = Non-Confirmable, 1 = Confirmable.]]></Description>
  346 + </Item>
  347 + <Item ID="27">
  348 + <Name>Profile ID Hash Algorithm</Name>
  349 + <Operations>RW</Operations>
  350 + <MultipleInstances>Single</MultipleInstances>
  351 + <Mandatory>Optional</Mandatory>
  352 + <Type>Integer</Type>
  353 + <RangeEnumeration>0..255</RangeEnumeration>
  354 + <Units/>
  355 + <Description><![CDATA[If this resource is defined, it contains the hash algorithm the LwM2M Server would prefer the LwM2M Client to use with the dynamically generated mode of creating Profile IDs. The numerical ID value of the 'Suite Identifiers' registered by RFC 6920 is used in this Resource.]]></Description>
  356 + </Item>
  357 + </Resources>
  358 + <Description2></Description2>
  359 + </Object>
  360 +</LWM2M>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +
  3 +<!--
  4 +FILE INFORMATION
  5 +
  6 +OMA Permanent Document
  7 + File: OMA-SUP-XML_2-V1_1-20201110-A.xml
  8 + Path: http://www.openmobilealliance.org/release/ObjLwM2M_ACL/
  9 +
  10 +OMNA LwM2M Registry
  11 + Path: https://github.com/OpenMobileAlliance/lwm2m-registry
  12 + Name: 2.xml
  13 +
  14 +NORMATIVE INFORMATION
  15 +
  16 + Information about this file can be found in the latest revision of
  17 +
  18 + OMA-TS-LightweightM2M_Core-V1_2
  19 +
  20 + This is available at http://www.openmobilealliance.org/release/LightweightM2M/
  21 +
  22 + Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
  23 +
  24 +LEGAL DISCLAIMER
  25 +
  26 + Copyright 2020 Open Mobile Alliance.
  27 +
  28 + Redistribution and use in source and binary forms, with or without
  29 + modification, are permitted provided that the following conditions
  30 + are met:
  31 +
  32 + 1. Redistributions of source code must retain the above copyright
  33 + notice, this list of conditions and the following disclaimer.
  34 + 2. Redistributions in binary form must reproduce the above copyright
  35 + notice, this list of conditions and the following disclaimer in the
  36 + documentation and/or other materials provided with the distribution.
  37 + 3. Neither the name of the copyright holder nor the names of its
  38 + contributors may be used to endorse or promote products derived
  39 + from this software without specific prior written permission.
  40 +
  41 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  42 + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  43 + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  44 + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  45 + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  46 + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  47 + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  48 + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  49 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  50 + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  51 + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  52 + POSSIBILITY OF SUCH DAMAGE.
  53 +
  54 + The above license is used as a license under copyright only. Please
  55 + reference the OMA IPR Policy for patent licensing terms:
  56 + https://www.omaspecworks.org/about/intellectual-property-rights/
  57 +-->
  58 +
  59 +<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M.xsd">
  60 + <Object ObjectType="MODefinition">
  61 + <Name>LwM2M Access Control</Name>
  62 + <Description1><![CDATA[Access Control Object is used to check whether the LwM2M Server has access right for performing an operation.]]></Description1>
  63 + <ObjectID>2</ObjectID>
  64 + <ObjectURN>urn:oma:lwm2m:oma:2:1.1</ObjectURN>
  65 + <LWM2MVersion>1.0</LWM2MVersion>
  66 + <ObjectVersion>1.1</ObjectVersion>
  67 + <MultipleInstances>Multiple</MultipleInstances>
  68 + <Mandatory>Optional</Mandatory>
  69 + <Resources>
  70 + <Item ID="0">
  71 + <Name>Object ID</Name>
  72 + <Operations>R</Operations>
  73 + <MultipleInstances>Single</MultipleInstances>
  74 + <Mandatory>Mandatory</Mandatory>
  75 + <Type>Integer</Type>
  76 + <RangeEnumeration>1..65534</RangeEnumeration>
  77 + <Units></Units>
  78 + <Description><![CDATA[Resources 0 and 1 point to the Object Instance for which the Instances of the ACL Resource of that Access Control Object Instance are applicable.]]></Description>
  79 + </Item>
  80 + <Item ID="1">
  81 + <Name>Object Instance ID</Name>
  82 + <Operations>R</Operations>
  83 + <MultipleInstances>Single</MultipleInstances>
  84 + <Mandatory>Mandatory</Mandatory>
  85 + <Type>Integer</Type>
  86 + <RangeEnumeration>0..65535</RangeEnumeration>
  87 + <Units></Units>
  88 + <Description><![CDATA[See above]]></Description>
  89 + </Item>
  90 + <Item ID="2">
  91 + <Name>ACL</Name>
  92 + <Operations>RW</Operations>
  93 + <MultipleInstances>Multiple</MultipleInstances>
  94 + <Mandatory>Optional</Mandatory>
  95 + <Type>Integer</Type>
  96 + <RangeEnumeration>0..31</RangeEnumeration>
  97 + <Units></Units>
  98 + <Description><![CDATA[The Resource Instance ID MUST be the Short Server ID of a certain LwM2M Server for which associated access rights are contained in the Resource Instance value.
  99 +The Resource Instance ID 0 is a specific ID, determining the ACL Instance which contains the default access rights.
  100 +Each bit set in the Resource Instance value, grants an access right to the LwM2M Server to the corresponding operation.
  101 +The bit order is specified as below.
  102 +1st LSB: R(Read, Observe, Write-Attributes)
  103 +2nd LSB: W(Write)
  104 +3rd LSB: E(Execute)
  105 +4th LSB: D(Delete)
  106 +5th LSB: C(Create)
  107 +Other bits are reserved for future use.]]></Description>
  108 + </Item>
  109 + <Item ID="3">
  110 + <Name>Access Control Owner</Name>
  111 + <Operations>RW</Operations>
  112 + <MultipleInstances>Single</MultipleInstances>
  113 + <Mandatory>Mandatory</Mandatory>
  114 + <Type>Integer</Type>
  115 + <RangeEnumeration>0..65535</RangeEnumeration>
  116 + <Units></Units>
  117 + <Description><![CDATA[Short Server ID of a certain LwM2M Server; only such an LwM2M Server can manage the Resources of this Object Instance.
  118 +The specific value MAX_ID=65535 means this Access Control Object Instance is created and modified during a Bootstrap phase only.]]></Description>
  119 + </Item>
  120 + </Resources>
  121 + <Description2><![CDATA[]]></Description2>
  122 + </Object>
  123 +</LWM2M>