diff --git a/application/src/main/data/json/system/widget_types/knob_control.json b/application/src/main/data/json/system/widget_types/knob_control.json index 876f872014..8840787f58 100644 --- a/application/src/main/data/json/system/widget_types/knob_control.json +++ b/application/src/main/data/json/system/widget_types/knob_control.json @@ -12,10 +12,9 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "", - "dataKeySettingsSchema": "{}\n", + "dataKeySettingsForm": [], "settingsDirective": "tb-knob-control-widget-settings", - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2,\"units\":null}" }, "tags": [ "dial", diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html index aa859b2557..a194812778 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html @@ -27,9 +27,6 @@
-
- -
{{ title }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts index 6dedab0bf2..8c1fc3ab2a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts @@ -14,37 +14,27 @@ /// limitations under the License. /// -import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; +import { ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { WidgetContext } from '@home/models/widget-component.models'; -import { UtilsService } from '@core/services/utils.service'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { isDefined, isNumber } from '@core/utils'; +import { deepClone, isDefined } from '@core/utils'; import { CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge'; import tinycolor from 'tinycolor2'; -import { ColorProcessor, gradientColor } from '@shared/models/widget-settings.models'; -import { getSourceTbUnitSymbol } from '@shared/models/unit.models'; +import { ColorProcessor, gradientColor, ValueFormatProcessor } from '@shared/models/widget-settings.models'; +import { + KnobSettings, + knobWidgetDefaultSettings, + prepareKnobSettings +} from '@shared/models/widget/rpc/knob.component.models'; +import { ValueType } from '@shared/models/constants'; +import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; import GenericOptions = CanvasGauges.GenericOptions; -interface KnobSettings { - minValue: number; - maxValue: number; - initialValue: number; - title: string; - getValueMethod: string; - setValueMethod: string; - requestTimeout: number; - requestPersistent: boolean; - persistentPollingInterval: number; -} - @Component({ selector: 'tb-knob', templateUrl: './knob.component.html', styleUrls: ['./knob.component.scss'] }) -export class KnobComponent extends PageComponent implements OnInit, OnDestroy { +export class KnobComponent extends BasicActionWidgetComponent implements OnInit, OnDestroy { @ViewChild('knob', {static: true}) knobRef: ElementRef; @ViewChild('knobContainer', {static: true}) knobContainerRef: ElementRef; @@ -52,8 +42,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { @ViewChild('knobTopPointer', {static: true}) knobTopPointerRef: ElementRef; @ViewChild('knobValueContainer', {static: true}) knobValueContainerRef: ElementRef; @ViewChild('knobValue', {static: true}) knobValueRef: ElementRef; - @ViewChild('knobErrorContainer', {static: true}) knobErrorContainerRef: ElementRef; - @ViewChild('knobError', {static: true}) knobErrorRef: ElementRef; @ViewChild('knobTitleContainer', {static: true}) knobTitleContainerRef: ElementRef; @ViewChild('knobTitle', {static: true}) knobTitleRef: ElementRef; @ViewChild('knobMinmaxContainer', {static: true}) knobMinmaxContainerRef: ElementRef; @@ -64,7 +52,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { ctx: WidgetContext; value = '0'; - error = ''; title = ''; minValue: number; maxValue: number; @@ -79,13 +66,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { private minDeg = -45; private maxDeg = 225; - private isSimulated: boolean; - private requestTimeout: number; - private requestPersistent: boolean; - private persistentPollingInterval: number; - private getValueMethod: string; - private setValueMethod: string; - private executingUpdateValue: boolean; private scheduledValue: number; private rpcValue: number; @@ -98,8 +78,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { private knobValue: JQuery; private knobTitleContainer: JQuery; private knobTitle: JQuery; - private knobErrorContainer: JQuery; - private knobError: JQuery; private knobMinmaxContainer: JQuery; private minmaxLabel: JQuery; private textMeasure: JQuery; @@ -109,9 +87,11 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { private knobResize$: ResizeObserver; - constructor(private utils: UtilsService, - protected store: Store) { - super(store); + private valueSetter: ValueSetter; + private valueFormat: ValueFormatProcessor; + + constructor(protected cd: ChangeDetectorRef) { + super(cd); } ngOnInit(): void { @@ -123,8 +103,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.knobValue = $(this.knobValueRef.nativeElement); this.knobTitleContainer = $(this.knobTitleContainerRef.nativeElement); this.knobTitle = $(this.knobTitleRef.nativeElement); - this.knobErrorContainer = $(this.knobErrorContainerRef.nativeElement); - this.knobError = $(this.knobErrorRef.nativeElement); this.knobMinmaxContainer = $(this.knobMinmaxContainerRef.nativeElement); this.minmaxLabel = this.knobMinmaxContainer.find('.minmax-label'); this.textMeasure = $(this.textMeasureRef.nativeElement); @@ -145,7 +123,31 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { } private init() { - const settings: KnobSettings = this.ctx.settings; + const settings: KnobSettings = prepareKnobSettings({...deepClone(knobWidgetDefaultSettings), ...this.ctx.settings}); + + let initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue; + + const getInitialStateSettings = + {...settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')}; + this.createValueGetter(getInitialStateSettings, ValueType.DOUBLE, { + next: (value) => { + if (this.canvasBar) { + this.setValue(value); + } else { + initialValue = value; + } + } + }); + + const valueChangeSettings = {...settings.valueChange, + actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; + this.valueSetter = this.createValueSetter(valueChangeSettings); + + this.valueFormat = ValueFormatProcessor.fromSettings(this.ctx.$injector, { + units: this.ctx.units, + decimals: this.ctx.decimals, + showZeroDecimals: true + }); this.minValue = isDefined(settings.minValue) ? settings.minValue : 0; this.maxValue = isDefined(settings.maxValue) ? settings.maxValue : 100; @@ -279,44 +281,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { }); - const subscription = this.ctx.defaultSubscription; - const rpcEnabled = subscription.rpcEnabled; - - this.isSimulated = this.utils.widgetEditMode; - - this.requestTimeout = 500; - if (settings.requestTimeout) { - this.requestTimeout = settings.requestTimeout; - } - this.requestPersistent = false; - if (settings.requestPersistent) { - this.requestPersistent = settings.requestPersistent; - } - this.persistentPollingInterval = 5000; - if (settings.persistentPollingInterval) { - this.persistentPollingInterval = settings.persistentPollingInterval; - } - this.getValueMethod = 'getValue'; - if (settings.getValueMethod && settings.getValueMethod.length) { - this.getValueMethod = settings.getValueMethod; - } - this.setValueMethod = 'setValue'; - if (settings.setValueMethod && settings.setValueMethod.length) { - this.setValueMethod = settings.setValueMethod; - } - import('@home/components/widget/lib/canvas-digital-gauge').then( (gauge) => { this.canvasBar = new gauge.CanvasDigitalGauge(canvasBarData).draw(); - const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue; this.setValue(initialValue); - if (!rpcEnabled) { - this.onError('Target device is not set!'); - } else { - if (!this.isSimulated) { - this.rpcRequestValue(); - } - } } ); @@ -348,7 +316,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.canvasBar.update({width: size, height: size} as GenericOptions); } this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width()); - this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width()); const minmaxHeight = this.knobMinmaxContainer.height(); this.minmaxLabel.css({fontSize: minmaxHeight + 'px', lineHeight: minmaxHeight + 'px'}); this.checkValueSize(); @@ -408,27 +375,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { } private formatValue(value: any): string { - return this.ctx.utils.formatValue(value, this.ctx.decimals, getSourceTbUnitSymbol(this.ctx.units), true); - } - - private rpcRequestValue() { - this.error = ''; - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout, - this.requestPersistent, this.persistentPollingInterval).subscribe( - (responseBody) => { - if (isNumber(responseBody)) { - const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals)); - this.setValue(numValue); - } else { - const errorText = `Unable to parse response: ${responseBody}`; - this.onError(errorText); - } - }, - () => { - const errorText = this.ctx.defaultSubscription.rpcErrorText; - this.onError(errorText); - } - ); + return this.valueFormat.format(value); } private rpcUpdateValue(value: number) { @@ -440,27 +387,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.rpcValue = value; this.executingUpdateValue = true; } - this.error = ''; - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout, - this.requestPersistent, this.persistentPollingInterval).subscribe( - () => { - this.executingUpdateValue = false; - if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { - this.rpcUpdateValue(this.scheduledValue); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + this.updateValue(this.valueSetter, value, { + next: () => { + this.executingUpdateValue = false; + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { + this.rpcUpdateValue(this.scheduledValue); + } + }, + error: () => { + this.executingUpdateValue = false; } - }, - () => { - this.executingUpdateValue = false; - const errorText = this.ctx.defaultSubscription.rpcErrorText; - this.onError(errorText); - } - ); - } - - private onError(error: string) { - this.error = error; - this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width()); - this.ctx.detectChanges(); + }); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.html index 6d2dfe2187..5016361604 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.html @@ -15,66 +15,66 @@ limitations under the License. --> -
-
- widgets.rpc.common-settings - - widgets.rpc.knob-title - - -
-
- widgets.rpc.value-settings - - widgets.rpc.initial-value - - -
- - widgets.rpc.min-value - + +
+
widgets.knob.behavior
+
+
widgets.knob.initial-value
+ +
+
+
widgets.knob.on-value-change
+ +
+
+
+
widget-config.appearance
+
+
widgets.rpc.knob-title
+ + - - widgets.rpc.max-value - +
+
+
{{ 'widgets.knob.range' | translate }}
+
+
widgets.knob.min
+ + + +
widgets.knob.max
+ + + +
+
+
+
{{ 'widgets.knob.value' | translate }}
+
+ + + +
widget-config.decimals-suffix
+
+
+
+
+
widgets.knob.fallback-initial-value
+ + -
- - widgets.rpc.get-value-method - - - - widgets.rpc.set-value-method - - -
-
- widgets.rpc.rpc-settings - - widgets.rpc.request-timeout - - -
- widgets.rpc.persistent-rpc-settings - - - - - {{ 'widgets.rpc.request-persistent' | translate }} - - - - widget-config.advanced-settings - - - - - widgets.rpc.persistent-polling-interval - - - - -
-
-
+ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.ts index 5ef0bebc73..e5c57474c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/knob-control-widget-settings.component.ts @@ -15,10 +15,13 @@ /// import { Component } from '@angular/core'; -import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { knobWidgetDefaultSettings, prepareKnobSettings } from '@shared/models/widget/rpc/knob.component.models'; +import { ValueType } from '@shared/models/constants'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-knob-control-widget-settings', @@ -27,6 +30,16 @@ import { AppState } from '@core/core.state'; }) export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent { + get targetDevice(): TargetDevice { + return this.widgetConfig?.config?.targetDevice; + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + + valueType = ValueType; + knobControlWidgetSettingsForm: UntypedFormGroup; constructor(protected store: Store, @@ -39,17 +52,25 @@ export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent } protected defaultSettings(): WidgetSettings { - return { - title: '', - minValue: 0, - maxValue: 100, - initialValue: 50, - getValueMethod: 'getValue', - setValueMethod: 'setValue', - requestTimeout: 500, - requestPersistent: false, - persistentPollingInterval: 5000 - }; + return knobWidgetDefaultSettings; + } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + const knobSettings = prepareKnobSettings(deepClone(settings) as any) as WidgetSettings; + knobSettings.valueDecimals = this.widgetConfig?.config?.decimals ?? 2; + knobSettings.valueUnits = deepClone(this.widgetConfig?.config?.units); + return super.prepareInputSettings(knobSettings); + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + const newSettings = deepClone(settings); + if (this.widgetConfig?.config) { + this.widgetConfig.config.units = settings.valueUnits; + this.widgetConfig.config.decimals = settings.valueDecimals; + } + delete newSettings.valueUnits; + delete newSettings.valueDecimals; + return super.prepareOutputSettings(newSettings); } protected onSettingsSet(settings: WidgetSettings) { @@ -61,35 +82,16 @@ export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent // Value settings - initialValue: [settings.initialValue, []], + initialState: [settings.initialState, []], + valueChange: [settings.valueChange, []], + minValue: [settings.minValue, [Validators.required]], maxValue: [settings.maxValue, [Validators.required]], - getValueMethod: [settings.getValueMethod, [Validators.required]], - setValueMethod: [settings.setValueMethod, [Validators.required]], + valueUnits: [settings.valueUnits, []], + valueDecimals: [settings.valueDecimals, []], + initialValue: [settings.initialValue, []], - // RPC settings - - requestTimeout: [settings.requestTimeout, [Validators.min(0), Validators.required]], - - // --> Persistent RPC settings - - requestPersistent: [settings.requestPersistent, []], - persistentPollingInterval: [settings.persistentPollingInterval, [Validators.min(1000)]] }); } - - protected validatorTriggers(): string[] { - return ['requestPersistent']; - } - - protected updateValidators(emitEvent: boolean): void { - const requestPersistent: boolean = this.knobControlWidgetSettingsForm.get('requestPersistent').value; - if (requestPersistent) { - this.knobControlWidgetSettingsForm.get('persistentPollingInterval').enable({emitEvent}); - } else { - this.knobControlWidgetSettingsForm.get('persistentPollingInterval').disable({emitEvent}); - } - this.knobControlWidgetSettingsForm.get('persistentPollingInterval').updateValueAndValidity({emitEvent: false}); - } } diff --git a/ui-ngx/src/app/shared/models/widget/rpc/knob.component.models.ts b/ui-ngx/src/app/shared/models/widget/rpc/knob.component.models.ts new file mode 100644 index 0000000000..2bc34666a5 --- /dev/null +++ b/ui-ngx/src/app/shared/models/widget/rpc/knob.component.models.ts @@ -0,0 +1,120 @@ +/// +/// Copyright © 2016-2025 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + DataToValueType, + GetValueAction, + GetValueSettings, + SetValueAction, + SetValueSettings, + ValueToDataType +} from '@shared/models/action-widget-settings.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { isDefinedAndNotNull } from '@core/utils'; + +export interface KnobSettings { + initialState: GetValueSettings; + valueChange: SetValueSettings; + minValue: number; + maxValue: number; + initialValue: number; + title: string; + getValueMethod?: string; //deprecated + setValueMethod?: string; //deprecated + requestTimeout?: number; //deprecated + requestPersistent?: boolean; //deprecated + persistentPollingInterval?: number; //deprecated +} + +export const knobWidgetDefaultSettings: KnobSettings = { + initialState: { + action: GetValueAction.EXECUTE_RPC, + defaultValue: 50, + executeRpc: { + method: 'getValue', + requestTimeout: 500, + requestPersistent: false, + persistentPollingInterval: 5000 + }, + getAttribute: { + key: 'value', + scope: null + }, + getTimeSeries: { + key: 'value' + }, + getAlarmStatus: { + severityList: null, + typeList: null + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return double value */\nreturn data;' + } + }, + valueChange: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setValue', + requestTimeout: 500, + requestPersistent: false, + persistentPollingInterval: 5000 + }, + setAttribute: { + key: 'value', + scope: AttributeScope.SERVER_SCOPE + }, + putTimeSeries: { + key: 'value' + }, + valueToData: { + type: ValueToDataType.VALUE, + constantValue: 0, + valueToDataFunction: '/* Convert input double value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + title: '', + minValue: 0, + maxValue: 100, + initialValue: 50 +} + +export const prepareKnobSettings = (settings: KnobSettings): KnobSettings => { + if (isDefinedAndNotNull(settings.getValueMethod)) { + settings.initialState.executeRpc.method = settings.getValueMethod; + } + + if (isDefinedAndNotNull(settings.setValueMethod)) { + settings.valueChange.executeRpc.method = settings.setValueMethod; + } + + if (isDefinedAndNotNull(settings.requestPersistent)) { + settings.initialState.executeRpc.requestPersistent = settings.requestPersistent; + settings.valueChange.executeRpc.requestPersistent = settings.requestPersistent; + } + + if (isDefinedAndNotNull(settings.persistentPollingInterval)) { + settings.initialState.executeRpc.persistentPollingInterval = settings.persistentPollingInterval; + settings.valueChange.executeRpc.persistentPollingInterval = settings.persistentPollingInterval; + } + + if (isDefinedAndNotNull(settings.requestTimeout)) { + settings.initialState.executeRpc.requestTimeout = settings.requestTimeout; + settings.valueChange.executeRpc.requestTimeout = settings.requestTimeout; + } + return settings; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 59b81edb63..1c213674c6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7960,6 +7960,17 @@ "fill-area-opacity": "Fill area opacity", "range-chart-style": "Range chart style" }, + "knob": { + "behavior": "Behavior", + "initial-value": "Initial value", + "initial-value-hint": "Action to get the initial value of the knob.", + "on-value-change": "On value change", + "on-value-change-hint": "Action triggered when the knob value is changed.", + "range": "Range", + "min": "min", + "max": "max", + "fallback-initial-value": "Fallback initial value" + }, "rpc": { "value-settings": "Value settings", "initial-value": "Initial value", @@ -8015,10 +8026,7 @@ "led-status-value-attribute": "Device attribute containing led status value", "led-status-value-timeseries": "Device time series containing led status value", "check-status-method": "RPC check device status method", - "parse-led-status-value-function": "Parse led status value function", - "knob-title": "Knob title", - "min-value": "Minimum value", - "max-value": "Maximum value" + "parse-led-status-value-function": "Parse led status value function" }, "maps": { "map-type": {