Merge pull request #12093 from dashevchenko/alarmStatusCmdFix

Add new behavior "get alarm status"
This commit is contained in:
Andrew Shvayka 2024-11-22 11:20:55 +01:00 committed by GitHub
commit eae6909be8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 289 additions and 21 deletions

View File

@ -15,21 +15,16 @@
*/
package org.thingsboard.server.service.subscription;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.OriginatorAlarmFilter;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import java.util.List;
@ -82,7 +77,10 @@ public class TbAlarmStatusSubCtx extends TbAbstractSubCtx {
}
public void sendUpdate() {
sendWsMsg(subscription.createUpdate());
sendWsMsg(AlarmStatusUpdate.builder()
.cmdId(cmdId)
.active(subscription.hasAlarms())
.build());
}
public void fetchActiveAlarms() {

View File

@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import java.util.HashSet;
@ -53,15 +52,12 @@ public class TbAlarmStatusSubscription extends TbSubscription<AlarmSubscriptionU
this.severityList = severityList;
}
public AlarmStatusUpdate createUpdate() {
return AlarmStatusUpdate.builder()
.cmdId(getSubscriptionId())
.active(!alarmIds.isEmpty())
.build();
}
public boolean matches(AlarmInfo alarm) {
return !alarm.isCleared() && (this.typeList == null || this.typeList.contains(alarm.getType())) &&
(this.severityList == null || this.severityList.contains(alarm.getSeverity()));
}
public boolean hasAlarms() {
return !alarmIds.isEmpty();
}
}

View File

@ -38,6 +38,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUnsubscribeCmd;
import java.util.List;
@ -67,6 +68,7 @@ public class WsCommandsWrapper {
@Type(name = "ENTITY_DATA_UNSUBSCRIBE", value = EntityDataUnsubscribeCmd.class),
@Type(name = "ENTITY_COUNT_UNSUBSCRIBE", value = EntityCountUnsubscribeCmd.class),
@Type(name = "NOTIFICATIONS_UNSUBSCRIBE", value = NotificationsUnsubCmd.class),
@Type(name = "ALARM_STATUS_UNSUBSCRIBE", value = AlarmStatusUnsubscribeCmd.class),
})
private List<WsCmd> cmds;

View File

@ -22,6 +22,9 @@ import {
AlarmDataCmd,
AlarmDataUnsubscribeCmd,
AlarmDataUpdate,
AlarmStatusCmd,
AlarmStatusUnsubscribeCmd,
AlarmStatusUpdate,
EntityCountCmd,
EntityCountUnsubscribeCmd,
EntityCountUpdate,
@ -30,6 +33,7 @@ import {
EntityDataUpdate,
isAlarmCountUpdateMsg,
isAlarmDataUpdateMsg,
isAlarmStatusUpdateMsg,
isEntityCountUpdateMsg,
isEntityDataUpdateMsg,
isNotificationCountUpdateMsg,
@ -121,6 +125,10 @@ export class TelemetryWebsocketService extends WebsocketService<TelemetrySubscri
const alarmCountUnsubscribeCmd = new AlarmCountUnsubscribeCmd();
alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd);
} else if (subscriptionCommand instanceof AlarmStatusCmd) {
const alarmCountUnsubscribeCmd = new AlarmStatusUnsubscribeCmd();
alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd);
} else if (subscriptionCommand instanceof UnreadCountSubCmd || subscriptionCommand instanceof UnreadSubCmd) {
const notificationsUnsubCmds = new UnsubscribeCmd();
notificationsUnsubCmds.cmdId = subscriptionCommand.cmdId;
@ -157,6 +165,8 @@ export class TelemetryWebsocketService extends WebsocketService<TelemetrySubscri
subscriber.onEntityCount(new EntityCountUpdate(message));
} else if (isAlarmCountUpdateMsg(message)) {
subscriber.onAlarmCount(new AlarmCountUpdate(message));
} else if (isAlarmStatusUpdateMsg(message)) {
subscriber.onAlarmStatus(new AlarmStatusUpdate(message))
}
}
} else if ('subscriptionId' in message && message.subscriptionId) {

View File

@ -242,6 +242,8 @@ export abstract class ValueGetter<V> extends ValueAction {
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_TIME_SERIES:
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_ALARM_STATUS:
return new AlarmStatusValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_DASHBOARD_STATE:
return new DashboardStateGetter<V>(ctx, settings, valueType, valueObserver, simulated);
}
@ -257,7 +259,7 @@ export abstract class ValueGetter<V> extends ValueAction {
protected valueObserver: Partial<Observer<V>>,
protected simulated: boolean) {
super(ctx, settings);
if (this.settings.action !== GetValueAction.DO_NOTHING) {
if (this.settings.action !== GetValueAction.DO_NOTHING && this.settings.action !== GetValueAction.GET_ALARM_STATUS) {
this.dataConverter = new DataToValueConverter<V>(settings.dataToValue, valueType);
}
}
@ -537,6 +539,58 @@ export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, TelemetryV
}
}
export class AlarmStatusValueGetter<V> extends ValueGetter<V> {
protected targetEntityId: EntityId;
private telemetrySubscriber: SharedTelemetrySubscriber;
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>,
protected simulated: boolean) {
super(ctx, settings, valueType, valueObserver, simulated);
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
this.targetEntityId = entityInfo?.entityId;
}
protected doGetValue(): Observable<boolean> {
if (this.simulated) {
return of(false).pipe(delay(100));
} else {
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) {
return this.subscribeForTelemetryValue();
} else {
return of(null);
}
}
}
private subscribeForTelemetryValue(): Observable<boolean> {
this.telemetrySubscriber =
SharedTelemetrySubscriber.createAlarmStatusSubscription(this.ctx.telemetryWsService, this.targetEntityId,
this.ctx.ngZone, this.settings.getAlarmStatus.severityList, this.settings.getAlarmStatus.typeList);
this.telemetrySubscriber.subscribe();
return this.telemetrySubscriber.alarmStatus$.pipe(
map((data) => {
return data.active;
})
);
}
destroy() {
if (this.telemetrySubscriber) {
this.telemetrySubscriber.unsubscribe();
this.telemetrySubscriber = null;
}
super.destroy();
}
}
export class DashboardStateGetter<V> extends ValueGetter<V> {
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,

View File

@ -38,6 +38,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -54,6 +58,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -62,6 +62,10 @@ export const commandButtonDefaultSettings: CommandButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -61,6 +61,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -77,6 +81,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -85,6 +85,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -101,6 +105,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -104,6 +104,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -120,6 +124,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -100,6 +100,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -116,6 +120,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -101,6 +101,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -117,6 +121,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -415,6 +415,10 @@ export const defaultGetValueSettings = (valueType: ValueType): GetValueSettings<
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -103,8 +103,31 @@
</div>
</ng-container>
</ng-template>
<ng-template [ngSwitchCase]="getValueAction.GET_ALARM_STATUS">
<ng-container formGroupName="getAlarmStatus">
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width" translate>alarm.alarm-severity</div>
<mat-chip-listbox multiple formControlName="severityList">
<mat-chip-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }}
</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm.alarm-types</div>
<tb-entity-subtype-list subscriptSizing="dynamic"
formControlName="typeList"
appearance="outline"
class="flex-1"
[additionalClasses]="['tb-chips', 'flex']"
[entityType]="entityType.ALARM">
</tb-entity-subtype-list>
</div>
</ng-container>
</ng-template>
</ng-container>
<div *ngIf="getValueSettingsFormGroup.get('action').value !== getValueAction.DO_NOTHING"
<div *ngIf="getValueSettingsFormGroup.get('action').value !== getValueAction.DO_NOTHING &&
getValueSettingsFormGroup.get('action').value !== getValueAction.GET_ALARM_STATUS"
class="tb-form-panel stroked" formGroupName="dataToValue">
<div class="tb-form-row no-padding no-border column-xs">
<div class="fixed-title-width" translate>widgets.value-action.action-result-converter</div>

View File

@ -33,6 +33,8 @@ import { TargetDevice, widgetType } 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';
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
import { EntityType } from '@shared/models/entity-type.models';
@Component({
selector: 'tb-get-value-action-settings-panel',
@ -96,6 +98,9 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
getValueSettingsFormGroup: UntypedFormGroup;
alarmSeverities = Object.keys(AlarmSeverity) as AlarmSeverity[];
alarmSeverityTranslationMap = alarmSeverityTranslations;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
@ -122,6 +127,10 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
getTimeSeries: this.fb.group({
key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]]
}),
getAlarmStatus: this.fb.group({
severityList: [this.getValueSettings?.getAlarmStatus?.severityList],
typeList: [this.getValueSettings?.getAlarmStatus?.typeList]
}),
dataToValue: this.fb.group({
type: [this.getValueSettings?.dataToValue?.type, [Validators.required]],
dataToValueFunction: [this.getValueSettings?.dataToValue?.dataToValueFunction, [Validators.required]],
@ -159,6 +168,7 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getAttribute').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getAlarmStatus').disable({emitEvent: false});
switch (action) {
case GetValueAction.DO_NOTHING:
this.getValueSettingsFormGroup.get('defaultValue').enable({emitEvent: false});
@ -178,8 +188,11 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
case GetValueAction.GET_TIME_SERIES:
this.getValueSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false});
break;
case GetValueAction.GET_ALARM_STATUS:
this.getValueSettingsFormGroup.get('getAlarmStatus').enable({emitEvent: false});
break;
}
if (action === GetValueAction.DO_NOTHING) {
if (action === GetValueAction.DO_NOTHING || action === GetValueAction.GET_ALARM_STATUS) {
this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false});
} else {
this.getValueSettingsFormGroup.get('dataToValue').enable({emitEvent: false});
@ -190,4 +203,6 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
}
}
}
protected readonly entityType = EntityType;
}

View File

@ -176,6 +176,9 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce
case GetValueAction.GET_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
break;
case GetValueAction.GET_ALARM_STATUS:
this.displayValue = this.translate.instant('widgets.value-action.get-alarm-status-text');
break;
case GetValueAction.GET_DASHBOARD_STATE:
if (this.valueType === ValueType.BOOLEAN) {
const state = this.modelValue.dataToValue?.compareToValue;

View File

@ -16,12 +16,14 @@
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { widgetType } from '@shared/models/widget.models';
import { AlarmSeverity } from '@shared/models/alarm.models';
export enum GetValueAction {
DO_NOTHING = 'DO_NOTHING',
EXECUTE_RPC = 'EXECUTE_RPC',
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
GET_TIME_SERIES = 'GET_TIME_SERIES',
GET_ALARM_STATUS = 'GET_ALARM_STATUS',
GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE'
}
@ -41,6 +43,7 @@ export const getValueActionTranslations = new Map<GetValueAction, string>(
[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'],
[GetValueAction.GET_ALARM_STATUS, 'widgets.value-action.get-alarm-status'],
[GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state']
]
);
@ -52,6 +55,11 @@ export interface RpcSettings {
persistentPollingInterval: number;
}
export interface AlarmStatusSettings {
severityList: Array<AlarmSeverity>;
typeList: Array<string>;
}
export interface TelemetryValueSettings {
key: string;
}
@ -85,6 +93,7 @@ export interface GetValueSettings<V> extends ValueActionSettings {
executeRpc?: RpcSettings;
getAttribute: GetAttributeValueSettings;
getTimeSeries: TelemetryValueSettings;
getAlarmStatus: AlarmStatusSettings;
dataToValue: DataToValueSettings;
}

View File

@ -33,9 +33,9 @@ import {
TsValue
} from '@shared/models/query/query.models';
import { PageData } from '@shared/models/page/page-data';
import { alarmFields } from '@shared/models/alarm.models';
import { alarmFields, AlarmSeverity } from '@shared/models/alarm.models';
import { entityFields } from '@shared/models/entity.models';
import { isDefinedAndNotNull, isUndefined } from '@core/utils';
import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { CmdWrapper, WsService, WsSubscriber } from '@shared/models/websocket/websocket.models';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { Notification, NotificationType } from '@shared/models/notification.models';
@ -141,6 +141,7 @@ export enum WsCmdType {
ENTITY_COUNT = 'ENTITY_COUNT',
ALARM_DATA = 'ALARM_DATA',
ALARM_COUNT = 'ALARM_COUNT',
ALARM_STATUS = 'ALARM_STATUS',
NOTIFICATIONS = 'NOTIFICATIONS',
NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT',
@ -149,6 +150,7 @@ export enum WsCmdType {
ALARM_DATA_UNSUBSCRIBE = 'ALARM_DATA_UNSUBSCRIBE',
ALARM_COUNT_UNSUBSCRIBE = 'ALARM_COUNT_UNSUBSCRIBE',
ALARM_STATUS_UNSUBSCRIBE = 'ALARM_STATUS_UNSUBSCRIBE',
ENTITY_DATA_UNSUBSCRIBE = 'ENTITY_DATA_UNSUBSCRIBE',
ENTITY_COUNT_UNSUBSCRIBE = 'ENTITY_COUNT_UNSUBSCRIBE',
NOTIFICATIONS_UNSUBSCRIBE = 'NOTIFICATIONS_UNSUBSCRIBE'
@ -298,6 +300,14 @@ export class AlarmCountCmd implements WebsocketCmd {
type = WsCmdType.ALARM_COUNT;
}
export class AlarmStatusCmd implements WebsocketCmd {
cmdId: number;
originatorId: EntityId;
severityList?: Array<AlarmSeverity>;
typeList?: Array<string>;
type = WsCmdType.ALARM_STATUS;
}
export class UnreadCountSubCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.NOTIFICATIONS_COUNT;
@ -352,6 +362,11 @@ export class AlarmCountUnsubscribeCmd implements WebsocketCmd {
type = WsCmdType.ALARM_COUNT_UNSUBSCRIBE;
}
export class AlarmStatusUnsubscribeCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.ALARM_STATUS_UNSUBSCRIBE;
}
export class UnsubscribeCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.NOTIFICATIONS_UNSUBSCRIBE;
@ -432,6 +447,7 @@ export enum CmdUpdateType {
ENTITY_DATA = 'ENTITY_DATA',
ALARM_DATA = 'ALARM_DATA',
ALARM_COUNT_DATA = 'ALARM_COUNT_DATA',
ALARM_STATUS = 'ALARM_STATUS',
COUNT_DATA = 'COUNT_DATA',
NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT',
NOTIFICATIONS = 'NOTIFICATIONS'
@ -469,6 +485,11 @@ export interface AlarmCountUpdateMsg extends CmdUpdateMsg {
count: number;
}
export interface AlarmStatusUpdateMsg extends CmdUpdateMsg {
cmdUpdateType: CmdUpdateType.ALARM_STATUS;
active: boolean;
}
export interface NotificationCountUpdateMsg extends CmdUpdateMsg {
cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT;
totalUnreadCount: number;
@ -506,6 +527,11 @@ export const isAlarmCountUpdateMsg = (message: WebsocketDataMsg): message is Ala
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_COUNT_DATA;
};
export const isAlarmStatusUpdateMsg = (message: WebsocketDataMsg): message is AlarmCountUpdateMsg => {
const updateMsg = (message as CmdUpdateMsg);
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_STATUS;
};
export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => {
const updateMsg = (message as CmdUpdateMsg);
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT;
@ -705,6 +731,15 @@ export class AlarmCountUpdate extends CmdUpdate {
}
}
export class AlarmStatusUpdate extends CmdUpdate {
active: boolean;
constructor(msg: AlarmStatusUpdateMsg) {
super(msg);
this.active = msg.active;
}
}
export class NotificationCountUpdate extends CmdUpdate {
totalUnreadCount: number;
sequenceNumber: number;
@ -750,14 +785,29 @@ export class SharedTelemetrySubscriber {
return key;
}
private static createAlarmStatusSubscriberKey (entityId: EntityId, severityList: AlarmSeverity[] = null, typeList: string[] = null): string {
let key = entityId.entityType + '_' + entityId.id;
if (severityList) {
key += '_' + severityList.sort().join('_');
}
if (typeList) {
key += '_' + typeList.sort().join('_');
}
return key;
}
private subscribed = false;
private attributeDataSubject = connectable(this.sharedSubscriptionInfo.subscriber.attributeData$(),
{ connector: () => new ReplaySubject<Array<AttributeData>>(1)});
private alarmStatusSubject = connectable(this.sharedSubscriptionInfo.subscriber.alarmStatus$,
{ connector: () => new ReplaySubject<AlarmStatusUpdate>()});
private subscriptions = new Array<Subscription>();
public attributeData$: Observable<Array<AttributeData>> = this.attributeDataSubject; //this.attributeDataSubject.asObservable();
public alarmStatus$: Observable<AlarmStatusUpdate> = this.alarmStatusSubject;
public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, attributeScope: TelemetryType,
@ -781,6 +831,28 @@ export class SharedTelemetrySubscriber {
return sharedSubscriber;
}
public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, zone: NgZone, severityList: AlarmSeverity[] = null,
typeList: string[] = null): SharedTelemetrySubscriber {
const key = SharedTelemetrySubscriber.createAlarmStatusSubscriberKey(entityId, severityList, typeList);
let info = SharedTelemetrySubscriber.subscribersCache[key];
if (!info) {
const subscriber = TelemetrySubscriber.createAlarmStatusSubscription(
telemetryService, entityId, zone, severityList, typeList
);
info = {
key,
subscriber,
subscribed: false,
sharedSubscribers: new Set<SharedTelemetrySubscriber>()
};
SharedTelemetrySubscriber.subscribersCache[key] = info;
}
const sharedSubscriber = new SharedTelemetrySubscriber(info);
info.sharedSubscribers.add(sharedSubscriber);
return sharedSubscriber;
}
private constructor(private sharedSubscriptionInfo: SharedSubscriptionInfo) {
}
@ -788,6 +860,7 @@ export class SharedTelemetrySubscriber {
if (!this.subscribed) {
this.subscribed = true;
this.subscriptions.push(this.attributeDataSubject.connect());
this.subscriptions.push(this.alarmStatusSubject.connect());
if (!this.sharedSubscriptionInfo.subscribed) {
this.sharedSubscriptionInfo.subscriber.subscribe();
this.sharedSubscriptionInfo.subscribed = true;
@ -823,6 +896,7 @@ export class TelemetrySubscriber extends WsSubscriber {
private alarmDataSubject = new ReplaySubject<AlarmDataUpdate>(1);
private entityCountSubject = new ReplaySubject<EntityCountUpdate>(1);
private alarmCountSubject = new ReplaySubject<AlarmCountUpdate>(1);
private alarmStatusSubject = new ReplaySubject<AlarmStatusUpdate>(1);
private tsOffset = undefined;
public data$ = this.dataSubject.asObservable();
@ -830,6 +904,7 @@ export class TelemetrySubscriber extends WsSubscriber {
public alarmData$ = this.alarmDataSubject.asObservable();
public entityCount$ = this.entityCountSubject.asObservable();
public alarmCount$ = this.alarmCountSubject.asObservable();
public alarmStatus$ = this.alarmStatusSubject.asObservable();
public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, attributeScope: TelemetryType,
@ -851,6 +926,17 @@ export class TelemetrySubscriber extends WsSubscriber {
return subscriber;
}
public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId,
zone: NgZone, severityList: AlarmSeverity[] = null, typeList: string[] = null): TelemetrySubscriber {
const subscriptionCommand = new AlarmStatusCmd();
subscriptionCommand.originatorId = deepClone(entityId);
subscriptionCommand.severityList = severityList;
subscriptionCommand.typeList = typeList;
const subscriber = new TelemetrySubscriber(telemetryService, zone);
subscriber.subscriptionCommands.push(subscriptionCommand);
return subscriber;
}
public static createEntityFilterLatestSubscription(telemetryService: TelemetryWebsocketService,
entityFilter: EntityFilter, zone: NgZone,
latestKeys: EntityKey[] = null): TelemetrySubscriber {
@ -882,6 +968,7 @@ export class TelemetrySubscriber extends WsSubscriber {
this.alarmDataSubject.complete();
this.entityCountSubject.complete();
this.alarmCountSubject.complete();
this.alarmStatusSubject.complete();
super.complete();
}
@ -971,6 +1058,18 @@ export class TelemetrySubscriber extends WsSubscriber {
}
}
public onAlarmStatus(message: AlarmStatusUpdate) {
if (this.zone) {
this.zone.run(
() => {
this.alarmStatusSubject.next(message);
}
);
} else {
this.alarmStatusSubject.next(message);
}
}
public attributeData$(): Observable<Array<AttributeData>> {
const attributeData = new Array<AttributeData>();
return this.data$.pipe(

View File

@ -596,6 +596,7 @@
"ack-time": "Acknowledged time",
"clear-time": "Cleared time",
"duration": "Duration",
"alarm-severity": "Alarm severity",
"alarm-severity-list": "Alarm severity list",
"any-severity": "Any severity",
"severity-critical": "Critical",
@ -634,6 +635,7 @@
"fetch-size": "Fetch size",
"fetch-size-required": "Fetch size is required.",
"fetch-size-error-min": "Minimum value is 10.",
"alarm-types": "Alarm types",
"alarm-type-list": "Alarm type list",
"any-type": "Any type",
"assigned-to-current-user": "Assigned to current user",
@ -7352,11 +7354,12 @@
"get-attribute": "Get attribute",
"set-attribute": "Set attribute",
"get-time-series": "Get time series",
"get-alarm-status": "Get alarm status",
"get-dashboard-state": "Get dashboard state",
"add-time-series": "Add time series",
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
"get-attribute-text": "Use attribute '{{key}}'",
"get-time-series-text": "Use time series '{{key}}'",
"get-alarm-status-text": "Use alarm status",
"get-dashboard-state-text": "Use dashboard state",
"when-dashboard-state-is-text": "When dashboard state is '{{state}}'",
"when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'",