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 146 <scope>runtime</scope>
147 147 </dependency>
148 148 <dependency>
  149 + <groupId>org.springframework.integration</groupId>
  150 + <artifactId>spring-integration-redis</artifactId>
  151 + </dependency>
  152 + <dependency>
149 153 <groupId>org.springframework.boot</groupId>
150 154 <artifactId>spring-boot-starter-security</artifactId>
151 155 </dependency>
... ...
1 1 {
2 2 "title": "Firmware",
  3 + "image": null,
3 4 "configuration": {
4 5 "description": "",
5 6 "widgets": {
... ... @@ -247,7 +248,7 @@
247 248 "name": "Edit firmware",
248 249 "icon": "edit",
249 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 252 "customCss": "",
252 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 254 "customResources": [],
... ... @@ -257,7 +258,7 @@
257 258 "name": "Download firware",
258 259 "icon": "file_download",
259 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 262 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
262 263 },
263 264 {
... ... @@ -1021,7 +1022,7 @@
1021 1022 "name": "Edit firmware",
1022 1023 "icon": "edit",
1023 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 1026 "customCss": "",
1026 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 1028 "customResources": [],
... ... @@ -1031,7 +1032,7 @@
1031 1032 "name": "Download firware",
1032 1033 "icon": "file_download",
1033 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 1036 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1036 1037 },
1037 1038 {
... ... @@ -1297,7 +1298,7 @@
1297 1298 "name": "Edit firmware",
1298 1299 "icon": "edit",
1299 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 1302 "customCss": "",
1302 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 1304 "customResources": [],
... ... @@ -1307,7 +1308,7 @@
1307 1308 "name": "Download firware",
1308 1309 "icon": "file_download",
1309 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 1312 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1312 1313 },
1313 1314 {
... ... @@ -1573,7 +1574,7 @@
1573 1574 "name": "Edit firmware",
1574 1575 "icon": "edit",
1575 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 1578 "customCss": "",
1578 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 1580 "customResources": [],
... ... @@ -1583,7 +1584,7 @@
1583 1584 "name": "Download firware",
1584 1585 "icon": "file_download",
1585 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 1588 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1588 1589 },
1589 1590 {
... ... @@ -1849,7 +1850,7 @@
1849 1850 "name": "Edit firmware",
1850 1851 "icon": "edit",
1851 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 1854 "customCss": "",
1854 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 1856 "customResources": [],
... ... @@ -1859,7 +1860,7 @@
1859 1860 "name": "Download firware",
1860 1861 "icon": "file_download",
1861 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 1864 "id": "12533058-42f6-e75f-620c-219c48d01ec0"
1864 1865 },
1865 1866 {
... ...
... ... @@ -36,7 +36,7 @@
36 36 "resources": [],
37 37 "templateHtml": "",
38 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 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 41 "dataKeySettingsSchema": "{}\n",
42 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 55 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
56 56 "templateCss": "",
57 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 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 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 72 "resources": [],
73 73 "templateHtml": "",
74 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 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 77 "dataKeySettingsSchema": "{}\n",
78 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 18 "resources": [],
19 19 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
20 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 23 "dataKeySettingsSchema": "{}\n",
24 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 43 into max_customer_ttl;
44 44 max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl);
45 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 47 partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date);
48 48 RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date;
49 49 IF partition_by_max_ttl_date IS NOT NULL THEN
... ...
... ... @@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS resource (
59 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 64 created_time bigint NOT NULL,
65 65 tenant_id uuid NOT NULL,
66 66 device_profile_id uuid,
... ... @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS firmware (
75 75 data_size bigint,
76 76 additional_info varchar,
77 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 81 ALTER TABLE dashboard
... ... @@ -101,13 +101,13 @@ DO $$
101 101 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
102 102 ALTER TABLE device_profile
103 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 105 END IF;
106 106
107 107 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN
108 108 ALTER TABLE device_profile
109 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 111 END IF;
112 112
113 113 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN
... ... @@ -119,13 +119,13 @@ DO $$
119 119 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
120 120 ALTER TABLE device
121 121 ADD CONSTRAINT fk_firmware_device
122   - FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  122 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
123 123 END IF;
124 124
125 125 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN
126 126 ALTER TABLE device
127 127 ADD CONSTRAINT fk_software_device
128   - FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  128 + FOREIGN KEY (firmware_id) REFERENCES ota_package(id);
129 129 END IF;
130 130 END;
131 131 $$;
... ...
... ... @@ -25,8 +25,8 @@ import lombok.extern.slf4j.Slf4j;
25 25 import org.apache.commons.collections.CollectionUtils;
26 26 import org.thingsboard.rule.engine.api.RpcError;
27 27 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
28   -import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
29 28 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
  29 +import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
30 30 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
31 31 import org.thingsboard.server.actors.ActorSystemContext;
32 32 import org.thingsboard.server.actors.TbActorCtx;
... ... @@ -73,7 +73,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM
73 73 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
74 74 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
75 75 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
76   -import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
77 76 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
78 77 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
79 78 import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
... ... @@ -164,8 +163,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
164 163 void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
165 164 ToDeviceRpcRequest request = msg.getMsg();
166 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 175 long timeout = request.getExpirationTime() - System.currentTimeMillis();
171 176 if (timeout <= 0) {
... ... @@ -258,8 +263,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
258 263 sentOneWayIds.add(entry.getKey());
259 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 274 sendToTransport(rpcRequest, sessionId, nodeId);
264 275 };
265 276 }
... ... @@ -306,25 +317,48 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
306 317
307 318 private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
308 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 364 private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {
... ... @@ -392,9 +426,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
392 426 }
393 427 if (hasNotificationData) {
394 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 431 } else {
400 432 log.debug("[{}] No registered attributes subscriptions to process!", deviceId);
... ... @@ -464,7 +496,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
464 496 if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
465 497 UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
466 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 502 sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId())));
... ... @@ -510,7 +542,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
510 542 notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
511 543 });
512 544 } else {
513   - sessions.forEach(this::notifyTransportAboutClosedSession);
  545 + sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!"));
514 546 attributeSubscriptions.clear();
515 547 rpcSubscriptions.clear();
516 548 dumpSessions();
... ... @@ -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 557 ToTransportMsg msg = ToTransportMsg.newBuilder()
523 558 .setSessionIdMSB(sessionId.getMostSignificantBits())
524 559 .setSessionIdLSB(sessionId.getLeastSignificantBits())
525   - .setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build();
  560 + .setSessionCloseNotification(sessionCloseNotificationProto)
  561 + .build();
526 562 systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
527 563 }
528 564
... ... @@ -730,7 +766,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
730 766 sessions.remove(sessionId);
731 767 rpcSubscriptions.remove(sessionId);
732 768 attributeSubscriptions.remove(sessionId);
733   - notifyTransportAboutClosedSession(sessionId, sessionMD);
  769 + notifyTransportAboutClosedSession(sessionId, sessionMD, "session timeout!");
734 770 });
735 771 if (!sessionsToRemove.isEmpty()) {
736 772 dumpSessions();
... ...
... ... @@ -39,8 +39,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
39 39 import org.thingsboard.server.common.data.EntityType;
40 40 import org.thingsboard.server.common.data.EntityView;
41 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 44 import org.thingsboard.server.common.data.HasName;
45 45 import org.thingsboard.server.common.data.HasTenantId;
46 46 import org.thingsboard.server.common.data.TbResource;
... ... @@ -70,7 +70,8 @@ import org.thingsboard.server.common.data.id.EdgeId;
70 70 import org.thingsboard.server.common.data.id.EntityId;
71 71 import org.thingsboard.server.common.data.id.EntityIdFactory;
72 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 75 import org.thingsboard.server.common.data.id.RuleChainId;
75 76 import org.thingsboard.server.common.data.id.RuleNodeId;
76 77 import org.thingsboard.server.common.data.id.TbResourceId;
... ... @@ -111,7 +112,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
111 112 import org.thingsboard.server.dao.entityview.EntityViewService;
112 113 import org.thingsboard.server.dao.exception.DataValidationException;
113 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 116 import org.thingsboard.server.dao.model.ModelConstants;
116 117 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
117 118 import org.thingsboard.server.dao.oauth2.OAuth2Service;
... ... @@ -129,9 +130,10 @@ import org.thingsboard.server.queue.discovery.PartitionService;
129 130 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
130 131 import org.thingsboard.server.queue.util.TbCoreComponent;
131 132 import org.thingsboard.server.service.component.ComponentDiscoveryService;
132   -import org.thingsboard.server.service.edge.EdgeNotificationService;
133 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 137 import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
136 138 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
137 139 import org.thingsboard.server.service.queue.TbClusterService;
... ... @@ -162,8 +164,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
162 164 public abstract class BaseController {
163 165
164 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 167 protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
168 168 protected static final String HOME_DASHBOARD = "homeDashboardId";
169 169
... ... @@ -256,10 +256,10 @@ public abstract class BaseController {
256 256 protected TbResourceService resourceService;
257 257
258 258 @Autowired
259   - protected FirmwareService firmwareService;
  259 + protected OtaPackageService otaPackageService;
260 260
261 261 @Autowired
262   - protected FirmwareStateService firmwareStateService;
  262 + protected OtaPackageStateService otaPackageStateService;
263 263
264 264 @Autowired
265 265 protected TbQueueProducerProvider producerProvider;
... ... @@ -514,8 +514,8 @@ public abstract class BaseController {
514 514 case TB_RESOURCE:
515 515 checkResourceId(new TbResourceId(entityId.getId()), operation);
516 516 return;
517   - case FIRMWARE:
518   - checkFirmwareId(new FirmwareId(entityId.getId()), operation);
  517 + case OTA_PACKAGE:
  518 + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);
519 519 return;
520 520 default:
521 521 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
... ... @@ -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 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 782 } catch (Exception e) {
783 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 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 794 } catch (Exception e) {
795 795 throw handleException(e, false);
796 796 }
... ...
... ... @@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
53 53 import org.thingsboard.server.common.data.id.DeviceProfileId;
54 54 import org.thingsboard.server.common.data.id.EdgeId;
55 55 import org.thingsboard.server.common.data.id.TenantId;
  56 +import org.thingsboard.server.common.data.ota.OtaPackageType;
56 57 import org.thingsboard.server.common.data.page.PageData;
57 58 import org.thingsboard.server.common.data.page.PageLink;
58 59 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -75,6 +76,7 @@ import javax.annotation.Nullable;
75 76 import java.io.IOException;
76 77 import java.util.ArrayList;
77 78 import java.util.List;
  79 +import java.util.UUID;
78 80 import java.util.stream.Collectors;
79 81
80 82 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
... ... @@ -153,7 +155,7 @@ public class DeviceController extends BaseController {
153 155 deviceStateService.onDeviceUpdated(savedDevice);
154 156 }
155 157
156   - firmwareStateService.update(savedDevice, oldDevice);
  158 + otaPackageStateService.update(savedDevice, oldDevice);
157 159
158 160 return savedDevice;
159 161 } catch (Exception e) {
... ... @@ -778,4 +780,19 @@ public class DeviceController extends BaseController {
778 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 168 null,
169 169 created ? ActionType.ADDED : ActionType.UPDATED, null);
170 170
171   - firmwareStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged);
  171 + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged);
172 172
173 173 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),
174 174 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.SecurityMode;
20 21 import org.springframework.security.access.prepost.PreAuthorize;
21 22 import org.springframework.web.bind.annotation.PathVariable;
22 23 import org.springframework.web.bind.annotation.RequestBody;
... ... @@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController {
46 47 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
47 48 @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
48 49 @ResponseBody
49   - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
  50 + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode,
50 51 @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException {
  52 + checkNotNull(strSecurityMode);
51 53 try {
  54 + SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode);
52 55 return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer);
53 56 } catch (Exception e) {
54 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 15 */
16 16 package org.thingsboard.server.controller;
17 17
18   -import com.google.common.hash.Hashing;
19 18 import lombok.extern.slf4j.Slf4j;
20 19 import org.apache.commons.lang3.StringUtils;
21 20 import org.springframework.core.io.ByteArrayResource;
... ... @@ -31,13 +30,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
31 30 import org.springframework.web.bind.annotation.RestController;
32 31 import org.springframework.web.multipart.MultipartFile;
33 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 35 import org.thingsboard.server.common.data.audit.ActionType;
37 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 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 41 import org.thingsboard.server.common.data.page.PageData;
42 42 import org.thingsboard.server.common.data.page.PageLink;
43 43 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -50,25 +50,26 @@ import java.nio.ByteBuffer;
50 50 @RestController
51 51 @TbCoreComponent
52 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 58 @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
58   - @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET)
  59 + @RequestMapping(value = "/otaPackage/{otaPackageId}/download", method = RequestMethod.GET)
59 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 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 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 71 .contentLength(resource.contentLength())
71   - .contentType(parseMediaType(firmware.getContentType()))
  72 + .contentType(parseMediaType(otaPackage.getContentType()))
72 73 .body(resource);
73 74 } catch (Exception e) {
74 75 throw handleException(e);
... ... @@ -76,142 +77,144 @@ public class FirmwareController extends BaseController {
76 77 }
77 78
78 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 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 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 87 } catch (Exception e) {
87 88 throw handleException(e);
88 89 }
89 90 }
90 91
91 92 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
92   - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET)
  93 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
93 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 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 100 } catch (Exception e) {
100 101 throw handleException(e);
101 102 }
102 103 }
103 104
104 105 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
105   - @RequestMapping(value = "/firmware", method = RequestMethod.POST)
  106 + @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
106 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 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 115 null, created ? ActionType.ADDED : ActionType.UPDATED, null);
115   - return savedFirmwareInfo;
  116 + return savedOtaPackageInfo;
116 117 } catch (Exception e) {
117   - logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo,
  118 + logEntityAction(emptyId(EntityType.OTA_PACKAGE), otaPackageInfo,
118 119 null, created ? ActionType.ADDED : ActionType.UPDATED, e);
119 120 throw handleException(e);
120 121 }
121 122 }
122 123
123 124 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
124   - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST)
  125 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST)
125 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 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 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 164 throw handleException(e);
162 165 }
163 166 }
164 167
165 168 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
166   - @RequestMapping(value = "/firmwares", method = RequestMethod.GET)
  169 + @RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
167 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 176 try {
174 177 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
175   - return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink));
  178 + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink));
176 179 } catch (Exception e) {
177 180 throw handleException(e);
178 181 }
179 182 }
180 183
181 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 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 195 checkParameter("deviceProfileId", strDeviceProfileId);
193 196 checkParameter("type", strType);
194 197 try {
195 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 201 } catch (Exception e) {
199 202 throw handleException(e);
200 203 }
201 204 }
202 205
203 206 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
204   - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
  207 + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
205 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 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 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 218 throw handleException(e);
216 219 }
217 220 }
... ...
... ... @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.id.DeviceId;
40 40 import org.thingsboard.server.common.data.id.EntityId;
41 41 import org.thingsboard.server.common.data.id.TenantId;
42 42 import org.thingsboard.server.common.data.id.UUIDBased;
43   -import org.thingsboard.server.common.data.rpc.RpcRequest;
44 43 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
45 44 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
46 45 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -97,22 +96,17 @@ public class RpcController extends BaseController {
97 96 private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
98 97 try {
99 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 100 SecurityUser currentUser = getCurrentUser();
107 101 TenantId tenantId = currentUser.getTenantId();
108 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 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 106 accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
113 107 @Override
114 108 public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
115   - ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
  109 + ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID,
116 110 tenantId,
117 111 deviceId,
118 112 oneWay,
... ...
... ... @@ -193,6 +193,9 @@ public class ThingsboardInstallService {
193 193 databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
194 194 case "3.2.2":
195 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 199 databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
197 200
198 201 dataUpdateService.updateData("3.2.2");
... ...
... ... @@ -65,7 +65,6 @@ import java.util.Collections;
65 65 import java.util.List;
66 66 import java.util.Optional;
67 67 import java.util.concurrent.ExecutionException;
68   -import java.util.concurrent.locks.ReentrantLock;
69 68
70 69
71 70 @Service
... ... @@ -78,8 +77,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
78 77 private static final String DEVICE_PROVISION_STATE = "provisionState";
79 78 private static final String PROVISIONED_STATE = "provisioned";
80 79
81   - private final ReentrantLock deviceCreationLock = new ReentrantLock();
82   -
83 80 @Autowired
84 81 DeviceDao deviceDao;
85 82
... ... @@ -177,12 +174,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
177 174 }
178 175
179 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 180 private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
... ... @@ -191,28 +183,26 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
191 183 }
192 184
193 185 private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
194   - Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
195 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 204 notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
213   - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
214 205 }
215   - } catch (InterruptedException | ExecutionException e) {
216 206 throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
217 207 }
218 208 }
... ...
... ... @@ -51,6 +51,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
51 51 case "2.5.0":
52 52 case "3.1.1":
53 53 case "3.2.1":
  54 + case "3.2.2":
54 55 break;
55 56 default:
56 57 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -209,6 +209,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
209 209 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);");
210 210 }
211 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 218 default:
213 219 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
214 220 }
... ...
... ... @@ -184,6 +184,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
184 184 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1");
185 185 }
186 186 break;
  187 + case "3.2.2":
  188 + break;
187 189 default:
188 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 18
19 19 import lombok.RequiredArgsConstructor;
20 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.leshan.core.SecurityMode;
21 22 import org.eclipse.leshan.core.util.Hex;
22 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
23 24 import org.springframework.stereotype.Service;
... ... @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
25 26 import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
26 27 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
27 28 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
28   -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
29 29
30 30 import java.math.BigInteger;
31 31 import java.security.AlgorithmParameters;
... ... @@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository {
55 55 * @param bootstrapServer
56 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 60 result.setBootstrapServerIs(bootstrapServer);
62 61 return result;
63 62 }
64 63
65   - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) {
  64 + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) {
66 65 ServerSecurityConfig bsServ = new ServerSecurityConfig();
67 66 bsServ.setServerId(serverConfig.getId());
68   - switch (mode) {
  67 + switch (securityMode) {
69 68 case NO_SEC:
70 69 bsServ.setHost(serverConfig.getHost());
71 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 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.firmware;
  16 +package org.thingsboard.server.service.ota;
17 17
18 18 import com.google.common.util.concurrent.FutureCallback;
19 19 import lombok.extern.slf4j.Slf4j;
... ... @@ -23,12 +23,9 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
23 23 import org.thingsboard.server.common.data.DataConstants;
24 24 import org.thingsboard.server.common.data.Device;
25 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 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 29 import org.thingsboard.server.common.data.id.TenantId;
33 30 import org.thingsboard.server.common.data.kv.AttributeKey;
34 31 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -37,13 +34,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
37 34 import org.thingsboard.server.common.data.kv.LongDataEntry;
38 35 import org.thingsboard.server.common.data.kv.StringDataEntry;
39 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 40 import org.thingsboard.server.common.data.page.PageData;
41 41 import org.thingsboard.server.common.data.page.PageLink;
42 42 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
43 43 import org.thingsboard.server.dao.device.DeviceProfileService;
44 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 47 import org.thingsboard.server.queue.TbQueueProducer;
48 48 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
49 49 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
... ... @@ -58,44 +58,43 @@ import java.util.List;
58 58 import java.util.Set;
59 59 import java.util.UUID;
60 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 75 @Slf4j
77 76 @Service
78 77 @TbCoreComponent
79   -public class DefaultFirmwareStateService implements FirmwareStateService {
  78 +public class DefaultOtaPackageStateService implements OtaPackageStateService {
80 79
81 80 private final TbClusterService tbClusterService;
82   - private final FirmwareService firmwareService;
  81 + private final OtaPackageService otaPackageService;
83 82 private final DeviceService deviceService;
84 83 private final DeviceProfileService deviceProfileService;
85 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 92 this.tbClusterService = tbClusterService;
94   - this.firmwareService = firmwareService;
  93 + this.otaPackageService = otaPackageService;
95 94 this.deviceService = deviceService;
96 95 this.deviceProfileService = deviceProfileService;
97 96 this.telemetryService = telemetryService;
98   - this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer();
  97 + this.otaPackageStateMsgProducer = coreQueueFactory.createToOtaPackageStateServiceMsgProducer();
99 98 }
100 99
101 100 @Override
... ... @@ -105,14 +104,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
105 104 }
106 105
107 106 private void updateFirmware(Device device, Device oldDevice) {
108   - FirmwareId newFirmwareId = device.getFirmwareId();
  107 + OtaPackageId newFirmwareId = device.getFirmwareId();
109 108 if (newFirmwareId == null) {
110 109 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
111 110 newFirmwareId = newDeviceProfile.getFirmwareId();
112 111 }
113 112 if (oldDevice != null) {
114 113 if (newFirmwareId != null) {
115   - FirmwareId oldFirmwareId = oldDevice.getFirmwareId();
  114 + OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
116 115 if (oldFirmwareId == null) {
117 116 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
118 117 oldFirmwareId = oldDeviceProfile.getFirmwareId();
... ... @@ -132,14 +131,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
132 131 }
133 132
134 133 private void updateSoftware(Device device, Device oldDevice) {
135   - FirmwareId newSoftwareId = device.getSoftwareId();
  134 + OtaPackageId newSoftwareId = device.getSoftwareId();
136 135 if (newSoftwareId == null) {
137 136 DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
138 137 newSoftwareId = newDeviceProfile.getSoftwareId();
139 138 }
140 139 if (oldDevice != null) {
141 140 if (newSoftwareId != null) {
142   - FirmwareId oldSoftwareId = oldDevice.getSoftwareId();
  141 + OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
143 142 if (oldSoftwareId == null) {
144 143 DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
145 144 oldSoftwareId = oldDeviceProfile.getSoftwareId();
... ... @@ -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 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 175 if (deviceProfile.getFirmwareId() != null) {
190 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 178 } else {
193   - updateConsumer = d -> remove(d, firmwareType);
  179 + updateConsumer = d -> remove(d, otaPackageType);
194 180 }
195 181
196 182 PageLink pageLink = new PageLink(100);
197 183 PageData<Device> pageData;
198 184 do {
199   - pageData = getDevicesFunction.apply(pageLink);
  185 + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId, deviceProfile.getId(), otaPackageType, pageLink);
200 186 pageData.getData().forEach(updateConsumer);
201 187
202 188 if (pageData.hasNext()) {
... ... @@ -206,60 +192,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
206 192 }
207 193
208 194 @Override
209   - public boolean process(ToFirmwareStateServiceMsg msg) {
  195 + public boolean process(ToOtaPackageStateServiceMsg msg) {
210 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 198 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
213 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 201 long ts = msg.getTs();
216 202
217 203 Device device = deviceService.findDeviceById(tenantId, deviceId);
218 204 if (device == null) {
219 205 log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId);
220 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 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 215 isSuccess = true;
230 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 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 225 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
240 226 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
241 227 .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
242 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 231 .setType(firmwareType.name())
246 232 .setTs(ts)
247 233 .build();
248 234
249   - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId);
  235 + OtaPackageInfo firmware = otaPackageService.findOtaPackageInfoById(tenantId, firmwareId);
250 236 if (firmware == null) {
251 237 log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId);
252 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 244 List<TsKvEntry> telemetry = new ArrayList<>();
259 245 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
260 246 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
261 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 250 telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
265 251 @Override
... ... @@ -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 265 TenantId tenantId = device.getTenantId();
280 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 270 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
285 271 @Override
... ... @@ -297,7 +283,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService {
297 283 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
298 284 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
299 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 287 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
302 288
303 289 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
... ... @@ -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 304 new FutureCallback<>() {
319 305 @Override
320 306 public void onSuccess(@Nullable Void tmp) {
321 307 log.trace("[{}] Success remove target firmware attributes!", device.getId());
322 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 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 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.firmware;
  16 +package org.thingsboard.server.service.ota;
17 17
18 18 import org.thingsboard.server.common.data.Device;
19 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 24 void update(Device device, Device oldDevice);
25 25
26 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 50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
51 51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
52 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 54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
55 55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
56 56 import org.thingsboard.server.queue.TbQueueConsumer;
... ... @@ -60,9 +60,10 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
60 60 import org.thingsboard.server.queue.util.TbCoreComponent;
61 61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
62 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 64 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
65 65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
  66 +import org.thingsboard.server.service.queue.processing.IdMsgPair;
66 67 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
67 68 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
68 69 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
... ... @@ -99,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
99 100 @Value("${queue.core.stats.enabled:false}")
100 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 104 private long firmwarePackInterval;
104   - @Value("${queue.core.firmware.pack-size:100}")
  105 + @Value("${queue.core.ota.pack-size:100}")
105 106 private int firmwarePackSize;
106 107
107 108 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer;
... ... @@ -111,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
111 112 private final SubscriptionManagerService subscriptionManagerService;
112 113 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
113 114 private final EdgeNotificationService edgeNotificationService;
114   - private final FirmwareStateService firmwareStateService;
  115 + private final OtaPackageStateService firmwareStateService;
115 116 private final TbCoreConsumerStats stats;
116 117 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
117   - private final TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> firmwareStatesConsumer;
  118 + private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
118 119
119 120 protected volatile ExecutorService usageStatsExecutor;
120 121
... ... @@ -133,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
133 134 TbTenantProfileCache tenantProfileCache,
134 135 TbApiUsageStateService apiUsageStateService,
135 136 EdgeNotificationService edgeNotificationService,
136   - FirmwareStateService firmwareStateService) {
  137 + OtaPackageStateService firmwareStateService) {
137 138 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
138 139 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
139 140 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
140   - this.firmwareStatesConsumer = tbCoreQueueFactory.createToFirmwareStateServiceMsgConsumer();
  141 + this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
141 142 this.stateService = stateService;
142 143 this.localSubscriptionService = localSubscriptionService;
143 144 this.subscriptionManagerService = subscriptionManagerService;
... ... @@ -171,7 +172,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
171 172 public void onApplicationEvent(ApplicationReadyEvent event) {
172 173 super.onApplicationEvent(event);
173 174 launchUsageStatsConsumer();
174   - launchFirmwareUpdateNotificationConsumer();
  175 + launchOtaPackageUpdateNotificationConsumer();
175 176 }
176 177
177 178 @Override
... ... @@ -198,14 +199,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
198 199 if (msgs.isEmpty()) {
199 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 205 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
204 206 TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>(
205 207 processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
206 208 PendingMsgHolder pendingMsgHolder = new PendingMsgHolder();
207 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 213 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
210 214 TbCallback callback = new TbPackCallback<>(id, ctx);
211 215 try {
... ... @@ -223,7 +227,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
223 227 } else if (toCoreMsg.hasEdgeNotificationMsg()) {
224 228 log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg());
225 229 forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback);
226   - } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
  230 + } else if (!toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
227 231 Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray());
228 232 if (actorMsg.isPresent()) {
229 233 TbActorMsg tbActorMsg = actorMsg.get();
... ... @@ -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 364 long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize;
361 365 firmwareStatesExecutor.submit(() -> {
362 366 while (!stopped) {
363 367 try {
364   - List<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration());
  368 + List<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration());
365 369 if (msgs.isEmpty()) {
366 370 continue;
367 371 }
368 372 long timeToSleep = maxProcessingTimeoutPerRecord;
369   - for (TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg : msgs) {
  373 + for (TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg : msgs) {
370 374 try {
371 375 long startTime = System.currentTimeMillis();
372   - boolean isSuccessUpdate = handleFirmwareUpdates(msg);
  376 + boolean isSuccessUpdate = handleOtaPackageUpdates(msg);
373 377 long endTime = System.currentTimeMillis();
374 378 long spentTime = endTime - startTime;
375 379 timeToSleep = timeToSleep - spentTime;
... ... @@ -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 409 statsService.process(msg, callback);
406 410 }
407 411
408   - private boolean handleFirmwareUpdates(TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg) {
  412 + private boolean handleOtaPackageUpdates(TbProtoQueueMsg<ToOtaPackageStateServiceMsg> msg) {
409 413 return firmwareStateService.process(msg.getValue());
410 414 }
411 415
... ...
... ... @@ -27,7 +27,7 @@ import java.util.stream.Collectors;
27 27 public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy {
28 28
29 29 protected final String queueName;
30   - protected List<IdMsgPair> orderedMsgList;
  30 + protected List<IdMsgPair<TransportProtos.ToRuleEngineMsg>> orderedMsgList;
31 31 private volatile boolean stopped;
32 32
33 33 public AbstractTbRuleEngineSubmitStrategy(String queueName) {
... ... @@ -38,7 +38,7 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
38 38
39 39 @Override
40 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 44 @Override
... ... @@ -48,8 +48,8 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine
48 48
49 49 @Override
50 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 53 if (reprocessMap.containsKey(pair.uuid)) {
54 54 newOrderedMsgList.add(pair);
55 55 }
... ...
... ... @@ -15,16 +15,18 @@
15 15 */
16 16 package org.thingsboard.server.service.queue.processing;
17 17
18   -import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
  18 +import lombok.Getter;
19 19 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
20 20
21 21 import java.util.UUID;
22 22
23   -public class IdMsgPair {
  23 +public class IdMsgPair<T extends com.google.protobuf.GeneratedMessageV3> {
  24 + @Getter
24 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 30 this.uuid = uuid;
29 31 this.msg = msg;
30 32 }
... ...
... ... @@ -33,7 +33,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
33 33
34 34 private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer;
35 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 38 public SequentialByEntityIdTbRuleEngineSubmitStrategy(String queueName) {
39 39 super(queueName);
... ... @@ -66,7 +66,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
66 66 protected void doOnSuccess(UUID id) {
67 67 EntityId entityId = msgToEntityIdMap.get(id);
68 68 if (entityId != null) {
69   - Queue<IdMsgPair> queue = entityIdToListMap.get(entityId);
  69 + Queue<IdMsgPair<TransportProtos.ToRuleEngineMsg>> queue = entityIdToListMap.get(entityId);
70 70 if (queue != null) {
71 71 IdMsgPair next = null;
72 72 synchronized (queue) {
... ... @@ -86,7 +86,7 @@ public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends Abs
86 86 private void initMaps() {
87 87 msgToEntityIdMap.clear();
88 88 entityIdToListMap.clear();
89   - for (IdMsgPair pair : orderedMsgList) {
  89 + for (IdMsgPair<TransportProtos.ToRuleEngineMsg> pair : orderedMsgList) {
90 90 EntityId entityId = getEntityId(pair.msg.getValue());
91 91 if (entityId != null) {
92 92 msgToEntityIdMap.put(pair.uuid, entityId);
... ...
... ... @@ -89,7 +89,7 @@ public class DefaultTbResourceService implements TbResourceService {
89 89 } catch (InvalidDDFFileException | IOException e) {
90 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 93 throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
94 94 }
95 95 } else {
... ... @@ -131,7 +131,7 @@ public class DefaultTbResourceService implements TbResourceService {
131 131 List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
132 132 objectIds);
133 133 return resources.stream()
134   - .flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
  134 + .flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false)))
135 135 .sorted(getComparator(sortProperty, sortOrder))
136 136 .collect(Collectors.toList());
137 137 }
... ... @@ -142,7 +142,7 @@ public class DefaultTbResourceService implements TbResourceService {
142 142 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
143 143 PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
144 144 return resourcePageData.getData().stream()
145   - .flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
  145 + .flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false)))
146 146 .sorted(getComparator(sortProperty, sortOrder))
147 147 .collect(Collectors.toList());
148 148 }
... ... @@ -167,7 +167,7 @@ public class DefaultTbResourceService implements TbResourceService {
167 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 171 try {
172 172 DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
173 173 List<ObjectModel> objectModels =
... ... @@ -186,12 +186,16 @@ public class DefaultTbResourceService implements TbResourceService {
186 186 instance.setId(0);
187 187 List<LwM2mResourceObserve> resources = new ArrayList<>();
188 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 194 LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
191 195 resources.add(lwM2MResourceObserve);
192 196 }
193 197 });
194   - if (resources.size() > 0) {
  198 + if (isSave || resources.size() > 0) {
195 199 instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
196 200 lwM2mObject.setInstances(new LwM2mInstance[]{instance});
197 201 return lwM2mObject;
... ...
... ... @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
26 26 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
27 27 import org.springframework.scheduling.annotation.Scheduled;
28 28 import org.springframework.stereotype.Service;
  29 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
29 30 import org.thingsboard.server.gen.js.JsInvokeProtos;
30 31 import org.thingsboard.server.queue.TbQueueRequestTemplate;
31 32 import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
... ... @@ -39,6 +40,8 @@ import javax.annotation.PreDestroy;
39 40 import java.util.Map;
40 41 import java.util.UUID;
41 42 import java.util.concurrent.ConcurrentHashMap;
  43 +import java.util.concurrent.ExecutorService;
  44 +import java.util.concurrent.Executors;
42 45 import java.util.concurrent.TimeUnit;
43 46 import java.util.concurrent.TimeoutException;
44 47 import java.util.concurrent.atomic.AtomicInteger;
... ... @@ -69,6 +72,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
69 72 private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
70 73 private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
71 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 78 public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) {
74 79 super(apiUsageStateService, apiUsageClient);
... ... @@ -139,7 +144,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
139 144 }
140 145 queueFailedMsgs.incrementAndGet();
141 146 }
142   - }, MoreExecutors.directExecutor());
  147 + }, callbackExecutor);
143 148 return Futures.transform(future, response -> {
144 149 JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse();
145 150 UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
... ... @@ -151,7 +156,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
151 156 log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
152 157 throw new RuntimeException(compilationResult.getErrorDetails());
153 158 }
154   - }, MoreExecutors.directExecutor());
  159 + }, callbackExecutor);
155 160 }
156 161
157 162 @Override
... ... @@ -194,7 +199,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
194 199 }
195 200 queueFailedMsgs.incrementAndGet();
196 201 }
197   - }, MoreExecutors.directExecutor());
  202 + }, callbackExecutor);
198 203 return Futures.transform(future, response -> {
199 204 JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse();
200 205 if (invokeResult.getSuccess()) {
... ... @@ -204,7 +209,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
204 209 log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
205 210 throw new RuntimeException(invokeResult.getErrorDetails());
206 211 }
207   - }, MoreExecutors.directExecutor());
  212 + }, callbackExecutor);
208 213 }
209 214
210 215 @Override
... ...
... ... @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Customer;
30 30 import org.thingsboard.server.common.data.Device;
31 31 import org.thingsboard.server.common.data.DeviceProfile;
32 32 import org.thingsboard.server.common.data.EntityView;
33   -import org.thingsboard.server.common.data.FirmwareInfo;
  33 +import org.thingsboard.server.common.data.OtaPackageInfo;
34 34 import org.thingsboard.server.common.data.TbResourceInfo;
35 35 import org.thingsboard.server.common.data.Tenant;
36 36 import org.thingsboard.server.common.data.User;
... ... @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
46 46 import org.thingsboard.server.common.data.id.EntityId;
47 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
48 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 50 import org.thingsboard.server.common.data.id.RuleChainId;
51 51 import org.thingsboard.server.common.data.id.RuleNodeId;
52 52 import org.thingsboard.server.common.data.id.TbResourceId;
... ... @@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService;
63 63 import org.thingsboard.server.dao.edge.EdgeService;
64 64 import org.thingsboard.server.dao.entityview.EntityViewService;
65 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 67 import org.thingsboard.server.dao.resource.ResourceService;
68 68 import org.thingsboard.server.dao.rule.RuleChainService;
69 69 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -135,7 +135,7 @@ public class AccessValidator {
135 135 protected ResourceService resourceService;
136 136
137 137 @Autowired
138   - protected FirmwareService firmwareService;
  138 + protected OtaPackageService otaPackageService;
139 139
140 140 private ExecutorService executor;
141 141
... ... @@ -232,8 +232,8 @@ public class AccessValidator {
232 232 case TB_RESOURCE:
233 233 validateResource(currentUser, operation, entityId, callback);
234 234 return;
235   - case FIRMWARE:
236   - validateFirmware(currentUser, operation, entityId, callback);
  235 + case OTA_PACKAGE:
  236 + validateOtaPackage(currentUser, operation, entityId, callback);
237 237 return;
238 238 default:
239 239 //TODO: add support of other entities
... ... @@ -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 304 if (currentUser.isSystemAdmin()) {
305 305 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
306 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 310 } else {
311 311 try {
312   - accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware);
  312 + accessControlService.checkPermission(currentUser, Resource.OTA_PACKAGE, operation, entityId, otaPackage);
313 313 } catch (ThingsboardException e) {
314 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 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
39 39 API_USAGE_STATE(EntityType.API_USAGE_STATE),
40 40 TB_RESOURCE(EntityType.TB_RESOURCE),
41   - FIRMWARE(EntityType.FIRMWARE),
  41 + OTA_PACKAGE(EntityType.OTA_PACKAGE),
42 42 EDGE(EntityType.EDGE);
43 43
44 44 private final EntityType entityType;
... ...
... ... @@ -42,7 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
42 42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
43 43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
44 44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
45   - put(Resource.FIRMWARE, tenantEntityPermissionChecker);
  45 + put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker);
46 46 put(Resource.EDGE, tenantEntityPermissionChecker);
47 47 }
48 48
... ...
... ... @@ -26,27 +26,27 @@ import lombok.extern.slf4j.Slf4j;
26 26 import org.springframework.stereotype.Service;
27 27 import org.springframework.util.StringUtils;
28 28 import org.thingsboard.common.util.JacksonUtil;
29   -import org.thingsboard.server.cache.firmware.FirmwareDataCache;
  29 +import org.thingsboard.server.cache.ota.OtaPackageDataCache;
30 30 import org.thingsboard.server.common.data.ApiUsageState;
31 31 import org.thingsboard.server.common.data.DataConstants;
32 32 import org.thingsboard.server.common.data.Device;
33 33 import org.thingsboard.server.common.data.DeviceProfile;
34 34 import org.thingsboard.server.common.data.DeviceTransportType;
35 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 38 import org.thingsboard.server.common.data.ResourceType;
39 39 import org.thingsboard.server.common.data.TbResource;
40 40 import org.thingsboard.server.common.data.TenantProfile;
41 41 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
42 42 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
43 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 46 import org.thingsboard.server.common.data.id.CustomerId;
47 47 import org.thingsboard.server.common.data.id.DeviceId;
48 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 50 import org.thingsboard.server.common.data.id.TenantId;
51 51 import org.thingsboard.server.common.data.page.PageData;
52 52 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -64,7 +64,7 @@ import org.thingsboard.server.dao.device.DeviceService;
64 64 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
65 65 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
66 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 68 import org.thingsboard.server.dao.relation.RelationService;
69 69 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
70 70 import org.thingsboard.server.gen.transport.TransportProtos;
... ... @@ -124,8 +124,8 @@ public class DefaultTransportApiService implements TransportApiService {
124 124 private final DataDecodingEncodingService dataDecodingEncodingService;
125 125 private final DeviceProvisionService deviceProvisionService;
126 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 130 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
131 131
... ... @@ -134,7 +134,7 @@ public class DefaultTransportApiService implements TransportApiService {
134 134 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
135 135 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
136 136 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
137   - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) {
  137 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, OtaPackageService otaPackageService, OtaPackageDataCache otaPackageDataCache) {
138 138 this.deviceProfileCache = deviceProfileCache;
139 139 this.tenantProfileCache = tenantProfileCache;
140 140 this.apiUsageStateService = apiUsageStateService;
... ... @@ -147,8 +147,8 @@ public class DefaultTransportApiService implements TransportApiService {
147 147 this.dataDecodingEncodingService = dataDecodingEncodingService;
148 148 this.deviceProvisionService = deviceProvisionService;
149 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 154 @Override
... ... @@ -184,8 +184,8 @@ public class DefaultTransportApiService implements TransportApiService {
184 184 result = handle(transportApiRequestMsg.getDeviceRequestMsg());
185 185 } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) {
186 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 191 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
... ... @@ -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 515 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
516 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 518 Device device = deviceService.findDeviceById(tenantId, deviceId);
519 519
520 520 if (device == null) {
521 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 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 533 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
534 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 538 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
539 539 } else {
540 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 555 return Futures.immediateFuture(
556 556 TransportApiResponseMsg.newBuilder()
557   - .setFirmwareResponseMsg(builder.build())
  557 + .setOtaPackageResponseMsg(builder.build())
558 558 .build());
559 559 }
560 560
... ...
... ... @@ -371,7 +371,10 @@ caffeine:
371 371 tokensOutdatageTime:
372 372 timeToLiveInMinutes: 20000
373 373 maxSize: 10000
374   - firmwares:
  374 + otaPackages:
  375 + timeToLiveInMinutes: 60
  376 + maxSize: 10
  377 + otaPackagesData:
375 378 timeToLiveInMinutes: 60
376 379 maxSize: 10
377 380 edges:
... ... @@ -497,7 +500,7 @@ audit-log:
497 500 "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
498 501 "edge": "${AUDIT_LOG_MASK_EDGE:W}"
499 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 504 sink:
502 505 # Type of external sink. possible options: none, elasticsearch
503 506 type: "${AUDIT_LOG_SINK_TYPE:none}"
... ... @@ -643,6 +646,7 @@ transport:
643 646 private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
644 647 # Only Certificate_x509:
645 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 650 bootstrap:
647 651 enable: "${LWM2M_ENABLED_BS:true}"
648 652 id: "${LWM2M_SERVER_ID_BS:111}"
... ... @@ -748,7 +752,7 @@ queue:
748 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 753 security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
750 754 consumer-properties-per-topic:
751   - tb_firmware:
  755 + tb_ota_package:
752 756 - key: max.poll.records
753 757 value: 10
754 758 other:
... ... @@ -758,7 +762,7 @@ queue:
758 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 763 notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
760 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 766 consumer-stats:
763 767 enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
764 768 print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
... ... @@ -829,10 +833,10 @@ queue:
829 833 poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
830 834 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
831 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 840 usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
837 841 stats:
838 842 enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
... ...
... ... @@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims;
22 22 import io.jsonwebtoken.Header;
23 23 import io.jsonwebtoken.Jwt;
24 24 import io.jsonwebtoken.Jwts;
  25 +import lombok.Getter;
25 26 import lombok.extern.slf4j.Slf4j;
26 27 import org.apache.commons.lang3.RandomStringUtils;
27 28 import org.apache.commons.lang3.StringUtils;
... ... @@ -120,7 +121,7 @@ public abstract class AbstractWebTest {
120 121 protected String refreshToken;
121 122 protected String username;
122 123
123   - private TenantId tenantId;
  124 + protected TenantId tenantId;
124 125
125 126 @SuppressWarnings("rawtypes")
126 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 25 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
26 26 import org.thingsboard.common.util.JacksonUtil;
27 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 30 import org.thingsboard.server.common.data.Tenant;
31 31 import org.thingsboard.server.common.data.User;
32   -import org.thingsboard.server.common.data.firmware.FirmwareType;
33 32 import org.thingsboard.server.common.data.id.DeviceProfileId;
34 33 import org.thingsboard.server.common.data.page.PageData;
35 34 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -41,11 +40,11 @@ import java.util.Collections;
41 40 import java.util.List;
42 41
43 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 49 public static final String TITLE = "My firmware";
51 50 private static final String FILE_NAME = "filename.txt";
... ... @@ -93,13 +92,13 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
93 92
94 93 @Test
95 94 public void testSaveFirmware() throws Exception {
96   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  95 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
97 96 firmwareInfo.setDeviceProfileId(deviceProfileId);
98 97 firmwareInfo.setType(FIRMWARE);
99 98 firmwareInfo.setTitle(TITLE);
100 99 firmwareInfo.setVersion(VERSION);
101 100
102   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  101 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
103 102
104 103 Assert.assertNotNull(savedFirmwareInfo);
105 104 Assert.assertNotNull(savedFirmwareInfo.getId());
... ... @@ -112,19 +111,19 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
112 111
113 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 115 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
117 116 }
118 117
119 118 @Test
120 119 public void testSaveFirmwareData() throws Exception {
121   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  120 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
122 121 firmwareInfo.setDeviceProfileId(deviceProfileId);
123 122 firmwareInfo.setType(FIRMWARE);
124 123 firmwareInfo.setTitle(TITLE);
125 124 firmwareInfo.setVersion(VERSION);
126 125
127   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  126 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
128 127
129 128 Assert.assertNotNull(savedFirmwareInfo);
130 129 Assert.assertNotNull(savedFirmwareInfo.getId());
... ... @@ -137,12 +136,12 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
137 136
138 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 140 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
142 141
143 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 146 Assert.assertEquals(FILE_NAME, savedFirmware.getFileName());
148 147 Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType());
... ... @@ -150,97 +149,97 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
150 149
151 150 @Test
152 151 public void testUpdateFirmwareFromDifferentTenant() throws Exception {
153   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  152 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
154 153 firmwareInfo.setDeviceProfileId(deviceProfileId);
155 154 firmwareInfo.setType(FIRMWARE);
156 155 firmwareInfo.setTitle(TITLE);
157 156 firmwareInfo.setVersion(VERSION);
158 157
159   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  158 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
160 159
161 160 loginDifferentTenant();
162   - doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden());
  161 + doPost("/api/otaPackage", savedFirmwareInfo, OtaPackageInfo.class, status().isForbidden());
163 162 deleteDifferentTenant();
164 163 }
165 164
166 165 @Test
167 166 public void testFindFirmwareInfoById() throws Exception {
168   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  167 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
169 168 firmwareInfo.setDeviceProfileId(deviceProfileId);
170 169 firmwareInfo.setType(FIRMWARE);
171 170 firmwareInfo.setTitle(TITLE);
172 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 176 Assert.assertNotNull(foundFirmware);
178 177 Assert.assertEquals(savedFirmwareInfo, foundFirmware);
179 178 }
180 179
181 180 @Test
182 181 public void testFindFirmwareById() throws Exception {
183   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  182 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
184 183 firmwareInfo.setDeviceProfileId(deviceProfileId);
185 184 firmwareInfo.setType(FIRMWARE);
186 185 firmwareInfo.setTitle(TITLE);
187 186 firmwareInfo.setVersion(VERSION);
188 187
189   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  188 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
190 189
191 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 195 Assert.assertNotNull(foundFirmware);
197 196 Assert.assertEquals(savedFirmware, foundFirmware);
198 197 }
199 198
200 199 @Test
201 200 public void testDeleteFirmware() throws Exception {
202   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  201 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
203 202 firmwareInfo.setDeviceProfileId(deviceProfileId);
204 203 firmwareInfo.setType(FIRMWARE);
205 204 firmwareInfo.setTitle(TITLE);
206 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 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 213 .andExpect(status().isNotFound());
215 214 }
216 215
217 216 @Test
218 217 public void testFindTenantFirmwares() throws Exception {
219   - List<FirmwareInfo> firmwares = new ArrayList<>();
  218 + List<OtaPackageInfo> otaPackages = new ArrayList<>();
220 219 for (int i = 0; i < 165; i++) {
221   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  220 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
222 221 firmwareInfo.setDeviceProfileId(deviceProfileId);
223 222 firmwareInfo.setType(FIRMWARE);
224 223 firmwareInfo.setTitle(TITLE);
225 224 firmwareInfo.setVersion(VERSION + i);
226 225
227   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  226 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
228 227
229 228 if (i > 100) {
230 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 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 239 PageLink pageLink = new PageLink(24);
241   - PageData<FirmwareInfo> pageData;
  240 + PageData<OtaPackageInfo> pageData;
242 241 do {
243   - pageData = doGetTypedWithPageLink("/api/firmwares?",
  242 + pageData = doGetTypedWithPageLink("/api/otaPackages?",
244 243 new TypeReference<>() {
245 244 }, pageLink);
246 245 loadedFirmwares.addAll(pageData.getData());
... ... @@ -249,41 +248,41 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
249 248 }
250 249 } while (pageData.hasNext());
251 250
252   - Collections.sort(firmwares, idComparator);
  251 + Collections.sort(otaPackages, idComparator);
253 252 Collections.sort(loadedFirmwares, idComparator);
254 253
255   - Assert.assertEquals(firmwares, loadedFirmwares);
  254 + Assert.assertEquals(otaPackages, loadedFirmwares);
256 255 }
257 256
258 257 @Test
259 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 262 for (int i = 0; i < 165; i++) {
264   - FirmwareInfo firmwareInfo = new FirmwareInfo();
  263 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
265 264 firmwareInfo.setDeviceProfileId(deviceProfileId);
266 265 firmwareInfo.setType(FIRMWARE);
267 266 firmwareInfo.setTitle(TITLE);
268 267 firmwareInfo.setVersion(VERSION + i);
269 268
270   - FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  269 + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
271 270
272 271 if (i > 100) {
273 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 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 282 PageLink pageLink = new PageLink(24);
284   - PageData<FirmwareInfo> pageData;
  283 + PageData<OtaPackageInfo> pageData;
285 284 do {
286   - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/true?",
  285 + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/true?",
287 286 new TypeReference<>() {
288 287 }, pageLink);
289 288 loadedFirmwaresWithData.addAll(pageData.getData());
... ... @@ -292,10 +291,10 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
292 291 }
293 292 } while (pageData.hasNext());
294 293
295   - List<FirmwareInfo> loadedFirmwaresWithoutData = new ArrayList<>();
  294 + List<OtaPackageInfo> loadedFirmwaresWithoutData = new ArrayList<>();
296 295 pageLink = new PageLink(24);
297 296 do {
298   - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/false?",
  297 + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/false?",
299 298 new TypeReference<>() {
300 299 }, pageLink);
301 300 loadedFirmwaresWithoutData.addAll(pageData.getData());
... ... @@ -304,25 +303,25 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest
304 303 }
305 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 308 Collections.sort(loadedFirmwaresWithData, idComparator);
310 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 321 MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
323 322 postRequest.file(content);
324 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 15 */
16 16 package org.thingsboard.server.controller.sql;
17 17
18   -import org.thingsboard.server.controller.BaseFirmwareControllerTest;
  18 +import org.thingsboard.server.controller.BaseOtaPackageControllerTest;
19 19 import org.thingsboard.server.dao.service.DaoSqlTest;
20 20
21 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 32 "org.thingsboard.server.transport.*.attributes.updates.sql.*Test",
33 33 "org.thingsboard.server.transport.*.attributes.request.sql.*Test",
34 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 38 public class TransportSqlTestSuite {
38 39
... ...
... ... @@ -44,6 +44,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
44 44
45 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 50 @Before
48 51 public void beforeTest() throws Exception {
49 52 processBeforeTest("Test Subscribe to attribute updates", null, null);
... ... @@ -56,50 +59,85 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
56 59
57 60 @Test
58 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 74 CoapClient client = getCoapClient(FeatureType.ATTRIBUTES);
65 75
66 76 CountDownLatch latch = new CountDownLatch(1);
67   - TestCoapCallback testCoapCallback = new TestCoapCallback(latch);
  77 + TestCoapCallback callback = new TestCoapCallback(latch);
68 78
69 79 Request request = Request.newGet().setObserve();
70 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 93 doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
76 94 latch.await(3, TimeUnit.SECONDS);
77 95
78   - validateUpdateAttributesResponse(testCoapCallback);
  96 + validateUpdateAttributesResponse(callback);
79 97
80 98 latch = new CountDownLatch(1);
81 99
82 100 doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
83 101 latch.await(3, TimeUnit.SECONDS);
84 102
85   - validateDeleteAttributesResponse(testCoapCallback);
  103 + validateDeleteAttributesResponse(callback);
86 104
87 105 observeRelation.proactiveCancel();
88 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 119 assertNotNull(callback.getPayloadBytes());
93 120 assertNotNull(callback.getObserve());
  121 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
94 122 assertEquals(0, callback.getObserve().intValue());
95 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 133 assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
97 134 }
98 135
99 136 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
100 137 assertNotNull(callback.getPayloadBytes());
101 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 141 String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
104 142 assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
105 143 }
... ... @@ -110,13 +148,18 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
110 148
111 149 private Integer observe;
112 150 private byte[] payloadBytes;
  151 + private CoAP.ResponseCode responseCode;
  152 +
  153 + public Integer getObserve() {
  154 + return observe;
  155 + }
113 156
114 157 public byte[] getPayloadBytes() {
115 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 165 private TestCoapCallback(CountDownLatch latch) {
... ... @@ -125,10 +168,9 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
125 168
126 169 @Override
127 170 public void onLoad(CoapResponse response) {
128   - assertNotNull(response.getPayload());
129   - assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT);
130 171 observe = response.getOptions().getObserve();
131 172 payloadBytes = response.getPayload();
  173 + responseCode = response.getCode();
132 174 latch.countDown();
133 175 }
134 176
... ...
... ... @@ -39,4 +39,9 @@ public abstract class AbstractCoapAttributesUpdatesJsonIntegrationTest extends A
39 39 public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
40 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 17
18 18 import com.google.protobuf.InvalidProtocolBufferException;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.coap.CoAP;
20 21 import org.junit.After;
21 22 import org.junit.Before;
22 23 import org.junit.Test;
... ... @@ -24,11 +25,15 @@ import org.thingsboard.server.common.data.CoapDeviceType;
24 25 import org.thingsboard.server.common.data.TransportPayloadType;
25 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 31 import java.util.List;
28 32 import java.util.stream.Collectors;
29 33
30 34 import static org.junit.Assert.assertEquals;
31 35 import static org.junit.Assert.assertNotNull;
  36 +import static org.junit.Assert.assertNull;
32 37 import static org.junit.Assert.assertTrue;
33 38
34 39 @Slf4j
... ... @@ -46,11 +51,54 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
46 51
47 52 @Test
48 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 97 protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
53 98 assertNotNull(callback.getPayloadBytes());
  99 + assertNotNull(callback.getObserve());
  100 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  101 + assertEquals(1, callback.getObserve().intValue());
54 102 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
55 103 List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
56 104 attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
... ... @@ -68,6 +116,9 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
68 116
69 117 protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
70 118 assertNotNull(callback.getPayloadBytes());
  119 + assertNotNull(callback.getObserve());
  120 + assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE);
  121 + assertEquals(2, callback.getObserve().intValue());
71 122 TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
72 123 attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
73 124
... ...
... ... @@ -83,7 +83,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab
83 83
84 84 @Test
85 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 15 */
16 16 package org.thingsboard.server.transport.coap.rpc;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.apache.commons.lang3.StringUtils;
20 21 import org.eclipse.californium.core.CoapClient;
... ... @@ -24,17 +25,18 @@ import org.eclipse.californium.core.CoapResponse;
24 25 import org.eclipse.californium.core.coap.CoAP;
25 26 import org.eclipse.californium.core.coap.MediaTypeRegistry;
26 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 29 import org.thingsboard.server.common.data.CoapDeviceType;
30 30 import org.thingsboard.server.common.data.TransportPayloadType;
31 31 import org.thingsboard.server.common.msg.session.FeatureType;
  32 +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
32 33
33 34 import java.util.concurrent.CountDownLatch;
34 35 import java.util.concurrent.TimeUnit;
35 36
36 37 import static org.junit.Assert.assertEquals;
37 38 import static org.junit.Assert.assertNotNull;
  39 +import static org.junit.Assert.assertNull;
38 40 import static org.junit.Assert.assertTrue;
39 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 client.useCONs();
56 58
57 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 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 71 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
64 72 String deviceId = savedDevice.getId().getId().toString();
65 73 String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
66   - Assert.assertTrue(StringUtils.isEmpty(result));
  74 +
67 75 latch.await(3, TimeUnit.SECONDS);
68   - assertEquals(0, testCoapCallback.getObserve().intValue());
  76 +
  77 + validateOneWayStateChangedNotification(callback, result);
  78 +
69 79 observeRelation.proactiveCancel();
70 80 assertTrue(observeRelation.isCanceled());
71 81 }
72 82
73   - protected void processTwoWayRpcTest() throws Exception {
  83 + protected void processTwoWayRpcTest(String expectedResponseResult) throws Exception {
74 84 CoapClient client = getCoapClient(FeatureType.RPC);
75 85 client.useCONs();
76 86
77 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 90 Request request = Request.newGet().setObserve();
81 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 98 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
85 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 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 113 observeRelation.proactiveCancel();
95 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 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 120 client.post(new CoapHandler() {
110 121 @Override
111 122 public void onLoad(CoapResponse response) {
... ... @@ -130,11 +141,21 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
130 141 private final CountDownLatch latch;
131 142 private final boolean isOneWayRpc;
132 143
  144 + private Integer observe;
  145 + private byte[] payloadBytes;
  146 + private CoAP.ResponseCode responseCode;
  147 +
133 148 public Integer getObserve() {
134 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 160 TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) {
140 161 this.client = client;
... ... @@ -144,14 +165,15 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
144 165
145 166 @Override
146 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 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 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 42
43 43 @Test
44 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 85
86 86 @Test
87 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 91 @Override
117 92 protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) {
118   - client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe));
119 93 ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
120 94 ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
121 95 DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
... ... @@ -123,25 +97,22 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst
123 97 byte[] requestPayload = response.getPayload();
124 98 DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
125 99 Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
126   - assertNotNull(rpcRequestMsgDescriptor);
127 100 try {
128 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 104 ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
134 105 DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
135 106 DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
136 107 Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
137   - assertNotNull(rpcResponseMsgDescriptor);
138 108 DynamicMessage rpcResponseMsg = rpcResponseBuilder
139 109 .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
140 110 .build();
  111 + client.setURI(getRpcResponseFeatureTokenUrl(accessToken, requestId));
141 112 client.post(new CoapHandler() {
142 113 @Override
143 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 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
\ No newline at end of file
... ...
... ... @@ -14,6 +14,8 @@
14 14 <logger name="org.springframework.boot.test" level="WARN"/>
15 15 <logger name="org.apache.cassandra" level="WARN"/>
16 16 <logger name="org.cassandraunit" level="INFO"/>
  17 + <logger name="org.eclipse.leshan" level="TRACE"/>
  18 +
17 19
18 20 <root level="WARN">
19 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>
... ...