Commit 831ddc43bc2c5d4eaf98b8ab393657b3afcefa50
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 | ... | ... |
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
0 → 100644
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 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java
0 → 100644
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 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java
0 → 100644
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 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
0 → 100644
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 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
0 → 100644
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 | +} | ... | ... |
... | ... | @@ -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"/> | ... | ... |
application/src/test/resources/lwm2m/0.xml
0 → 100644
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> | ... | ... |
application/src/test/resources/lwm2m/1.xml
0 → 100644
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> | ... | ... |
application/src/test/resources/lwm2m/2.xml
0 → 100644
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> | ... | ... |