From 55504225eab6e068cc9285139a824131774ead1d Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 26 Jun 2024 18:50:40 +0300 Subject: [PATCH 01/14] UI: fixed hidden widgets not rendering in edit mode --- .../components/widget/widget.component.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index a79fa89242..533b2fbda2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -487,6 +487,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } if (!this.widgetContext.inited && this.isReady()) { this.widgetContext.inited = true; + this.widgetContext.destroyed = false; this.dashboardWidget.updateWidgetParams(); this.widgetContext.detectContainerChanges(); if (this.cafs.init) { @@ -1544,19 +1545,20 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private checkSize(): boolean { - const width = this.widgetContext.$containerParent.width(); - const height = this.widgetContext.$containerParent.height(); + const parentWidth = this.widgetContext.$containerParent.width(); + const parentHeight = this.widgetContext.$containerParent.height(); + const width = this.widgetContext.$container?.width(); + const height = this.widgetContext.$container?.height(); let sizeChanged = false; - if (!this.widgetContext.width || this.widgetContext.width !== width || - !this.widgetContext.height || this.widgetContext.height !== height) { - if (width > 0 && height > 0) { + if (parentWidth > 0 && parentHeight > 0) { + if (!this.widgetContext.width || !this.widgetContext.height || width !== parentWidth || height !== parentHeight) { if (this.widgetContext.$container) { - this.widgetContext.$container.css('height', height + 'px'); - this.widgetContext.$container.css('width', width + 'px'); + this.widgetContext.$container.css('height', parentHeight + 'px'); + this.widgetContext.$container.css('width', parentWidth + 'px'); } - this.widgetContext.width = width; - this.widgetContext.height = height; + this.widgetContext.width = parentWidth; + this.widgetContext.height = parentHeight; sizeChanged = true; this.widgetSizeDetected = true; } From 9a051640ec776acf6070b44fa511e7c2675f0be8 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 8 Jul 2024 15:29:07 +0300 Subject: [PATCH 02/14] UI: fixed hidden by default chart series not rendering on unhide --- .../widget/lib/chart/time-series-chart-widget.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index 0a242eba36..053598763e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -170,5 +170,9 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV public toggleLegendKey(legendKey: LegendKey) { this.timeSeriesChart.toggleKey(legendKey.dataKey); + for (const id of Object.keys(this.ctx.subscriptions)) { + const subscription = this.ctx.subscriptions[id]; + subscription.updateDataVisibility(legendKey.dataIndex); + } } } From 7b2e38e04bd51158d769173eb829535871a40304 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 9 Jul 2024 13:27:50 +0300 Subject: [PATCH 03/14] UI: refactoring --- .../chart/time-series-chart-widget.component.ts | 6 +----- .../widget/lib/chart/time-series-chart.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index 053598763e..def776897e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -169,10 +169,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV } public toggleLegendKey(legendKey: LegendKey) { - this.timeSeriesChart.toggleKey(legendKey.dataKey); - for (const id of Object.keys(this.ctx.subscriptions)) { - const subscription = this.ctx.subscriptions[id]; - subscription.updateDataVisibility(legendKey.dataIndex); - } + this.timeSeriesChart.toggleKey(legendKey.dataKey, legendKey.dataIndex); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 913c346712..a76643ea20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -60,7 +60,14 @@ import { measureAxisNameSize } from '@home/components/widget/lib/chart/echarts-widget.models'; import { DateFormatProcessor, ValueSourceType } from '@shared/models/widget-settings.models'; -import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; +import { + formattedDataFormDatasourceData, + formatValue, + isDefined, + isDefinedAndNotNull, + isEqual, + mergeDeep +} from '@core/utils'; import { DataKey, Datasource, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared'; @@ -290,7 +297,7 @@ export class TbTimeSeriesChart { } } - public toggleKey(dataKey: DataKey): void { + public toggleKey(dataKey: DataKey, dataIndex?: number): void { const enable = dataKey.hidden; const dataItem = this.dataItems.find(d => d.dataKey === dataKey); if (dataItem) { @@ -310,6 +317,9 @@ export class TbTimeSeriesChart { this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList}); this.updateAxes(); dataKey.hidden = !enable; + if (isDefined(dataIndex)) { + this.ctx.defaultSubscription.updateDataVisibility(dataIndex); + } if (enable) { this.timeSeriesChart.dispatchAction({ type: 'highlight', From c45cadd4783967d270af3a8d8a1def0f6a2a0cc6 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 9 Jul 2024 16:17:36 +0300 Subject: [PATCH 04/14] UI: revert checkSize method of widget component, clearing width and height of widget context on destroy --- .../components/widget/widget.component.ts | 19 +++++++++---------- .../home/models/widget-component.models.ts | 2 ++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 533b2fbda2..bae0389e61 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -1545,20 +1545,19 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private checkSize(): boolean { - const parentWidth = this.widgetContext.$containerParent.width(); - const parentHeight = this.widgetContext.$containerParent.height(); - const width = this.widgetContext.$container?.width(); - const height = this.widgetContext.$container?.height(); + const width = this.widgetContext.$containerParent.width(); + const height = this.widgetContext.$containerParent.height(); let sizeChanged = false; - if (parentWidth > 0 && parentHeight > 0) { - if (!this.widgetContext.width || !this.widgetContext.height || width !== parentWidth || height !== parentHeight) { + if (!this.widgetContext.width || this.widgetContext.width !== width || + !this.widgetContext.height || this.widgetContext.height !== height) { + if (width > 0 && height > 0) { if (this.widgetContext.$container) { - this.widgetContext.$container.css('height', parentHeight + 'px'); - this.widgetContext.$container.css('width', parentWidth + 'px'); + this.widgetContext.$container.css('height', height + 'px'); + this.widgetContext.$container.css('width', width + 'px'); } - this.widgetContext.width = parentWidth; - this.widgetContext.height = parentHeight; + this.widgetContext.width = width; + this.widgetContext.height = height; sizeChanged = true; this.widgetSizeDetected = true; } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 581fded240..fbda78a128 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -449,6 +449,8 @@ export class WidgetContext { labelPattern.destroy(); } this.labelPatterns.clear(); + this.width = undefined; + this.height = undefined; this.destroyed = true; } From ff2c426e71f8359347df09103afc695944b3bd9f Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 10 Jul 2024 12:32:51 +0300 Subject: [PATCH 05/14] UI: Fixed notify again with deleted recipient --- .../src/app/shared/components/entity/entity-list.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 4e33944f29..383d5bc8f4 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -179,6 +179,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV (entities) => { this.entities = entities; this.entityListFormGroup.get('entities').setValue(this.entities); + if(!entities.length && this.required) { + this.modelValue = null; + this.propagateChange(this.modelValue); + } } ); } else { From 93b0ea3c59866cc739bf62fd8907dcf5438419f2 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 10 Jul 2024 14:48:16 +0300 Subject: [PATCH 06/14] UI: Fixed String input widgets --- .../system/widget_types/update_server_string_attribute.json | 2 +- .../system/widget_types/update_shared_string_attribute.json | 2 +- .../json/system/widget_types/update_string_timeseries.json | 2 +- ui-ngx/src/app/core/services/utils.service.ts | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json index 193300cb06..d8c33bffcc 100644 --- a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [$scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n \n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n \n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json index 4c5333ec59..7578838066 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [$scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/update_string_timeseries.json b/application/src/main/data/json/system/widget_types/update_string_timeseries.json index 43a96d6f69..d54224ef36 100644 --- a/application/src/main/data/json/system/widget_types/update_string_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_string_timeseries.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, validators]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 097c3b8d49..ae2754c0eb 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -456,6 +456,10 @@ export class UtilsService { return isDefined(value); } + public isDefinedAndNotNull(value: any): boolean { + return isDefinedAndNotNull(value); + } + public defaultValue(value: any, defaultValue: any): any { if (isDefinedAndNotNull(value)) { return value; From a5556b9e549cc4503b3a5c9c106b52524645ea59 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 10 Jul 2024 18:25:26 +0300 Subject: [PATCH 07/14] UI: fixed invalid tooltip in chart fill settings --- .../chart/bar-chart-with-labels-basic-config.component.html | 2 +- .../chart/bar-chart-with-labels-widget-settings.component.html | 2 +- .../lib/settings/common/chart/chart-bar-settings.component.html | 2 +- .../settings/common/chart/chart-fill-settings.component.html | 2 +- .../lib/settings/common/chart/chart-fill-settings.component.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index 0d8537a3a6..774e6d88c5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -160,7 +160,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html index d79e84d98e..fe92ec2af3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html @@ -18,7 +18,7 @@
-
{{ title | translate }}
+
{{ rowTitle | translate }}
{{ chartFillTypeTranslationMap.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts index 718c4cce02..c909df3511 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts @@ -55,7 +55,7 @@ export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor disabled: boolean; @Input() - title = 'widgets.chart.fill'; + rowTitle = 'widgets.chart.fill'; @Input() fillNoneTitle = 'widgets.chart.fill-type-none'; From 50b9d693860941a93091828533ae1dd0b9577ae9 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 11 Jul 2024 11:59:03 +0300 Subject: [PATCH 08/14] UI: Refactoring --- .../entity/entity-list.component.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 383d5bc8f4..db785ab034 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -25,7 +25,15 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -49,6 +57,11 @@ import { SubscriptSizing } from '@angular/material/form-field'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EntityListComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => EntityListComponent), + multi: true } ] }) @@ -179,10 +192,6 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV (entities) => { this.entities = entities; this.entityListFormGroup.get('entities').setValue(this.entities); - if(!entities.length && this.required) { - this.modelValue = null; - this.propagateChange(this.modelValue); - } } ); } else { @@ -193,6 +202,12 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.dirty = true; } + validate(): ValidationErrors | null { + return this.entityListFormGroup.valid ? null : { + entities: {valid: false} + }; + } + reset() { this.entities = []; this.entityListFormGroup.get('entities').setValue(this.entities); From aa27c84ddea08bb2eb99935ed461bd0fbe4ffb8d Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 11 Jul 2024 12:20:10 +0300 Subject: [PATCH 09/14] UI: Refactoring --- .../entity/entity-list.component.ts | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index db785ab034..a4d7c9bab0 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -36,8 +36,6 @@ import { } from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; -import { Store } from '@ngrx/store'; -import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntityType } from '@shared/models/entity-type.models'; import { BaseData } from '@shared/models/base-data'; @@ -69,7 +67,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV entityListFormGroup: UntypedFormGroup; - modelValue: Array | null; + private modelValue: Array | null; @Input() entityType: EntityType; @@ -121,17 +119,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV private propagateChange = (v: any) => { }; - constructor(private store: Store, - public translate: TranslateService, + constructor(public translate: TranslateService, private entityService: EntityService, private fb: UntypedFormBuilder) { this.entityListFormGroup = this.fb.group({ - entities: [this.entities, this.required ? [Validators.required] : []], + entities: [this.entities], entity: [null] }); } - updateValidators() { + private updateValidators() { this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []); this.entityListFormGroup.get('entities').updateValueAndValidity(); } @@ -208,7 +205,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV }; } - reset() { + private reset() { this.entities = []; this.entityListFormGroup.get('entities').setValue(this.entities); this.modelValue = null; @@ -220,7 +217,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.dirty = true; } - add(entity: BaseData): void { + private add(entity: BaseData): void { if (!this.modelValue || this.modelValue.indexOf(entity.id.id) === -1) { if (!this.modelValue) { this.modelValue = []; @@ -233,7 +230,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.clear(); } - remove(entity: BaseData) { + public remove(entity: BaseData) { let index = this.entities.indexOf(entity); if (index >= 0) { this.entities.splice(index, 1); @@ -248,11 +245,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } } - displayEntityFn(entity?: BaseData): string | undefined { + public displayEntityFn(entity?: BaseData): string | undefined { return entity ? entity.name : undefined; } - fetchEntities(searchText?: string): Observable>> { + private fetchEntities(searchText?: string): Observable>> { this.searchText = searchText; return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, @@ -260,14 +257,14 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV map((data) => data ? data : [])); } - onFocus() { + public onFocus() { if (this.dirty) { this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true}); this.dirty = false; } } - clear(value: string = '') { + private clear(value: string = '') { this.entityInput.nativeElement.value = value; this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); setTimeout(() => { @@ -276,8 +273,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV }, 0); } - textIsNotEmpty(text: string): boolean { + public textIsNotEmpty(text: string): boolean { return (text && text.length > 0); } - } From dfb4c01cca89c96d1930b65271b8ea6fd1afc135 Mon Sep 17 00:00:00 2001 From: rusikv Date: Fri, 12 Jul 2024 12:28:43 +0300 Subject: [PATCH 10/14] UI: refactoring --- .../chart/bar-chart-with-labels-basic-config.component.html | 2 +- .../chart/bar-chart-with-labels-widget-settings.component.html | 2 +- .../lib/settings/common/chart/chart-bar-settings.component.html | 2 +- .../settings/common/chart/chart-fill-settings.component.html | 2 +- .../lib/settings/common/chart/chart-fill-settings.component.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index 774e6d88c5..76b458e4e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -160,7 +160,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html index fe92ec2af3..e56fc1ec00 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html @@ -18,7 +18,7 @@
-
{{ rowTitle | translate }}
+
{{ titleText | translate }}
{{ chartFillTypeTranslationMap.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts index c909df3511..bf02937221 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts @@ -55,7 +55,7 @@ export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor disabled: boolean; @Input() - rowTitle = 'widgets.chart.fill'; + titleText = 'widgets.chart.fill'; @Input() fillNoneTitle = 'widgets.chart.fill-type-none'; From 1aa53c3a1571cba577205e2800be1a09f224fda1 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 18 Jul 2024 15:11:26 +0300 Subject: [PATCH 11/14] UI: Prevent action on preview and edit mode --- .../unread-notification-widget.component.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts index fcad768e66..2dfded3f96 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts @@ -203,23 +203,29 @@ export class UnreadNotificationWidgetComponent implements OnInit, OnDestroy { } markAsRead(id: string) { - const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]); - cmd.subscribe(); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]); + cmd.subscribe(); + } } markAsAllRead($event: Event) { - if ($event) { - $event.stopPropagation(); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + if ($event) { + $event.stopPropagation(); + } + const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService); + cmd.subscribe(); } - const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService); - cmd.subscribe(); } viewAll($event: Event) { - if ($event) { - $event.stopPropagation(); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + if ($event) { + $event.stopPropagation(); + } + this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {}); } - this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {}); } trackById(index: number, item: NotificationRequest): string { From 7e0a9bbebfd48dfb3cfb88a4687bd284638f6f2b Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 22 Jul 2024 17:39:46 +0300 Subject: [PATCH 12/14] tbel: added some float methods --- .../thingsboard/script/api/tbel/TbUtils.java | 165 ++++++++++++------ .../script/api/tbel/TbUtilsTest.java | 75 +++++++- 2 files changed, 182 insertions(+), 58 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index 852f7a054f..034d6b4686 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -61,6 +61,7 @@ public class TbUtils { private static final int HEX_LEN_MIN = -1; private static final int HEX_LEN_INT_MAX = 8; private static final int HEX_LEN_LONG_MAX = 16; + private static final int BYTES_LEN_INT_MAX = 4; private static final int BYTES_LEN_LONG_MAX = 8; private static final LinkedHashMap mdnEncodingReplacements = new LinkedHashMap<>(); @@ -117,6 +118,8 @@ public class TbUtils { String.class))); parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat", String.class, int.class))); + parserConfig.addImport("parseHexIntLongToFloat", new MethodStub(TbUtils.class.getMethod("parseHexIntLongToFloat", + String.class, boolean.class))); parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble", String.class))); parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt", @@ -159,14 +162,18 @@ public class TbUtils { String.class))); parserConfig.addImport("parseHexToFloat", new MethodStub(TbUtils.class.getMethod("parseHexToFloat", String.class, boolean.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - byte[].class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", byte[].class, int.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - List.class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + List.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + List.class, int.class, int.class, boolean.class))); parserConfig.addImport("parseLittleEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToDouble", String.class))); parserConfig.addImport("parseBigEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToDouble", @@ -201,17 +208,17 @@ public class TbUtils { Long.class))); parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class))); - parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", + parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class, boolean.class))); - parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", + parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class, boolean.class, int.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class, boolean.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class, boolean.class, boolean.class))); parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex", Float.class))); @@ -334,10 +341,14 @@ public class TbUtils { } public static Integer parseInt(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); + return parseInt(value, radix, true); + } + + private static Integer parseInt(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue >= 25 && radixValue <= MAX_RADIX) { + if (radixValue >= 25 && radixValue <= MAX_RADIX) { return (Integer) compareIntLongValueMinMax(valueP, radixValue, Integer.MAX_VALUE, Integer.MIN_VALUE); } return switch (radixValue) { @@ -354,10 +365,14 @@ public class TbUtils { } public static Long parseLong(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); + return parseLong(value, radix, true); + } + + private static Long parseLong(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue >= 25 && radixValue <= MAX_RADIX) { + if (radixValue >= 25 && radixValue <= MAX_RADIX) { return (Long) compareIntLongValueMinMax(valueP, radixValue, Long.MAX_VALUE, Long.MIN_VALUE); } return switch (radixValue) { @@ -385,6 +400,7 @@ public class TbUtils { return Integer.parseInt(binaryString, MIN_RADIX); } } + private static long parseBinaryStringAsSignedLong(String binaryString) { if (binaryString.length() != 64) { // Pad the binary string to 64 bits if it is not already @@ -420,34 +436,58 @@ public class TbUtils { } public static Float parseFloat(String value) { - return parseFloat(value, 0); + return parseFloat(value, ZERO_RADIX); } public static Float parseFloat(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); - int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue == DEC_RADIX) { - return Float.parseFloat(value); - } else { - int bits = Integer.parseUnsignedInt(valueP, HEX_RADIX); - return Float.intBitsToFloat(bits); + String valueP = prepareNumberString(value, true); + if (valueP != null) { + return parseFloatFromString(value, valueP, radix); + } + return null; + } + + private static Float parseFloatFromString(String value, String valueP, int radix) { + int radixValue = isValidStringAndRadix(valueP, radix, value); + if (radixValue == HEX_RADIX) { + int bits = (int) Long.parseLong(valueP, HEX_RADIX); + // Hex representation is a standard IEEE 754 float value (eg "0x41200000" for 10.0f). + return Float.intBitsToFloat(bits); + } else { + return Float.parseFloat(value); + } + } + + public static Float parseHexIntLongToFloat(String value, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { + int radixValue = isValidStringAndRadix(valueP, HEX_RADIX, value); + if (radixValue == HEX_RADIX) { + int bits = (int) Long.parseLong(valueP, HEX_RADIX); + // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). + float floatValue = (float) bits; + return Float.valueOf(floatValue); } } return null; } + public static Double parseDouble(String value) { int radix = getRadix10_16(value); return parseDouble(value, radix); } public static Double parseDouble(String value, int radix) { - if (value != null) { - String valueP = prepareNumberString(value); + return parseDouble(value, radix, true); + } + + private static Double parseDouble(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); if (radixValue == DEC_RADIX) { - return Double.parseDouble(prepareNumberString(value)); + return Double.parseDouble(valueP); } else { long bits = Long.parseUnsignedLong(valueP, HEX_RADIX); return Double.longBitsToDouble(bits); @@ -469,9 +509,7 @@ public class TbUtils { } public static Integer parseHexToInt(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? hexValue : reverseHexStringByOrder(hexValue); - return parseInt(hex, HEX_RADIX); + return parseInt(value, HEX_RADIX, bigEndian); } public static long parseLittleEndianHexToLong(String hex) { @@ -487,9 +525,7 @@ public class TbUtils { } public static Long parseHexToLong(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseLong(hex, HEX_RADIX); + return parseLong(value, HEX_RADIX, bigEndian); } public static float parseLittleEndianHexToFloat(String hex) { @@ -505,9 +541,11 @@ public class TbUtils { } public static Float parseHexToFloat(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseFloat(hex, HEX_RADIX); + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { + return parseFloatFromString(value, valueP, HEX_RADIX); + } + return null; } public static double parseLittleEndianHexToDouble(String hex) { @@ -523,13 +561,11 @@ public class TbUtils { } public static double parseHexToDouble(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseDouble(hex, HEX_RADIX); + return parseDouble(value, HEX_RADIX, bigEndian); } public static ExecutionArrayList hexToBytes(ExecutionContext ctx, String value) { - String hex = prepareNumberString(value); + String hex = prepareNumberString(value, true); int len = hex.length(); if (len % 2 > 0) { throw new IllegalArgumentException("Hex string must be even-length."); @@ -575,6 +611,7 @@ public class TbUtils { public static String longToHex(Long l) { return prepareNumberHexString(l, true, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX); } + public static String longToHex(Long l, boolean bigEndian) { return prepareNumberHexString(l, bigEndian, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX); } @@ -604,7 +641,7 @@ public class TbUtils { return Long.toString(number, radix); } return switch (radix) { - case MIN_RADIX -> Long.toBinaryString(number); + case MIN_RADIX -> Long.toBinaryString(number); case OCTAL_RADIX -> Long.toOctalString(number); case DEC_RADIX -> Long.toString(number); case HEX_RADIX -> prepareNumberHexString(number, bigEndian, pref, -1, -1); @@ -646,13 +683,13 @@ public class TbUtils { private static String removeLeadingZero_FF(String hex, Long number, int hexLenMax) { String hexWithoutZero = hex.replaceFirst("^0+(?!$)", ""); // Remove leading zeros except for the last one - hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero; + hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero; if (number >= 0) { return hexWithoutZero; } else { String hexWithoutZeroFF = hexWithoutZero.replaceFirst("^F+(?!$)", ""); - hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF; - if (hexWithoutZeroFF.length() > hexLenMax) { + hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF; + if (hexWithoutZeroFF.length() > hexLenMax) { return hexWithoutZeroFF.substring(hexWithoutZeroFF.length() - hexLenMax); } else if (hexWithoutZeroFF.length() == hexLenMax) { return hexWithoutZeroFF; @@ -668,7 +705,7 @@ public class TbUtils { public static String floatToHex(Float f, boolean bigEndian) { // Convert the float to its raw integer bits representation - int bits = Float.floatToRawIntBits(f); + int bits = Float.floatToIntBits(f); // Format the integer bits as a hexadecimal string String result = String.format("0x%08X", bits); @@ -719,7 +756,7 @@ public class TbUtils { if (offset > data.length) { throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); } - if (length > 4) { + if (length > BYTES_LEN_INT_MAX) { throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); } if (offset + length > data.length) { @@ -784,10 +821,33 @@ public class TbUtils { } public static float parseBytesToFloat(byte[] data, int offset, boolean bigEndian) { - byte[] bytesToNumber = prepareBytesToNumber(data, offset, 4, bigEndian); - return ByteBuffer.wrap(bytesToNumber).getFloat(); + return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX, bigEndian); } + public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) { + return parseBytesToFloat(Bytes.toArray(data), offset, length, bigEndian); + } + + public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) { + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); + if (length > BYTES_LEN_INT_MAX) { + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); + } + if (bytesToNumber.length < BYTES_LEN_INT_MAX) { + byte[] extendedBytes = new byte[4]; + Arrays.fill(extendedBytes, (byte) 0); + System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); + bytesToNumber = extendedBytes; + } + float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat(); + if (!Float.isNaN(floatValue)){ + return floatValue; + } else { + long longValue = parseBytesToLong(bytesToNumber, offset, length, bigEndian); + BigDecimal bigDecimalValue = new BigDecimal(longValue); + return bigDecimalValue.floatValue(); + } + } public static double parseBytesToDouble(byte[] data, int offset) { return parseBytesToDouble(data, offset, true); @@ -931,14 +991,15 @@ public class TbUtils { } } - private static String prepareNumberString(String value) { - if (value != null) { + private static String prepareNumberString(String value, boolean bigEndian) { + if (StringUtils.isNotBlank(value)) { value = value.trim(); value = value.replace("0x", ""); value = value.replace("0X", ""); value = value.replace(",", "."); + return bigEndian ? value : reverseHexStringByOrder(value); } - return value; + return null; } private static int isValidStringAndRadix(String valueP, int radix, String value) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index 1d4e12c00e..1df1fc93cc 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -47,7 +47,7 @@ public class TbUtilsTest { private ExecutionContext ctx; - private final float floatVal = 29.29824f; + private final Float floatVal = 29.29824f; private final float floatValRev = -5.948442E7f; @@ -256,7 +256,7 @@ public class TbUtilsTest { @Test public void parseFloat() { - String floatValStr = "29.29824"; + String floatValStr = floatVal.toString(); Assertions.assertEquals(java.util.Optional.of(floatVal).get(), TbUtils.parseFloat(floatValStr)); String floatValHex = "41EA62CC"; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseHexToFloat(floatValHex))); @@ -274,7 +274,7 @@ public class TbUtilsTest { } @Test - public void arseBytesToFloat() { + public void parseBytesToFloat() { byte[] floatValByte = {65, -22, 98, -52}; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false))); @@ -282,6 +282,33 @@ public class TbUtilsTest { List floatVaList = Bytes.asList(floatValByte); Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatVaList, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + + // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} + floatValByte = new byte[] {-1, -1, -1, -1}; + float floatExpectedBe = 4294.9673f; + float floatExpectedLe = 4.2949673E9f; + float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, false))); + + floatVaList = Bytes.asList(floatValByte); + actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + + // 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00} + floatValByte = new byte[] {0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; + floatExpectedBe = 2143.3547f; + floatExpectedLe = -3.984375f; + actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2,false))); + + floatVaList = Bytes.asList(floatValByte); + floatExpectedLe = 8372479.0f; + actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, 3, false))); } @Test @@ -605,15 +632,51 @@ public class TbUtilsTest { @Test public void floatToHex_Test() { - Float value = 20.89f; - String expectedHex = "0x41A71EB8"; - String valueHexRev = "0xB81EA741"; + Float value = 123456789.00f; + String expectedHex = "0x4CEB79A3"; + String valueHexRev = "0xA379EB4C"; String actual = TbUtils.floatToHex(value); Assertions.assertEquals(expectedHex, actual); Float valueActual = TbUtils.parseHexToFloat(actual); Assertions.assertEquals(value, valueActual); valueActual = TbUtils.parseHexToFloat(valueHexRev, false); Assertions.assertEquals(value, valueActual); + value = 123456789.67f; + expectedHex = "0x4CEB79A3"; + valueHexRev = "0xA379EB4C"; + actual = TbUtils.floatToHex(value); + Assertions.assertEquals(expectedHex, actual); + valueActual = TbUtils.parseHexToFloat(actual); + Assertions.assertEquals(value, valueActual); + valueActual = TbUtils.parseHexToFloat(valueHexRev, false); + Assertions.assertEquals(value, valueActual); + value = 10.0f; + expectedHex = "0x41200000"; + valueHexRev = "0x00002041"; + actual = TbUtils.floatToHex(value); + Assertions.assertEquals(expectedHex, actual); + valueActual = TbUtils.parseHexToFloat(actual); + Assertions.assertEquals(value, valueActual); + valueActual = TbUtils.parseHexToFloat(valueHexRev, false); + Assertions.assertEquals(value, valueActual); + } + // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). + @Test + public void parseHexIntLongToFloat_Test() { + Float valueExpected = 10.0f; + Float valueActual = TbUtils.parseHexIntLongToFloat("0x0A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A", false); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x00000A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A0000", false); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 2570.0f; + valueActual = TbUtils.parseHexIntLongToFloat("0x000A0A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A0A00", false); + Assertions.assertEquals(valueExpected, valueActual); } @Test From 2f838c4fbf1b5819f93dea946ab70109129767ad Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 23 Jul 2024 12:37:48 +0300 Subject: [PATCH 13/14] tbel: added some double methods --- .../thingsboard/script/api/tbel/TbUtils.java | 46 +++++++++--- .../script/api/tbel/TbUtilsTest.java | 71 +++++++++++++------ 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index 034d6b4686..b1d0ba6dfd 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -170,7 +170,7 @@ public class TbUtils { byte[].class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class, boolean.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class, int.class, boolean.class))); @@ -186,10 +186,14 @@ public class TbUtils { byte[].class, int.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", byte[].class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", List.class, int.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", List.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + List.class, int.class, int.class, boolean.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", double.class, int.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", @@ -798,11 +802,11 @@ public class TbUtils { if (offset + length > data.length) { throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); } - var bb = ByteBuffer.allocate(8); + var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX); if (!bigEndian) { bb.order(ByteOrder.LITTLE_ENDIAN); } - bb.position(bigEndian ? 8 - length : 0); + bb.position(bigEndian ? BYTES_LEN_LONG_MAX - length : 0); bb.put(data, offset, length); bb.position(0); return bb.getLong(); @@ -831,21 +835,21 @@ public class TbUtils { public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) { byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); if (length > BYTES_LEN_INT_MAX) { - throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_INT_MAX + " bytes is allowed!"); } if (bytesToNumber.length < BYTES_LEN_INT_MAX) { - byte[] extendedBytes = new byte[4]; + byte[] extendedBytes = new byte[BYTES_LEN_INT_MAX]; Arrays.fill(extendedBytes, (byte) 0); System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); bytesToNumber = extendedBytes; } float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat(); - if (!Float.isNaN(floatValue)){ + if (!Float.isNaN(floatValue)) { return floatValue; } else { long longValue = parseBytesToLong(bytesToNumber, offset, length, bigEndian); BigDecimal bigDecimalValue = new BigDecimal(longValue); - return bigDecimalValue.floatValue(); + return bigDecimalValue.floatValue(); } } @@ -861,9 +865,33 @@ public class TbUtils { return parseBytesToDouble(Bytes.toArray(data), offset, bigEndian); } + public static double parseBytesToDouble(List data, int offset, int length, boolean bigEndian) { + return parseBytesToDouble(Bytes.toArray(data), offset, length, bigEndian); + } + public static double parseBytesToDouble(byte[] data, int offset, boolean bigEndian) { - byte[] bytesToNumber = prepareBytesToNumber(data, offset, BYTES_LEN_LONG_MAX, bigEndian); - return ByteBuffer.wrap(bytesToNumber).getDouble(); + return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX, bigEndian); + } + + public static double parseBytesToDouble(byte[] data, int offset, int length, boolean bigEndian) { + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); + if (length > BYTES_LEN_LONG_MAX) { + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!"); + } + if (bytesToNumber.length < BYTES_LEN_LONG_MAX) { + byte[] extendedBytes = new byte[BYTES_LEN_LONG_MAX]; + Arrays.fill(extendedBytes, (byte) 0); + System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); + bytesToNumber = extendedBytes; + } + double doubleValue = ByteBuffer.wrap(bytesToNumber).getDouble(); + if (!Double.isNaN(doubleValue)) { + return doubleValue; + } else { + BigInteger bigInt = new BigInteger(1, bytesToNumber); + BigDecimal bigDecimalValue = new BigDecimal(bigInt); + return bigDecimalValue.doubleValue(); + } } private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index 1df1fc93cc..e0eb90802c 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -30,6 +30,7 @@ import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionHashMap; import java.io.IOException; +import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -279,36 +280,36 @@ public class TbUtilsTest { Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false))); - List floatVaList = Bytes.asList(floatValByte); - Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatVaList, 0))); - Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + List floatValList = Bytes.asList(floatValByte); + Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValList, 0))); + Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, false))); // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} - floatValByte = new byte[] {-1, -1, -1, -1}; + floatValByte = new byte[]{-1, -1, -1, -1}; float floatExpectedBe = 4294.9673f; float floatExpectedLe = 4.2949673E9f; float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); - Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, false))); - floatVaList = Bytes.asList(floatValByte); - actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); - Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + floatValList = Bytes.asList(floatValByte); + actualBe = TbUtils.parseBytesToFloat(floatValList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, false))); // 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00} - floatValByte = new byte[] {0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; + floatValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; floatExpectedBe = 2143.3547f; floatExpectedLe = -3.984375f; actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); - Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2,false))); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2, false))); - floatVaList = Bytes.asList(floatValByte); + floatValList = Bytes.asList(floatValByte); floatExpectedLe = 8372479.0f; - actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); - Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, 3, false))); + actualBe = TbUtils.parseBytesToFloat(floatValList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 3, false))); } @Test @@ -385,9 +386,38 @@ public class TbUtilsTest { Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0))); Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, false))); - List doubleVaList = Bytes.asList(doubleValByte); - Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleVaList, 0))); - Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleVaList, 0, false))); + List doubleValList = Bytes.asList(doubleValByte); + Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValList, 0))); + Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, false))); + + // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}; + double doubleExpectedBe = 18446.744073709553d; + double doubleExpectedLe = 1.8446744073709552E19d; + double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); + Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe / 1000000000000000L)); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, false))); + + doubleValList = Bytes.asList(doubleValByte); + Assertions.assertEquals(0, Double.compare(doubleExpectedBe, TbUtils.parseBytesToDouble(doubleValList, 0) / 1000000000000000L)); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, false))); + + doubleValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00, 0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; + doubleExpectedBe = 2387013.651780523d; + doubleExpectedLe = 7.234601680440024E-304d; + actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); + BigDecimal bigDecimal = new BigDecimal(actualBe); + // We move the decimal point to the left by 301 positions + actualBe = bigDecimal.movePointLeft(301).doubleValue(); + Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe)); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); + doubleValList = Bytes.asList(doubleValByte); + doubleExpectedLe = 5.828674572203954E303d; + actualBe = TbUtils.parseBytesToDouble(doubleValList, 0); + bigDecimal = new BigDecimal(actualBe); + actualBe = bigDecimal.movePointLeft(301).doubleValue(); + Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe)); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 5, false))); } @Test @@ -660,7 +690,8 @@ public class TbUtilsTest { valueActual = TbUtils.parseHexToFloat(valueHexRev, false); Assertions.assertEquals(value, valueActual); } - // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). + + // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). @Test public void parseHexIntLongToFloat_Test() { Float valueExpected = 10.0f; From c347c067e7468f4b8a248145966a515207e9a4a0 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 23 Jul 2024 16:28:17 +0300 Subject: [PATCH 14/14] tbel: added some parseBytesTo Int/Long/Float/Double methods with length --- .../thingsboard/script/api/tbel/TbUtils.java | 156 +++++++++++++----- .../script/api/tbel/TbUtilsTest.java | 26 ++- 2 files changed, 130 insertions(+), 52 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index b1d0ba6dfd..9286ffec46 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -130,10 +130,18 @@ public class TbUtils { String.class))); parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt", String.class, boolean.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + List.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + List.class, int.class))); parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", List.class, int.class, int.class))); parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + byte[].class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + byte[].class, int.class))); parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", byte[].class, int.class, int.class))); parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", @@ -146,10 +154,18 @@ public class TbUtils { String.class))); parserConfig.addImport("parseHexToLong", new MethodStub(TbUtils.class.getMethod("parseHexToLong", String.class, boolean.class))); + parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", + List.class))); + parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", + List.class, int.class))); parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", List.class, int.class, int.class))); parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", + byte[].class))); + parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", + byte[].class, int.class))); parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", byte[].class, int.class, int.class))); parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong", @@ -163,17 +179,21 @@ public class TbUtils { parserConfig.addImport("parseHexToFloat", new MethodStub(TbUtils.class.getMethod("parseHexToFloat", String.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - byte[].class, int.class))); + List.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - byte[].class, int.class, boolean.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - List.class, int.class, boolean.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - byte[].class, int.class, int.class, boolean.class))); + List.class, int.class, int.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, int.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("parseLittleEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToDouble", String.class))); parserConfig.addImport("parseBigEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToDouble", @@ -183,17 +203,21 @@ public class TbUtils { parserConfig.addImport("parseHexToDouble", new MethodStub(TbUtils.class.getMethod("parseHexToDouble", String.class, boolean.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", - byte[].class, int.class))); - parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", - byte[].class, int.class, boolean.class))); - parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", - byte[].class, int.class, int.class, boolean.class))); + List.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", List.class, int.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", - List.class, int.class, boolean.class))); + List.class, int.class, int.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + byte[].class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + byte[].class, int.class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + byte[].class, int.class, int.class))); + parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", + byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", double.class, int.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", @@ -740,16 +764,28 @@ public class TbUtils { return Base64.getDecoder().decode(input); } + public static int parseBytesToInt(List data) { + return parseBytesToInt(Bytes.toArray(data)); + } + + public static int parseBytesToInt(List data, int offset) { + return parseBytesToInt(Bytes.toArray(data), offset); + } + public static int parseBytesToInt(List data, int offset, int length) { - return parseBytesToInt(data, offset, length, true); + return parseBytesToInt(Bytes.toArray(data), offset, length); } public static int parseBytesToInt(List data, int offset, int length, boolean bigEndian) { - final byte[] bytes = new byte[data.size()]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = data.get(i); - } - return parseBytesToInt(bytes, offset, length, bigEndian); + return parseBytesToInt(Bytes.toArray(data), offset, length, bigEndian); + } + + public static int parseBytesToInt(byte[] data) { + return parseBytesToInt(data, 0); + } + + public static int parseBytesToInt(byte[] data, int offset) { + return parseBytesToInt(data, offset, BYTES_LEN_INT_MAX); } public static int parseBytesToInt(byte[] data, int offset, int length) { @@ -776,16 +812,28 @@ public class TbUtils { return bb.getInt(); } + public static long parseBytesToLong(List data) { + return parseBytesToLong(Bytes.toArray(data)); + } + + public static long parseBytesToLong(List data, int offset) { + return parseBytesToLong(Bytes.toArray(data), offset); + } + public static long parseBytesToLong(List data, int offset, int length) { - return parseBytesToLong(data, offset, length, true); + return parseBytesToLong(Bytes.toArray(data), offset, length); } public static long parseBytesToLong(List data, int offset, int length, boolean bigEndian) { - final byte[] bytes = new byte[data.size()]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = data.get(i); - } - return parseBytesToLong(bytes, offset, length, bigEndian); + return parseBytesToLong(Bytes.toArray(data), offset, length, bigEndian); + } + + public static long parseBytesToLong(byte[] data) { + return parseBytesToLong(data, 0); + } + + public static long parseBytesToLong(byte[] data, int offset) { + return parseBytesToLong(data, offset, BYTES_LEN_LONG_MAX); } public static long parseBytesToLong(byte[] data, int offset, int length) { @@ -812,31 +860,42 @@ public class TbUtils { return bb.getLong(); } - public static float parseBytesToFloat(byte[] data, int offset) { - return parseBytesToFloat(data, offset, true); + public static float parseBytesToFloat(List data) { + return parseBytesToFloat(Bytes.toArray(data), 0); } public static float parseBytesToFloat(List data, int offset) { - return parseBytesToFloat(data, offset, true); + return parseBytesToFloat(Bytes.toArray(data), offset, BYTES_LEN_INT_MAX); } - public static float parseBytesToFloat(List data, int offset, boolean bigEndian) { - return parseBytesToFloat(Bytes.toArray(data), offset, bigEndian); - } - - public static float parseBytesToFloat(byte[] data, int offset, boolean bigEndian) { - return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX, bigEndian); + public static float parseBytesToFloat(List data, int offset, int length) { + return parseBytesToFloat(Bytes.toArray(data), offset, length, true); } public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) { return parseBytesToFloat(Bytes.toArray(data), offset, length, bigEndian); } + public static float parseBytesToFloat(byte[] data) { + return parseBytesToFloat(data, 0); + } + + public static float parseBytesToFloat(byte[] data, int offset) { + return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX); + } + + public static float parseBytesToFloat(byte[] data, int offset, int length) { + return parseBytesToFloat(data, offset, length, true); + } + public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) { - byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); if (length > BYTES_LEN_INT_MAX) { throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_INT_MAX + " bytes is allowed!"); } + if (offset + length > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); + } + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); if (bytesToNumber.length < BYTES_LEN_INT_MAX) { byte[] extendedBytes = new byte[BYTES_LEN_INT_MAX]; Arrays.fill(extendedBytes, (byte) 0); @@ -847,37 +906,48 @@ public class TbUtils { if (!Float.isNaN(floatValue)) { return floatValue; } else { - long longValue = parseBytesToLong(bytesToNumber, offset, length, bigEndian); + long longValue = parseBytesToLong(bytesToNumber, 0, BYTES_LEN_INT_MAX); BigDecimal bigDecimalValue = new BigDecimal(longValue); return bigDecimalValue.floatValue(); } } - public static double parseBytesToDouble(byte[] data, int offset) { - return parseBytesToDouble(data, offset, true); + public static double parseBytesToDouble(List data) { + return parseBytesToDouble(Bytes.toArray(data)); } public static double parseBytesToDouble(List data, int offset) { - return parseBytesToDouble(data, offset, true); + return parseBytesToDouble(Bytes.toArray(data), offset); } - public static double parseBytesToDouble(List data, int offset, boolean bigEndian) { - return parseBytesToDouble(Bytes.toArray(data), offset, bigEndian); + public static double parseBytesToDouble(List data, int offset, int length) { + return parseBytesToDouble(Bytes.toArray(data), offset, length); } public static double parseBytesToDouble(List data, int offset, int length, boolean bigEndian) { return parseBytesToDouble(Bytes.toArray(data), offset, length, bigEndian); } - public static double parseBytesToDouble(byte[] data, int offset, boolean bigEndian) { - return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX, bigEndian); + public static double parseBytesToDouble(byte[] data) { + return parseBytesToDouble(data, 0); + } + + public static double parseBytesToDouble(byte[] data, int offset) { + return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX); + } + + public static double parseBytesToDouble(byte[] data, int offset, int length) { + return parseBytesToDouble(data, offset, length, true); } public static double parseBytesToDouble(byte[] data, int offset, int length, boolean bigEndian) { - byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); if (length > BYTES_LEN_LONG_MAX) { throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!"); } + if (offset + length > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); + } + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); if (bytesToNumber.length < BYTES_LEN_LONG_MAX) { byte[] extendedBytes = new byte[BYTES_LEN_LONG_MAX]; Arrays.fill(extendedBytes, (byte) 0); diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index e0eb90802c..52bafe2fd2 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -278,11 +278,11 @@ public class TbUtilsTest { public void parseBytesToFloat() { byte[] floatValByte = {65, -22, 98, -52}; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0))); - Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false))); + Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false))); List floatValList = Bytes.asList(floatValByte); Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValList, 0))); - Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, false))); + Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, 4, false))); // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} floatValByte = new byte[]{-1, -1, -1, -1}; @@ -290,12 +290,12 @@ public class TbUtilsTest { float floatExpectedLe = 4.2949673E9f; float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, false))); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false))); floatValList = Bytes.asList(floatValByte); actualBe = TbUtils.parseBytesToFloat(floatValList, 0); Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, false))); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 4, false))); // 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00} floatValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; @@ -306,10 +306,18 @@ public class TbUtilsTest { Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2, false))); floatValList = Bytes.asList(floatValByte); - floatExpectedLe = 8372479.0f; + floatExpectedLe = 4.2908055E9f; actualBe = TbUtils.parseBytesToFloat(floatValList, 0); Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 3, false))); + // "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33"; + String intToHexBe = "01752B0367FA000500010488FFFFFFFFFFFFFFFF33"; + floatExpectedLe = 4294.9673f; + floatValList = TbUtils.hexToBytes(ctx, intToHexBe); + float actualLe = TbUtils.parseBytesToFloat(floatValList, 12, 4, false); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000)); + actualLe = TbUtils.parseBytesToFloat(floatValList, 12 + 4, 4, false); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000)); } @Test @@ -384,11 +392,11 @@ public class TbUtilsTest { public void parseBytesToDouble() { byte[] doubleValByte = {64, -101, 4, -79, 12, -78, -107, -22}; Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0))); - Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, false))); + Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); List doubleValList = Bytes.asList(doubleValByte); Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValList, 0))); - Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, false))); + Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false))); // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}; @@ -396,11 +404,11 @@ public class TbUtilsTest { double doubleExpectedLe = 1.8446744073709552E19d; double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe / 1000000000000000L)); - Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, false))); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); doubleValList = Bytes.asList(doubleValByte); Assertions.assertEquals(0, Double.compare(doubleExpectedBe, TbUtils.parseBytesToDouble(doubleValList, 0) / 1000000000000000L)); - Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, false))); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false))); doubleValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00, 0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; doubleExpectedBe = 2387013.651780523d;