49 changed files with 1718 additions and 1580 deletions
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sql; |
||||
|
|
||||
|
import org.thingsboard.server.dao.model.BaseEntity; |
||||
|
import org.thingsboard.server.dao.util.SqlDao; |
||||
|
|
||||
|
import javax.persistence.EntityManager; |
||||
|
import javax.persistence.PersistenceContext; |
||||
|
|
||||
|
@SqlDao |
||||
|
public abstract class JpaPartitionedAbstractDao<E extends BaseEntity<D>, D> extends JpaAbstractDao<E, D> { |
||||
|
|
||||
|
@PersistenceContext |
||||
|
private EntityManager entityManager; |
||||
|
|
||||
|
@Override |
||||
|
protected E doSave(E entity, boolean isNew) { |
||||
|
createPartition(entity); |
||||
|
if (isNew) { |
||||
|
entityManager.persist(entity); |
||||
|
} else { |
||||
|
entity = entityManager.merge(entity); |
||||
|
} |
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
public abstract void createPartition(E entity); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,659 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
AttributeData, |
||||
|
AttributeScope, |
||||
|
LatestTelemetry, |
||||
|
TelemetrySubscriber, |
||||
|
TelemetryType, |
||||
|
telemetryTypeTranslationsShort |
||||
|
} from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { WidgetContext } from '@home/models/widget-component.models'; |
||||
|
import { BehaviorSubject, forkJoin, Observable, Observer, of, throwError } from 'rxjs'; |
||||
|
import { catchError, delay, map, share, take } from 'rxjs/operators'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; |
||||
|
import { |
||||
|
DataToValueSettings, |
||||
|
DataToValueType, |
||||
|
GetAttributeValueSettings, |
||||
|
GetTelemetryValueSettings, |
||||
|
GetValueAction, |
||||
|
GetValueSettings, |
||||
|
RpcSettings, |
||||
|
SetAttributeValueSettings, |
||||
|
SetValueAction, |
||||
|
SetValueSettings, |
||||
|
TelemetryValueSettings, |
||||
|
ValueActionSettings, |
||||
|
ValueToDataSettings, |
||||
|
ValueToDataType |
||||
|
} from '@shared/models/action-widget-settings.models'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
|
||||
|
@Directive() |
||||
|
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
|
export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, AfterViewInit { |
||||
|
|
||||
|
@Input() |
||||
|
ctx: WidgetContext; |
||||
|
|
||||
|
@Input() |
||||
|
widgetTitlePanel: TemplateRef<any>; |
||||
|
|
||||
|
private loadingSubject = new BehaviorSubject(false); |
||||
|
private valueGetters: ValueGetter<any>[] = []; |
||||
|
private valueActions: ValueAction[] = []; |
||||
|
|
||||
|
loading$ = this.loadingSubject.asObservable().pipe(share()); |
||||
|
|
||||
|
error = ''; |
||||
|
|
||||
|
protected constructor(protected cd: ChangeDetectorRef) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.ctx.$scope.actionWidget = this; |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
const getValueObservables: Array<Observable<any>> = []; |
||||
|
this.valueGetters.forEach(valueGetter => { |
||||
|
getValueObservables.push(valueGetter.getValue()); |
||||
|
}); |
||||
|
this.loadingSubject.next(true); |
||||
|
forkJoin(getValueObservables).subscribe( |
||||
|
{ |
||||
|
next: () => { |
||||
|
this.loadingSubject.next(false); |
||||
|
}, |
||||
|
error: () => { |
||||
|
this.loadingSubject.next(false); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
this.valueActions.forEach(v => v.destroy()); |
||||
|
} |
||||
|
|
||||
|
public onInit() { |
||||
|
} |
||||
|
|
||||
|
public clearError() { |
||||
|
this.error = ''; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
protected createValueGetter<V>(getValueSettings: GetValueSettings<V>, |
||||
|
valueType: ValueType, |
||||
|
valueObserver?: Partial<Observer<V>>): ValueGetter<V> { |
||||
|
const observer: Partial<Observer<V>> = { |
||||
|
next: (value: V) => { |
||||
|
if (valueObserver?.next) { |
||||
|
valueObserver.next(value); |
||||
|
} |
||||
|
}, |
||||
|
error: (err: any) => { |
||||
|
this.onError(err); |
||||
|
if (valueObserver?.error) { |
||||
|
valueObserver.error(err); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
const valueGetter = ValueGetter.fromSettings(this.ctx, getValueSettings, valueType, observer); |
||||
|
this.valueGetters.push(valueGetter); |
||||
|
this.valueActions.push(valueGetter); |
||||
|
return valueGetter; |
||||
|
} |
||||
|
|
||||
|
protected createValueSetter<V>(setValueSettings: SetValueSettings): ValueSetter<V> { |
||||
|
const valueSetter = ValueSetter.fromSettings<V>(this.ctx, setValueSettings); |
||||
|
this.valueActions.push(valueSetter); |
||||
|
return valueSetter; |
||||
|
} |
||||
|
|
||||
|
private onError(error: string) { |
||||
|
this.error = error; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
protected updateValue<V>(valueSetter: ValueSetter<V>, |
||||
|
value: V, |
||||
|
setValueObserver?: Partial<Observer<void>>): void { |
||||
|
this.clearError(); |
||||
|
this.loadingSubject.next(true); |
||||
|
valueSetter.setValue(value).subscribe({ |
||||
|
next: () => { |
||||
|
if (setValueObserver?.next) { |
||||
|
setValueObserver.next(); |
||||
|
} |
||||
|
this.loadingSubject.next(false); |
||||
|
}, |
||||
|
error: (err) => { |
||||
|
this.loadingSubject.next(false); |
||||
|
if (setValueObserver?.error) { |
||||
|
setValueObserver.error(err); |
||||
|
} |
||||
|
const message = parseError(this.ctx, err); |
||||
|
this.onError(message); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
type DataToValueFunction<V> = (data: any) => V; |
||||
|
|
||||
|
export class DataToValueConverter<V> { |
||||
|
|
||||
|
private readonly dataToValueFunction: DataToValueFunction<V>; |
||||
|
private readonly compareToValue: any; |
||||
|
|
||||
|
constructor(private settings: DataToValueSettings, |
||||
|
private valueType: ValueType) { |
||||
|
this.compareToValue = settings.compareToValue; |
||||
|
switch (settings.type) { |
||||
|
case DataToValueType.FUNCTION: |
||||
|
try { |
||||
|
this.dataToValueFunction = new Function('data', settings.dataToValueFunction) as DataToValueFunction<V>; |
||||
|
} catch (e) { |
||||
|
this.dataToValueFunction = (data) => data; |
||||
|
} |
||||
|
break; |
||||
|
case DataToValueType.NONE: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
dataToValue(data: any): V { |
||||
|
let result: V; |
||||
|
switch (this.settings.type) { |
||||
|
case DataToValueType.FUNCTION: |
||||
|
result = data; |
||||
|
try { |
||||
|
result = this.dataToValueFunction(!!data ? JSON.parse(data) : data); |
||||
|
} catch (e) {} |
||||
|
break; |
||||
|
case DataToValueType.NONE: |
||||
|
result = data; |
||||
|
break; |
||||
|
} |
||||
|
if (this.valueType === ValueType.BOOLEAN) { |
||||
|
result = (result === this.compareToValue) as any; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export abstract class ValueAction { |
||||
|
|
||||
|
protected constructor(protected ctx: WidgetContext, |
||||
|
protected settings: ValueActionSettings) {} |
||||
|
|
||||
|
protected handleError(err: any): Error { |
||||
|
const reason = parseError(this.ctx, err); |
||||
|
let errorMessage = this.ctx.translate.instant('widgets.value-action.error.failed-to-perform-action', |
||||
|
{actionLabel: this.settings.actionLabel}); |
||||
|
if (reason) { |
||||
|
errorMessage += '<br>' + reason; |
||||
|
} |
||||
|
return new Error(errorMessage); |
||||
|
} |
||||
|
|
||||
|
destroy(): void {} |
||||
|
} |
||||
|
|
||||
|
export abstract class ValueGetter<V> extends ValueAction { |
||||
|
|
||||
|
static fromSettings<V>(ctx: WidgetContext, |
||||
|
settings: GetValueSettings<V>, |
||||
|
valueType: ValueType, |
||||
|
valueObserver: Partial<Observer<V>>): ValueGetter<V> { |
||||
|
switch (settings.action) { |
||||
|
case GetValueAction.DO_NOTHING: |
||||
|
return new DefaultValueGetter<V>(ctx, settings, valueType, valueObserver); |
||||
|
case GetValueAction.EXECUTE_RPC: |
||||
|
return new ExecuteRpcValueGetter<V>(ctx, settings, valueType, valueObserver); |
||||
|
case GetValueAction.GET_ATTRIBUTE: |
||||
|
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver); |
||||
|
case GetValueAction.GET_TIME_SERIES: |
||||
|
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly isSimulated: boolean; |
||||
|
private readonly dataConverter: DataToValueConverter<V>; |
||||
|
|
||||
|
protected constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings); |
||||
|
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; |
||||
|
if (this.settings.action !== GetValueAction.DO_NOTHING) { |
||||
|
this.dataConverter = new DataToValueConverter<V>(settings.dataToValue, valueType); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getValue(): Observable<V> { |
||||
|
const valueObservable: Observable<V> = (this.isSimulated ? of(null).pipe(delay(500)) : this.doGetValue()).pipe( |
||||
|
map((data) => { |
||||
|
if (this.dataConverter) { |
||||
|
return this.dataConverter.dataToValue(data); |
||||
|
} else { |
||||
|
return data; |
||||
|
} |
||||
|
}), |
||||
|
catchError(err => { |
||||
|
throw this.handleError(err); |
||||
|
}) |
||||
|
); |
||||
|
valueObservable.subscribe({ |
||||
|
next: (value) => { |
||||
|
this.valueObserver.next(value); |
||||
|
}, |
||||
|
error: (err) => { |
||||
|
this.valueObserver.error(err); |
||||
|
} |
||||
|
}); |
||||
|
return valueObservable.pipe( |
||||
|
take(1) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
destroy() { |
||||
|
super.destroy(); |
||||
|
} |
||||
|
|
||||
|
protected abstract doGetValue(): Observable<any>; |
||||
|
} |
||||
|
|
||||
|
type ValueToDataFunction<V> = (value: V) => any; |
||||
|
|
||||
|
export class ValueToDataConverter<V> { |
||||
|
|
||||
|
private readonly constantValue: any; |
||||
|
private readonly valueToDataFunction: ValueToDataFunction<V>; |
||||
|
|
||||
|
constructor(protected settings: ValueToDataSettings) { |
||||
|
switch (settings.type) { |
||||
|
case ValueToDataType.CONSTANT: |
||||
|
this.constantValue = this.settings.constantValue; |
||||
|
break; |
||||
|
case ValueToDataType.FUNCTION: |
||||
|
try { |
||||
|
this.valueToDataFunction = new Function('value', settings.valueToDataFunction) as ValueToDataFunction<V>; |
||||
|
} catch (e) { |
||||
|
this.valueToDataFunction = (data) => data; |
||||
|
} |
||||
|
break; |
||||
|
case ValueToDataType.NONE: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
valueToData(value: V): any { |
||||
|
switch (this.settings.type) { |
||||
|
case ValueToDataType.CONSTANT: |
||||
|
return this.constantValue; |
||||
|
case ValueToDataType.FUNCTION: |
||||
|
let result = value; |
||||
|
try { |
||||
|
result = this.valueToDataFunction(value); |
||||
|
} catch (e) {} |
||||
|
return result; |
||||
|
case ValueToDataType.NONE: |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export abstract class ValueSetter<V> extends ValueAction { |
||||
|
|
||||
|
static fromSettings<V>(ctx: WidgetContext, |
||||
|
settings: SetValueSettings): ValueSetter<V> { |
||||
|
switch (settings.action) { |
||||
|
case SetValueAction.EXECUTE_RPC: |
||||
|
return new ExecuteRpcValueSetter<V>(ctx, settings); |
||||
|
case SetValueAction.SET_ATTRIBUTE: |
||||
|
return new AttributeValueSetter<V>(ctx, settings); |
||||
|
case SetValueAction.ADD_TIME_SERIES: |
||||
|
return new TimeSeriesValueSetter<V>(ctx, settings); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly isSimulated: boolean; |
||||
|
private readonly valueToDataConverter: ValueToDataConverter<V>; |
||||
|
|
||||
|
protected constructor(protected ctx: WidgetContext, |
||||
|
protected settings: SetValueSettings) { |
||||
|
super(ctx, settings); |
||||
|
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; |
||||
|
this.valueToDataConverter = new ValueToDataConverter<V>(settings.valueToData); |
||||
|
} |
||||
|
|
||||
|
setValue(value: V): Observable<any> { |
||||
|
if (this.isSimulated) { |
||||
|
return of(null).pipe(delay(500)); |
||||
|
} else { |
||||
|
return this.doSetValue(this.valueToDataConverter.valueToData(value)).pipe( |
||||
|
catchError(err => { |
||||
|
throw this.handleError(err); |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract doSetValue(data: any): Observable<any>; |
||||
|
} |
||||
|
|
||||
|
export class DefaultValueGetter<V> extends ValueGetter<V> { |
||||
|
|
||||
|
private readonly defaultValue: V; |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings, valueType, valueObserver); |
||||
|
this.defaultValue = settings.defaultValue; |
||||
|
} |
||||
|
|
||||
|
protected doGetValue(): Observable<V> { |
||||
|
return of(this.defaultValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class ExecuteRpcValueGetter<V> extends ValueGetter<V> { |
||||
|
|
||||
|
private readonly executeRpcSettings: RpcSettings; |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings, valueType, valueObserver); |
||||
|
this.executeRpcSettings = settings.executeRpc; |
||||
|
} |
||||
|
|
||||
|
protected doGetValue(): Observable<V> { |
||||
|
return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null, |
||||
|
this.executeRpcSettings.requestTimeout, |
||||
|
this.executeRpcSettings.requestPersistent, |
||||
|
this.executeRpcSettings.persistentPollingInterval).pipe( |
||||
|
catchError((err) => { |
||||
|
throw handleRpcError(this.ctx, err); |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export abstract class TelemetryValueGetter<V, S extends GetTelemetryValueSettings> extends ValueGetter<V> { |
||||
|
|
||||
|
protected targetEntityId: EntityId; |
||||
|
private telemetrySubscriber: TelemetrySubscriber; |
||||
|
|
||||
|
protected constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings, valueType, valueObserver); |
||||
|
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); |
||||
|
this.targetEntityId = entityInfo?.entityId; |
||||
|
} |
||||
|
|
||||
|
protected doGetValue(): Observable<V> { |
||||
|
if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) { |
||||
|
return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set'))); |
||||
|
} |
||||
|
if (this.targetEntityId) { |
||||
|
const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope()); |
||||
|
if (err) { |
||||
|
return throwError(() => err); |
||||
|
} |
||||
|
if (this.getTelemetryValueSettings().subscribeForUpdates) { |
||||
|
return this.subscribeForTelemetryValue(); |
||||
|
} else { |
||||
|
return this.doGetTelemetryValue(); |
||||
|
} |
||||
|
} else { |
||||
|
return of(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private subscribeForTelemetryValue(): Observable<V> { |
||||
|
this.telemetrySubscriber = |
||||
|
TelemetrySubscriber.createEntityAttributesSubscription(this.ctx.telemetryWsService, this.targetEntityId, |
||||
|
this.scope(), this.ctx.ngZone, [this.getTelemetryValueSettings().key]); |
||||
|
this.telemetrySubscriber.subscribe(); |
||||
|
return this.telemetrySubscriber.attributeData$().pipe( |
||||
|
map((data) => { |
||||
|
let value: V = null; |
||||
|
const entry = data.find(attr => attr.key === this.getTelemetryValueSettings().key); |
||||
|
if (entry) { |
||||
|
value = entry.value; |
||||
|
try { |
||||
|
value = JSON.parse(entry.value); |
||||
|
} catch (_e) {} |
||||
|
} |
||||
|
return value; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
protected scope(): TelemetryType { |
||||
|
return LatestTelemetry.LATEST_TELEMETRY; |
||||
|
} |
||||
|
|
||||
|
protected abstract getTelemetryValueSettings(): S; |
||||
|
|
||||
|
protected abstract doGetTelemetryValue(): Observable<V>; |
||||
|
|
||||
|
destroy() { |
||||
|
if (this.telemetrySubscriber) { |
||||
|
this.telemetrySubscriber.unsubscribe(); |
||||
|
this.telemetrySubscriber = null; |
||||
|
} |
||||
|
super.destroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class AttributeValueGetter<V> extends TelemetryValueGetter<V, GetAttributeValueSettings> { |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings, valueType, valueObserver); |
||||
|
} |
||||
|
|
||||
|
protected getTelemetryValueSettings(): GetAttributeValueSettings { |
||||
|
return this.settings.getAttribute; |
||||
|
} |
||||
|
|
||||
|
protected scope(): TelemetryType { |
||||
|
return this.getTelemetryValueSettings().scope; |
||||
|
} |
||||
|
|
||||
|
protected doGetTelemetryValue(): Observable<V> { |
||||
|
const getAttributeValueSettings = this.getTelemetryValueSettings(); |
||||
|
return this.ctx.attributeService.getEntityAttributes(this.targetEntityId, |
||||
|
getAttributeValueSettings.scope, [getAttributeValueSettings.key], {ignoreLoading: true, ignoreErrors: true}).pipe( |
||||
|
map((data) => data.find(attr => attr.key === getAttributeValueSettings.key)?.value) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, GetTelemetryValueSettings> { |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: GetValueSettings<V>, |
||||
|
protected valueType: ValueType, |
||||
|
protected valueObserver: Partial<Observer<V>>) { |
||||
|
super(ctx, settings, valueType, valueObserver); |
||||
|
} |
||||
|
|
||||
|
protected getTelemetryValueSettings(): GetTelemetryValueSettings { |
||||
|
return this.settings.getTimeSeries; |
||||
|
} |
||||
|
|
||||
|
protected doGetTelemetryValue(): Observable<V> { |
||||
|
const getTelemetryValueSettings = this.getTelemetryValueSettings(); |
||||
|
return this.ctx.attributeService.getEntityTimeseriesLatest(this.targetEntityId, |
||||
|
[getTelemetryValueSettings.key], true, {ignoreLoading: true, ignoreErrors: true}) |
||||
|
.pipe( |
||||
|
map((data) => { |
||||
|
let value: any = null; |
||||
|
if (data[getTelemetryValueSettings.key]) { |
||||
|
const dataSet = data[getTelemetryValueSettings.key]; |
||||
|
if (dataSet.length) { |
||||
|
value = dataSet[0].value; |
||||
|
} |
||||
|
} |
||||
|
return value; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export class ExecuteRpcValueSetter<V> extends ValueSetter<V> { |
||||
|
|
||||
|
private readonly executeRpcSettings: RpcSettings; |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: SetValueSettings) { |
||||
|
super(ctx, settings); |
||||
|
this.executeRpcSettings = settings.executeRpc; |
||||
|
} |
||||
|
|
||||
|
protected doSetValue(data: any): Observable<any> { |
||||
|
return this.ctx.controlApi.sendOneWayCommand(this.executeRpcSettings.method, data, |
||||
|
this.executeRpcSettings.requestTimeout, |
||||
|
this.executeRpcSettings.requestPersistent, |
||||
|
this.executeRpcSettings.persistentPollingInterval).pipe( |
||||
|
catchError((err) => { |
||||
|
throw handleRpcError(this.ctx, err); |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export abstract class TelemetryValueSetter<V> extends ValueSetter<V> { |
||||
|
|
||||
|
protected targetEntityId: EntityId; |
||||
|
|
||||
|
protected constructor(protected ctx: WidgetContext, |
||||
|
protected settings: SetValueSettings) { |
||||
|
super(ctx, settings); |
||||
|
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); |
||||
|
this.targetEntityId = entityInfo?.entityId; |
||||
|
} |
||||
|
|
||||
|
protected doSetValue(data: any): Observable<V> { |
||||
|
if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) { |
||||
|
return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set'))); |
||||
|
} |
||||
|
if (this.targetEntityId) { |
||||
|
const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope()); |
||||
|
if (err) { |
||||
|
return throwError(() => err); |
||||
|
} |
||||
|
return this.doSetTelemetryValue(data); |
||||
|
} else { |
||||
|
return of(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected scope(): TelemetryType { |
||||
|
return LatestTelemetry.LATEST_TELEMETRY; |
||||
|
} |
||||
|
|
||||
|
protected abstract doSetTelemetryValue(data: any): Observable<V>; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export class AttributeValueSetter<V> extends TelemetryValueSetter<V> { |
||||
|
|
||||
|
private readonly setAttributeValueSettings: SetAttributeValueSettings; |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: SetValueSettings) { |
||||
|
super(ctx, settings); |
||||
|
this.setAttributeValueSettings = settings.setAttribute; |
||||
|
} |
||||
|
|
||||
|
protected doSetTelemetryValue(data: any): Observable<any> { |
||||
|
const attributes: Array<AttributeData> = [{key: this.setAttributeValueSettings.key, value: data}]; |
||||
|
return this.ctx.attributeService.saveEntityAttributes(this.targetEntityId, |
||||
|
this.setAttributeValueSettings.scope, attributes, {ignoreLoading: true, ignoreErrors: true}); |
||||
|
} |
||||
|
|
||||
|
protected scope(): TelemetryType { |
||||
|
return this.setAttributeValueSettings.scope; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export class TimeSeriesValueSetter<V> extends TelemetryValueSetter<V> { |
||||
|
|
||||
|
private readonly putTimeSeriesValueSettings: TelemetryValueSettings; |
||||
|
|
||||
|
constructor(protected ctx: WidgetContext, |
||||
|
protected settings: SetValueSettings) { |
||||
|
super(ctx, settings); |
||||
|
this.putTimeSeriesValueSettings = settings.putTimeSeries; |
||||
|
} |
||||
|
|
||||
|
protected doSetTelemetryValue(data: any): Observable<any> { |
||||
|
const timeSeries: Array<AttributeData> = [{key: this.putTimeSeriesValueSettings.key, value: data}]; |
||||
|
return this.ctx.attributeService.saveEntityTimeseries(this.targetEntityId, |
||||
|
LatestTelemetry.LATEST_TELEMETRY, timeSeries, {ignoreLoading: true, ignoreErrors: true}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
const parseError = (ctx: WidgetContext, err: any): string => |
||||
|
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error'; |
||||
|
|
||||
|
const handleRpcError = (ctx: WidgetContext, err: any): Error => { |
||||
|
let reason: string; |
||||
|
if (ctx.defaultSubscription.rpcErrorText) { |
||||
|
reason = ctx.defaultSubscription.rpcErrorText; |
||||
|
} else { |
||||
|
reason = parseError(ctx, err); |
||||
|
} |
||||
|
return new Error(reason); |
||||
|
}; |
||||
|
|
||||
|
const validateAttributeScope = (ctx: WidgetContext, targetEntityId: EntityId, scope?: TelemetryType): Error | null => { |
||||
|
if (targetEntityId.entityType !== EntityType.DEVICE && scope && |
||||
|
![AttributeScope.SERVER_SCOPE, LatestTelemetry.LATEST_TELEMETRY].includes(scope)) { |
||||
|
const scopeStr = ctx.translate.instant(telemetryTypeTranslationsShort.get(scope)); |
||||
|
const entityType = |
||||
|
ctx.translate.instant(entityTypeTranslations.get(targetEntityId.entityType).type); |
||||
|
const errorMessage = |
||||
|
ctx.translate.instant('widgets.value-action.error.invalid-attribute-scope', {scope: scopeStr, entityType}); |
||||
|
return new Error(errorMessage); |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
}; |
||||
@ -1,626 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { |
|
||||
AttributeData, |
|
||||
AttributeScope, |
|
||||
LatestTelemetry, |
|
||||
telemetryTypeTranslationsShort |
|
||||
} from '@shared/models/telemetry/telemetry.models'; |
|
||||
import { WidgetContext } from '@home/models/widget-component.models'; |
|
||||
import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; |
|
||||
import { catchError, delay, map, share } from 'rxjs/operators'; |
|
||||
import { UtilsService } from '@core/services/utils.service'; |
|
||||
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnInit, TemplateRef } from '@angular/core'; |
|
||||
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; |
|
||||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|
||||
import { DomSanitizer } from '@angular/platform-browser'; |
|
||||
import { |
|
||||
RpcActionSettings, |
|
||||
RpcGetAttributeSettings, |
|
||||
RpcSetAttributeSettings, |
|
||||
RpcSettings, |
|
||||
RpcStateToParamsSettings, |
|
||||
RpcStateToParamsType, |
|
||||
RpcStateWidgetSettings, |
|
||||
RpcTelemetrySettings, |
|
||||
RpcUpdateStateAction |
|
||||
} from '@shared/models/rpc-widget-settings.models'; |
|
||||
import { |
|
||||
RpcDataToStateSettings, |
|
||||
RpcDataToStateType, |
|
||||
RpcInitialStateAction, |
|
||||
RpcInitialStateSettings, |
|
||||
RpcStateBehaviourSettings, |
|
||||
RpcUpdateStateSettings |
|
||||
} from '@app/shared/models/rpc-widget-settings.models'; |
|
||||
import { ValueType } from '@shared/models/constants'; |
|
||||
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|
||||
|
|
||||
@Directive() |
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
||||
export abstract class BasicRpcStateWidgetComponent<V, S extends RpcStateWidgetSettings<V>> implements OnInit, AfterViewInit { |
|
||||
|
|
||||
@Input() |
|
||||
ctx: WidgetContext; |
|
||||
|
|
||||
@Input() |
|
||||
widgetTitlePanel: TemplateRef<any>; |
|
||||
|
|
||||
settings: S; |
|
||||
|
|
||||
behaviorApi: RpcStateBehaviorApi<V>; |
|
||||
loading$: Observable<boolean>; |
|
||||
|
|
||||
backgroundStyle$: Observable<ComponentStyle>; |
|
||||
overlayStyle: ComponentStyle = {}; |
|
||||
|
|
||||
value: V; |
|
||||
|
|
||||
error = ''; |
|
||||
|
|
||||
protected constructor(protected imagePipe: ImagePipe, |
|
||||
protected sanitizer: DomSanitizer, |
|
||||
protected cd: ChangeDetectorRef) { |
|
||||
} |
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
this.ctx.$scope.rpcWidget = this; |
|
||||
this.settings = {...this.defaultSettings(), ...this.ctx.settings}; |
|
||||
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
|
||||
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
|
||||
|
|
||||
const behaviourSettings: RpcStateBehaviourSettings<V> = { |
|
||||
initialState: this.initialState(), |
|
||||
updateStateByValue: val => this.getUpdateStateSettingsForValue(val) |
|
||||
}; |
|
||||
|
|
||||
const callbacks: RpcStateCallbacks<V> = { |
|
||||
onStateValue: val => this.setValue(val), |
|
||||
onError: err => this.onError(err), |
|
||||
validateStateValue: val => this.validateValue(val) |
|
||||
}; |
|
||||
|
|
||||
this.behaviorApi = new RpcStateBehaviorApi<V>(this.defaultValue(), this.ctx, |
|
||||
behaviourSettings, callbacks, this.stateValueType()); |
|
||||
this.loading$ = this.behaviorApi.loading$.pipe(share()); |
|
||||
} |
|
||||
|
|
||||
ngAfterViewInit(): void { |
|
||||
this.behaviorApi.initState(); |
|
||||
} |
|
||||
|
|
||||
public onInit() { |
|
||||
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|
||||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
|
||||
this.cd.detectChanges(); |
|
||||
} |
|
||||
|
|
||||
public clearError() { |
|
||||
this.error = ''; |
|
||||
} |
|
||||
|
|
||||
public updateValue() { |
|
||||
this.behaviorApi.updateState(this.value); |
|
||||
} |
|
||||
|
|
||||
private onError(error: string) { |
|
||||
this.error = error; |
|
||||
this.ctx.detectChanges(); |
|
||||
} |
|
||||
|
|
||||
protected abstract defaultSettings(): S; |
|
||||
|
|
||||
protected abstract initialState(): RpcInitialStateSettings<V>; |
|
||||
|
|
||||
protected abstract getUpdateStateSettingsForValue(value: V): RpcUpdateStateSettings; |
|
||||
|
|
||||
protected stateValueType(): ValueType { |
|
||||
return ValueType.BOOLEAN; |
|
||||
} |
|
||||
|
|
||||
protected defaultValue(): V { |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
protected setValue(value: V) { |
|
||||
this.value = value; |
|
||||
} |
|
||||
|
|
||||
protected validateValue(value: any): V { |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
export abstract class RpcHasLoading { |
|
||||
|
|
||||
public get loading$() { |
|
||||
return this.loadingSubject.asObservable(); |
|
||||
} |
|
||||
|
|
||||
protected loadingSubject = new BehaviorSubject(false); |
|
||||
} |
|
||||
|
|
||||
export interface RpcStateCallbacks<V> { |
|
||||
onStateValue: (value: V) => void; |
|
||||
validateStateValue: (value: any) => V; |
|
||||
onError: (error: string) => void; |
|
||||
} |
|
||||
|
|
||||
export class RpcStateBehaviorApi<V> extends RpcHasLoading { |
|
||||
|
|
||||
private readonly initialStateGetter: RpcInitialStateGetter<V>; |
|
||||
private readonly stateUpdatersMap: Map<RpcUpdateStateSettings, RpcStateUpdater<V>>; |
|
||||
|
|
||||
constructor(private state: V, |
|
||||
private ctx: WidgetContext, |
|
||||
private settings: RpcStateBehaviourSettings<V>, |
|
||||
private callbacks: RpcStateCallbacks<V>, |
|
||||
stateValueType: ValueType) { |
|
||||
super(); |
|
||||
this.initialStateGetter = RpcInitialStateGetter.fromSettings(ctx, settings.initialState, stateValueType, callbacks); |
|
||||
this.stateUpdatersMap = new Map<RpcUpdateStateSettings, RpcStateUpdater<V>>(); |
|
||||
} |
|
||||
|
|
||||
initState() { |
|
||||
if (this.ctx.defaultSubscription.targetEntityId || this.ctx.defaultSubscription.rpcEnabled) { |
|
||||
this.loadingSubject.next(true); |
|
||||
this.initialStateGetter.initState().subscribe( |
|
||||
{ |
|
||||
next: (value) => { |
|
||||
this.state = value; |
|
||||
this.loadingSubject.next(false); |
|
||||
this.callbacks.onStateValue(value); |
|
||||
this.ctx.detectChanges(); |
|
||||
}, |
|
||||
error: (err: any) => { |
|
||||
this.loadingSubject.next(false); |
|
||||
const message = parseError(this.ctx, err); |
|
||||
this.callbacks.onError(message); |
|
||||
} |
|
||||
} |
|
||||
); |
|
||||
} else { |
|
||||
this.callbacks.onError(this.ctx.translate.instant('widgets.rpc-state.error.target-entity-is-not-set')); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
updateState(value: V) { |
|
||||
this.callbacks.onError(null); |
|
||||
let updater: RpcStateUpdater<V>; |
|
||||
const updateStateSettings = this.settings.updateStateByValue(value); |
|
||||
if (updateStateSettings) { |
|
||||
updater = this.stateUpdatersMap.get(updateStateSettings); |
|
||||
if (!updater) { |
|
||||
updater = RpcStateUpdater.fromSettings(this.ctx, updateStateSettings); |
|
||||
this.stateUpdatersMap.set(updateStateSettings, updater); |
|
||||
} |
|
||||
} |
|
||||
if (updater) { |
|
||||
this.loadingSubject.next(true); |
|
||||
updater.updateState(value).subscribe( |
|
||||
{ |
|
||||
next: () => { |
|
||||
this.state = value; |
|
||||
this.loadingSubject.next(false); |
|
||||
this.ctx.detectChanges(); |
|
||||
}, |
|
||||
error: (err: any) => { |
|
||||
this.loadingSubject.next(false); |
|
||||
const message = parseError(this.ctx, err); |
|
||||
this.callbacks.onStateValue(this.state); |
|
||||
this.callbacks.onError(message); |
|
||||
this.ctx.detectChanges(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
type RpcDataToStateFunction<V> = (data: any) => V; |
|
||||
|
|
||||
export class RpcDataToStateConverter<V> { |
|
||||
|
|
||||
private readonly dataToStateFunction: RpcDataToStateFunction<V>; |
|
||||
private readonly compareToValue: any; |
|
||||
|
|
||||
constructor(private settings: RpcDataToStateSettings, |
|
||||
private stateValueType: ValueType, |
|
||||
private callbacks: RpcStateCallbacks<V>) { |
|
||||
this.compareToValue = settings.compareToValue; |
|
||||
switch (settings.type) { |
|
||||
case RpcDataToStateType.FUNCTION: |
|
||||
try { |
|
||||
this.dataToStateFunction = new Function('data', settings.dataToStateFunction) as RpcDataToStateFunction<V>; |
|
||||
} catch (e) { |
|
||||
this.dataToStateFunction = (data) => data; |
|
||||
} |
|
||||
break; |
|
||||
case RpcDataToStateType.NONE: |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
dataToStateValue(data: any): V { |
|
||||
let result: V; |
|
||||
switch (this.settings.type) { |
|
||||
case RpcDataToStateType.FUNCTION: |
|
||||
result = data; |
|
||||
try { |
|
||||
result = this.dataToStateFunction(!!data ? JSON.parse(data) : data); |
|
||||
} catch (e) {} |
|
||||
break; |
|
||||
case RpcDataToStateType.NONE: |
|
||||
result = data; |
|
||||
break; |
|
||||
} |
|
||||
if (this.stateValueType === ValueType.BOOLEAN) { |
|
||||
result = (result === this.compareToValue) as any; |
|
||||
} |
|
||||
result = this.callbacks.validateStateValue(result); |
|
||||
return result; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export abstract class RpcAction { |
|
||||
|
|
||||
protected constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcActionSettings) {} |
|
||||
|
|
||||
handleError(err: any): Error { |
|
||||
const reason = parseError(this.ctx, err); |
|
||||
let errorMessage = this.ctx.translate.instant('widgets.rpc-state.error.failed-to-perform-action', |
|
||||
{actionLabel: this.settings.actionLabel}); |
|
||||
if (reason) { |
|
||||
errorMessage += '<br>' + reason; |
|
||||
} |
|
||||
return new Error(errorMessage); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export abstract class RpcInitialStateGetter<V> extends RpcAction { |
|
||||
|
|
||||
static fromSettings<V>(ctx: WidgetContext, |
|
||||
settings: RpcInitialStateSettings<V>, |
|
||||
stateValueType: ValueType, |
|
||||
callbacks: RpcStateCallbacks<V>): RpcInitialStateGetter<V> { |
|
||||
switch (settings.action) { |
|
||||
case RpcInitialStateAction.DO_NOTHING: |
|
||||
return new RpcDefaultStateGetter<V>(ctx, settings, stateValueType, callbacks); |
|
||||
case RpcInitialStateAction.EXECUTE_RPC: |
|
||||
return new ExecuteRpcStateGetter<V>(ctx, settings, stateValueType, callbacks); |
|
||||
case RpcInitialStateAction.GET_ATTRIBUTE: |
|
||||
return new RpcAttributeStateGetter<V>(ctx, settings, stateValueType, callbacks); |
|
||||
case RpcInitialStateAction.GET_TIME_SERIES: |
|
||||
return new RpcTimeSeriesStateGetter<V>(ctx, settings, stateValueType, callbacks); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private readonly isSimulated: boolean; |
|
||||
private readonly dataConverter: RpcDataToStateConverter<V>; |
|
||||
|
|
||||
protected constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcInitialStateSettings<V>, |
|
||||
protected stateValueType: ValueType, |
|
||||
protected callbacks: RpcStateCallbacks<V>) { |
|
||||
super(ctx, settings); |
|
||||
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; |
|
||||
if (this.settings.action !== RpcInitialStateAction.DO_NOTHING) { |
|
||||
this.dataConverter = new RpcDataToStateConverter<V>(settings.dataToState, stateValueType, this.callbacks); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
initState(): Observable<V> { |
|
||||
const stateObservable: Observable<V> = this.isSimulated ? of(null).pipe(delay(500)) : this.doGetState(); |
|
||||
return stateObservable.pipe( |
|
||||
map((data) => { |
|
||||
if (this.dataConverter) { |
|
||||
return this.dataConverter.dataToStateValue(data); |
|
||||
} else { |
|
||||
return data; |
|
||||
} |
|
||||
}), |
|
||||
catchError(err => { |
|
||||
throw this.handleError(err); |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
protected abstract doGetState(): Observable<any>; |
|
||||
} |
|
||||
|
|
||||
type RpcStateToParamsFunction<V> = (state: V) => any; |
|
||||
|
|
||||
export class RpcStateToParamsConverter<V> { |
|
||||
|
|
||||
private readonly constantValue: any; |
|
||||
private readonly stateToParamsFunction: RpcStateToParamsFunction<V>; |
|
||||
|
|
||||
constructor(protected settings: RpcStateToParamsSettings) { |
|
||||
switch (settings.type) { |
|
||||
case RpcStateToParamsType.CONSTANT: |
|
||||
this.constantValue = this.settings.constantValue; |
|
||||
break; |
|
||||
case RpcStateToParamsType.FUNCTION: |
|
||||
try { |
|
||||
this.stateToParamsFunction = new Function('value', settings.stateToParamsFunction) as RpcStateToParamsFunction<V>; |
|
||||
} catch (e) { |
|
||||
this.stateToParamsFunction = (data) => data; |
|
||||
} |
|
||||
break; |
|
||||
case RpcStateToParamsType.NONE: |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
stateToParams(state: V): any { |
|
||||
switch (this.settings.type) { |
|
||||
case RpcStateToParamsType.CONSTANT: |
|
||||
return this.constantValue; |
|
||||
case RpcStateToParamsType.FUNCTION: |
|
||||
let result = state; |
|
||||
try { |
|
||||
result = this.stateToParamsFunction(state); |
|
||||
} catch (e) {} |
|
||||
return result; |
|
||||
case RpcStateToParamsType.NONE: |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export abstract class RpcStateUpdater<V> extends RpcAction { |
|
||||
|
|
||||
static fromSettings<V>(ctx: WidgetContext, |
|
||||
settings: RpcUpdateStateSettings): RpcStateUpdater<V> { |
|
||||
switch (settings.action) { |
|
||||
case RpcUpdateStateAction.EXECUTE_RPC: |
|
||||
return new ExecuteRpcStateUpdater<V>(ctx, settings); |
|
||||
case RpcUpdateStateAction.SET_ATTRIBUTE: |
|
||||
return new RpcAttributeStateUpdater<V>(ctx, settings); |
|
||||
case RpcUpdateStateAction.ADD_TIME_SERIES: |
|
||||
return new RpcTimeSeriesStateUpdater<V>(ctx, settings); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private readonly isSimulated: boolean; |
|
||||
private readonly paramsConverter: RpcStateToParamsConverter<V>; |
|
||||
|
|
||||
protected constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcUpdateStateSettings) { |
|
||||
super(ctx, settings); |
|
||||
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; |
|
||||
this.paramsConverter = new RpcStateToParamsConverter<V>(settings.stateToParams); |
|
||||
} |
|
||||
|
|
||||
updateState(state: V): Observable<any> { |
|
||||
if (this.isSimulated) { |
|
||||
return of(null).pipe(delay(500)); |
|
||||
} else { |
|
||||
return this.doUpdateState(this.paramsConverter.stateToParams(state)).pipe( |
|
||||
catchError(err => { |
|
||||
throw this.handleError(err); |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected abstract doUpdateState(params: any): Observable<any>; |
|
||||
} |
|
||||
|
|
||||
export class RpcDefaultStateGetter<V> extends RpcInitialStateGetter<V> { |
|
||||
|
|
||||
private readonly defaultValue: V; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcInitialStateSettings<V>, |
|
||||
protected stateValueType: ValueType, |
|
||||
protected callbacks: RpcStateCallbacks<V>) { |
|
||||
super(ctx, settings, stateValueType, callbacks); |
|
||||
this.defaultValue = settings.defaultValue; |
|
||||
} |
|
||||
|
|
||||
protected doGetState(): Observable<V> { |
|
||||
return of(this.defaultValue); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export class ExecuteRpcStateGetter<V> extends RpcInitialStateGetter<V> { |
|
||||
|
|
||||
private readonly executeRpcSettings: RpcSettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcInitialStateSettings<V>, |
|
||||
protected stateValueType: ValueType, |
|
||||
protected callbacks: RpcStateCallbacks<V>) { |
|
||||
super(ctx, settings, stateValueType, callbacks); |
|
||||
this.executeRpcSettings = settings.executeRpc; |
|
||||
} |
|
||||
|
|
||||
protected doGetState(): Observable<V> { |
|
||||
return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null, |
|
||||
this.executeRpcSettings.requestTimeout, |
|
||||
this.executeRpcSettings.requestPersistent, |
|
||||
this.executeRpcSettings.persistentPollingInterval).pipe( |
|
||||
catchError((err) => { |
|
||||
throw handleRpcError(this.ctx, err); |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export class RpcAttributeStateGetter<V> extends RpcInitialStateGetter<V> { |
|
||||
|
|
||||
private readonly getAttributeSettings: RpcGetAttributeSettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcInitialStateSettings<V>, |
|
||||
protected stateValueType: ValueType, |
|
||||
protected callbacks: RpcStateCallbacks<V>) { |
|
||||
super(ctx, settings, stateValueType, callbacks); |
|
||||
this.getAttributeSettings = settings.getAttribute; |
|
||||
} |
|
||||
|
|
||||
protected doGetState(): Observable<V> { |
|
||||
if (this.ctx.defaultSubscription.targetEntityId) { |
|
||||
const err = validateAttributeScope(this.ctx, this.getAttributeSettings.scope); |
|
||||
if (err) { |
|
||||
return throwError(() => err); |
|
||||
} |
|
||||
return this.ctx.attributeService.getEntityAttributes(this.ctx.defaultSubscription.targetEntityId, |
|
||||
this.getAttributeSettings.scope, [this.getAttributeSettings.key], {ignoreLoading: true, ignoreErrors: true}) |
|
||||
.pipe( |
|
||||
map((data) => data.find(attr => attr.key === this.getAttributeSettings.key)?.value) |
|
||||
); |
|
||||
} else { |
|
||||
return of(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
export class RpcTimeSeriesStateGetter<V> extends RpcInitialStateGetter<V> { |
|
||||
|
|
||||
private readonly getTimeSeriesSettings: RpcTelemetrySettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcInitialStateSettings<V>, |
|
||||
protected stateValueType: ValueType, |
|
||||
protected callbacks: RpcStateCallbacks<V>) { |
|
||||
super(ctx, settings, stateValueType, callbacks); |
|
||||
this.getTimeSeriesSettings = settings.getTimeSeries; |
|
||||
} |
|
||||
|
|
||||
protected doGetState(): Observable<V> { |
|
||||
if (this.ctx.defaultSubscription.targetEntityId) { |
|
||||
return this.ctx.attributeService.getEntityTimeseriesLatest(this.ctx.defaultSubscription.targetEntityId, |
|
||||
[this.getTimeSeriesSettings.key], true, {ignoreLoading: true, ignoreErrors: true}) |
|
||||
.pipe( |
|
||||
map((data) => { |
|
||||
let value: any = null; |
|
||||
if (data[this.getTimeSeriesSettings.key]) { |
|
||||
const dataSet = data[this.getTimeSeriesSettings.key]; |
|
||||
if (dataSet.length) { |
|
||||
value = dataSet[0].value; |
|
||||
} |
|
||||
} |
|
||||
return value; |
|
||||
}) |
|
||||
); |
|
||||
} else { |
|
||||
return of(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
export class ExecuteRpcStateUpdater<V> extends RpcStateUpdater<V> { |
|
||||
|
|
||||
private readonly executeRpcSettings: RpcSettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcUpdateStateSettings) { |
|
||||
super(ctx, settings); |
|
||||
this.executeRpcSettings = settings.executeRpc; |
|
||||
} |
|
||||
|
|
||||
protected doUpdateState(params: any): Observable<any> { |
|
||||
return this.ctx.controlApi.sendOneWayCommand(this.executeRpcSettings.method, params, |
|
||||
this.executeRpcSettings.requestTimeout, |
|
||||
this.executeRpcSettings.requestPersistent, |
|
||||
this.executeRpcSettings.persistentPollingInterval).pipe( |
|
||||
catchError((err) => { |
|
||||
throw handleRpcError(this.ctx, err); |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export class RpcAttributeStateUpdater<V> extends RpcStateUpdater<V> { |
|
||||
|
|
||||
private readonly setAttributeSettings: RpcSetAttributeSettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcUpdateStateSettings) { |
|
||||
super(ctx, settings); |
|
||||
this.setAttributeSettings = settings.setAttribute; |
|
||||
} |
|
||||
|
|
||||
protected doUpdateState(params: any): Observable<any> { |
|
||||
if (this.ctx.defaultSubscription.targetEntityId) { |
|
||||
const err = validateAttributeScope(this.ctx, this.setAttributeSettings.scope); |
|
||||
if (err) { |
|
||||
return throwError(() => err); |
|
||||
} |
|
||||
const attributes: Array<AttributeData> = [{key: this.setAttributeSettings.key, value: params}]; |
|
||||
return this.ctx.attributeService.saveEntityAttributes(this.ctx.defaultSubscription.targetEntityId, |
|
||||
this.setAttributeSettings.scope, attributes, {ignoreLoading: true, ignoreErrors: true}); |
|
||||
} else { |
|
||||
return of(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
export class RpcTimeSeriesStateUpdater<V> extends RpcStateUpdater<V> { |
|
||||
|
|
||||
private readonly putTimeSeriesSettings: RpcTelemetrySettings; |
|
||||
|
|
||||
constructor(protected ctx: WidgetContext, |
|
||||
protected settings: RpcUpdateStateSettings) { |
|
||||
super(ctx, settings); |
|
||||
this.putTimeSeriesSettings = settings.putTimeSeries; |
|
||||
} |
|
||||
|
|
||||
protected doUpdateState(params: any): Observable<any> { |
|
||||
if (this.ctx.defaultSubscription.targetEntityId) { |
|
||||
const timeSeries: Array<AttributeData> = [{key: this.putTimeSeriesSettings.key, value: params}]; |
|
||||
return this.ctx.attributeService.saveEntityTimeseries(this.ctx.defaultSubscription.targetEntityId, |
|
||||
LatestTelemetry.LATEST_TELEMETRY, timeSeries, {ignoreLoading: true, ignoreErrors: true}); |
|
||||
} else { |
|
||||
return of(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
const parseError = (ctx: WidgetContext, err: any): string => |
|
||||
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error'; |
|
||||
|
|
||||
const handleRpcError = (ctx: WidgetContext, err: any): Error => { |
|
||||
let reason: string; |
|
||||
if (ctx.defaultSubscription.rpcErrorText) { |
|
||||
reason = ctx.defaultSubscription.rpcErrorText; |
|
||||
} else { |
|
||||
reason = parseError(ctx, err); |
|
||||
} |
|
||||
return new Error(reason); |
|
||||
}; |
|
||||
|
|
||||
const validateAttributeScope = (ctx: WidgetContext, scope?: AttributeScope): Error | null => { |
|
||||
if (ctx.defaultSubscription.targetEntityId.entityType !== EntityType.DEVICE && scope && scope !== AttributeScope.SERVER_SCOPE) { |
|
||||
const scopeStr = ctx.translate.instant(telemetryTypeTranslationsShort.get(scope)); |
|
||||
const entityType = |
|
||||
ctx.translate.instant(entityTypeTranslations.get(ctx.defaultSubscription.targetEntityId.entityType).type); |
|
||||
const errorMessage = |
|
||||
ctx.translate.instant('widgets.rpc-state.error.invalid-attribute-scope', {scope: scopeStr, entityType}); |
|
||||
return new Error(errorMessage); |
|
||||
} else { |
|
||||
return null; |
|
||||
} |
|
||||
}; |
|
||||
@ -0,0 +1,182 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { merge } from 'rxjs'; |
||||
|
import { |
||||
|
DataToValueType, |
||||
|
GetValueAction, |
||||
|
getValueActions, |
||||
|
getValueActionTranslations, |
||||
|
GetValueSettings |
||||
|
} from '@shared/models/action-widget-settings.models'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { TargetDevice } from '@shared/models/widget.models'; |
||||
|
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { WidgetService } from '@core/http/widget.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-get-value-action-settings-panel', |
||||
|
templateUrl: './get-value-action-settings-panel.component.html', |
||||
|
providers: [], |
||||
|
styleUrls: ['./value-action-settings-panel.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class GetValueActionSettingsPanelComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
getValueSettings: GetValueSettings<any>; |
||||
|
|
||||
|
@Input() |
||||
|
panelTitle: string; |
||||
|
|
||||
|
@Input() |
||||
|
valueType: ValueType; |
||||
|
|
||||
|
@Input() |
||||
|
aliasController: IAliasController; |
||||
|
|
||||
|
@Input() |
||||
|
targetDevice: TargetDevice; |
||||
|
|
||||
|
@Input() |
||||
|
popover: TbPopoverComponent<GetValueActionSettingsPanelComponent>; |
||||
|
|
||||
|
@Output() |
||||
|
getValueSettingsApplied = new EventEmitter<GetValueSettings<any>>(); |
||||
|
|
||||
|
getValueAction = GetValueAction; |
||||
|
|
||||
|
getValueActions = getValueActions; |
||||
|
|
||||
|
getValueActionTranslationsMap = getValueActionTranslations; |
||||
|
|
||||
|
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort; |
||||
|
|
||||
|
attributeScopes = Object.keys(AttributeScope) as AttributeScope[]; |
||||
|
|
||||
|
dataKeyType = DataKeyType; |
||||
|
|
||||
|
dataToValueType = DataToValueType; |
||||
|
|
||||
|
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); |
||||
|
|
||||
|
ValueType = ValueType; |
||||
|
|
||||
|
getValueSettingsFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
constructor(private fb: UntypedFormBuilder, |
||||
|
private widgetService: WidgetService, |
||||
|
protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.getValueSettingsFormGroup = this.fb.group( |
||||
|
{ |
||||
|
action: [this.getValueSettings?.action, []], |
||||
|
defaultValue: [this.getValueSettings?.defaultValue, [Validators.required]], |
||||
|
executeRpc: this.fb.group({ |
||||
|
method: [this.getValueSettings?.executeRpc?.method, [Validators.required]], |
||||
|
requestTimeout: [this.getValueSettings?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]], |
||||
|
requestPersistent: [this.getValueSettings?.executeRpc?.requestPersistent, []], |
||||
|
persistentPollingInterval: |
||||
|
[this.getValueSettings?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]] |
||||
|
}), |
||||
|
getAttribute: this.fb.group({ |
||||
|
scope: [this.getValueSettings?.getAttribute?.scope, []], |
||||
|
key: [this.getValueSettings?.getAttribute?.key, [Validators.required]], |
||||
|
subscribeForUpdates: [this.getValueSettings?.getAttribute?.subscribeForUpdates, []] |
||||
|
}), |
||||
|
getTimeSeries: this.fb.group({ |
||||
|
key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]], |
||||
|
subscribeForUpdates: [this.getValueSettings?.getTimeSeries?.subscribeForUpdates, []] |
||||
|
}), |
||||
|
dataToValue: this.fb.group({ |
||||
|
type: [this.getValueSettings?.dataToValue?.type, [Validators.required]], |
||||
|
dataToValueFunction: [this.getValueSettings?.dataToValue?.dataToValueFunction, [Validators.required]], |
||||
|
}), |
||||
|
} |
||||
|
); |
||||
|
if (this.valueType === ValueType.BOOLEAN) { |
||||
|
(this.getValueSettingsFormGroup.get('dataToValue') as UntypedFormGroup).addControl( |
||||
|
'compareToValue', this.fb.control(this.getValueSettings?.dataToValue?.compareToValue, [Validators.required]) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
merge(this.getValueSettingsFormGroup.get('action').valueChanges, |
||||
|
this.getValueSettingsFormGroup.get('dataToValue').get('type').valueChanges, |
||||
|
this.getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => { |
||||
|
this.updateValidators(); |
||||
|
}); |
||||
|
this.updateValidators(); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.popover?.hide(); |
||||
|
} |
||||
|
|
||||
|
applyGetValueSettings() { |
||||
|
const getValueSettings: GetValueSettings<any> = this.getValueSettingsFormGroup.getRawValue(); |
||||
|
this.getValueSettingsApplied.emit(getValueSettings); |
||||
|
} |
||||
|
|
||||
|
private updateValidators() { |
||||
|
const action: GetValueAction = this.getValueSettingsFormGroup.get('action').value; |
||||
|
const dataToValueType: DataToValueType = this.getValueSettingsFormGroup.get('dataToValue').get('type').value; |
||||
|
|
||||
|
this.getValueSettingsFormGroup.get('defaultValue').disable({emitEvent: false}); |
||||
|
this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); |
||||
|
this.getValueSettingsFormGroup.get('getAttribute').disable({emitEvent: false}); |
||||
|
this.getValueSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false}); |
||||
|
switch (action) { |
||||
|
case GetValueAction.DO_NOTHING: |
||||
|
this.getValueSettingsFormGroup.get('defaultValue').enable({emitEvent: false}); |
||||
|
break; |
||||
|
case GetValueAction.EXECUTE_RPC: |
||||
|
this.getValueSettingsFormGroup.get('executeRpc').enable({emitEvent: false}); |
||||
|
const requestPersistent: boolean = this.getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value; |
||||
|
if (requestPersistent) { |
||||
|
this.getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false}); |
||||
|
} |
||||
|
break; |
||||
|
case GetValueAction.GET_ATTRIBUTE: |
||||
|
this.getValueSettingsFormGroup.get('getAttribute').enable({emitEvent: false}); |
||||
|
break; |
||||
|
case GetValueAction.GET_TIME_SERIES: |
||||
|
this.getValueSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false}); |
||||
|
break; |
||||
|
} |
||||
|
if (action === GetValueAction.DO_NOTHING) { |
||||
|
this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.getValueSettingsFormGroup.get('dataToValue').enable({emitEvent: false}); |
||||
|
if (dataToValueType === DataToValueType.FUNCTION) { |
||||
|
this.getValueSettingsFormGroup.get('dataToValue').get('dataToValueFunction').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.getValueSettingsFormGroup.get('dataToValue').get('dataToValueFunction').disable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,182 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { merge } from 'rxjs'; |
||||
|
import { |
||||
|
getValueActions, |
||||
|
SetValueAction, |
||||
|
setValueActions, |
||||
|
setValueActionTranslations, |
||||
|
SetValueSettings, |
||||
|
ValueToDataType |
||||
|
} from '@shared/models/action-widget-settings.models'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { TargetDevice } from '@shared/models/widget.models'; |
||||
|
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { WidgetService } from '@core/http/widget.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-set-value-action-settings-panel', |
||||
|
templateUrl: './set-value-action-settings-panel.component.html', |
||||
|
providers: [], |
||||
|
styleUrls: ['./value-action-settings-panel.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class SetValueActionSettingsPanelComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
panelTitle: string; |
||||
|
|
||||
|
@Input() |
||||
|
setValueSettings: SetValueSettings; |
||||
|
|
||||
|
@Input() |
||||
|
valueType: ValueType; |
||||
|
|
||||
|
@Input() |
||||
|
aliasController: IAliasController; |
||||
|
|
||||
|
@Input() |
||||
|
targetDevice: TargetDevice; |
||||
|
|
||||
|
@Input() |
||||
|
popover: TbPopoverComponent<SetValueActionSettingsPanelComponent>; |
||||
|
|
||||
|
@Output() |
||||
|
setValueSettingsApplied = new EventEmitter<SetValueSettings>(); |
||||
|
|
||||
|
setValueAction = SetValueAction; |
||||
|
|
||||
|
setValueActions = setValueActions; |
||||
|
|
||||
|
setValueActionTranslationsMap = setValueActionTranslations; |
||||
|
|
||||
|
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort; |
||||
|
|
||||
|
attributeScopes = [AttributeScope.SERVER_SCOPE, AttributeScope.SHARED_SCOPE]; |
||||
|
|
||||
|
dataKeyType = DataKeyType; |
||||
|
|
||||
|
valueToDataType = ValueToDataType; |
||||
|
|
||||
|
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); |
||||
|
|
||||
|
ValueType = ValueType; |
||||
|
|
||||
|
setValueSettingsFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
constructor(private fb: UntypedFormBuilder, |
||||
|
private widgetService: WidgetService, |
||||
|
protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.setValueSettingsFormGroup = this.fb.group( |
||||
|
{ |
||||
|
action: [this.setValueSettings?.action, []], |
||||
|
executeRpc: this.fb.group({ |
||||
|
method: [this.setValueSettings?.executeRpc?.method, [Validators.required]], |
||||
|
requestTimeout: [this.setValueSettings?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]], |
||||
|
requestPersistent: [this.setValueSettings?.executeRpc?.requestPersistent, []], |
||||
|
persistentPollingInterval: |
||||
|
[this.setValueSettings?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]] |
||||
|
}), |
||||
|
setAttribute: this.fb.group({ |
||||
|
scope: [this.setValueSettings?.setAttribute?.scope, []], |
||||
|
key: [this.setValueSettings?.setAttribute?.key, [Validators.required]], |
||||
|
}), |
||||
|
putTimeSeries: this.fb.group({ |
||||
|
key: [this.setValueSettings?.putTimeSeries?.key, [Validators.required]], |
||||
|
}), |
||||
|
valueToData: this.fb.group({ |
||||
|
type: [this.setValueSettings?.valueToData?.type, [Validators.required]], |
||||
|
constantValue: [this.setValueSettings?.valueToData?.constantValue, [Validators.required]], |
||||
|
valueToDataFunction: [this.setValueSettings?.valueToData?.valueToDataFunction, [Validators.required]], |
||||
|
}), |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
merge(this.setValueSettingsFormGroup.get('action').valueChanges, |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('type').valueChanges, |
||||
|
this.setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => { |
||||
|
this.updateValidators(); |
||||
|
}); |
||||
|
this.updateValidators(); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.popover?.hide(); |
||||
|
} |
||||
|
|
||||
|
applySetValueSettings() { |
||||
|
const setValueSettings: SetValueSettings = this.setValueSettingsFormGroup.getRawValue(); |
||||
|
this.setValueSettingsApplied.emit(setValueSettings); |
||||
|
} |
||||
|
|
||||
|
private updateValidators() { |
||||
|
const action: SetValueAction = this.setValueSettingsFormGroup.get('action').value; |
||||
|
let valueToDataType: ValueToDataType = this.setValueSettingsFormGroup.get('valueToData').get('type').value; |
||||
|
|
||||
|
this.setValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); |
||||
|
this.setValueSettingsFormGroup.get('setAttribute').disable({emitEvent: false}); |
||||
|
this.setValueSettingsFormGroup.get('putTimeSeries').disable({emitEvent: false}); |
||||
|
switch (action) { |
||||
|
case SetValueAction.EXECUTE_RPC: |
||||
|
this.setValueSettingsFormGroup.get('executeRpc').enable({emitEvent: false}); |
||||
|
const requestPersistent: boolean = this.setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value; |
||||
|
if (requestPersistent) { |
||||
|
this.setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false}); |
||||
|
} |
||||
|
break; |
||||
|
case SetValueAction.SET_ATTRIBUTE: |
||||
|
case SetValueAction.ADD_TIME_SERIES: |
||||
|
if (valueToDataType === ValueToDataType.NONE) { |
||||
|
valueToDataType = ValueToDataType.CONSTANT; |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('type').patchValue(valueToDataType, {emitEvent: false}); |
||||
|
} |
||||
|
if (action === SetValueAction.SET_ATTRIBUTE) { |
||||
|
this.setValueSettingsFormGroup.get('setAttribute').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.setValueSettingsFormGroup.get('putTimeSeries').enable({emitEvent: false}); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
switch (valueToDataType) { |
||||
|
case ValueToDataType.CONSTANT: |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').enable({emitEvent: false}); |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').disable({emitEvent: false}); |
||||
|
break; |
||||
|
case ValueToDataType.FUNCTION: |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').disable({emitEvent: false}); |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').enable({emitEvent: false}); |
||||
|
break; |
||||
|
case ValueToDataType.NONE: |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').disable({emitEvent: false}); |
||||
|
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').disable({emitEvent: false}); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,176 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
|
||||
import { PageComponent } from '@shared/components/page.component'; |
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component'; |
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|
||||
import { Store } from '@ngrx/store'; |
|
||||
import { AppState } from '@core/core.state'; |
|
||||
import { merge } from 'rxjs'; |
|
||||
import { |
|
||||
RpcDataToStateType, |
|
||||
RpcInitialStateAction, |
|
||||
rpcInitialStateActions, |
|
||||
RpcInitialStateSettings, |
|
||||
rpcInitialStateTranslations |
|
||||
} from '@shared/models/rpc-widget-settings.models'; |
|
||||
import { ValueType } from '@shared/models/constants'; |
|
||||
import { TargetDevice } from '@shared/models/widget.models'; |
|
||||
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; |
|
||||
import { IAliasController } from '@core/api/widget-api.models'; |
|
||||
import { WidgetService } from '@core/http/widget.service'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-rpc-initial-state-settings-panel', |
|
||||
templateUrl: './rpc-initial-state-settings-panel.component.html', |
|
||||
providers: [], |
|
||||
styleUrls: ['./rpc-state-settings-panel.component.scss'], |
|
||||
encapsulation: ViewEncapsulation.None |
|
||||
}) |
|
||||
export class RpcInitialStateSettingsPanelComponent extends PageComponent implements OnInit { |
|
||||
|
|
||||
@Input() |
|
||||
initialState: RpcInitialStateSettings<any>; |
|
||||
|
|
||||
@Input() |
|
||||
stateValueType: ValueType; |
|
||||
|
|
||||
@Input() |
|
||||
aliasController: IAliasController; |
|
||||
|
|
||||
@Input() |
|
||||
targetDevice: TargetDevice; |
|
||||
|
|
||||
@Input() |
|
||||
popover: TbPopoverComponent<RpcInitialStateSettingsPanelComponent>; |
|
||||
|
|
||||
@Output() |
|
||||
initialStateSettingsApplied = new EventEmitter<RpcInitialStateSettings<any>>(); |
|
||||
|
|
||||
rpcInitialStateAction = RpcInitialStateAction; |
|
||||
|
|
||||
rpcInitialStateActions = rpcInitialStateActions; |
|
||||
|
|
||||
rpcInitialStateTranslationsMap = rpcInitialStateTranslations; |
|
||||
|
|
||||
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort; |
|
||||
|
|
||||
attributeScopes = Object.keys(AttributeScope) as AttributeScope[]; |
|
||||
|
|
||||
dataKeyType = DataKeyType; |
|
||||
|
|
||||
dataToStateType = RpcDataToStateType; |
|
||||
|
|
||||
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); |
|
||||
|
|
||||
valueType = ValueType; |
|
||||
|
|
||||
initialStateSettingsFormGroup: UntypedFormGroup; |
|
||||
|
|
||||
constructor(private fb: UntypedFormBuilder, |
|
||||
private widgetService: WidgetService, |
|
||||
protected store: Store<AppState>) { |
|
||||
super(store); |
|
||||
} |
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
this.initialStateSettingsFormGroup = this.fb.group( |
|
||||
{ |
|
||||
action: [this.initialState?.action, []], |
|
||||
defaultValue: [this.initialState?.defaultValue, [Validators.required]], |
|
||||
executeRpc: this.fb.group({ |
|
||||
method: [this.initialState?.executeRpc?.method, [Validators.required]], |
|
||||
requestTimeout: [this.initialState?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]], |
|
||||
requestPersistent: [this.initialState?.executeRpc?.requestPersistent, []], |
|
||||
persistentPollingInterval: [this.initialState?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]] |
|
||||
}), |
|
||||
getAttribute: this.fb.group({ |
|
||||
scope: [this.initialState?.getAttribute?.scope, []], |
|
||||
key: [this.initialState?.getAttribute?.key, [Validators.required]], |
|
||||
}), |
|
||||
getTimeSeries: this.fb.group({ |
|
||||
key: [this.initialState?.getTimeSeries?.key, [Validators.required]], |
|
||||
}), |
|
||||
dataToState: this.fb.group({ |
|
||||
type: [this.initialState?.dataToState?.type, [Validators.required]], |
|
||||
dataToStateFunction: [this.initialState?.dataToState?.dataToStateFunction, [Validators.required]], |
|
||||
}), |
|
||||
} |
|
||||
); |
|
||||
if (this.stateValueType === ValueType.BOOLEAN) { |
|
||||
(this.initialStateSettingsFormGroup.get('dataToState') as UntypedFormGroup).addControl( |
|
||||
'compareToValue', this.fb.control(this.initialState?.dataToState?.compareToValue, [Validators.required]) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
merge(this.initialStateSettingsFormGroup.get('action').valueChanges, |
|
||||
this.initialStateSettingsFormGroup.get('dataToState').get('type').valueChanges, |
|
||||
this.initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => { |
|
||||
this.updateValidators(); |
|
||||
}); |
|
||||
this.updateValidators(); |
|
||||
} |
|
||||
|
|
||||
cancel() { |
|
||||
this.popover?.hide(); |
|
||||
} |
|
||||
|
|
||||
applyInitialStateSettings() { |
|
||||
const initialStateSettings: RpcInitialStateSettings<any> = this.initialStateSettingsFormGroup.getRawValue(); |
|
||||
this.initialStateSettingsApplied.emit(initialStateSettings); |
|
||||
} |
|
||||
|
|
||||
private updateValidators() { |
|
||||
const action: RpcInitialStateAction = this.initialStateSettingsFormGroup.get('action').value; |
|
||||
const dataToStateType: RpcDataToStateType = this.initialStateSettingsFormGroup.get('dataToState').get('type').value; |
|
||||
|
|
||||
this.initialStateSettingsFormGroup.get('defaultValue').disable({emitEvent: false}); |
|
||||
this.initialStateSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); |
|
||||
this.initialStateSettingsFormGroup.get('getAttribute').disable({emitEvent: false}); |
|
||||
this.initialStateSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false}); |
|
||||
switch (action) { |
|
||||
case RpcInitialStateAction.DO_NOTHING: |
|
||||
this.initialStateSettingsFormGroup.get('defaultValue').enable({emitEvent: false}); |
|
||||
break; |
|
||||
case RpcInitialStateAction.EXECUTE_RPC: |
|
||||
this.initialStateSettingsFormGroup.get('executeRpc').enable({emitEvent: false}); |
|
||||
const requestPersistent: boolean = this.initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value; |
|
||||
if (requestPersistent) { |
|
||||
this.initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false}); |
|
||||
} |
|
||||
break; |
|
||||
case RpcInitialStateAction.GET_ATTRIBUTE: |
|
||||
this.initialStateSettingsFormGroup.get('getAttribute').enable({emitEvent: false}); |
|
||||
break; |
|
||||
case RpcInitialStateAction.GET_TIME_SERIES: |
|
||||
this.initialStateSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false}); |
|
||||
break; |
|
||||
} |
|
||||
if (action === RpcInitialStateAction.DO_NOTHING) { |
|
||||
this.initialStateSettingsFormGroup.get('dataToState').disable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.initialStateSettingsFormGroup.get('dataToState').enable({emitEvent: false}); |
|
||||
if (dataToStateType === RpcDataToStateType.FUNCTION) { |
|
||||
this.initialStateSettingsFormGroup.get('dataToState').get('dataToStateFunction').enable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.initialStateSettingsFormGroup.get('dataToState').get('dataToStateFunction').disable({emitEvent: false}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,180 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
|
||||
import { PageComponent } from '@shared/components/page.component'; |
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component'; |
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|
||||
import { Store } from '@ngrx/store'; |
|
||||
import { AppState } from '@core/core.state'; |
|
||||
import { merge } from 'rxjs'; |
|
||||
import { |
|
||||
RpcStateToParamsType, |
|
||||
RpcUpdateStateAction, |
|
||||
rpcUpdateStateActions, |
|
||||
RpcUpdateStateSettings, |
|
||||
rpcUpdateStateTranslations |
|
||||
} from '@shared/models/rpc-widget-settings.models'; |
|
||||
import { ValueType } from '@shared/models/constants'; |
|
||||
import { TargetDevice } from '@shared/models/widget.models'; |
|
||||
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; |
|
||||
import { IAliasController } from '@core/api/widget-api.models'; |
|
||||
import { WidgetService } from '@core/http/widget.service'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-rpc-update-state-settings-panel', |
|
||||
templateUrl: './rpc-update-state-settings-panel.component.html', |
|
||||
providers: [], |
|
||||
styleUrls: ['./rpc-state-settings-panel.component.scss'], |
|
||||
encapsulation: ViewEncapsulation.None |
|
||||
}) |
|
||||
export class RpcUpdateStateSettingsPanelComponent extends PageComponent implements OnInit { |
|
||||
|
|
||||
@Input() |
|
||||
panelTitle: string; |
|
||||
|
|
||||
@Input() |
|
||||
updateState: RpcUpdateStateSettings; |
|
||||
|
|
||||
@Input() |
|
||||
stateValueType: ValueType; |
|
||||
|
|
||||
@Input() |
|
||||
aliasController: IAliasController; |
|
||||
|
|
||||
@Input() |
|
||||
targetDevice: TargetDevice; |
|
||||
|
|
||||
@Input() |
|
||||
popover: TbPopoverComponent<RpcUpdateStateSettingsPanelComponent>; |
|
||||
|
|
||||
@Output() |
|
||||
updateStateSettingsApplied = new EventEmitter<RpcUpdateStateSettings>(); |
|
||||
|
|
||||
rpcUpdateStateAction = RpcUpdateStateAction; |
|
||||
|
|
||||
rpcUpdateStateActions = rpcUpdateStateActions; |
|
||||
|
|
||||
rpcUpdateStateTranslationsMap = rpcUpdateStateTranslations; |
|
||||
|
|
||||
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort; |
|
||||
|
|
||||
attributeScopes = [AttributeScope.SERVER_SCOPE, AttributeScope.SHARED_SCOPE]; |
|
||||
|
|
||||
dataKeyType = DataKeyType; |
|
||||
|
|
||||
stateToParamsType = RpcStateToParamsType; |
|
||||
|
|
||||
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); |
|
||||
|
|
||||
valueType = ValueType; |
|
||||
|
|
||||
updateStateSettingsFormGroup: UntypedFormGroup; |
|
||||
|
|
||||
constructor(private fb: UntypedFormBuilder, |
|
||||
private widgetService: WidgetService, |
|
||||
protected store: Store<AppState>) { |
|
||||
super(store); |
|
||||
} |
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
this.updateStateSettingsFormGroup = this.fb.group( |
|
||||
{ |
|
||||
action: [this.updateState?.action, []], |
|
||||
executeRpc: this.fb.group({ |
|
||||
method: [this.updateState?.executeRpc?.method, [Validators.required]], |
|
||||
requestTimeout: [this.updateState?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]], |
|
||||
requestPersistent: [this.updateState?.executeRpc?.requestPersistent, []], |
|
||||
persistentPollingInterval: [this.updateState?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]] |
|
||||
}), |
|
||||
setAttribute: this.fb.group({ |
|
||||
scope: [this.updateState?.setAttribute?.scope, []], |
|
||||
key: [this.updateState?.setAttribute?.key, [Validators.required]], |
|
||||
}), |
|
||||
putTimeSeries: this.fb.group({ |
|
||||
key: [this.updateState?.putTimeSeries?.key, [Validators.required]], |
|
||||
}), |
|
||||
stateToParams: this.fb.group({ |
|
||||
type: [this.updateState?.stateToParams?.type, [Validators.required]], |
|
||||
constantValue: [this.updateState?.stateToParams?.constantValue, [Validators.required]], |
|
||||
stateToParamsFunction: [this.updateState?.stateToParams?.stateToParamsFunction, [Validators.required]], |
|
||||
}), |
|
||||
} |
|
||||
); |
|
||||
|
|
||||
merge(this.updateStateSettingsFormGroup.get('action').valueChanges, |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('type').valueChanges, |
|
||||
this.updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => { |
|
||||
this.updateValidators(); |
|
||||
}); |
|
||||
this.updateValidators(); |
|
||||
} |
|
||||
|
|
||||
cancel() { |
|
||||
this.popover?.hide(); |
|
||||
} |
|
||||
|
|
||||
applyUpdateStateSettings() { |
|
||||
const updateStateSettings: RpcUpdateStateSettings = this.updateStateSettingsFormGroup.getRawValue(); |
|
||||
this.updateStateSettingsApplied.emit(updateStateSettings); |
|
||||
} |
|
||||
|
|
||||
private updateValidators() { |
|
||||
const action: RpcUpdateStateAction = this.updateStateSettingsFormGroup.get('action').value; |
|
||||
let stateToParamsType: RpcStateToParamsType = this.updateStateSettingsFormGroup.get('stateToParams').get('type').value; |
|
||||
|
|
||||
this.updateStateSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); |
|
||||
this.updateStateSettingsFormGroup.get('setAttribute').disable({emitEvent: false}); |
|
||||
this.updateStateSettingsFormGroup.get('putTimeSeries').disable({emitEvent: false}); |
|
||||
switch (action) { |
|
||||
case RpcUpdateStateAction.EXECUTE_RPC: |
|
||||
this.updateStateSettingsFormGroup.get('executeRpc').enable({emitEvent: false}); |
|
||||
const requestPersistent: boolean = this.updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value; |
|
||||
if (requestPersistent) { |
|
||||
this.updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false}); |
|
||||
} |
|
||||
break; |
|
||||
case RpcUpdateStateAction.SET_ATTRIBUTE: |
|
||||
case RpcUpdateStateAction.ADD_TIME_SERIES: |
|
||||
if (stateToParamsType === RpcStateToParamsType.NONE) { |
|
||||
stateToParamsType = RpcStateToParamsType.CONSTANT; |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('type').patchValue(stateToParamsType, {emitEvent: false}); |
|
||||
} |
|
||||
if (action === RpcUpdateStateAction.SET_ATTRIBUTE) { |
|
||||
this.updateStateSettingsFormGroup.get('setAttribute').enable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.updateStateSettingsFormGroup.get('putTimeSeries').enable({emitEvent: false}); |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
switch (stateToParamsType) { |
|
||||
case RpcStateToParamsType.CONSTANT: |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').enable({emitEvent: false}); |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').disable({emitEvent: false}); |
|
||||
break; |
|
||||
case RpcStateToParamsType.FUNCTION: |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').disable({emitEvent: false}); |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').enable({emitEvent: false}); |
|
||||
break; |
|
||||
case RpcStateToParamsType.NONE: |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').disable({emitEvent: false}); |
|
||||
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').disable({emitEvent: false}); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,128 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
||||
|
|
||||
|
export enum GetValueAction { |
||||
|
DO_NOTHING = 'DO_NOTHING', |
||||
|
EXECUTE_RPC = 'EXECUTE_RPC', |
||||
|
GET_ATTRIBUTE = 'GET_ATTRIBUTE', |
||||
|
GET_TIME_SERIES = 'GET_TIME_SERIES' |
||||
|
} |
||||
|
|
||||
|
export const getValueActions = Object.keys(GetValueAction) as GetValueAction[]; |
||||
|
|
||||
|
export const getValueActionTranslations = new Map<GetValueAction, string>( |
||||
|
[ |
||||
|
[GetValueAction.DO_NOTHING, 'widgets.value-action.do-nothing'], |
||||
|
[GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'], |
||||
|
[GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'], |
||||
|
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export interface RpcSettings { |
||||
|
method: string; |
||||
|
requestTimeout: number; |
||||
|
requestPersistent: boolean; |
||||
|
persistentPollingInterval: number; |
||||
|
} |
||||
|
|
||||
|
export interface TelemetryValueSettings { |
||||
|
key: string; |
||||
|
} |
||||
|
|
||||
|
export interface GetTelemetryValueSettings extends TelemetryValueSettings { |
||||
|
subscribeForUpdates: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface GetAttributeValueSettings extends GetTelemetryValueSettings { |
||||
|
scope: AttributeScope | null; |
||||
|
} |
||||
|
|
||||
|
export interface SetAttributeValueSettings extends TelemetryValueSettings { |
||||
|
scope: AttributeScope.SERVER_SCOPE | AttributeScope.SHARED_SCOPE; |
||||
|
} |
||||
|
|
||||
|
export enum DataToValueType { |
||||
|
NONE = 'NONE', |
||||
|
FUNCTION = 'FUNCTION' |
||||
|
} |
||||
|
|
||||
|
export interface DataToValueSettings { |
||||
|
type: DataToValueType; |
||||
|
dataToValueFunction: string; |
||||
|
compareToValue?: any; |
||||
|
} |
||||
|
|
||||
|
export interface ValueActionSettings { |
||||
|
actionLabel?: string; |
||||
|
} |
||||
|
|
||||
|
export interface GetValueSettings<V> extends ValueActionSettings { |
||||
|
action: GetValueAction; |
||||
|
defaultValue: V; |
||||
|
executeRpc: RpcSettings; |
||||
|
getAttribute: GetAttributeValueSettings; |
||||
|
getTimeSeries: GetTelemetryValueSettings; |
||||
|
dataToValue: DataToValueSettings; |
||||
|
} |
||||
|
|
||||
|
export enum SetValueAction { |
||||
|
EXECUTE_RPC = 'EXECUTE_RPC', |
||||
|
SET_ATTRIBUTE = 'SET_ATTRIBUTE', |
||||
|
ADD_TIME_SERIES = 'ADD_TIME_SERIES' |
||||
|
} |
||||
|
|
||||
|
export const setValueActions = Object.keys(SetValueAction) as SetValueAction[]; |
||||
|
|
||||
|
export const setValueActionTranslations = new Map<SetValueAction, string>( |
||||
|
[ |
||||
|
[SetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'], |
||||
|
[SetValueAction.SET_ATTRIBUTE, 'widgets.value-action.set-attribute'], |
||||
|
[SetValueAction.ADD_TIME_SERIES, 'widgets.value-action.add-time-series'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export enum ValueToDataType { |
||||
|
CONSTANT = 'CONSTANT', |
||||
|
FUNCTION = 'FUNCTION', |
||||
|
NONE = 'NONE' |
||||
|
} |
||||
|
|
||||
|
export interface ValueToDataSettings { |
||||
|
type: ValueToDataType; |
||||
|
constantValue: any; |
||||
|
valueToDataFunction: string; |
||||
|
} |
||||
|
|
||||
|
export interface SetValueSettings extends ValueActionSettings { |
||||
|
action: SetValueAction; |
||||
|
executeRpc: RpcSettings; |
||||
|
setAttribute: SetAttributeValueSettings; |
||||
|
putTimeSeries: TelemetryValueSettings; |
||||
|
valueToData: ValueToDataSettings; |
||||
|
} |
||||
|
|
||||
|
/*export interface RpcStateBehaviourSettings<V> { |
||||
|
initialState: RpcInitialStateSettings<V>; |
||||
|
updateStateByValue: (value: V) => RpcUpdateStateSettings; |
||||
|
} |
||||
|
|
||||
|
export interface RpcStateWidgetSettings<V> { |
||||
|
initialState: RpcInitialStateSettings<V>; |
||||
|
background: BackgroundSettings; |
||||
|
}*/ |
||||
@ -1,125 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
|
||||
import { BackgroundSettings } from '@shared/models/widget-settings.models'; |
|
||||
|
|
||||
export enum RpcInitialStateAction { |
|
||||
DO_NOTHING = 'DO_NOTHING', |
|
||||
EXECUTE_RPC = 'EXECUTE_RPC', |
|
||||
GET_ATTRIBUTE = 'GET_ATTRIBUTE', |
|
||||
GET_TIME_SERIES = 'GET_TIME_SERIES' |
|
||||
} |
|
||||
|
|
||||
export const rpcInitialStateActions = Object.keys(RpcInitialStateAction) as RpcInitialStateAction[]; |
|
||||
|
|
||||
export const rpcInitialStateTranslations = new Map<RpcInitialStateAction, string>( |
|
||||
[ |
|
||||
[RpcInitialStateAction.DO_NOTHING, 'widgets.rpc-state.do-nothing'], |
|
||||
[RpcInitialStateAction.EXECUTE_RPC, 'widgets.rpc-state.execute-rpc'], |
|
||||
[RpcInitialStateAction.GET_ATTRIBUTE, 'widgets.rpc-state.get-attribute'], |
|
||||
[RpcInitialStateAction.GET_TIME_SERIES, 'widgets.rpc-state.get-time-series'] |
|
||||
] |
|
||||
); |
|
||||
|
|
||||
export interface RpcSettings { |
|
||||
method: string; |
|
||||
requestTimeout: number; |
|
||||
requestPersistent: boolean; |
|
||||
persistentPollingInterval: number; |
|
||||
} |
|
||||
|
|
||||
export interface RpcTelemetrySettings { |
|
||||
key: string; |
|
||||
} |
|
||||
|
|
||||
export interface RpcGetAttributeSettings extends RpcTelemetrySettings { |
|
||||
scope: AttributeScope | null; |
|
||||
} |
|
||||
|
|
||||
export interface RpcSetAttributeSettings extends RpcTelemetrySettings { |
|
||||
scope: AttributeScope.SERVER_SCOPE | AttributeScope.SHARED_SCOPE; |
|
||||
} |
|
||||
|
|
||||
export enum RpcDataToStateType { |
|
||||
NONE = 'NONE', |
|
||||
FUNCTION = 'FUNCTION' |
|
||||
} |
|
||||
|
|
||||
export interface RpcDataToStateSettings { |
|
||||
type: RpcDataToStateType; |
|
||||
dataToStateFunction: string; |
|
||||
compareToValue?: any; |
|
||||
} |
|
||||
|
|
||||
export interface RpcActionSettings { |
|
||||
actionLabel?: string; |
|
||||
} |
|
||||
|
|
||||
export interface RpcInitialStateSettings<V> extends RpcActionSettings { |
|
||||
action: RpcInitialStateAction; |
|
||||
defaultValue: V; |
|
||||
executeRpc: RpcSettings; |
|
||||
getAttribute: RpcGetAttributeSettings; |
|
||||
getTimeSeries: RpcTelemetrySettings; |
|
||||
dataToState: RpcDataToStateSettings; |
|
||||
} |
|
||||
|
|
||||
export enum RpcUpdateStateAction { |
|
||||
EXECUTE_RPC = 'EXECUTE_RPC', |
|
||||
SET_ATTRIBUTE = 'SET_ATTRIBUTE', |
|
||||
ADD_TIME_SERIES = 'ADD_TIME_SERIES' |
|
||||
} |
|
||||
|
|
||||
export const rpcUpdateStateActions = Object.keys(RpcUpdateStateAction) as RpcUpdateStateAction[]; |
|
||||
|
|
||||
export const rpcUpdateStateTranslations = new Map<RpcUpdateStateAction, string>( |
|
||||
[ |
|
||||
[RpcUpdateStateAction.EXECUTE_RPC, 'widgets.rpc-state.execute-rpc'], |
|
||||
[RpcUpdateStateAction.SET_ATTRIBUTE, 'widgets.rpc-state.set-attribute'], |
|
||||
[RpcUpdateStateAction.ADD_TIME_SERIES, 'widgets.rpc-state.add-time-series'] |
|
||||
] |
|
||||
); |
|
||||
|
|
||||
export enum RpcStateToParamsType { |
|
||||
CONSTANT = 'CONSTANT', |
|
||||
FUNCTION = 'FUNCTION', |
|
||||
NONE = 'NONE' |
|
||||
} |
|
||||
|
|
||||
export interface RpcStateToParamsSettings { |
|
||||
type: RpcStateToParamsType; |
|
||||
constantValue: any; |
|
||||
stateToParamsFunction: string; |
|
||||
} |
|
||||
|
|
||||
export interface RpcUpdateStateSettings extends RpcActionSettings { |
|
||||
action: RpcUpdateStateAction; |
|
||||
executeRpc: RpcSettings; |
|
||||
setAttribute: RpcSetAttributeSettings; |
|
||||
putTimeSeries: RpcTelemetrySettings; |
|
||||
stateToParams: RpcStateToParamsSettings; |
|
||||
} |
|
||||
|
|
||||
export interface RpcStateBehaviourSettings<V> { |
|
||||
initialState: RpcInitialStateSettings<V>; |
|
||||
updateStateByValue: (value: V) => RpcUpdateStateSettings; |
|
||||
} |
|
||||
|
|
||||
export interface RpcStateWidgetSettings<V> { |
|
||||
initialState: RpcInitialStateSettings<V>; |
|
||||
background: BackgroundSettings; |
|
||||
} |
|
||||
Loading…
Reference in new issue