Compare commits

...

6 Commits

Author SHA1 Message Date
Joseph Doherty 864b9f4bd3 Remove the AlarmClientDiscovery probe log
Delete docs/AlarmClientDiscovery.md — an archival AVEVA alarm-consumer
investigation log whose durable findings now live in the alarm
worker/monitor code. Drop the now-dangling links from Grpc.md and
GatewayConfiguration.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:04:29 -04:00
Joseph Doherty de58872435 Document the session-less StreamAlarms feed and alarm config
Update the gateway docs for the central alarm monitor reversal:
Grpc.md replaces QueryActiveAlarms with the session-less StreamAlarms
RPC and notes AcknowledgeAlarm no longer needs a session;
Authorization.md maps StreamAlarmsRequest to events:read;
GatewayConfiguration.md adds the MxGateway:Alarms options block; and
GatewayDashboardDesign.md points the Alarms page at the central
monitor cache instead of a per-session subscription.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:59:48 -04:00
Joseph Doherty 6777d49030 Point the Java client at the StreamAlarms alarm feed
Regenerate the Java protobuf stubs and replace queryActiveAlarms with
streamAlarms, returning a MxGatewayAlarmFeedSubscription over
AlarmFeedMessage served by the gateway's central alarm monitor
(snapshot, snapshot_complete, then live transitions). Drops session_id
from the acknowledge surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:55:20 -04:00
Joseph Doherty 1b6ca07bb5 Point the Rust client at the StreamAlarms alarm feed
Replace GatewayClient::query_active_alarms with stream_alarms, an
AlarmFeedStream over AlarmFeedMessage served by the gateway's central
alarm monitor (snapshot, snapshot_complete, then live transitions).
Drops session_id from the acknowledge surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:49:26 -04:00
Joseph Doherty 1ad0be8276 Point the Python client at the StreamAlarms alarm feed
Regenerate the Python protobuf stubs and replace query_active_alarms
with stream_alarms, an AsyncIterator over AlarmFeedMessage served by
the gateway's central alarm monitor (snapshot, snapshot_complete, then
live transitions). Drops session_id from the acknowledge surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:45:53 -04:00
Joseph Doherty 9328c4f657 Point the Go client at the StreamAlarms alarm feed
Regenerate the Go protobuf stubs and replace the session-scoped
QueryActiveAlarms surface with the session-less StreamAlarms feed:
snapshot-then-live AlarmFeedMessage fan-out served by the gateway's
central alarm monitor. Drops session_id from the acknowledge surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:45:47 -04:00
23 changed files with 2360 additions and 2216 deletions
@@ -687,18 +687,32 @@ func (x *GalaxyObject) GetAttributes() []*GalaxyAttribute {
} }
type GalaxyAttribute struct { type GalaxyAttribute struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
AttributeName string `protobuf:"bytes,1,opt,name=attribute_name,json=attributeName,proto3" json:"attribute_name,omitempty"` AttributeName string `protobuf:"bytes,1,opt,name=attribute_name,json=attributeName,proto3" json:"attribute_name,omitempty"`
FullTagReference string `protobuf:"bytes,2,opt,name=full_tag_reference,json=fullTagReference,proto3" json:"full_tag_reference,omitempty"` FullTagReference string `protobuf:"bytes,2,opt,name=full_tag_reference,json=fullTagReference,proto3" json:"full_tag_reference,omitempty"`
MxDataType int32 `protobuf:"varint,3,opt,name=mx_data_type,json=mxDataType,proto3" json:"mx_data_type,omitempty"` // Raw Galaxy SQL `dbo.data_type` identifier, passed through unchanged.
DataTypeName string `protobuf:"bytes,4,opt,name=data_type_name,json=dataTypeName,proto3" json:"data_type_name,omitempty"` // This is NOT a member of `mxaccess_gateway.v1.MxDataType` — Galaxy's
IsArray bool `protobuf:"varint,5,opt,name=is_array,json=isArray,proto3" json:"is_array,omitempty"` // type enumeration is distinct from MXAccess's wire data-type enum and
ArrayDimension int32 `protobuf:"varint,6,opt,name=array_dimension,json=arrayDimension,proto3" json:"array_dimension,omitempty"` // the two must not be cast or compared. The GalaxyRepository service is
ArrayDimensionPresent bool `protobuf:"varint,7,opt,name=array_dimension_present,json=arrayDimensionPresent,proto3" json:"array_dimension_present,omitempty"` // metadata-only and deliberately does not share types with
MxAttributeCategory int32 `protobuf:"varint,8,opt,name=mx_attribute_category,json=mxAttributeCategory,proto3" json:"mx_attribute_category,omitempty"` // mxaccess_gateway.proto. See docs/GalaxyRepository.md.
SecurityClassification int32 `protobuf:"varint,9,opt,name=security_classification,json=securityClassification,proto3" json:"security_classification,omitempty"` MxDataType int32 `protobuf:"varint,3,opt,name=mx_data_type,json=mxDataType,proto3" json:"mx_data_type,omitempty"`
IsHistorized bool `protobuf:"varint,10,opt,name=is_historized,json=isHistorized,proto3" json:"is_historized,omitempty"` // Human-readable name from Galaxy's `dbo.data_type` table (e.g. "Float",
IsAlarm bool `protobuf:"varint,11,opt,name=is_alarm,json=isAlarm,proto3" json:"is_alarm,omitempty"` // "Integer", "Boolean"). Free-form Galaxy text; not a stable enum.
DataTypeName string `protobuf:"bytes,4,opt,name=data_type_name,json=dataTypeName,proto3" json:"data_type_name,omitempty"`
IsArray bool `protobuf:"varint,5,opt,name=is_array,json=isArray,proto3" json:"is_array,omitempty"`
ArrayDimension int32 `protobuf:"varint,6,opt,name=array_dimension,json=arrayDimension,proto3" json:"array_dimension,omitempty"`
ArrayDimensionPresent bool `protobuf:"varint,7,opt,name=array_dimension_present,json=arrayDimensionPresent,proto3" json:"array_dimension_present,omitempty"`
// Raw Galaxy SQL attribute-category identifier, passed through unchanged.
// Galaxy-specific; not mapped to any gateway enum. See
// docs/GalaxyRepository.md.
MxAttributeCategory int32 `protobuf:"varint,8,opt,name=mx_attribute_category,json=mxAttributeCategory,proto3" json:"mx_attribute_category,omitempty"`
// Raw Galaxy SQL security-classification identifier, passed through
// unchanged. Galaxy-specific; not mapped to any gateway enum. See
// docs/GalaxyRepository.md.
SecurityClassification int32 `protobuf:"varint,9,opt,name=security_classification,json=securityClassification,proto3" json:"security_classification,omitempty"`
IsHistorized bool `protobuf:"varint,10,opt,name=is_historized,json=isHistorized,proto3" json:"is_historized,omitempty"`
IsAlarm bool `protobuf:"varint,11,opt,name=is_alarm,json=isAlarm,proto3" json:"is_alarm,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -3792,9 +3792,11 @@ type WriteSecuredBulkEntry struct {
ItemHandle int32 `protobuf:"varint,1,opt,name=item_handle,json=itemHandle,proto3" json:"item_handle,omitempty"` ItemHandle int32 `protobuf:"varint,1,opt,name=item_handle,json=itemHandle,proto3" json:"item_handle,omitempty"`
CurrentUserId int32 `protobuf:"varint,2,opt,name=current_user_id,json=currentUserId,proto3" json:"current_user_id,omitempty"` CurrentUserId int32 `protobuf:"varint,2,opt,name=current_user_id,json=currentUserId,proto3" json:"current_user_id,omitempty"`
VerifierUserId int32 `protobuf:"varint,3,opt,name=verifier_user_id,json=verifierUserId,proto3" json:"verifier_user_id,omitempty"` VerifierUserId int32 `protobuf:"varint,3,opt,name=verifier_user_id,json=verifierUserId,proto3" json:"verifier_user_id,omitempty"`
Value *MxValue `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` // Credential-sensitive write value. Implementations must not log this field
unknownFields protoimpl.UnknownFields // unless an explicit redacted value-logging path is enabled.
sizeCache protoimpl.SizeCache Value *MxValue `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *WriteSecuredBulkEntry) Reset() { func (x *WriteSecuredBulkEntry) Reset() {
@@ -3914,8 +3916,10 @@ type WriteSecured2BulkEntry struct {
ItemHandle int32 `protobuf:"varint,1,opt,name=item_handle,json=itemHandle,proto3" json:"item_handle,omitempty"` ItemHandle int32 `protobuf:"varint,1,opt,name=item_handle,json=itemHandle,proto3" json:"item_handle,omitempty"`
CurrentUserId int32 `protobuf:"varint,2,opt,name=current_user_id,json=currentUserId,proto3" json:"current_user_id,omitempty"` CurrentUserId int32 `protobuf:"varint,2,opt,name=current_user_id,json=currentUserId,proto3" json:"current_user_id,omitempty"`
VerifierUserId int32 `protobuf:"varint,3,opt,name=verifier_user_id,json=verifierUserId,proto3" json:"verifier_user_id,omitempty"` VerifierUserId int32 `protobuf:"varint,3,opt,name=verifier_user_id,json=verifierUserId,proto3" json:"verifier_user_id,omitempty"`
Value *MxValue `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` // Credential-sensitive write value. Implementations must not log this field
TimestampValue *MxValue `protobuf:"bytes,5,opt,name=timestamp_value,json=timestampValue,proto3" json:"timestamp_value,omitempty"` // unless an explicit redacted value-logging path is enabled.
Value *MxValue `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
TimestampValue *MxValue `protobuf:"bytes,5,opt,name=timestamp_value,json=timestampValue,proto3" json:"timestamp_value,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -3987,6 +3991,7 @@ func (x *WriteSecured2BulkEntry) GetTimestampValue() *MxValue {
// Bulk Read — snapshot the current value for each requested tag. MXAccess COM // Bulk Read — snapshot the current value for each requested tag. MXAccess COM
// has no synchronous Read; the worker implements ReadBulk as: // has no synchronous Read; the worker implements ReadBulk as:
//
// - If the tag is already in the session's item registry AND that item is // - If the tag is already in the session's item registry AND that item is
// currently advised AND the worker has a cached OnDataChange for it, the // currently advised AND the worker has a cached OnDataChange for it, the
// reply returns the cached value WITHOUT modifying the existing // reply returns the cached value WITHOUT modifying the existing
@@ -5245,9 +5250,11 @@ func (x *BulkSubscribeReply) GetResults() []*SubscribeResult {
// Per-item result for the four bulk write families. `item_handle` mirrors the // Per-item result for the four bulk write families. `item_handle` mirrors the
// request entry's item_handle so callers can correlate inputs to outputs even // request entry's item_handle so callers can correlate inputs to outputs even
// when the gateway's tag-allowlist filter dropped some entries before reaching // when the gateway's per-entry `IConstraintEnforcer.CheckWriteHandleAsync`
// the worker. Per-item failures populate `error_message` + `hresult` and never // filter (see `MxAccessGatewayService.ReplaceWriteBulkEntries` and
// raise — callers iterate and inspect each entry. // `docs/Authorization.md`) dropped some entries before reaching the worker.
// Per-item failures populate `error_message` + `hresult` and never raise —
// callers iterate and inspect each entry.
type BulkWriteResult struct { type BulkWriteResult struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"` ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"`
@@ -5380,6 +5387,20 @@ func (x *BulkWriteReply) GetResults() []*BulkWriteResult {
// an existing live subscription's last OnDataChange (the worker did not touch // an existing live subscription's last OnDataChange (the worker did not touch
// the subscription); false when the worker took the AddItem + Advise + wait + // the subscription); false when the worker took the AddItem + Advise + wait +
// UnAdvise + RemoveItem snapshot lifecycle itself. // UnAdvise + RemoveItem snapshot lifecycle itself.
//
// On `was_successful = true`, `value`, `quality`, `source_timestamp`, and
// `statuses` carry the read data (from the cached subscription or the snapshot
// lifecycle, depending on `was_cached`) and `error_message` is empty. On
// `was_successful = false`, only `server_handle`, `tag_address`, `item_handle`
// (when allocated), `was_cached`, and `error_message` are populated; `value`,
// `quality`, `source_timestamp`, and `statuses` are left at their proto3
// defaults (null / 0 / null / empty) and must not be read as data — they are
// wire-indistinguishable from "value is null with quality bad" data and serve
// only as absent markers. ReadBulk has no `hresult` field by design (its
// outcomes are timeout / cache / lifecycle states, not MXAccess COM return
// codes — see `docs/DesignDecisions.md` "Bulk Command Family"). Per-tag
// failures populate `error_message` and never raise — callers iterate and
// inspect each entry.
type BulkReadResult struct { type BulkReadResult struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"` ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"`
@@ -6528,7 +6549,6 @@ func (x *ActiveAlarmSnapshot) GetLimitValue() *MxValue {
type AcknowledgeAlarmRequest struct { type AcknowledgeAlarmRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
ClientCorrelationId string `protobuf:"bytes,2,opt,name=client_correlation_id,json=clientCorrelationId,proto3" json:"client_correlation_id,omitempty"` ClientCorrelationId string `protobuf:"bytes,2,opt,name=client_correlation_id,json=clientCorrelationId,proto3" json:"client_correlation_id,omitempty"`
// Fully-qualified alarm reference matching OnAlarmTransitionEvent.alarm_full_reference. // Fully-qualified alarm reference matching OnAlarmTransitionEvent.alarm_full_reference.
AlarmFullReference string `protobuf:"bytes,3,opt,name=alarm_full_reference,json=alarmFullReference,proto3" json:"alarm_full_reference,omitempty"` AlarmFullReference string `protobuf:"bytes,3,opt,name=alarm_full_reference,json=alarmFullReference,proto3" json:"alarm_full_reference,omitempty"`
@@ -6571,13 +6591,6 @@ func (*AcknowledgeAlarmRequest) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{77} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{77}
} }
func (x *AcknowledgeAlarmRequest) GetSessionId() string {
if x != nil {
return x.SessionId
}
return ""
}
func (x *AcknowledgeAlarmRequest) GetClientCorrelationId() string { func (x *AcknowledgeAlarmRequest) GetClientCorrelationId() string {
if x != nil { if x != nil {
return x.ClientCorrelationId return x.ClientCorrelationId
@@ -6608,7 +6621,6 @@ func (x *AcknowledgeAlarmRequest) GetOperatorUser() string {
type AcknowledgeAlarmReply struct { type AcknowledgeAlarmReply struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
CorrelationId string `protobuf:"bytes,2,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` CorrelationId string `protobuf:"bytes,2,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"`
ProtocolStatus *ProtocolStatus `protobuf:"bytes,3,opt,name=protocol_status,json=protocolStatus,proto3" json:"protocol_status,omitempty"` ProtocolStatus *ProtocolStatus `protobuf:"bytes,3,opt,name=protocol_status,json=protocolStatus,proto3" json:"protocol_status,omitempty"`
// Native ack return code echoed from the worker. The worker carries the // Native ack return code echoed from the worker. The worker carries the
@@ -6659,13 +6671,6 @@ func (*AcknowledgeAlarmReply) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{78} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{78}
} }
func (x *AcknowledgeAlarmReply) GetSessionId() string {
if x != nil {
return x.SessionId
}
return ""
}
func (x *AcknowledgeAlarmReply) GetCorrelationId() string { func (x *AcknowledgeAlarmReply) GetCorrelationId() string {
if x != nil { if x != nil {
return x.CorrelationId return x.CorrelationId
@@ -6701,31 +6706,31 @@ func (x *AcknowledgeAlarmReply) GetDiagnosticMessage() string {
return "" return ""
} }
type QueryActiveAlarmsRequest struct { // Request to attach to the gateway's central alarm feed (StreamAlarms).
type StreamAlarmsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` ClientCorrelationId string `protobuf:"bytes,1,opt,name=client_correlation_id,json=clientCorrelationId,proto3" json:"client_correlation_id,omitempty"`
ClientCorrelationId string `protobuf:"bytes,2,opt,name=client_correlation_id,json=clientCorrelationId,proto3" json:"client_correlation_id,omitempty"` // Optional alarm-reference prefix scoping the feed to an equipment
// Optional alarm-reference prefix used to scope a partial ConditionRefresh // sub-tree. Empty streams every active alarm.
// (e.g. equipment sub-tree). Empty means full refresh. AlarmFilterPrefix string `protobuf:"bytes,2,opt,name=alarm_filter_prefix,json=alarmFilterPrefix,proto3" json:"alarm_filter_prefix,omitempty"`
AlarmFilterPrefix string `protobuf:"bytes,3,opt,name=alarm_filter_prefix,json=alarmFilterPrefix,proto3" json:"alarm_filter_prefix,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *QueryActiveAlarmsRequest) Reset() { func (x *StreamAlarmsRequest) Reset() {
*x = QueryActiveAlarmsRequest{} *x = StreamAlarmsRequest{}
mi := &file_mxaccess_gateway_proto_msgTypes[79] mi := &file_mxaccess_gateway_proto_msgTypes[79]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
func (x *QueryActiveAlarmsRequest) String() string { func (x *StreamAlarmsRequest) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
} }
func (*QueryActiveAlarmsRequest) ProtoMessage() {} func (*StreamAlarmsRequest) ProtoMessage() {}
func (x *QueryActiveAlarmsRequest) ProtoReflect() protoreflect.Message { func (x *StreamAlarmsRequest) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[79] mi := &file_mxaccess_gateway_proto_msgTypes[79]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -6737,32 +6742,130 @@ func (x *QueryActiveAlarmsRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
// Deprecated: Use QueryActiveAlarmsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use StreamAlarmsRequest.ProtoReflect.Descriptor instead.
func (*QueryActiveAlarmsRequest) Descriptor() ([]byte, []int) { func (*StreamAlarmsRequest) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{79} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{79}
} }
func (x *QueryActiveAlarmsRequest) GetSessionId() string { func (x *StreamAlarmsRequest) GetClientCorrelationId() string {
if x != nil {
return x.SessionId
}
return ""
}
func (x *QueryActiveAlarmsRequest) GetClientCorrelationId() string {
if x != nil { if x != nil {
return x.ClientCorrelationId return x.ClientCorrelationId
} }
return "" return ""
} }
func (x *QueryActiveAlarmsRequest) GetAlarmFilterPrefix() string { func (x *StreamAlarmsRequest) GetAlarmFilterPrefix() string {
if x != nil { if x != nil {
return x.AlarmFilterPrefix return x.AlarmFilterPrefix
} }
return "" return ""
} }
// One message on the StreamAlarms feed. The stream opens with one
// `active_alarm` per currently-active alarm, then a single
// `snapshot_complete`, then a `transition` for every subsequent change.
type AlarmFeedMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Payload:
//
// *AlarmFeedMessage_ActiveAlarm
// *AlarmFeedMessage_SnapshotComplete
// *AlarmFeedMessage_Transition
Payload isAlarmFeedMessage_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AlarmFeedMessage) Reset() {
*x = AlarmFeedMessage{}
mi := &file_mxaccess_gateway_proto_msgTypes[80]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AlarmFeedMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AlarmFeedMessage) ProtoMessage() {}
func (x *AlarmFeedMessage) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[80]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AlarmFeedMessage.ProtoReflect.Descriptor instead.
func (*AlarmFeedMessage) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{80}
}
func (x *AlarmFeedMessage) GetPayload() isAlarmFeedMessage_Payload {
if x != nil {
return x.Payload
}
return nil
}
func (x *AlarmFeedMessage) GetActiveAlarm() *ActiveAlarmSnapshot {
if x != nil {
if x, ok := x.Payload.(*AlarmFeedMessage_ActiveAlarm); ok {
return x.ActiveAlarm
}
}
return nil
}
func (x *AlarmFeedMessage) GetSnapshotComplete() bool {
if x != nil {
if x, ok := x.Payload.(*AlarmFeedMessage_SnapshotComplete); ok {
return x.SnapshotComplete
}
}
return false
}
func (x *AlarmFeedMessage) GetTransition() *OnAlarmTransitionEvent {
if x != nil {
if x, ok := x.Payload.(*AlarmFeedMessage_Transition); ok {
return x.Transition
}
}
return nil
}
type isAlarmFeedMessage_Payload interface {
isAlarmFeedMessage_Payload()
}
type AlarmFeedMessage_ActiveAlarm struct {
// Part of the initial active-alarm snapshot (ConditionRefresh).
ActiveAlarm *ActiveAlarmSnapshot `protobuf:"bytes,1,opt,name=active_alarm,json=activeAlarm,proto3,oneof"`
}
type AlarmFeedMessage_SnapshotComplete struct {
// Sentinel: the initial snapshot is fully delivered and `transition`
// messages follow. Always true when present.
SnapshotComplete bool `protobuf:"varint,2,opt,name=snapshot_complete,json=snapshotComplete,proto3,oneof"`
}
type AlarmFeedMessage_Transition struct {
// A live alarm state change (raise / acknowledge / clear).
Transition *OnAlarmTransitionEvent `protobuf:"bytes,3,opt,name=transition,proto3,oneof"`
}
func (*AlarmFeedMessage_ActiveAlarm) isAlarmFeedMessage_Payload() {}
func (*AlarmFeedMessage_SnapshotComplete) isAlarmFeedMessage_Payload() {}
func (*AlarmFeedMessage_Transition) isAlarmFeedMessage_Payload() {}
type MxStatusProxy struct { type MxStatusProxy struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// Mirrors the `success` member of the MXAccess MXSTATUS_PROXY struct // Mirrors the `success` member of the MXAccess MXSTATUS_PROXY struct
@@ -6787,7 +6890,7 @@ type MxStatusProxy struct {
func (x *MxStatusProxy) Reset() { func (x *MxStatusProxy) Reset() {
*x = MxStatusProxy{} *x = MxStatusProxy{}
mi := &file_mxaccess_gateway_proto_msgTypes[80] mi := &file_mxaccess_gateway_proto_msgTypes[81]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -6799,7 +6902,7 @@ func (x *MxStatusProxy) String() string {
func (*MxStatusProxy) ProtoMessage() {} func (*MxStatusProxy) ProtoMessage() {}
func (x *MxStatusProxy) ProtoReflect() protoreflect.Message { func (x *MxStatusProxy) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[80] mi := &file_mxaccess_gateway_proto_msgTypes[81]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -6812,7 +6915,7 @@ func (x *MxStatusProxy) ProtoReflect() protoreflect.Message {
// Deprecated: Use MxStatusProxy.ProtoReflect.Descriptor instead. // Deprecated: Use MxStatusProxy.ProtoReflect.Descriptor instead.
func (*MxStatusProxy) Descriptor() ([]byte, []int) { func (*MxStatusProxy) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{80} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{81}
} }
func (x *MxStatusProxy) GetSuccess() int32 { func (x *MxStatusProxy) GetSuccess() int32 {
@@ -6889,7 +6992,7 @@ type MxValue struct {
func (x *MxValue) Reset() { func (x *MxValue) Reset() {
*x = MxValue{} *x = MxValue{}
mi := &file_mxaccess_gateway_proto_msgTypes[81] mi := &file_mxaccess_gateway_proto_msgTypes[82]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -6901,7 +7004,7 @@ func (x *MxValue) String() string {
func (*MxValue) ProtoMessage() {} func (*MxValue) ProtoMessage() {}
func (x *MxValue) ProtoReflect() protoreflect.Message { func (x *MxValue) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[81] mi := &file_mxaccess_gateway_proto_msgTypes[82]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -6914,7 +7017,7 @@ func (x *MxValue) ProtoReflect() protoreflect.Message {
// Deprecated: Use MxValue.ProtoReflect.Descriptor instead. // Deprecated: Use MxValue.ProtoReflect.Descriptor instead.
func (*MxValue) Descriptor() ([]byte, []int) { func (*MxValue) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{81} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{82}
} }
func (x *MxValue) GetDataType() MxDataType { func (x *MxValue) GetDataType() MxDataType {
@@ -7122,7 +7225,7 @@ type MxArray struct {
func (x *MxArray) Reset() { func (x *MxArray) Reset() {
*x = MxArray{} *x = MxArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[82] mi := &file_mxaccess_gateway_proto_msgTypes[83]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7134,7 +7237,7 @@ func (x *MxArray) String() string {
func (*MxArray) ProtoMessage() {} func (*MxArray) ProtoMessage() {}
func (x *MxArray) ProtoReflect() protoreflect.Message { func (x *MxArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[82] mi := &file_mxaccess_gateway_proto_msgTypes[83]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7147,7 +7250,7 @@ func (x *MxArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use MxArray.ProtoReflect.Descriptor instead. // Deprecated: Use MxArray.ProtoReflect.Descriptor instead.
func (*MxArray) Descriptor() ([]byte, []int) { func (*MxArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{82} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{83}
} }
func (x *MxArray) GetElementDataType() MxDataType { func (x *MxArray) GetElementDataType() MxDataType {
@@ -7325,7 +7428,7 @@ type BoolArray struct {
func (x *BoolArray) Reset() { func (x *BoolArray) Reset() {
*x = BoolArray{} *x = BoolArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[83] mi := &file_mxaccess_gateway_proto_msgTypes[84]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7337,7 +7440,7 @@ func (x *BoolArray) String() string {
func (*BoolArray) ProtoMessage() {} func (*BoolArray) ProtoMessage() {}
func (x *BoolArray) ProtoReflect() protoreflect.Message { func (x *BoolArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[83] mi := &file_mxaccess_gateway_proto_msgTypes[84]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7350,7 +7453,7 @@ func (x *BoolArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use BoolArray.ProtoReflect.Descriptor instead. // Deprecated: Use BoolArray.ProtoReflect.Descriptor instead.
func (*BoolArray) Descriptor() ([]byte, []int) { func (*BoolArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{83} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{84}
} }
func (x *BoolArray) GetValues() []bool { func (x *BoolArray) GetValues() []bool {
@@ -7369,7 +7472,7 @@ type Int32Array struct {
func (x *Int32Array) Reset() { func (x *Int32Array) Reset() {
*x = Int32Array{} *x = Int32Array{}
mi := &file_mxaccess_gateway_proto_msgTypes[84] mi := &file_mxaccess_gateway_proto_msgTypes[85]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7381,7 +7484,7 @@ func (x *Int32Array) String() string {
func (*Int32Array) ProtoMessage() {} func (*Int32Array) ProtoMessage() {}
func (x *Int32Array) ProtoReflect() protoreflect.Message { func (x *Int32Array) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[84] mi := &file_mxaccess_gateway_proto_msgTypes[85]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7394,7 +7497,7 @@ func (x *Int32Array) ProtoReflect() protoreflect.Message {
// Deprecated: Use Int32Array.ProtoReflect.Descriptor instead. // Deprecated: Use Int32Array.ProtoReflect.Descriptor instead.
func (*Int32Array) Descriptor() ([]byte, []int) { func (*Int32Array) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{84} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{85}
} }
func (x *Int32Array) GetValues() []int32 { func (x *Int32Array) GetValues() []int32 {
@@ -7413,7 +7516,7 @@ type Int64Array struct {
func (x *Int64Array) Reset() { func (x *Int64Array) Reset() {
*x = Int64Array{} *x = Int64Array{}
mi := &file_mxaccess_gateway_proto_msgTypes[85] mi := &file_mxaccess_gateway_proto_msgTypes[86]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7425,7 +7528,7 @@ func (x *Int64Array) String() string {
func (*Int64Array) ProtoMessage() {} func (*Int64Array) ProtoMessage() {}
func (x *Int64Array) ProtoReflect() protoreflect.Message { func (x *Int64Array) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[85] mi := &file_mxaccess_gateway_proto_msgTypes[86]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7438,7 +7541,7 @@ func (x *Int64Array) ProtoReflect() protoreflect.Message {
// Deprecated: Use Int64Array.ProtoReflect.Descriptor instead. // Deprecated: Use Int64Array.ProtoReflect.Descriptor instead.
func (*Int64Array) Descriptor() ([]byte, []int) { func (*Int64Array) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{85} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{86}
} }
func (x *Int64Array) GetValues() []int64 { func (x *Int64Array) GetValues() []int64 {
@@ -7457,7 +7560,7 @@ type FloatArray struct {
func (x *FloatArray) Reset() { func (x *FloatArray) Reset() {
*x = FloatArray{} *x = FloatArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[86] mi := &file_mxaccess_gateway_proto_msgTypes[87]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7469,7 +7572,7 @@ func (x *FloatArray) String() string {
func (*FloatArray) ProtoMessage() {} func (*FloatArray) ProtoMessage() {}
func (x *FloatArray) ProtoReflect() protoreflect.Message { func (x *FloatArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[86] mi := &file_mxaccess_gateway_proto_msgTypes[87]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7482,7 +7585,7 @@ func (x *FloatArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use FloatArray.ProtoReflect.Descriptor instead. // Deprecated: Use FloatArray.ProtoReflect.Descriptor instead.
func (*FloatArray) Descriptor() ([]byte, []int) { func (*FloatArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{86} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{87}
} }
func (x *FloatArray) GetValues() []float32 { func (x *FloatArray) GetValues() []float32 {
@@ -7501,7 +7604,7 @@ type DoubleArray struct {
func (x *DoubleArray) Reset() { func (x *DoubleArray) Reset() {
*x = DoubleArray{} *x = DoubleArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[87] mi := &file_mxaccess_gateway_proto_msgTypes[88]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7513,7 +7616,7 @@ func (x *DoubleArray) String() string {
func (*DoubleArray) ProtoMessage() {} func (*DoubleArray) ProtoMessage() {}
func (x *DoubleArray) ProtoReflect() protoreflect.Message { func (x *DoubleArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[87] mi := &file_mxaccess_gateway_proto_msgTypes[88]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7526,7 +7629,7 @@ func (x *DoubleArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use DoubleArray.ProtoReflect.Descriptor instead. // Deprecated: Use DoubleArray.ProtoReflect.Descriptor instead.
func (*DoubleArray) Descriptor() ([]byte, []int) { func (*DoubleArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{87} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{88}
} }
func (x *DoubleArray) GetValues() []float64 { func (x *DoubleArray) GetValues() []float64 {
@@ -7545,7 +7648,7 @@ type StringArray struct {
func (x *StringArray) Reset() { func (x *StringArray) Reset() {
*x = StringArray{} *x = StringArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[88] mi := &file_mxaccess_gateway_proto_msgTypes[89]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7557,7 +7660,7 @@ func (x *StringArray) String() string {
func (*StringArray) ProtoMessage() {} func (*StringArray) ProtoMessage() {}
func (x *StringArray) ProtoReflect() protoreflect.Message { func (x *StringArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[88] mi := &file_mxaccess_gateway_proto_msgTypes[89]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7570,7 +7673,7 @@ func (x *StringArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use StringArray.ProtoReflect.Descriptor instead. // Deprecated: Use StringArray.ProtoReflect.Descriptor instead.
func (*StringArray) Descriptor() ([]byte, []int) { func (*StringArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{88} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{89}
} }
func (x *StringArray) GetValues() []string { func (x *StringArray) GetValues() []string {
@@ -7589,7 +7692,7 @@ type TimestampArray struct {
func (x *TimestampArray) Reset() { func (x *TimestampArray) Reset() {
*x = TimestampArray{} *x = TimestampArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[89] mi := &file_mxaccess_gateway_proto_msgTypes[90]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7601,7 +7704,7 @@ func (x *TimestampArray) String() string {
func (*TimestampArray) ProtoMessage() {} func (*TimestampArray) ProtoMessage() {}
func (x *TimestampArray) ProtoReflect() protoreflect.Message { func (x *TimestampArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[89] mi := &file_mxaccess_gateway_proto_msgTypes[90]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7614,7 +7717,7 @@ func (x *TimestampArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use TimestampArray.ProtoReflect.Descriptor instead. // Deprecated: Use TimestampArray.ProtoReflect.Descriptor instead.
func (*TimestampArray) Descriptor() ([]byte, []int) { func (*TimestampArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{89} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{90}
} }
func (x *TimestampArray) GetValues() []*timestamppb.Timestamp { func (x *TimestampArray) GetValues() []*timestamppb.Timestamp {
@@ -7633,7 +7736,7 @@ type RawArray struct {
func (x *RawArray) Reset() { func (x *RawArray) Reset() {
*x = RawArray{} *x = RawArray{}
mi := &file_mxaccess_gateway_proto_msgTypes[90] mi := &file_mxaccess_gateway_proto_msgTypes[91]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7645,7 +7748,7 @@ func (x *RawArray) String() string {
func (*RawArray) ProtoMessage() {} func (*RawArray) ProtoMessage() {}
func (x *RawArray) ProtoReflect() protoreflect.Message { func (x *RawArray) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[90] mi := &file_mxaccess_gateway_proto_msgTypes[91]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7658,7 +7761,7 @@ func (x *RawArray) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawArray.ProtoReflect.Descriptor instead. // Deprecated: Use RawArray.ProtoReflect.Descriptor instead.
func (*RawArray) Descriptor() ([]byte, []int) { func (*RawArray) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{90} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{91}
} }
func (x *RawArray) GetValues() [][]byte { func (x *RawArray) GetValues() [][]byte {
@@ -7678,7 +7781,7 @@ type ProtocolStatus struct {
func (x *ProtocolStatus) Reset() { func (x *ProtocolStatus) Reset() {
*x = ProtocolStatus{} *x = ProtocolStatus{}
mi := &file_mxaccess_gateway_proto_msgTypes[91] mi := &file_mxaccess_gateway_proto_msgTypes[92]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -7690,7 +7793,7 @@ func (x *ProtocolStatus) String() string {
func (*ProtocolStatus) ProtoMessage() {} func (*ProtocolStatus) ProtoMessage() {}
func (x *ProtocolStatus) ProtoReflect() protoreflect.Message { func (x *ProtocolStatus) ProtoReflect() protoreflect.Message {
mi := &file_mxaccess_gateway_proto_msgTypes[91] mi := &file_mxaccess_gateway_proto_msgTypes[92]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -7703,7 +7806,7 @@ func (x *ProtocolStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProtocolStatus.ProtoReflect.Descriptor instead. // Deprecated: Use ProtocolStatus.ProtoReflect.Descriptor instead.
func (*ProtocolStatus) Descriptor() ([]byte, []int) { func (*ProtocolStatus) Descriptor() ([]byte, []int) {
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{91} return file_mxaccess_gateway_proto_rawDescGZIP(), []int{92}
} }
func (x *ProtocolStatus) GetCode() ProtocolStatusCode { func (x *ProtocolStatus) GetCode() ProtocolStatusCode {
@@ -8155,29 +8258,32 @@ const file_mxaccess_gateway_proto_rawDesc = "" +
"\x10operator_comment\x18\v \x01(\tR\x0foperatorComment\x12A\n" + "\x10operator_comment\x18\v \x01(\tR\x0foperatorComment\x12A\n" +
"\rcurrent_value\x18\f \x01(\v2\x1c.mxaccess_gateway.v1.MxValueR\fcurrentValue\x12=\n" + "\rcurrent_value\x18\f \x01(\v2\x1c.mxaccess_gateway.v1.MxValueR\fcurrentValue\x12=\n" +
"\vlimit_value\x18\r \x01(\v2\x1c.mxaccess_gateway.v1.MxValueR\n" + "\vlimit_value\x18\r \x01(\v2\x1c.mxaccess_gateway.v1.MxValueR\n" +
"limitValue\"\xdd\x01\n" + "limitValue\"\xd0\x01\n" +
"\x17AcknowledgeAlarmRequest\x12\x1d\n" + "\x17AcknowledgeAlarmRequest\x122\n" +
"\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x122\n" +
"\x15client_correlation_id\x18\x02 \x01(\tR\x13clientCorrelationId\x120\n" + "\x15client_correlation_id\x18\x02 \x01(\tR\x13clientCorrelationId\x120\n" +
"\x14alarm_full_reference\x18\x03 \x01(\tR\x12alarmFullReference\x12\x18\n" + "\x14alarm_full_reference\x18\x03 \x01(\tR\x12alarmFullReference\x12\x18\n" +
"\acomment\x18\x04 \x01(\tR\acomment\x12#\n" + "\acomment\x18\x04 \x01(\tR\acomment\x12#\n" +
"\roperator_user\x18\x05 \x01(\tR\foperatorUser\"\xc1\x02\n" + "\roperator_user\x18\x05 \x01(\tR\foperatorUserJ\x04\b\x01\x10\x02R\n" +
"\x15AcknowledgeAlarmReply\x12\x1d\n" + "session_id\"\xb4\x02\n" +
"\n" + "\x15AcknowledgeAlarmReply\x12%\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12%\n" +
"\x0ecorrelation_id\x18\x02 \x01(\tR\rcorrelationId\x12L\n" + "\x0ecorrelation_id\x18\x02 \x01(\tR\rcorrelationId\x12L\n" +
"\x0fprotocol_status\x18\x03 \x01(\v2#.mxaccess_gateway.v1.ProtocolStatusR\x0eprotocolStatus\x12\x1d\n" + "\x0fprotocol_status\x18\x03 \x01(\v2#.mxaccess_gateway.v1.ProtocolStatusR\x0eprotocolStatus\x12\x1d\n" +
"\ahresult\x18\x04 \x01(\x05H\x00R\ahresult\x88\x01\x01\x12:\n" + "\ahresult\x18\x04 \x01(\x05H\x00R\ahresult\x88\x01\x01\x12:\n" +
"\x06status\x18\x05 \x01(\v2\".mxaccess_gateway.v1.MxStatusProxyR\x06status\x12-\n" + "\x06status\x18\x05 \x01(\v2\".mxaccess_gateway.v1.MxStatusProxyR\x06status\x12-\n" +
"\x12diagnostic_message\x18\x06 \x01(\tR\x11diagnosticMessageB\n" + "\x12diagnostic_message\x18\x06 \x01(\tR\x11diagnosticMessageB\n" +
"\n" + "\n" +
"\b_hresult\"\x9d\x01\n" + "\b_hresultJ\x04\b\x01\x10\x02R\n" +
"\x18QueryActiveAlarmsRequest\x12\x1d\n" + "session_id\"y\n" +
"\x13StreamAlarmsRequest\x122\n" +
"\x15client_correlation_id\x18\x01 \x01(\tR\x13clientCorrelationId\x12.\n" +
"\x13alarm_filter_prefix\x18\x02 \x01(\tR\x11alarmFilterPrefix\"\xea\x01\n" +
"\x10AlarmFeedMessage\x12M\n" +
"\factive_alarm\x18\x01 \x01(\v2(.mxaccess_gateway.v1.ActiveAlarmSnapshotH\x00R\vactiveAlarm\x12-\n" +
"\x11snapshot_complete\x18\x02 \x01(\bH\x00R\x10snapshotComplete\x12M\n" +
"\n" + "\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x122\n" + "transition\x18\x03 \x01(\v2+.mxaccess_gateway.v1.OnAlarmTransitionEventH\x00R\n" +
"\x15client_correlation_id\x18\x02 \x01(\tR\x13clientCorrelationId\x12.\n" + "transitionB\t\n" +
"\x13alarm_filter_prefix\x18\x03 \x01(\tR\x11alarmFilterPrefix\"\xbe\x02\n" + "\apayload\"\xbe\x02\n" +
"\rMxStatusProxy\x12\x18\n" + "\rMxStatusProxy\x12\x18\n" +
"\asuccess\x18\x01 \x01(\x05R\asuccess\x12A\n" + "\asuccess\x18\x01 \x01(\x05R\asuccess\x12A\n" +
"\bcategory\x18\x02 \x01(\x0e2%.mxaccess_gateway.v1.MxStatusCategoryR\bcategory\x12D\n" + "\bcategory\x18\x02 \x01(\x0e2%.mxaccess_gateway.v1.MxStatusCategoryR\bcategory\x12D\n" +
@@ -8377,14 +8483,14 @@ const file_mxaccess_gateway_proto_rawDesc = "" +
"\x13SESSION_STATE_READY\x10\x06\x12\x19\n" + "\x13SESSION_STATE_READY\x10\x06\x12\x19\n" +
"\x15SESSION_STATE_CLOSING\x10\a\x12\x18\n" + "\x15SESSION_STATE_CLOSING\x10\a\x12\x18\n" +
"\x14SESSION_STATE_CLOSED\x10\b\x12\x19\n" + "\x14SESSION_STATE_CLOSED\x10\b\x12\x19\n" +
"\x15SESSION_STATE_FAULTED\x10\t2\xe0\x04\n" + "\x15SESSION_STATE_FAULTED\x10\t2\xd3\x04\n" +
"\x0fMxAccessGateway\x12]\n" + "\x0fMxAccessGateway\x12]\n" +
"\vOpenSession\x12'.mxaccess_gateway.v1.OpenSessionRequest\x1a%.mxaccess_gateway.v1.OpenSessionReply\x12`\n" + "\vOpenSession\x12'.mxaccess_gateway.v1.OpenSessionRequest\x1a%.mxaccess_gateway.v1.OpenSessionReply\x12`\n" +
"\fCloseSession\x12(.mxaccess_gateway.v1.CloseSessionRequest\x1a&.mxaccess_gateway.v1.CloseSessionReply\x12T\n" + "\fCloseSession\x12(.mxaccess_gateway.v1.CloseSessionRequest\x1a&.mxaccess_gateway.v1.CloseSessionReply\x12T\n" +
"\x06Invoke\x12%.mxaccess_gateway.v1.MxCommandRequest\x1a#.mxaccess_gateway.v1.MxCommandReply\x12X\n" + "\x06Invoke\x12%.mxaccess_gateway.v1.MxCommandRequest\x1a#.mxaccess_gateway.v1.MxCommandReply\x12X\n" +
"\fStreamEvents\x12(.mxaccess_gateway.v1.StreamEventsRequest\x1a\x1c.mxaccess_gateway.v1.MxEvent0\x01\x12l\n" + "\fStreamEvents\x12(.mxaccess_gateway.v1.StreamEventsRequest\x1a\x1c.mxaccess_gateway.v1.MxEvent0\x01\x12l\n" +
"\x10AcknowledgeAlarm\x12,.mxaccess_gateway.v1.AcknowledgeAlarmRequest\x1a*.mxaccess_gateway.v1.AcknowledgeAlarmReply\x12n\n" + "\x10AcknowledgeAlarm\x12,.mxaccess_gateway.v1.AcknowledgeAlarmRequest\x1a*.mxaccess_gateway.v1.AcknowledgeAlarmReply\x12a\n" +
"\x11QueryActiveAlarms\x12-.mxaccess_gateway.v1.QueryActiveAlarmsRequest\x1a(.mxaccess_gateway.v1.ActiveAlarmSnapshot0\x01B\x1c\xaa\x02\x19MxGateway.Contracts.Protob\x06proto3" "\fStreamAlarms\x12(.mxaccess_gateway.v1.StreamAlarmsRequest\x1a%.mxaccess_gateway.v1.AlarmFeedMessage0\x01B\x1c\xaa\x02\x19MxGateway.Contracts.Protob\x06proto3"
var ( var (
file_mxaccess_gateway_proto_rawDescOnce sync.Once file_mxaccess_gateway_proto_rawDescOnce sync.Once
@@ -8399,7 +8505,7 @@ func file_mxaccess_gateway_proto_rawDescGZIP() []byte {
} }
var file_mxaccess_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 9) var file_mxaccess_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 9)
var file_mxaccess_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 92) var file_mxaccess_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 93)
var file_mxaccess_gateway_proto_goTypes = []any{ var file_mxaccess_gateway_proto_goTypes = []any{
(MxCommandKind)(0), // 0: mxaccess_gateway.v1.MxCommandKind (MxCommandKind)(0), // 0: mxaccess_gateway.v1.MxCommandKind
(MxEventFamily)(0), // 1: mxaccess_gateway.v1.MxEventFamily (MxEventFamily)(0), // 1: mxaccess_gateway.v1.MxEventFamily
@@ -8489,28 +8595,29 @@ var file_mxaccess_gateway_proto_goTypes = []any{
(*ActiveAlarmSnapshot)(nil), // 85: mxaccess_gateway.v1.ActiveAlarmSnapshot (*ActiveAlarmSnapshot)(nil), // 85: mxaccess_gateway.v1.ActiveAlarmSnapshot
(*AcknowledgeAlarmRequest)(nil), // 86: mxaccess_gateway.v1.AcknowledgeAlarmRequest (*AcknowledgeAlarmRequest)(nil), // 86: mxaccess_gateway.v1.AcknowledgeAlarmRequest
(*AcknowledgeAlarmReply)(nil), // 87: mxaccess_gateway.v1.AcknowledgeAlarmReply (*AcknowledgeAlarmReply)(nil), // 87: mxaccess_gateway.v1.AcknowledgeAlarmReply
(*QueryActiveAlarmsRequest)(nil), // 88: mxaccess_gateway.v1.QueryActiveAlarmsRequest (*StreamAlarmsRequest)(nil), // 88: mxaccess_gateway.v1.StreamAlarmsRequest
(*MxStatusProxy)(nil), // 89: mxaccess_gateway.v1.MxStatusProxy (*AlarmFeedMessage)(nil), // 89: mxaccess_gateway.v1.AlarmFeedMessage
(*MxValue)(nil), // 90: mxaccess_gateway.v1.MxValue (*MxStatusProxy)(nil), // 90: mxaccess_gateway.v1.MxStatusProxy
(*MxArray)(nil), // 91: mxaccess_gateway.v1.MxArray (*MxValue)(nil), // 91: mxaccess_gateway.v1.MxValue
(*BoolArray)(nil), // 92: mxaccess_gateway.v1.BoolArray (*MxArray)(nil), // 92: mxaccess_gateway.v1.MxArray
(*Int32Array)(nil), // 93: mxaccess_gateway.v1.Int32Array (*BoolArray)(nil), // 93: mxaccess_gateway.v1.BoolArray
(*Int64Array)(nil), // 94: mxaccess_gateway.v1.Int64Array (*Int32Array)(nil), // 94: mxaccess_gateway.v1.Int32Array
(*FloatArray)(nil), // 95: mxaccess_gateway.v1.FloatArray (*Int64Array)(nil), // 95: mxaccess_gateway.v1.Int64Array
(*DoubleArray)(nil), // 96: mxaccess_gateway.v1.DoubleArray (*FloatArray)(nil), // 96: mxaccess_gateway.v1.FloatArray
(*StringArray)(nil), // 97: mxaccess_gateway.v1.StringArray (*DoubleArray)(nil), // 97: mxaccess_gateway.v1.DoubleArray
(*TimestampArray)(nil), // 98: mxaccess_gateway.v1.TimestampArray (*StringArray)(nil), // 98: mxaccess_gateway.v1.StringArray
(*RawArray)(nil), // 99: mxaccess_gateway.v1.RawArray (*TimestampArray)(nil), // 99: mxaccess_gateway.v1.TimestampArray
(*ProtocolStatus)(nil), // 100: mxaccess_gateway.v1.ProtocolStatus (*RawArray)(nil), // 100: mxaccess_gateway.v1.RawArray
(*durationpb.Duration)(nil), // 101: google.protobuf.Duration (*ProtocolStatus)(nil), // 101: mxaccess_gateway.v1.ProtocolStatus
(*timestamppb.Timestamp)(nil), // 102: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 102: google.protobuf.Duration
(*timestamppb.Timestamp)(nil), // 103: google.protobuf.Timestamp
} }
var file_mxaccess_gateway_proto_depIdxs = []int32{ var file_mxaccess_gateway_proto_depIdxs = []int32{
101, // 0: mxaccess_gateway.v1.OpenSessionRequest.command_timeout:type_name -> google.protobuf.Duration 102, // 0: mxaccess_gateway.v1.OpenSessionRequest.command_timeout:type_name -> google.protobuf.Duration
101, // 1: mxaccess_gateway.v1.OpenSessionReply.default_command_timeout:type_name -> google.protobuf.Duration 102, // 1: mxaccess_gateway.v1.OpenSessionReply.default_command_timeout:type_name -> google.protobuf.Duration
100, // 2: mxaccess_gateway.v1.OpenSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus 101, // 2: mxaccess_gateway.v1.OpenSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
8, // 3: mxaccess_gateway.v1.CloseSessionReply.final_state:type_name -> mxaccess_gateway.v1.SessionState 8, // 3: mxaccess_gateway.v1.CloseSessionReply.final_state:type_name -> mxaccess_gateway.v1.SessionState
100, // 4: mxaccess_gateway.v1.CloseSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus 101, // 4: mxaccess_gateway.v1.CloseSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
15, // 5: mxaccess_gateway.v1.MxCommandRequest.command:type_name -> mxaccess_gateway.v1.MxCommand 15, // 5: mxaccess_gateway.v1.MxCommandRequest.command:type_name -> mxaccess_gateway.v1.MxCommand
0, // 6: mxaccess_gateway.v1.MxCommand.kind:type_name -> mxaccess_gateway.v1.MxCommandKind 0, // 6: mxaccess_gateway.v1.MxCommand.kind:type_name -> mxaccess_gateway.v1.MxCommandKind
16, // 7: mxaccess_gateway.v1.MxCommand.register:type_name -> mxaccess_gateway.v1.RegisterCommand 16, // 7: mxaccess_gateway.v1.MxCommand.register:type_name -> mxaccess_gateway.v1.RegisterCommand
@@ -8552,27 +8659,27 @@ var file_mxaccess_gateway_proto_depIdxs = []int32{
56, // 43: mxaccess_gateway.v1.MxCommand.get_worker_info:type_name -> mxaccess_gateway.v1.GetWorkerInfoCommand 56, // 43: mxaccess_gateway.v1.MxCommand.get_worker_info:type_name -> mxaccess_gateway.v1.GetWorkerInfoCommand
57, // 44: mxaccess_gateway.v1.MxCommand.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsCommand 57, // 44: mxaccess_gateway.v1.MxCommand.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsCommand
58, // 45: mxaccess_gateway.v1.MxCommand.shutdown_worker:type_name -> mxaccess_gateway.v1.ShutdownWorkerCommand 58, // 45: mxaccess_gateway.v1.MxCommand.shutdown_worker:type_name -> mxaccess_gateway.v1.ShutdownWorkerCommand
90, // 46: mxaccess_gateway.v1.WriteCommand.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 46: mxaccess_gateway.v1.WriteCommand.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 47: mxaccess_gateway.v1.Write2Command.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 47: mxaccess_gateway.v1.Write2Command.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 48: mxaccess_gateway.v1.Write2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 48: mxaccess_gateway.v1.Write2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
90, // 49: mxaccess_gateway.v1.WriteSecuredCommand.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 49: mxaccess_gateway.v1.WriteSecuredCommand.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 50: mxaccess_gateway.v1.WriteSecured2Command.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 50: mxaccess_gateway.v1.WriteSecured2Command.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 51: mxaccess_gateway.v1.WriteSecured2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 51: mxaccess_gateway.v1.WriteSecured2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
46, // 52: mxaccess_gateway.v1.WriteBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteBulkEntry 46, // 52: mxaccess_gateway.v1.WriteBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteBulkEntry
90, // 53: mxaccess_gateway.v1.WriteBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 53: mxaccess_gateway.v1.WriteBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
48, // 54: mxaccess_gateway.v1.Write2BulkCommand.entries:type_name -> mxaccess_gateway.v1.Write2BulkEntry 48, // 54: mxaccess_gateway.v1.Write2BulkCommand.entries:type_name -> mxaccess_gateway.v1.Write2BulkEntry
90, // 55: mxaccess_gateway.v1.Write2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 55: mxaccess_gateway.v1.Write2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 56: mxaccess_gateway.v1.Write2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 56: mxaccess_gateway.v1.Write2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
50, // 57: mxaccess_gateway.v1.WriteSecuredBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecuredBulkEntry 50, // 57: mxaccess_gateway.v1.WriteSecuredBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecuredBulkEntry
90, // 58: mxaccess_gateway.v1.WriteSecuredBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 58: mxaccess_gateway.v1.WriteSecuredBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
52, // 59: mxaccess_gateway.v1.WriteSecured2BulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecured2BulkEntry 52, // 59: mxaccess_gateway.v1.WriteSecured2BulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecured2BulkEntry
90, // 60: mxaccess_gateway.v1.WriteSecured2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 60: mxaccess_gateway.v1.WriteSecured2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
90, // 61: mxaccess_gateway.v1.WriteSecured2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 61: mxaccess_gateway.v1.WriteSecured2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
101, // 62: mxaccess_gateway.v1.ShutdownWorkerCommand.grace_period:type_name -> google.protobuf.Duration 102, // 62: mxaccess_gateway.v1.ShutdownWorkerCommand.grace_period:type_name -> google.protobuf.Duration
0, // 63: mxaccess_gateway.v1.MxCommandReply.kind:type_name -> mxaccess_gateway.v1.MxCommandKind 0, // 63: mxaccess_gateway.v1.MxCommandReply.kind:type_name -> mxaccess_gateway.v1.MxCommandKind
100, // 64: mxaccess_gateway.v1.MxCommandReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus 101, // 64: mxaccess_gateway.v1.MxCommandReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
90, // 65: mxaccess_gateway.v1.MxCommandReply.return_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 65: mxaccess_gateway.v1.MxCommandReply.return_value:type_name -> mxaccess_gateway.v1.MxValue
89, // 66: mxaccess_gateway.v1.MxCommandReply.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 66: mxaccess_gateway.v1.MxCommandReply.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
60, // 67: mxaccess_gateway.v1.MxCommandReply.register:type_name -> mxaccess_gateway.v1.RegisterReply 60, // 67: mxaccess_gateway.v1.MxCommandReply.register:type_name -> mxaccess_gateway.v1.RegisterReply
61, // 68: mxaccess_gateway.v1.MxCommandReply.add_item:type_name -> mxaccess_gateway.v1.AddItemReply 61, // 68: mxaccess_gateway.v1.MxCommandReply.add_item:type_name -> mxaccess_gateway.v1.AddItemReply
62, // 69: mxaccess_gateway.v1.MxCommandReply.add_item2:type_name -> mxaccess_gateway.v1.AddItem2Reply 62, // 69: mxaccess_gateway.v1.MxCommandReply.add_item2:type_name -> mxaccess_gateway.v1.AddItem2Reply
@@ -8597,77 +8704,79 @@ var file_mxaccess_gateway_proto_depIdxs = []int32{
74, // 88: mxaccess_gateway.v1.MxCommandReply.session_state:type_name -> mxaccess_gateway.v1.SessionStateReply 74, // 88: mxaccess_gateway.v1.MxCommandReply.session_state:type_name -> mxaccess_gateway.v1.SessionStateReply
75, // 89: mxaccess_gateway.v1.MxCommandReply.worker_info:type_name -> mxaccess_gateway.v1.WorkerInfoReply 75, // 89: mxaccess_gateway.v1.MxCommandReply.worker_info:type_name -> mxaccess_gateway.v1.WorkerInfoReply
76, // 90: mxaccess_gateway.v1.MxCommandReply.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsReply 76, // 90: mxaccess_gateway.v1.MxCommandReply.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsReply
89, // 91: mxaccess_gateway.v1.SuspendReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 91: mxaccess_gateway.v1.SuspendReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
89, // 92: mxaccess_gateway.v1.ActivateReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 92: mxaccess_gateway.v1.ActivateReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
68, // 93: mxaccess_gateway.v1.BulkSubscribeReply.results:type_name -> mxaccess_gateway.v1.SubscribeResult 68, // 93: mxaccess_gateway.v1.BulkSubscribeReply.results:type_name -> mxaccess_gateway.v1.SubscribeResult
89, // 94: mxaccess_gateway.v1.BulkWriteResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 94: mxaccess_gateway.v1.BulkWriteResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
70, // 95: mxaccess_gateway.v1.BulkWriteReply.results:type_name -> mxaccess_gateway.v1.BulkWriteResult 70, // 95: mxaccess_gateway.v1.BulkWriteReply.results:type_name -> mxaccess_gateway.v1.BulkWriteResult
90, // 96: mxaccess_gateway.v1.BulkReadResult.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 96: mxaccess_gateway.v1.BulkReadResult.value:type_name -> mxaccess_gateway.v1.MxValue
102, // 97: mxaccess_gateway.v1.BulkReadResult.source_timestamp:type_name -> google.protobuf.Timestamp 103, // 97: mxaccess_gateway.v1.BulkReadResult.source_timestamp:type_name -> google.protobuf.Timestamp
89, // 98: mxaccess_gateway.v1.BulkReadResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 98: mxaccess_gateway.v1.BulkReadResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
72, // 99: mxaccess_gateway.v1.BulkReadReply.results:type_name -> mxaccess_gateway.v1.BulkReadResult 72, // 99: mxaccess_gateway.v1.BulkReadReply.results:type_name -> mxaccess_gateway.v1.BulkReadResult
8, // 100: mxaccess_gateway.v1.SessionStateReply.state:type_name -> mxaccess_gateway.v1.SessionState 8, // 100: mxaccess_gateway.v1.SessionStateReply.state:type_name -> mxaccess_gateway.v1.SessionState
79, // 101: mxaccess_gateway.v1.DrainEventsReply.events:type_name -> mxaccess_gateway.v1.MxEvent 79, // 101: mxaccess_gateway.v1.DrainEventsReply.events:type_name -> mxaccess_gateway.v1.MxEvent
85, // 102: mxaccess_gateway.v1.QueryActiveAlarmsReplyPayload.snapshots:type_name -> mxaccess_gateway.v1.ActiveAlarmSnapshot 85, // 102: mxaccess_gateway.v1.QueryActiveAlarmsReplyPayload.snapshots:type_name -> mxaccess_gateway.v1.ActiveAlarmSnapshot
1, // 103: mxaccess_gateway.v1.MxEvent.family:type_name -> mxaccess_gateway.v1.MxEventFamily 1, // 103: mxaccess_gateway.v1.MxEvent.family:type_name -> mxaccess_gateway.v1.MxEventFamily
90, // 104: mxaccess_gateway.v1.MxEvent.value:type_name -> mxaccess_gateway.v1.MxValue 91, // 104: mxaccess_gateway.v1.MxEvent.value:type_name -> mxaccess_gateway.v1.MxValue
102, // 105: mxaccess_gateway.v1.MxEvent.source_timestamp:type_name -> google.protobuf.Timestamp 103, // 105: mxaccess_gateway.v1.MxEvent.source_timestamp:type_name -> google.protobuf.Timestamp
89, // 106: mxaccess_gateway.v1.MxEvent.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 106: mxaccess_gateway.v1.MxEvent.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
102, // 107: mxaccess_gateway.v1.MxEvent.worker_timestamp:type_name -> google.protobuf.Timestamp 103, // 107: mxaccess_gateway.v1.MxEvent.worker_timestamp:type_name -> google.protobuf.Timestamp
102, // 108: mxaccess_gateway.v1.MxEvent.gateway_receive_timestamp:type_name -> google.protobuf.Timestamp 103, // 108: mxaccess_gateway.v1.MxEvent.gateway_receive_timestamp:type_name -> google.protobuf.Timestamp
80, // 109: mxaccess_gateway.v1.MxEvent.on_data_change:type_name -> mxaccess_gateway.v1.OnDataChangeEvent 80, // 109: mxaccess_gateway.v1.MxEvent.on_data_change:type_name -> mxaccess_gateway.v1.OnDataChangeEvent
81, // 110: mxaccess_gateway.v1.MxEvent.on_write_complete:type_name -> mxaccess_gateway.v1.OnWriteCompleteEvent 81, // 110: mxaccess_gateway.v1.MxEvent.on_write_complete:type_name -> mxaccess_gateway.v1.OnWriteCompleteEvent
82, // 111: mxaccess_gateway.v1.MxEvent.operation_complete:type_name -> mxaccess_gateway.v1.OperationCompleteEvent 82, // 111: mxaccess_gateway.v1.MxEvent.operation_complete:type_name -> mxaccess_gateway.v1.OperationCompleteEvent
83, // 112: mxaccess_gateway.v1.MxEvent.on_buffered_data_change:type_name -> mxaccess_gateway.v1.OnBufferedDataChangeEvent 83, // 112: mxaccess_gateway.v1.MxEvent.on_buffered_data_change:type_name -> mxaccess_gateway.v1.OnBufferedDataChangeEvent
84, // 113: mxaccess_gateway.v1.MxEvent.on_alarm_transition:type_name -> mxaccess_gateway.v1.OnAlarmTransitionEvent 84, // 113: mxaccess_gateway.v1.MxEvent.on_alarm_transition:type_name -> mxaccess_gateway.v1.OnAlarmTransitionEvent
6, // 114: mxaccess_gateway.v1.OnBufferedDataChangeEvent.data_type:type_name -> mxaccess_gateway.v1.MxDataType 6, // 114: mxaccess_gateway.v1.OnBufferedDataChangeEvent.data_type:type_name -> mxaccess_gateway.v1.MxDataType
91, // 115: mxaccess_gateway.v1.OnBufferedDataChangeEvent.quality_values:type_name -> mxaccess_gateway.v1.MxArray 92, // 115: mxaccess_gateway.v1.OnBufferedDataChangeEvent.quality_values:type_name -> mxaccess_gateway.v1.MxArray
91, // 116: mxaccess_gateway.v1.OnBufferedDataChangeEvent.timestamp_values:type_name -> mxaccess_gateway.v1.MxArray 92, // 116: mxaccess_gateway.v1.OnBufferedDataChangeEvent.timestamp_values:type_name -> mxaccess_gateway.v1.MxArray
2, // 117: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_kind:type_name -> mxaccess_gateway.v1.AlarmTransitionKind 2, // 117: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_kind:type_name -> mxaccess_gateway.v1.AlarmTransitionKind
102, // 118: mxaccess_gateway.v1.OnAlarmTransitionEvent.original_raise_timestamp:type_name -> google.protobuf.Timestamp 103, // 118: mxaccess_gateway.v1.OnAlarmTransitionEvent.original_raise_timestamp:type_name -> google.protobuf.Timestamp
102, // 119: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_timestamp:type_name -> google.protobuf.Timestamp 103, // 119: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_timestamp:type_name -> google.protobuf.Timestamp
90, // 120: mxaccess_gateway.v1.OnAlarmTransitionEvent.current_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 120: mxaccess_gateway.v1.OnAlarmTransitionEvent.current_value:type_name -> mxaccess_gateway.v1.MxValue
90, // 121: mxaccess_gateway.v1.OnAlarmTransitionEvent.limit_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 121: mxaccess_gateway.v1.OnAlarmTransitionEvent.limit_value:type_name -> mxaccess_gateway.v1.MxValue
102, // 122: mxaccess_gateway.v1.ActiveAlarmSnapshot.original_raise_timestamp:type_name -> google.protobuf.Timestamp 103, // 122: mxaccess_gateway.v1.ActiveAlarmSnapshot.original_raise_timestamp:type_name -> google.protobuf.Timestamp
3, // 123: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_state:type_name -> mxaccess_gateway.v1.AlarmConditionState 3, // 123: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_state:type_name -> mxaccess_gateway.v1.AlarmConditionState
102, // 124: mxaccess_gateway.v1.ActiveAlarmSnapshot.last_transition_timestamp:type_name -> google.protobuf.Timestamp 103, // 124: mxaccess_gateway.v1.ActiveAlarmSnapshot.last_transition_timestamp:type_name -> google.protobuf.Timestamp
90, // 125: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 125: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_value:type_name -> mxaccess_gateway.v1.MxValue
90, // 126: mxaccess_gateway.v1.ActiveAlarmSnapshot.limit_value:type_name -> mxaccess_gateway.v1.MxValue 91, // 126: mxaccess_gateway.v1.ActiveAlarmSnapshot.limit_value:type_name -> mxaccess_gateway.v1.MxValue
100, // 127: mxaccess_gateway.v1.AcknowledgeAlarmReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus 101, // 127: mxaccess_gateway.v1.AcknowledgeAlarmReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
89, // 128: mxaccess_gateway.v1.AcknowledgeAlarmReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy 90, // 128: mxaccess_gateway.v1.AcknowledgeAlarmReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
4, // 129: mxaccess_gateway.v1.MxStatusProxy.category:type_name -> mxaccess_gateway.v1.MxStatusCategory 85, // 129: mxaccess_gateway.v1.AlarmFeedMessage.active_alarm:type_name -> mxaccess_gateway.v1.ActiveAlarmSnapshot
5, // 130: mxaccess_gateway.v1.MxStatusProxy.detected_by:type_name -> mxaccess_gateway.v1.MxStatusSource 84, // 130: mxaccess_gateway.v1.AlarmFeedMessage.transition:type_name -> mxaccess_gateway.v1.OnAlarmTransitionEvent
6, // 131: mxaccess_gateway.v1.MxValue.data_type:type_name -> mxaccess_gateway.v1.MxDataType 4, // 131: mxaccess_gateway.v1.MxStatusProxy.category:type_name -> mxaccess_gateway.v1.MxStatusCategory
102, // 132: mxaccess_gateway.v1.MxValue.timestamp_value:type_name -> google.protobuf.Timestamp 5, // 132: mxaccess_gateway.v1.MxStatusProxy.detected_by:type_name -> mxaccess_gateway.v1.MxStatusSource
91, // 133: mxaccess_gateway.v1.MxValue.array_value:type_name -> mxaccess_gateway.v1.MxArray 6, // 133: mxaccess_gateway.v1.MxValue.data_type:type_name -> mxaccess_gateway.v1.MxDataType
6, // 134: mxaccess_gateway.v1.MxArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType 103, // 134: mxaccess_gateway.v1.MxValue.timestamp_value:type_name -> google.protobuf.Timestamp
92, // 135: mxaccess_gateway.v1.MxArray.bool_values:type_name -> mxaccess_gateway.v1.BoolArray 92, // 135: mxaccess_gateway.v1.MxValue.array_value:type_name -> mxaccess_gateway.v1.MxArray
93, // 136: mxaccess_gateway.v1.MxArray.int32_values:type_name -> mxaccess_gateway.v1.Int32Array 6, // 136: mxaccess_gateway.v1.MxArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType
94, // 137: mxaccess_gateway.v1.MxArray.int64_values:type_name -> mxaccess_gateway.v1.Int64Array 93, // 137: mxaccess_gateway.v1.MxArray.bool_values:type_name -> mxaccess_gateway.v1.BoolArray
95, // 138: mxaccess_gateway.v1.MxArray.float_values:type_name -> mxaccess_gateway.v1.FloatArray 94, // 138: mxaccess_gateway.v1.MxArray.int32_values:type_name -> mxaccess_gateway.v1.Int32Array
96, // 139: mxaccess_gateway.v1.MxArray.double_values:type_name -> mxaccess_gateway.v1.DoubleArray 95, // 139: mxaccess_gateway.v1.MxArray.int64_values:type_name -> mxaccess_gateway.v1.Int64Array
97, // 140: mxaccess_gateway.v1.MxArray.string_values:type_name -> mxaccess_gateway.v1.StringArray 96, // 140: mxaccess_gateway.v1.MxArray.float_values:type_name -> mxaccess_gateway.v1.FloatArray
98, // 141: mxaccess_gateway.v1.MxArray.timestamp_values:type_name -> mxaccess_gateway.v1.TimestampArray 97, // 141: mxaccess_gateway.v1.MxArray.double_values:type_name -> mxaccess_gateway.v1.DoubleArray
99, // 142: mxaccess_gateway.v1.MxArray.raw_values:type_name -> mxaccess_gateway.v1.RawArray 98, // 142: mxaccess_gateway.v1.MxArray.string_values:type_name -> mxaccess_gateway.v1.StringArray
102, // 143: mxaccess_gateway.v1.TimestampArray.values:type_name -> google.protobuf.Timestamp 99, // 143: mxaccess_gateway.v1.MxArray.timestamp_values:type_name -> mxaccess_gateway.v1.TimestampArray
7, // 144: mxaccess_gateway.v1.ProtocolStatus.code:type_name -> mxaccess_gateway.v1.ProtocolStatusCode 100, // 144: mxaccess_gateway.v1.MxArray.raw_values:type_name -> mxaccess_gateway.v1.RawArray
9, // 145: mxaccess_gateway.v1.MxAccessGateway.OpenSession:input_type -> mxaccess_gateway.v1.OpenSessionRequest 103, // 145: mxaccess_gateway.v1.TimestampArray.values:type_name -> google.protobuf.Timestamp
11, // 146: mxaccess_gateway.v1.MxAccessGateway.CloseSession:input_type -> mxaccess_gateway.v1.CloseSessionRequest 7, // 146: mxaccess_gateway.v1.ProtocolStatus.code:type_name -> mxaccess_gateway.v1.ProtocolStatusCode
14, // 147: mxaccess_gateway.v1.MxAccessGateway.Invoke:input_type -> mxaccess_gateway.v1.MxCommandRequest 9, // 147: mxaccess_gateway.v1.MxAccessGateway.OpenSession:input_type -> mxaccess_gateway.v1.OpenSessionRequest
13, // 148: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:input_type -> mxaccess_gateway.v1.StreamEventsRequest 11, // 148: mxaccess_gateway.v1.MxAccessGateway.CloseSession:input_type -> mxaccess_gateway.v1.CloseSessionRequest
86, // 149: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:input_type -> mxaccess_gateway.v1.AcknowledgeAlarmRequest 14, // 149: mxaccess_gateway.v1.MxAccessGateway.Invoke:input_type -> mxaccess_gateway.v1.MxCommandRequest
88, // 150: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:input_type -> mxaccess_gateway.v1.QueryActiveAlarmsRequest 13, // 150: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:input_type -> mxaccess_gateway.v1.StreamEventsRequest
10, // 151: mxaccess_gateway.v1.MxAccessGateway.OpenSession:output_type -> mxaccess_gateway.v1.OpenSessionReply 86, // 151: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:input_type -> mxaccess_gateway.v1.AcknowledgeAlarmRequest
12, // 152: mxaccess_gateway.v1.MxAccessGateway.CloseSession:output_type -> mxaccess_gateway.v1.CloseSessionReply 88, // 152: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:input_type -> mxaccess_gateway.v1.StreamAlarmsRequest
59, // 153: mxaccess_gateway.v1.MxAccessGateway.Invoke:output_type -> mxaccess_gateway.v1.MxCommandReply 10, // 153: mxaccess_gateway.v1.MxAccessGateway.OpenSession:output_type -> mxaccess_gateway.v1.OpenSessionReply
79, // 154: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:output_type -> mxaccess_gateway.v1.MxEvent 12, // 154: mxaccess_gateway.v1.MxAccessGateway.CloseSession:output_type -> mxaccess_gateway.v1.CloseSessionReply
87, // 155: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:output_type -> mxaccess_gateway.v1.AcknowledgeAlarmReply 59, // 155: mxaccess_gateway.v1.MxAccessGateway.Invoke:output_type -> mxaccess_gateway.v1.MxCommandReply
85, // 156: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:output_type -> mxaccess_gateway.v1.ActiveAlarmSnapshot 79, // 156: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:output_type -> mxaccess_gateway.v1.MxEvent
151, // [151:157] is the sub-list for method output_type 87, // 157: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:output_type -> mxaccess_gateway.v1.AcknowledgeAlarmReply
145, // [145:151] is the sub-list for method input_type 89, // 158: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:output_type -> mxaccess_gateway.v1.AlarmFeedMessage
145, // [145:145] is the sub-list for extension type_name 153, // [153:159] is the sub-list for method output_type
145, // [145:145] is the sub-list for extension extendee 147, // [147:153] is the sub-list for method input_type
0, // [0:145] is the sub-list for field type_name 147, // [147:147] is the sub-list for extension type_name
147, // [147:147] is the sub-list for extension extendee
0, // [0:147] is the sub-list for field type_name
} }
func init() { file_mxaccess_gateway_proto_init() } func init() { file_mxaccess_gateway_proto_init() }
@@ -8751,7 +8860,12 @@ func file_mxaccess_gateway_proto_init() {
(*MxEvent_OnAlarmTransition)(nil), (*MxEvent_OnAlarmTransition)(nil),
} }
file_mxaccess_gateway_proto_msgTypes[78].OneofWrappers = []any{} file_mxaccess_gateway_proto_msgTypes[78].OneofWrappers = []any{}
file_mxaccess_gateway_proto_msgTypes[81].OneofWrappers = []any{ file_mxaccess_gateway_proto_msgTypes[80].OneofWrappers = []any{
(*AlarmFeedMessage_ActiveAlarm)(nil),
(*AlarmFeedMessage_SnapshotComplete)(nil),
(*AlarmFeedMessage_Transition)(nil),
}
file_mxaccess_gateway_proto_msgTypes[82].OneofWrappers = []any{
(*MxValue_BoolValue)(nil), (*MxValue_BoolValue)(nil),
(*MxValue_Int32Value)(nil), (*MxValue_Int32Value)(nil),
(*MxValue_Int64Value)(nil), (*MxValue_Int64Value)(nil),
@@ -8762,7 +8876,7 @@ func file_mxaccess_gateway_proto_init() {
(*MxValue_ArrayValue)(nil), (*MxValue_ArrayValue)(nil),
(*MxValue_RawValue)(nil), (*MxValue_RawValue)(nil),
} }
file_mxaccess_gateway_proto_msgTypes[82].OneofWrappers = []any{ file_mxaccess_gateway_proto_msgTypes[83].OneofWrappers = []any{
(*MxArray_BoolValues)(nil), (*MxArray_BoolValues)(nil),
(*MxArray_Int32Values)(nil), (*MxArray_Int32Values)(nil),
(*MxArray_Int64Values)(nil), (*MxArray_Int64Values)(nil),
@@ -8778,7 +8892,7 @@ func file_mxaccess_gateway_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_mxaccess_gateway_proto_rawDesc), len(file_mxaccess_gateway_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_mxaccess_gateway_proto_rawDesc), len(file_mxaccess_gateway_proto_rawDesc)),
NumEnums: 9, NumEnums: 9,
NumMessages: 92, NumMessages: 93,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
@@ -19,12 +19,12 @@ import (
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const ( const (
MxAccessGateway_OpenSession_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/OpenSession" MxAccessGateway_OpenSession_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/OpenSession"
MxAccessGateway_CloseSession_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/CloseSession" MxAccessGateway_CloseSession_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/CloseSession"
MxAccessGateway_Invoke_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/Invoke" MxAccessGateway_Invoke_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/Invoke"
MxAccessGateway_StreamEvents_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/StreamEvents" MxAccessGateway_StreamEvents_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/StreamEvents"
MxAccessGateway_AcknowledgeAlarm_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/AcknowledgeAlarm" MxAccessGateway_AcknowledgeAlarm_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/AcknowledgeAlarm"
MxAccessGateway_QueryActiveAlarms_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/QueryActiveAlarms" MxAccessGateway_StreamAlarms_FullMethodName = "/mxaccess_gateway.v1.MxAccessGateway/StreamAlarms"
) )
// MxAccessGatewayClient is the client API for MxAccessGateway service. // MxAccessGatewayClient is the client API for MxAccessGateway service.
@@ -38,7 +38,12 @@ type MxAccessGatewayClient interface {
Invoke(ctx context.Context, in *MxCommandRequest, opts ...grpc.CallOption) (*MxCommandReply, error) Invoke(ctx context.Context, in *MxCommandRequest, opts ...grpc.CallOption) (*MxCommandReply, error)
StreamEvents(ctx context.Context, in *StreamEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MxEvent], error) StreamEvents(ctx context.Context, in *StreamEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MxEvent], error)
AcknowledgeAlarm(ctx context.Context, in *AcknowledgeAlarmRequest, opts ...grpc.CallOption) (*AcknowledgeAlarmReply, error) AcknowledgeAlarm(ctx context.Context, in *AcknowledgeAlarmRequest, opts ...grpc.CallOption) (*AcknowledgeAlarmReply, error)
QueryActiveAlarms(ctx context.Context, in *QueryActiveAlarmsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ActiveAlarmSnapshot], error) // Session-less central alarm feed. The stream opens with the current
// active-alarm snapshot (one `active_alarm` per alarm), then a single
// `snapshot_complete`, then a `transition` for every subsequent change.
// Served by the gateway's always-on alarm monitor; any number of clients
// fan out from the single monitor without opening a worker session.
StreamAlarms(ctx context.Context, in *StreamAlarmsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AlarmFeedMessage], error)
} }
type mxAccessGatewayClient struct { type mxAccessGatewayClient struct {
@@ -108,13 +113,13 @@ func (c *mxAccessGatewayClient) AcknowledgeAlarm(ctx context.Context, in *Acknow
return out, nil return out, nil
} }
func (c *mxAccessGatewayClient) QueryActiveAlarms(ctx context.Context, in *QueryActiveAlarmsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ActiveAlarmSnapshot], error) { func (c *mxAccessGatewayClient) StreamAlarms(ctx context.Context, in *StreamAlarmsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AlarmFeedMessage], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &MxAccessGateway_ServiceDesc.Streams[1], MxAccessGateway_QueryActiveAlarms_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &MxAccessGateway_ServiceDesc.Streams[1], MxAccessGateway_StreamAlarms_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &grpc.GenericClientStream[QueryActiveAlarmsRequest, ActiveAlarmSnapshot]{ClientStream: stream} x := &grpc.GenericClientStream[StreamAlarmsRequest, AlarmFeedMessage]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil { if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err return nil, err
} }
@@ -125,7 +130,7 @@ func (c *mxAccessGatewayClient) QueryActiveAlarms(ctx context.Context, in *Query
} }
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MxAccessGateway_QueryActiveAlarmsClient = grpc.ServerStreamingClient[ActiveAlarmSnapshot] type MxAccessGateway_StreamAlarmsClient = grpc.ServerStreamingClient[AlarmFeedMessage]
// MxAccessGatewayServer is the server API for MxAccessGateway service. // MxAccessGatewayServer is the server API for MxAccessGateway service.
// All implementations must embed UnimplementedMxAccessGatewayServer // All implementations must embed UnimplementedMxAccessGatewayServer
@@ -138,7 +143,12 @@ type MxAccessGatewayServer interface {
Invoke(context.Context, *MxCommandRequest) (*MxCommandReply, error) Invoke(context.Context, *MxCommandRequest) (*MxCommandReply, error)
StreamEvents(*StreamEventsRequest, grpc.ServerStreamingServer[MxEvent]) error StreamEvents(*StreamEventsRequest, grpc.ServerStreamingServer[MxEvent]) error
AcknowledgeAlarm(context.Context, *AcknowledgeAlarmRequest) (*AcknowledgeAlarmReply, error) AcknowledgeAlarm(context.Context, *AcknowledgeAlarmRequest) (*AcknowledgeAlarmReply, error)
QueryActiveAlarms(*QueryActiveAlarmsRequest, grpc.ServerStreamingServer[ActiveAlarmSnapshot]) error // Session-less central alarm feed. The stream opens with the current
// active-alarm snapshot (one `active_alarm` per alarm), then a single
// `snapshot_complete`, then a `transition` for every subsequent change.
// Served by the gateway's always-on alarm monitor; any number of clients
// fan out from the single monitor without opening a worker session.
StreamAlarms(*StreamAlarmsRequest, grpc.ServerStreamingServer[AlarmFeedMessage]) error
mustEmbedUnimplementedMxAccessGatewayServer() mustEmbedUnimplementedMxAccessGatewayServer()
} }
@@ -164,8 +174,8 @@ func (UnimplementedMxAccessGatewayServer) StreamEvents(*StreamEventsRequest, grp
func (UnimplementedMxAccessGatewayServer) AcknowledgeAlarm(context.Context, *AcknowledgeAlarmRequest) (*AcknowledgeAlarmReply, error) { func (UnimplementedMxAccessGatewayServer) AcknowledgeAlarm(context.Context, *AcknowledgeAlarmRequest) (*AcknowledgeAlarmReply, error) {
return nil, status.Error(codes.Unimplemented, "method AcknowledgeAlarm not implemented") return nil, status.Error(codes.Unimplemented, "method AcknowledgeAlarm not implemented")
} }
func (UnimplementedMxAccessGatewayServer) QueryActiveAlarms(*QueryActiveAlarmsRequest, grpc.ServerStreamingServer[ActiveAlarmSnapshot]) error { func (UnimplementedMxAccessGatewayServer) StreamAlarms(*StreamAlarmsRequest, grpc.ServerStreamingServer[AlarmFeedMessage]) error {
return status.Error(codes.Unimplemented, "method QueryActiveAlarms not implemented") return status.Error(codes.Unimplemented, "method StreamAlarms not implemented")
} }
func (UnimplementedMxAccessGatewayServer) mustEmbedUnimplementedMxAccessGatewayServer() {} func (UnimplementedMxAccessGatewayServer) mustEmbedUnimplementedMxAccessGatewayServer() {}
func (UnimplementedMxAccessGatewayServer) testEmbeddedByValue() {} func (UnimplementedMxAccessGatewayServer) testEmbeddedByValue() {}
@@ -271,16 +281,16 @@ func _MxAccessGateway_AcknowledgeAlarm_Handler(srv interface{}, ctx context.Cont
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _MxAccessGateway_QueryActiveAlarms_Handler(srv interface{}, stream grpc.ServerStream) error { func _MxAccessGateway_StreamAlarms_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(QueryActiveAlarmsRequest) m := new(StreamAlarmsRequest)
if err := stream.RecvMsg(m); err != nil { if err := stream.RecvMsg(m); err != nil {
return err return err
} }
return srv.(MxAccessGatewayServer).QueryActiveAlarms(m, &grpc.GenericServerStream[QueryActiveAlarmsRequest, ActiveAlarmSnapshot]{ServerStream: stream}) return srv.(MxAccessGatewayServer).StreamAlarms(m, &grpc.GenericServerStream[StreamAlarmsRequest, AlarmFeedMessage]{ServerStream: stream})
} }
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MxAccessGateway_QueryActiveAlarmsServer = grpc.ServerStreamingServer[ActiveAlarmSnapshot] type MxAccessGateway_StreamAlarmsServer = grpc.ServerStreamingServer[AlarmFeedMessage]
// MxAccessGateway_ServiceDesc is the grpc.ServiceDesc for MxAccessGateway service. // MxAccessGateway_ServiceDesc is the grpc.ServiceDesc for MxAccessGateway service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
@@ -313,8 +323,8 @@ var MxAccessGateway_ServiceDesc = grpc.ServiceDesc{
ServerStreams: true, ServerStreams: true,
}, },
{ {
StreamName: "QueryActiveAlarms", StreamName: "StreamAlarms",
Handler: _MxAccessGateway_QueryActiveAlarms_Handler, Handler: _MxAccessGateway_StreamAlarms_Handler,
ServerStreams: true, ServerStreams: true,
}, },
}, },
+10 -8
View File
@@ -31,22 +31,24 @@ func (c *Client) AcknowledgeAlarm(ctx context.Context, req *AcknowledgeAlarmRequ
return reply, nil return reply, nil
} }
// QueryActiveAlarms streams a snapshot of all alarms currently Active or // StreamAlarms attaches to the gateway's central alarm feed. The stream opens
// ActiveAcked — the gateway's ConditionRefresh equivalent. Used after reconnect // with one AlarmFeedMessage per currently-active alarm (the ConditionRefresh
// to seed local Part 9 state, or to reconcile alarms that may have been missed // snapshot), then a single snapshot-complete sentinel, then a transition for
// during a transport blip. // every subsequent raise / acknowledge / clear. It is served by the gateway's
// always-on alarm monitor — no worker session is opened — so any number of
// clients may attach.
// //
// The returned stream is owned by the caller; cancel ctx to release it. // The returned stream is owned by the caller; cancel ctx to release it.
// Optional alarm-reference prefix scoping (req.AlarmFilterPrefix) limits the // Optional alarm-reference prefix scoping (req.AlarmFilterPrefix) limits the
// stream to a sub-tree. // stream to a sub-tree.
func (c *Client) QueryActiveAlarms(ctx context.Context, req *QueryActiveAlarmsRequest) (QueryActiveAlarmsClient, error) { func (c *Client) StreamAlarms(ctx context.Context, req *StreamAlarmsRequest) (StreamAlarmsClient, error) {
if req == nil { if req == nil {
return nil, errors.New("mxgateway: query active alarms request is required") return nil, errors.New("mxgateway: stream alarms request is required")
} }
stream, err := c.raw.QueryActiveAlarms(ctx, req) stream, err := c.raw.StreamAlarms(ctx, req)
if err != nil { if err != nil {
return nil, &GatewayError{Op: "query active alarms", Err: err} return nil, &GatewayError{Op: "stream alarms", Err: err}
} }
return stream, nil return stream, nil
+29 -30
View File
@@ -14,13 +14,11 @@ import (
"google.golang.org/grpc/test/bufconn" "google.golang.org/grpc/test/bufconn"
) )
// PR E.4 — pins the Go SDK surface for the new alarm RPCs: // Pins the Go SDK surface for the alarm RPCs: AcknowledgeAlarm + StreamAlarms.
// AcknowledgeAlarm + QueryActiveAlarms.
func TestAcknowledgeAlarmSendsRequestAndReturnsReply(t *testing.T) { func TestAcknowledgeAlarmSendsRequestAndReturnsReply(t *testing.T) {
fake := &fakeGatewayWithAlarms{ fake := &fakeGatewayWithAlarms{
acknowledgeReply: &pb.AcknowledgeAlarmReply{ acknowledgeReply: &pb.AcknowledgeAlarmReply{
SessionId: "session-1",
CorrelationId: "corr-1", CorrelationId: "corr-1",
ProtocolStatus: &pb.ProtocolStatus{ ProtocolStatus: &pb.ProtocolStatus{
Code: pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK, Code: pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK,
@@ -35,7 +33,6 @@ func TestAcknowledgeAlarmSendsRequestAndReturnsReply(t *testing.T) {
defer cleanup() defer cleanup()
reply, err := client.AcknowledgeAlarm(context.Background(), &pb.AcknowledgeAlarmRequest{ reply, err := client.AcknowledgeAlarm(context.Background(), &pb.AcknowledgeAlarmRequest{
SessionId: "session-1",
ClientCorrelationId: "corr-1", ClientCorrelationId: "corr-1",
AlarmFullReference: "Tank01.Level.HiHi", AlarmFullReference: "Tank01.Level.HiHi",
Comment: "investigating", Comment: "investigating",
@@ -77,7 +74,6 @@ func TestAcknowledgeAlarmMapsUnauthenticated(t *testing.T) {
defer cleanup() defer cleanup()
_, err := client.AcknowledgeAlarm(context.Background(), &pb.AcknowledgeAlarmRequest{ _, err := client.AcknowledgeAlarm(context.Background(), &pb.AcknowledgeAlarmRequest{
SessionId: "session-1",
AlarmFullReference: "Tank01.Level.HiHi", AlarmFullReference: "Tank01.Level.HiHi",
OperatorUser: "alice", OperatorUser: "alice",
}) })
@@ -93,7 +89,7 @@ func TestAcknowledgeAlarmMapsUnauthenticated(t *testing.T) {
} }
} }
func TestQueryActiveAlarmsStreamsSnapshots(t *testing.T) { func TestStreamAlarmsStreamsSnapshotThenSnapshotComplete(t *testing.T) {
fake := &fakeGatewayWithAlarms{ fake := &fakeGatewayWithAlarms{
activeSnapshots: []*pb.ActiveAlarmSnapshot{ activeSnapshots: []*pb.ActiveAlarmSnapshot{
{ {
@@ -111,46 +107,46 @@ func TestQueryActiveAlarmsStreamsSnapshots(t *testing.T) {
client, cleanup := newBufconnClientWithAlarms(t, fake) client, cleanup := newBufconnClientWithAlarms(t, fake)
defer cleanup() defer cleanup()
stream, err := client.QueryActiveAlarms(context.Background(), &pb.QueryActiveAlarmsRequest{ stream, err := client.StreamAlarms(context.Background(), &pb.StreamAlarmsRequest{})
SessionId: "session-1",
})
if err != nil { if err != nil {
t.Fatalf("QueryActiveAlarms() error = %v", err) t.Fatalf("StreamAlarms() error = %v", err)
} }
var received []*pb.ActiveAlarmSnapshot var received []*pb.AlarmFeedMessage
for { for {
snap, err := stream.Recv() msg, err := stream.Recv()
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
break break
} }
if err != nil { if err != nil {
t.Fatalf("stream.Recv() error = %v", err) t.Fatalf("stream.Recv() error = %v", err)
} }
received = append(received, snap) received = append(received, msg)
} }
if len(received) != 2 { if len(received) != 3 {
t.Fatalf("snapshot count = %d, want 2", len(received)) t.Fatalf("message count = %d, want 3", len(received))
} }
if received[0].GetAlarmFullReference() != "Tank01.Level.HiHi" { if received[0].GetActiveAlarm().GetAlarmFullReference() != "Tank01.Level.HiHi" {
t.Fatalf("snapshot[0] ref = %q", received[0].GetAlarmFullReference()) t.Fatalf("message[0] ref = %q", received[0].GetActiveAlarm().GetAlarmFullReference())
} }
if received[1].GetCurrentState() != pb.AlarmConditionState_ALARM_CONDITION_STATE_ACTIVE_ACKED { if received[1].GetActiveAlarm().GetCurrentState() != pb.AlarmConditionState_ALARM_CONDITION_STATE_ACTIVE_ACKED {
t.Fatalf("snapshot[1] state = %v", received[1].GetCurrentState()) t.Fatalf("message[1] state = %v", received[1].GetActiveAlarm().GetCurrentState())
}
if !received[2].GetSnapshotComplete() {
t.Fatalf("final message is not snapshot_complete")
} }
} }
func TestQueryActiveAlarmsPassesFilterPrefix(t *testing.T) { func TestStreamAlarmsPassesFilterPrefix(t *testing.T) {
fake := &fakeGatewayWithAlarms{} fake := &fakeGatewayWithAlarms{}
client, cleanup := newBufconnClientWithAlarms(t, fake) client, cleanup := newBufconnClientWithAlarms(t, fake)
defer cleanup() defer cleanup()
stream, err := client.QueryActiveAlarms(context.Background(), &pb.QueryActiveAlarmsRequest{ stream, err := client.StreamAlarms(context.Background(), &pb.StreamAlarmsRequest{
SessionId: "session-1",
AlarmFilterPrefix: "Tank01.", AlarmFilterPrefix: "Tank01.",
}) })
if err != nil { if err != nil {
t.Fatalf("QueryActiveAlarms() error = %v", err) t.Fatalf("StreamAlarms() error = %v", err)
} }
for { for {
_, err := stream.Recv() _, err := stream.Recv()
@@ -162,7 +158,7 @@ func TestQueryActiveAlarmsPassesFilterPrefix(t *testing.T) {
} }
} }
if got := fake.queryRequest.GetAlarmFilterPrefix(); got != "Tank01." { if got := fake.streamRequest.GetAlarmFilterPrefix(); got != "Tank01." {
t.Fatalf("captured filter prefix = %q", got) t.Fatalf("captured filter prefix = %q", got)
} }
} }
@@ -175,7 +171,7 @@ type fakeGatewayWithAlarms struct {
acknowledgeError error acknowledgeError error
acknowledgeAuth string acknowledgeAuth string
queryRequest *pb.QueryActiveAlarmsRequest streamRequest *pb.StreamAlarmsRequest
activeSnapshots []*pb.ActiveAlarmSnapshot activeSnapshots []*pb.ActiveAlarmSnapshot
} }
@@ -189,21 +185,24 @@ func (s *fakeGatewayWithAlarms) AcknowledgeAlarm(ctx context.Context, req *pb.Ac
return s.acknowledgeReply, nil return s.acknowledgeReply, nil
} }
return &pb.AcknowledgeAlarmReply{ return &pb.AcknowledgeAlarmReply{
SessionId: req.GetSessionId(),
ProtocolStatus: &pb.ProtocolStatus{ ProtocolStatus: &pb.ProtocolStatus{
Code: pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK, Code: pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK,
}, },
}, nil }, nil
} }
func (s *fakeGatewayWithAlarms) QueryActiveAlarms(req *pb.QueryActiveAlarmsRequest, stream grpc.ServerStreamingServer[pb.ActiveAlarmSnapshot]) error { func (s *fakeGatewayWithAlarms) StreamAlarms(req *pb.StreamAlarmsRequest, stream grpc.ServerStreamingServer[pb.AlarmFeedMessage]) error {
s.queryRequest = req s.streamRequest = req
for _, snap := range s.activeSnapshots { for _, snap := range s.activeSnapshots {
if err := stream.Send(snap); err != nil { if err := stream.Send(&pb.AlarmFeedMessage{
Payload: &pb.AlarmFeedMessage_ActiveAlarm{ActiveAlarm: snap},
}); err != nil {
return err return err
} }
} }
return nil return stream.Send(&pb.AlarmFeedMessage{
Payload: &pb.AlarmFeedMessage_SnapshotComplete{SnapshotComplete: true},
})
} }
func newBufconnClientWithAlarms(t *testing.T, fake *fakeGatewayWithAlarms) (*Client, func()) { func newBufconnClientWithAlarms(t *testing.T, fake *fakeGatewayWithAlarms) (*Client, func()) {
+9 -6
View File
@@ -110,9 +110,12 @@ type (
AcknowledgeAlarmRequest = pb.AcknowledgeAlarmRequest AcknowledgeAlarmRequest = pb.AcknowledgeAlarmRequest
// AcknowledgeAlarmReply is the gateway AcknowledgeAlarm reply message. // AcknowledgeAlarmReply is the gateway AcknowledgeAlarm reply message.
AcknowledgeAlarmReply = pb.AcknowledgeAlarmReply AcknowledgeAlarmReply = pb.AcknowledgeAlarmReply
// QueryActiveAlarmsRequest is the gateway QueryActiveAlarms request message. // StreamAlarmsRequest is the gateway StreamAlarms request message.
QueryActiveAlarmsRequest = pb.QueryActiveAlarmsRequest StreamAlarmsRequest = pb.StreamAlarmsRequest
// ActiveAlarmSnapshot is one row in a ConditionRefresh stream. // AlarmFeedMessage is one message on the StreamAlarms feed — an
// active-alarm snapshot row, a snapshot-complete sentinel, or a transition.
AlarmFeedMessage = pb.AlarmFeedMessage
// ActiveAlarmSnapshot is one currently-active alarm in the feed snapshot.
ActiveAlarmSnapshot = pb.ActiveAlarmSnapshot ActiveAlarmSnapshot = pb.ActiveAlarmSnapshot
// OnAlarmTransitionEvent is the body carried by alarm-transition MxEvents. // OnAlarmTransitionEvent is the body carried by alarm-transition MxEvents.
OnAlarmTransitionEvent = pb.OnAlarmTransitionEvent OnAlarmTransitionEvent = pb.OnAlarmTransitionEvent
@@ -126,9 +129,9 @@ type AlarmTransitionKind = pb.AlarmTransitionKind
// ConditionRefresh snapshot. // ConditionRefresh snapshot.
type AlarmConditionState = pb.AlarmConditionState type AlarmConditionState = pb.AlarmConditionState
// QueryActiveAlarmsClient is the generated server-streaming client for the // StreamAlarmsClient is the generated server-streaming client for the
// QueryActiveAlarms RPC. // StreamAlarms RPC.
type QueryActiveAlarmsClient = pb.MxAccessGateway_QueryActiveAlarmsClient type StreamAlarmsClient = pb.MxAccessGateway_StreamAlarmsClient
// Enumerations from the generated contract re-exported for client callers. // Enumerations from the generated contract re-exported for client callers.
type ( type (
@@ -5,33 +5,33 @@ import io.grpc.stub.ClientResponseObserver;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot; import mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage;
import mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest; import mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest;
/** /**
* Cancellable handle returned by {@code queryActiveAlarms}. * Cancellable handle returned by {@code streamAlarms}.
* *
* <p>Wraps a caller-supplied {@link StreamObserver} and exposes a * <p>Wraps a caller-supplied {@link StreamObserver} and exposes a
* {@link #cancel()} entry point that aborts the underlying gRPC call. The * {@link #cancel()} entry point that aborts the underlying gRPC call. The
* subscription also implements {@link AutoCloseable} so it can participate in * subscription also implements {@link AutoCloseable} so it can participate in
* try-with-resources blocks. * try-with-resources blocks.
*/ */
public final class MxGatewayActiveAlarmsSubscription implements AutoCloseable { public final class MxGatewayAlarmFeedSubscription implements AutoCloseable {
private final AtomicReference<ClientCallStreamObserver<QueryActiveAlarmsRequest>> requestStream = new AtomicReference<>(); private final AtomicReference<ClientCallStreamObserver<StreamAlarmsRequest>> requestStream = new AtomicReference<>();
private final AtomicBoolean cancelled = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean();
ClientResponseObserver<QueryActiveAlarmsRequest, ActiveAlarmSnapshot> wrap(StreamObserver<ActiveAlarmSnapshot> observer) { ClientResponseObserver<StreamAlarmsRequest, AlarmFeedMessage> wrap(StreamObserver<AlarmFeedMessage> observer) {
return new ClientResponseObserver<>() { return new ClientResponseObserver<>() {
@Override @Override
public void beforeStart(ClientCallStreamObserver<QueryActiveAlarmsRequest> stream) { public void beforeStart(ClientCallStreamObserver<StreamAlarmsRequest> stream) {
requestStream.set(stream); requestStream.set(stream);
if (cancelled.get()) { if (cancelled.get()) {
stream.cancel("client cancelled active-alarms query", null); stream.cancel("client cancelled alarm feed", null);
} }
} }
@Override @Override
public void onNext(ActiveAlarmSnapshot value) { public void onNext(AlarmFeedMessage value) {
observer.onNext(value); observer.onNext(value);
} }
@@ -54,9 +54,9 @@ public final class MxGatewayActiveAlarmsSubscription implements AutoCloseable {
*/ */
public void cancel() { public void cancel() {
cancelled.set(true); cancelled.set(true);
ClientCallStreamObserver<QueryActiveAlarmsRequest> stream = requestStream.get(); ClientCallStreamObserver<StreamAlarmsRequest> stream = requestStream.get();
if (stream != null) { if (stream != null) {
stream.cancel("client cancelled active-alarms query", null); stream.cancel("client cancelled alarm feed", null);
} }
} }
@@ -10,7 +10,7 @@ import java.util.concurrent.CompletableFuture;
import mxaccess_gateway.v1.MxAccessGatewayGrpc; import mxaccess_gateway.v1.MxAccessGatewayGrpc;
import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply; import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply;
import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest; import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest;
import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot; import mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage;
import mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply; import mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply;
import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest; import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest;
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply; import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
@@ -19,7 +19,7 @@ import mxaccess_gateway.v1.MxaccessGateway.MxEvent;
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply; import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply;
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest; import mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest;
import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatusCode; import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatusCode;
import mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest; import mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest;
import mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest; import mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest;
/** /**
@@ -328,20 +328,24 @@ public final class MxGatewayClient implements AutoCloseable {
} }
/** /**
* Streams a snapshot of all alarms currently Active or ActiveAcked — the * Attaches to the gateway's central alarm feed. The stream opens with one
* gateway's ConditionRefresh equivalent. Used after reconnect to seed * {@code AlarmFeedMessage} per currently-active alarm (the ConditionRefresh
* local Part 9 state. * snapshot), then a single {@code snapshot_complete}, then a
* {@code transition} for every subsequent raise / acknowledge / clear.
* *
* @param request the {@code QueryActiveAlarmsRequest}, optionally scoped by * <p>Served by the gateway's always-on alarm monitor — no worker session is
* opened — so any number of clients may attach.
*
* @param request the {@code StreamAlarmsRequest}, optionally scoped by
* alarm-reference prefix * alarm-reference prefix
* @param observer caller-supplied observer that receives snapshots and completion * @param observer caller-supplied observer that receives feed messages and completion
* @return a cancellable subscription handle * @return a cancellable subscription handle
*/ */
public MxGatewayActiveAlarmsSubscription queryActiveAlarms( public MxGatewayAlarmFeedSubscription streamAlarms(
QueryActiveAlarmsRequest request, StreamObserver<ActiveAlarmSnapshot> observer) { StreamAlarmsRequest request, StreamObserver<AlarmFeedMessage> observer) {
MxGatewayActiveAlarmsSubscription subscription = new MxGatewayActiveAlarmsSubscription(); MxGatewayAlarmFeedSubscription subscription = new MxGatewayAlarmFeedSubscription();
MxGatewayChannels.withStreamDeadline(rawAsyncStub(), options) MxGatewayChannels.withStreamDeadline(rawAsyncStub(), options)
.queryActiveAlarms(request, subscription.wrap(observer)); .streamAlarms(request, subscription.wrap(observer));
return subscription; return subscription;
} }
@@ -30,10 +30,11 @@ import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply;
import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest; import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest;
import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot; import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot;
import mxaccess_gateway.v1.MxaccessGateway.AlarmConditionState; import mxaccess_gateway.v1.MxaccessGateway.AlarmConditionState;
import mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage;
import mxaccess_gateway.v1.MxaccessGateway.MxEvent; import mxaccess_gateway.v1.MxaccessGateway.MxEvent;
import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatus; import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatus;
import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatusCode; import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatusCode;
import mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest; import mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest;
import mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest; import mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -57,7 +58,6 @@ final class MxGatewayLowFindingsTests {
AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) { AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) {
seen.set(request); seen.set(request);
responseObserver.onNext(AcknowledgeAlarmReply.newBuilder() responseObserver.onNext(AcknowledgeAlarmReply.newBuilder()
.setSessionId(request.getSessionId())
.setProtocolStatus(ok()) .setProtocolStatus(ok())
.setDiagnosticMessage("acked") .setDiagnosticMessage("acked")
.build()); .build());
@@ -67,7 +67,6 @@ final class MxGatewayLowFindingsTests {
try (Harness harness = Harness.start(service, "mxgw_keyid_secret", authorization)) { try (Harness harness = Harness.start(service, "mxgw_keyid_secret", authorization)) {
AcknowledgeAlarmReply reply = harness.client().acknowledgeAlarm(AcknowledgeAlarmRequest.newBuilder() AcknowledgeAlarmReply reply = harness.client().acknowledgeAlarm(AcknowledgeAlarmRequest.newBuilder()
.setSessionId("s-1")
.setAlarmFullReference("Area1.Pump.PV.HiHi") .setAlarmFullReference("Area1.Pump.PV.HiHi")
.setComment("operator note") .setComment("operator note")
.build()); .build());
@@ -84,7 +83,6 @@ final class MxGatewayLowFindingsTests {
public void acknowledgeAlarm( public void acknowledgeAlarm(
AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) { AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) {
responseObserver.onNext(AcknowledgeAlarmReply.newBuilder() responseObserver.onNext(AcknowledgeAlarmReply.newBuilder()
.setSessionId(request.getSessionId())
.setProtocolStatus(ProtocolStatus.newBuilder() .setProtocolStatus(ProtocolStatus.newBuilder()
.setCode(ProtocolStatusCode.PROTOCOL_STATUS_CODE_SESSION_NOT_FOUND)) .setCode(ProtocolStatusCode.PROTOCOL_STATUS_CODE_SESSION_NOT_FOUND))
.build()); .build());
@@ -96,7 +94,7 @@ final class MxGatewayLowFindingsTests {
assertThrows( assertThrows(
MxGatewayException.class, MxGatewayException.class,
() -> harness.client().acknowledgeAlarm(AcknowledgeAlarmRequest.newBuilder() () -> harness.client().acknowledgeAlarm(AcknowledgeAlarmRequest.newBuilder()
.setSessionId("missing") .setAlarmFullReference("Area1.Pump.PV.HiHi")
.build())); .build()));
} }
} }
@@ -108,7 +106,6 @@ final class MxGatewayLowFindingsTests {
public void acknowledgeAlarm( public void acknowledgeAlarm(
AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) { AcknowledgeAlarmRequest request, StreamObserver<AcknowledgeAlarmReply> responseObserver) {
responseObserver.onNext(AcknowledgeAlarmReply.newBuilder() responseObserver.onNext(AcknowledgeAlarmReply.newBuilder()
.setSessionId(request.getSessionId())
.setProtocolStatus(ok()) .setProtocolStatus(ok())
.setDiagnosticMessage("async-acked") .setDiagnosticMessage("async-acked")
.build()); .build());
@@ -118,7 +115,9 @@ final class MxGatewayLowFindingsTests {
try (Harness harness = Harness.start(service)) { try (Harness harness = Harness.start(service)) {
CompletableFuture<AcknowledgeAlarmReply> future = harness.client() CompletableFuture<AcknowledgeAlarmReply> future = harness.client()
.acknowledgeAlarmAsync(AcknowledgeAlarmRequest.newBuilder().setSessionId("s-2").build()); .acknowledgeAlarmAsync(AcknowledgeAlarmRequest.newBuilder()
.setAlarmFullReference("Area1.Pump.PV.HiHi")
.build());
assertEquals("async-acked", future.get(5, TimeUnit.SECONDS).getDiagnosticMessage()); assertEquals("async-acked", future.get(5, TimeUnit.SECONDS).getDiagnosticMessage());
} }
} }
@@ -135,39 +134,45 @@ final class MxGatewayLowFindingsTests {
try (Harness harness = Harness.start(service)) { try (Harness harness = Harness.start(service)) {
CompletableFuture<AcknowledgeAlarmReply> future = harness.client() CompletableFuture<AcknowledgeAlarmReply> future = harness.client()
.acknowledgeAlarmAsync(AcknowledgeAlarmRequest.newBuilder().setSessionId("s-3").build()); .acknowledgeAlarmAsync(AcknowledgeAlarmRequest.newBuilder()
.setAlarmFullReference("Area1.Pump.PV.HiHi")
.build());
ExecutionException error = assertThrows( ExecutionException error = assertThrows(
ExecutionException.class, () -> future.get(5, TimeUnit.SECONDS)); ExecutionException.class, () -> future.get(5, TimeUnit.SECONDS));
assertTrue(error.getCause() instanceof MxGatewayException, () -> String.valueOf(error.getCause())); assertTrue(error.getCause() instanceof MxGatewayException, () -> String.valueOf(error.getCause()));
} }
} }
// --- Client.Java-007: QueryActiveAlarms RPC + subscription coverage --- // --- Client.Java-007: StreamAlarms RPC + subscription coverage ---
@Test @Test
void queryActiveAlarmsDeliversSnapshotsToObserver() throws Exception { void streamAlarmsDeliversFeedMessagesToObserver() throws Exception {
ActiveAlarmSnapshot snapshot = ActiveAlarmSnapshot.newBuilder() AlarmFeedMessage active = AlarmFeedMessage.newBuilder()
.setAlarmFullReference("Area1.Tank.Level.Hi") .setActiveAlarm(ActiveAlarmSnapshot.newBuilder()
.setSeverity(800) .setAlarmFullReference("Area1.Tank.Level.Hi")
.setCurrentState(AlarmConditionState.ALARM_CONDITION_STATE_ACTIVE) .setSeverity(800)
.setCurrentState(AlarmConditionState.ALARM_CONDITION_STATE_ACTIVE))
.build(); .build();
AlarmFeedMessage snapshotComplete =
AlarmFeedMessage.newBuilder().setSnapshotComplete(true).build();
TestService service = new TestService() { TestService service = new TestService() {
@Override @Override
public void queryActiveAlarms( public void streamAlarms(
QueryActiveAlarmsRequest request, StreamObserver<ActiveAlarmSnapshot> responseObserver) { StreamAlarmsRequest request, StreamObserver<AlarmFeedMessage> responseObserver) {
responseObserver.onNext(snapshot); responseObserver.onNext(active);
responseObserver.onNext(snapshotComplete);
responseObserver.onCompleted(); responseObserver.onCompleted();
} }
}; };
try (Harness harness = Harness.start(service)) { try (Harness harness = Harness.start(service)) {
List<ActiveAlarmSnapshot> received = new ArrayList<>(); List<AlarmFeedMessage> received = new ArrayList<>();
CountDownLatch done = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(1);
harness.client().queryActiveAlarms( harness.client().streamAlarms(
QueryActiveAlarmsRequest.newBuilder().setSessionId("s-4").build(), StreamAlarmsRequest.newBuilder().build(),
new StreamObserver<>() { new StreamObserver<>() {
@Override @Override
public void onNext(ActiveAlarmSnapshot value) { public void onNext(AlarmFeedMessage value) {
received.add(value); received.add(value);
} }
@@ -182,18 +187,19 @@ final class MxGatewayLowFindingsTests {
} }
}); });
assertTrue(done.await(5, TimeUnit.SECONDS), "stream should complete"); assertTrue(done.await(5, TimeUnit.SECONDS), "stream should complete");
assertEquals(1, received.size()); assertEquals(2, received.size());
assertEquals("Area1.Tank.Level.Hi", received.get(0).getAlarmFullReference()); assertEquals("Area1.Tank.Level.Hi", received.get(0).getActiveAlarm().getAlarmFullReference());
assertTrue(received.get(1).getSnapshotComplete());
} }
} }
@Test @Test
void activeAlarmsSubscriptionCancelBeforeBeforeStartCancelsStream() { void alarmFeedSubscriptionCancelBeforeBeforeStartCancelsStream() {
MxGatewayActiveAlarmsSubscription subscription = new MxGatewayActiveAlarmsSubscription(); MxGatewayAlarmFeedSubscription subscription = new MxGatewayAlarmFeedSubscription();
ClientResponseObserver<QueryActiveAlarmsRequest, ActiveAlarmSnapshot> observer = ClientResponseObserver<StreamAlarmsRequest, AlarmFeedMessage> observer =
subscription.wrap(new StreamObserver<>() { subscription.wrap(new StreamObserver<>() {
@Override @Override
public void onNext(ActiveAlarmSnapshot value) { public void onNext(AlarmFeedMessage value) {
} }
@Override @Override
@@ -204,13 +210,13 @@ final class MxGatewayLowFindingsTests {
public void onCompleted() { public void onCompleted() {
} }
}); });
RecordingActiveAlarmsRequestStream requestStream = new RecordingActiveAlarmsRequestStream(); RecordingAlarmFeedRequestStream requestStream = new RecordingAlarmFeedRequestStream();
subscription.cancel(); subscription.cancel();
observer.beforeStart(requestStream); observer.beforeStart(requestStream);
assertTrue(requestStream.cancelled); assertTrue(requestStream.cancelled);
assertEquals("client cancelled active-alarms query", requestStream.cancelMessage); assertEquals("client cancelled alarm feed", requestStream.cancelMessage);
} }
// --- Client.Java-007: async streamEvents + subscription cancellation --- // --- Client.Java-007: async streamEvents + subscription cancellation ---
@@ -456,8 +462,8 @@ final class MxGatewayLowFindingsTests {
} }
} }
private static final class RecordingActiveAlarmsRequestStream private static final class RecordingAlarmFeedRequestStream
extends ClientCallStreamObserver<QueryActiveAlarmsRequest> { extends ClientCallStreamObserver<StreamAlarmsRequest> {
private boolean cancelled; private boolean cancelled;
private String cancelMessage; private String cancelMessage;
@@ -489,7 +495,7 @@ final class MxGatewayLowFindingsTests {
} }
@Override @Override
public void onNext(QueryActiveAlarmsRequest value) { public void onNext(StreamAlarmsRequest value) {
} }
@Override @Override
@@ -170,35 +170,35 @@ public final class MxAccessGatewayGrpc {
return getAcknowledgeAlarmMethod; return getAcknowledgeAlarmMethod;
} }
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod; mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> getStreamAlarmsMethod;
@io.grpc.stub.annotations.RpcMethod( @io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "QueryActiveAlarms", fullMethodName = SERVICE_NAME + '/' + "StreamAlarms",
requestType = mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest.class, requestType = mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot.class, responseType = mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage.class,
methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod() { mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> getStreamAlarmsMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> getQueryActiveAlarmsMethod; io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> getStreamAlarmsMethod;
if ((getQueryActiveAlarmsMethod = MxAccessGatewayGrpc.getQueryActiveAlarmsMethod) == null) { if ((getStreamAlarmsMethod = MxAccessGatewayGrpc.getStreamAlarmsMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) { synchronized (MxAccessGatewayGrpc.class) {
if ((getQueryActiveAlarmsMethod = MxAccessGatewayGrpc.getQueryActiveAlarmsMethod) == null) { if ((getStreamAlarmsMethod = MxAccessGatewayGrpc.getStreamAlarmsMethod) == null) {
MxAccessGatewayGrpc.getQueryActiveAlarmsMethod = getQueryActiveAlarmsMethod = MxAccessGatewayGrpc.getStreamAlarmsMethod = getStreamAlarmsMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>newBuilder() io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "QueryActiveAlarms")) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamAlarms"))
.setSampledToLocalTracing(true) .setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest.getDefaultInstance())) mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot.getDefaultInstance())) mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("QueryActiveAlarms")) .setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("StreamAlarms"))
.build(); .build();
} }
} }
} }
return getQueryActiveAlarmsMethod; return getStreamAlarmsMethod;
} }
/** /**
@@ -303,10 +303,17 @@ public final class MxAccessGatewayGrpc {
} }
/** /**
* <pre>
* Session-less central alarm feed. The stream opens with the current
* active-alarm snapshot (one `active_alarm` per alarm), then a single
* `snapshot_complete`, then a `transition` for every subsequent change.
* Served by the gateway's always-on alarm monitor; any number of clients
* fan out from the single monitor without opening a worker session.
* </pre>
*/ */
default void queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request, default void streamAlarms(mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> responseObserver) { io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getQueryActiveAlarmsMethod(), responseObserver); io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStreamAlarmsMethod(), responseObserver);
} }
} }
@@ -384,11 +391,18 @@ public final class MxAccessGatewayGrpc {
} }
/** /**
* <pre>
* Session-less central alarm feed. The stream opens with the current
* active-alarm snapshot (one `active_alarm` per alarm), then a single
* `snapshot_complete`, then a `transition` for every subsequent change.
* Served by the gateway's always-on alarm monitor; any number of clients
* fan out from the single monitor without opening a worker session.
* </pre>
*/ */
public void queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request, public void streamAlarms(mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> responseObserver) { io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> responseObserver) {
io.grpc.stub.ClientCalls.asyncServerStreamingCall( io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getQueryActiveAlarmsMethod(), getCallOptions()), request, responseObserver); getChannel().newCall(getStreamAlarmsMethod(), getCallOptions()), request, responseObserver);
} }
} }
@@ -449,12 +463,19 @@ public final class MxAccessGatewayGrpc {
} }
/** /**
* <pre>
* Session-less central alarm feed. The stream opens with the current
* active-alarm snapshot (one `active_alarm` per alarm), then a single
* `snapshot_complete`, then a `transition` for every subsequent change.
* Served by the gateway's always-on alarm monitor; any number of clients
* fan out from the single monitor without opening a worker session.
* </pre>
*/ */
@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918")
public io.grpc.stub.BlockingClientCall<?, mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> public io.grpc.stub.BlockingClientCall<?, mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage>
queryActiveAlarms(mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request) { streamAlarms(mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest request) {
return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall(
getChannel(), getQueryActiveAlarmsMethod(), getCallOptions(), request); getChannel(), getStreamAlarmsMethod(), getCallOptions(), request);
} }
} }
@@ -514,11 +535,18 @@ public final class MxAccessGatewayGrpc {
} }
/** /**
* <pre>
* Session-less central alarm feed. The stream opens with the current
* active-alarm snapshot (one `active_alarm` per alarm), then a single
* `snapshot_complete`, then a `transition` for every subsequent change.
* Served by the gateway's always-on alarm monitor; any number of clients
* fan out from the single monitor without opening a worker session.
* </pre>
*/ */
public java.util.Iterator<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot> queryActiveAlarms( public java.util.Iterator<mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage> streamAlarms(
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest request) { mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest request) {
return io.grpc.stub.ClientCalls.blockingServerStreamingCall( return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
getChannel(), getQueryActiveAlarmsMethod(), getCallOptions(), request); getChannel(), getStreamAlarmsMethod(), getCallOptions(), request);
} }
} }
@@ -579,7 +607,7 @@ public final class MxAccessGatewayGrpc {
private static final int METHODID_INVOKE = 2; private static final int METHODID_INVOKE = 2;
private static final int METHODID_STREAM_EVENTS = 3; private static final int METHODID_STREAM_EVENTS = 3;
private static final int METHODID_ACKNOWLEDGE_ALARM = 4; private static final int METHODID_ACKNOWLEDGE_ALARM = 4;
private static final int METHODID_QUERY_ACTIVE_ALARMS = 5; private static final int METHODID_STREAM_ALARMS = 5;
private static final class MethodHandlers<Req, Resp> implements private static final class MethodHandlers<Req, Resp> implements
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>, io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
@@ -618,9 +646,9 @@ public final class MxAccessGatewayGrpc {
serviceImpl.acknowledgeAlarm((mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest) request, serviceImpl.acknowledgeAlarm((mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>) responseObserver); (io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>) responseObserver);
break; break;
case METHODID_QUERY_ACTIVE_ALARMS: case METHODID_STREAM_ALARMS:
serviceImpl.queryActiveAlarms((mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest) request, serviceImpl.streamAlarms((mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>) responseObserver); (io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage>) responseObserver);
break; break;
default: default:
throw new AssertionError(); throw new AssertionError();
@@ -676,12 +704,12 @@ public final class MxAccessGatewayGrpc {
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>( mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply>(
service, METHODID_ACKNOWLEDGE_ALARM))) service, METHODID_ACKNOWLEDGE_ALARM)))
.addMethod( .addMethod(
getQueryActiveAlarmsMethod(), getStreamAlarmsMethod(),
io.grpc.stub.ServerCalls.asyncServerStreamingCall( io.grpc.stub.ServerCalls.asyncServerStreamingCall(
new MethodHandlers< new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.QueryActiveAlarmsRequest, mxaccess_gateway.v1.MxaccessGateway.StreamAlarmsRequest,
mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot>( mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage>(
service, METHODID_QUERY_ACTIVE_ALARMS))) service, METHODID_STREAM_ALARMS)))
.build(); .build();
} }
@@ -735,7 +763,7 @@ public final class MxAccessGatewayGrpc {
.addMethod(getInvokeMethod()) .addMethod(getInvokeMethod())
.addMethod(getStreamEventsMethod()) .addMethod(getStreamEventsMethod())
.addMethod(getAcknowledgeAlarmMethod()) .addMethod(getAcknowledgeAlarmMethod())
.addMethod(getQueryActiveAlarmsMethod()) .addMethod(getStreamAlarmsMethod())
.build(); .build();
} }
} }
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -166,25 +166,27 @@ class GatewayClient:
ensure_protocol_success("acknowledge alarm", reply.protocol_status, reply) ensure_protocol_success("acknowledge alarm", reply.protocol_status, reply)
return reply return reply
def query_active_alarms( def stream_alarms(
self, self,
request: pb.QueryActiveAlarmsRequest, request: pb.StreamAlarmsRequest,
*, *,
metadata: Sequence[tuple[str, str]] | None = None, metadata: Sequence[tuple[str, str]] | None = None,
) -> AsyncIterator[pb.ActiveAlarmSnapshot]: ) -> AsyncIterator[pb.AlarmFeedMessage]:
"""Stream a snapshot of all alarms currently Active or ActiveAcked. """Attach to the gateway's central alarm feed.
The gateway's ConditionRefresh equivalent. Use after reconnect to seed The stream opens with one ``AlarmFeedMessage`` per currently-active
local Part 9 state, or to reconcile alarms that may have been missed alarm (the ConditionRefresh snapshot), then a single
during a transport blip. Optionally scoped by alarm-reference prefix ``snapshot_complete``, then a ``transition`` for every subsequent
(``request.alarm_filter_prefix``) so a partial refresh can target an raise / acknowledge / clear. Served by the gateway's always-on alarm
equipment sub-tree. monitor — no worker session is opened — so any number of clients may
attach. Optionally scoped by alarm-reference prefix
(``request.alarm_filter_prefix``).
""" """
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)} kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
if self.options.stream_timeout is not None: if self.options.stream_timeout is not None:
kwargs["timeout"] = self.options.stream_timeout kwargs["timeout"] = self.options.stream_timeout
call = _open_stream(self.raw_stub.QueryActiveAlarms, request, kwargs) call = _open_stream(self.raw_stub.StreamAlarms, request, kwargs)
return _canceling_iterator(call, "query active alarms") return _canceling_iterator(call, "stream alarms")
async def _unary( async def _unary(
self, self,
File diff suppressed because one or more lines are too long
@@ -30,8 +30,7 @@ class MxAccessGatewayStub(object):
additively only. Never renumber or repurpose an existing field number or additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor (and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved` cannot accidentally reuse the retired tag.
declarations today because no field or enum value has ever been removed.
Public client API for MXAccess sessions hosted by the gateway. Public client API for MXAccess sessions hosted by the gateway.
""" """
@@ -67,10 +66,10 @@ class MxAccessGatewayStub(object):
request_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.SerializeToString, request_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.FromString, response_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.FromString,
_registered_method=True) _registered_method=True)
self.QueryActiveAlarms = channel.unary_stream( self.StreamAlarms = channel.unary_stream(
'/mxaccess_gateway.v1.MxAccessGateway/QueryActiveAlarms', '/mxaccess_gateway.v1.MxAccessGateway/StreamAlarms',
request_serializer=mxaccess__gateway__pb2.QueryActiveAlarmsRequest.SerializeToString, request_serializer=mxaccess__gateway__pb2.StreamAlarmsRequest.SerializeToString,
response_deserializer=mxaccess__gateway__pb2.ActiveAlarmSnapshot.FromString, response_deserializer=mxaccess__gateway__pb2.AlarmFeedMessage.FromString,
_registered_method=True) _registered_method=True)
@@ -79,8 +78,7 @@ class MxAccessGatewayServicer(object):
additively only. Never renumber or repurpose an existing field number or additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor (and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved` cannot accidentally reuse the retired tag.
declarations today because no field or enum value has ever been removed.
Public client API for MXAccess sessions hosted by the gateway. Public client API for MXAccess sessions hosted by the gateway.
""" """
@@ -115,8 +113,13 @@ class MxAccessGatewayServicer(object):
context.set_details('Method not implemented!') context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!') raise NotImplementedError('Method not implemented!')
def QueryActiveAlarms(self, request, context): def StreamAlarms(self, request, context):
"""Missing associated documentation comment in .proto file.""" """Session-less central alarm feed. The stream opens with the current
active-alarm snapshot (one `active_alarm` per alarm), then a single
`snapshot_complete`, then a `transition` for every subsequent change.
Served by the gateway's always-on alarm monitor; any number of clients
fan out from the single monitor without opening a worker session.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!') context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!') raise NotImplementedError('Method not implemented!')
@@ -149,10 +152,10 @@ def add_MxAccessGatewayServicer_to_server(servicer, server):
request_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.FromString, request_deserializer=mxaccess__gateway__pb2.AcknowledgeAlarmRequest.FromString,
response_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.SerializeToString, response_serializer=mxaccess__gateway__pb2.AcknowledgeAlarmReply.SerializeToString,
), ),
'QueryActiveAlarms': grpc.unary_stream_rpc_method_handler( 'StreamAlarms': grpc.unary_stream_rpc_method_handler(
servicer.QueryActiveAlarms, servicer.StreamAlarms,
request_deserializer=mxaccess__gateway__pb2.QueryActiveAlarmsRequest.FromString, request_deserializer=mxaccess__gateway__pb2.StreamAlarmsRequest.FromString,
response_serializer=mxaccess__gateway__pb2.ActiveAlarmSnapshot.SerializeToString, response_serializer=mxaccess__gateway__pb2.AlarmFeedMessage.SerializeToString,
), ),
} }
generic_handler = grpc.method_handlers_generic_handler( generic_handler = grpc.method_handlers_generic_handler(
@@ -167,8 +170,7 @@ class MxAccessGateway(object):
additively only. Never renumber or repurpose an existing field number or additively only. Never renumber or repurpose an existing field number or
enum value. When a field or enum value is removed, add a `reserved` range enum value. When a field or enum value is removed, add a `reserved` range
(and `reserved` name) covering it in the same change so a future editor (and `reserved` name) covering it in the same change so a future editor
cannot accidentally reuse the retired tag. There are no `reserved` cannot accidentally reuse the retired tag.
declarations today because no field or enum value has ever been removed.
Public client API for MXAccess sessions hosted by the gateway. Public client API for MXAccess sessions hosted by the gateway.
""" """
@@ -309,7 +311,7 @@ class MxAccessGateway(object):
_registered_method=True) _registered_method=True)
@staticmethod @staticmethod
def QueryActiveAlarms(request, def StreamAlarms(request,
target, target,
options=(), options=(),
channel_credentials=None, channel_credentials=None,
@@ -322,9 +324,9 @@ class MxAccessGateway(object):
return grpc.experimental.unary_stream( return grpc.experimental.unary_stream(
request, request,
target, target,
'/mxaccess_gateway.v1.MxAccessGateway/QueryActiveAlarms', '/mxaccess_gateway.v1.MxAccessGateway/StreamAlarms',
mxaccess__gateway__pb2.QueryActiveAlarmsRequest.SerializeToString, mxaccess__gateway__pb2.StreamAlarmsRequest.SerializeToString,
mxaccess__gateway__pb2.ActiveAlarmSnapshot.FromString, mxaccess__gateway__pb2.AlarmFeedMessage.FromString,
options, options,
channel_credentials, channel_credentials,
insecure, insecure,
+62 -61
View File
@@ -1,8 +1,7 @@
"""Tests for the AcknowledgeAlarm + QueryActiveAlarms client surface (PR E.3).""" """Tests for the AcknowledgeAlarm + StreamAlarms client surface."""
from __future__ import annotations from __future__ import annotations
import asyncio
from typing import Any from typing import Any
import grpc import grpc
@@ -18,7 +17,6 @@ async def test_acknowledge_alarm_sends_request_and_returns_reply() -> None:
stub = FakeGatewayStub() stub = FakeGatewayStub()
stub.acknowledge_alarm.replies = [ stub.acknowledge_alarm.replies = [
pb.AcknowledgeAlarmReply( pb.AcknowledgeAlarmReply(
session_id="session-1",
correlation_id="corr-7", correlation_id="corr-7",
protocol_status=pb.ProtocolStatus(code=pb.PROTOCOL_STATUS_CODE_OK), protocol_status=pb.ProtocolStatus(code=pb.PROTOCOL_STATUS_CODE_OK),
status=pb.MxStatusProxy(success=1, category=pb.MX_STATUS_CATEGORY_OK), status=pb.MxStatusProxy(success=1, category=pb.MX_STATUS_CATEGORY_OK),
@@ -31,7 +29,6 @@ async def test_acknowledge_alarm_sends_request_and_returns_reply() -> None:
reply = await client.acknowledge_alarm( reply = await client.acknowledge_alarm(
pb.AcknowledgeAlarmRequest( pb.AcknowledgeAlarmRequest(
session_id="session-1",
client_correlation_id="corr-7", client_correlation_id="corr-7",
alarm_full_reference="Tank01.Level.HiHi", alarm_full_reference="Tank01.Level.HiHi",
comment="investigating", comment="investigating",
@@ -61,7 +58,6 @@ async def test_acknowledge_alarm_unauthenticated_raises_typed_error() -> None:
with pytest.raises(MxGatewayAuthenticationError): with pytest.raises(MxGatewayAuthenticationError):
await client.acknowledge_alarm( await client.acknowledge_alarm(
pb.AcknowledgeAlarmRequest( pb.AcknowledgeAlarmRequest(
session_id="session-1",
alarm_full_reference="Tank01.Level.HiHi", alarm_full_reference="Tank01.Level.HiHi",
comment="", comment="",
operator_user="alice", operator_user="alice",
@@ -81,7 +77,6 @@ async def test_acknowledge_alarm_permission_denied_raises_typed_error() -> None:
with pytest.raises(MxGatewayAuthorizationError): with pytest.raises(MxGatewayAuthorizationError):
await client.acknowledge_alarm( await client.acknowledge_alarm(
pb.AcknowledgeAlarmRequest( pb.AcknowledgeAlarmRequest(
session_id="session-1",
alarm_full_reference="Tank01.Level.HiHi", alarm_full_reference="Tank01.Level.HiHi",
comment="", comment="",
operator_user="alice", operator_user="alice",
@@ -90,84 +85,90 @@ async def test_acknowledge_alarm_permission_denied_raises_typed_error() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_active_alarms_streams_snapshots() -> None: async def test_stream_alarms_streams_snapshot_then_snapshot_complete() -> None:
snapshots = [ messages = [
pb.ActiveAlarmSnapshot( pb.AlarmFeedMessage(
alarm_full_reference="Tank01.Level.HiHi", active_alarm=pb.ActiveAlarmSnapshot(
current_state=pb.ALARM_CONDITION_STATE_ACTIVE, alarm_full_reference="Tank01.Level.HiHi",
severity=750, current_state=pb.ALARM_CONDITION_STATE_ACTIVE,
severity=750,
),
), ),
pb.ActiveAlarmSnapshot( pb.AlarmFeedMessage(
alarm_full_reference="Tank02.Level.HiHi", active_alarm=pb.ActiveAlarmSnapshot(
current_state=pb.ALARM_CONDITION_STATE_ACTIVE_ACKED, alarm_full_reference="Tank02.Level.HiHi",
severity=750, current_state=pb.ALARM_CONDITION_STATE_ACTIVE_ACKED,
severity=750,
),
), ),
pb.AlarmFeedMessage(snapshot_complete=True),
] ]
stream = FakeSnapshotStream(snapshots) stream = FakeAlarmFeedStream(messages)
stub = FakeGatewayStub(snapshot_stream=stream) stub = FakeGatewayStub(alarm_feed_stream=stream)
client = await GatewayClient.connect( client = await GatewayClient.connect(
ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True), ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True),
stub=stub, stub=stub,
) )
received: list[pb.ActiveAlarmSnapshot] = [] received: list[pb.AlarmFeedMessage] = []
async for snapshot in client.query_active_alarms( async for message in client.stream_alarms(pb.StreamAlarmsRequest()):
pb.QueryActiveAlarmsRequest(session_id="session-1"), received.append(message)
):
received.append(snapshot)
assert len(received) == 2 assert len(received) == 3
assert received[0].alarm_full_reference == "Tank01.Level.HiHi" assert received[0].active_alarm.alarm_full_reference == "Tank01.Level.HiHi"
assert received[0].current_state == pb.ALARM_CONDITION_STATE_ACTIVE assert received[0].active_alarm.current_state == pb.ALARM_CONDITION_STATE_ACTIVE
assert received[1].current_state == pb.ALARM_CONDITION_STATE_ACTIVE_ACKED assert received[1].active_alarm.current_state == pb.ALARM_CONDITION_STATE_ACTIVE_ACKED
assert stub.query_metadata == (("authorization", "Bearer mxgw_test_secret"),) assert received[2].snapshot_complete is True
assert stub.stream_metadata == (("authorization", "Bearer mxgw_test_secret"),)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_active_alarms_passes_filter_prefix() -> None: async def test_stream_alarms_passes_filter_prefix() -> None:
stream = FakeSnapshotStream([]) stream = FakeAlarmFeedStream([])
stub = FakeGatewayStub(snapshot_stream=stream) stub = FakeGatewayStub(alarm_feed_stream=stream)
client = await GatewayClient.connect( client = await GatewayClient.connect(
ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True), ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True),
stub=stub, stub=stub,
) )
iterator = client.query_active_alarms( iterator = client.stream_alarms(
pb.QueryActiveAlarmsRequest(session_id="session-1", alarm_filter_prefix="Tank01."), pb.StreamAlarmsRequest(alarm_filter_prefix="Tank01."),
) )
# Drain to trigger the stub call. # Drain to trigger the stub call.
async for _ in iterator: async for _ in iterator:
pass pass
assert stub.query_request is not None assert stub.stream_request is not None
assert stub.query_request.alarm_filter_prefix == "Tank01." assert stub.stream_request.alarm_filter_prefix == "Tank01."
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_active_alarms_cancels_underlying_stream_on_close() -> None: async def test_stream_alarms_cancels_underlying_stream_on_close() -> None:
snapshots = [ messages = [
pb.ActiveAlarmSnapshot( pb.AlarmFeedMessage(
alarm_full_reference="Tank01.Level.HiHi", active_alarm=pb.ActiveAlarmSnapshot(
current_state=pb.ALARM_CONDITION_STATE_ACTIVE, alarm_full_reference="Tank01.Level.HiHi",
current_state=pb.ALARM_CONDITION_STATE_ACTIVE,
),
), ),
] ]
stream = FakeSnapshotStream(snapshots) stream = FakeAlarmFeedStream(messages)
stub = FakeGatewayStub(snapshot_stream=stream) stub = FakeGatewayStub(alarm_feed_stream=stream)
client = await GatewayClient.connect( client = await GatewayClient.connect(
ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True), ClientOptions(endpoint="fake", api_key="mxgw_test_secret", plaintext=True),
stub=stub, stub=stub,
) )
iterator = client.query_active_alarms(pb.QueryActiveAlarmsRequest(session_id="session-1")) iterator = client.stream_alarms(pb.StreamAlarmsRequest())
first = await anext(iterator) first = await anext(iterator)
await iterator.aclose() await iterator.aclose()
assert first.alarm_full_reference == "Tank01.Level.HiHi" assert first.active_alarm.alarm_full_reference == "Tank01.Level.HiHi"
assert stream.cancelled assert stream.cancelled
class FakeGatewayStub: class FakeGatewayStub:
def __init__(self, snapshot_stream: "FakeSnapshotStream | None" = None) -> None: def __init__(self, alarm_feed_stream: "FakeAlarmFeedStream | None" = None) -> None:
self.open_session = FakeUnary( self.open_session = FakeUnary(
[ [
pb.OpenSessionReply( pb.OpenSessionReply(
@@ -179,19 +180,19 @@ class FakeGatewayStub:
self.acknowledge_alarm = FakeUnary([]) self.acknowledge_alarm = FakeUnary([])
self.OpenSession = self.open_session self.OpenSession = self.open_session
self.AcknowledgeAlarm = self.acknowledge_alarm self.AcknowledgeAlarm = self.acknowledge_alarm
self._snapshot_stream = snapshot_stream or FakeSnapshotStream([]) self._alarm_feed_stream = alarm_feed_stream or FakeAlarmFeedStream([])
self.query_request: pb.QueryActiveAlarmsRequest | None = None self.stream_request: pb.StreamAlarmsRequest | None = None
self.query_metadata: tuple[tuple[str, str], ...] | None = None self.stream_metadata: tuple[tuple[str, str], ...] | None = None
def QueryActiveAlarms( def StreamAlarms(
self, self,
request: pb.QueryActiveAlarmsRequest, request: pb.StreamAlarmsRequest,
*, *,
metadata: tuple[tuple[str, str], ...], metadata: tuple[tuple[str, str], ...],
) -> "FakeSnapshotStream": ) -> "FakeAlarmFeedStream":
self.query_request = request self.stream_request = request
self.query_metadata = metadata self.stream_metadata = metadata
return self._snapshot_stream return self._alarm_feed_stream
class FakeUnary: class FakeUnary:
@@ -214,18 +215,18 @@ class FakeUnary:
return self.replies.pop(0) return self.replies.pop(0)
class FakeSnapshotStream: class FakeAlarmFeedStream:
def __init__(self, snapshots: list[pb.ActiveAlarmSnapshot]) -> None: def __init__(self, messages: list[pb.AlarmFeedMessage]) -> None:
self._snapshots = list(snapshots) self._messages = list(messages)
self.cancelled = False self.cancelled = False
def __aiter__(self) -> "FakeSnapshotStream": def __aiter__(self) -> "FakeAlarmFeedStream":
return self return self
async def __anext__(self) -> pb.ActiveAlarmSnapshot: async def __anext__(self) -> pb.AlarmFeedMessage:
if not self._snapshots: if not self._messages:
raise StopAsyncIteration raise StopAsyncIteration
return self._snapshots.pop(0) return self._messages.pop(0)
def cancel(self) -> None: def cancel(self) -> None:
self.cancelled = True self.cancelled = True
@@ -1,6 +1,6 @@
"""Regression tests for Client.Python-003: stream timeout-kwarg fallback. """Regression tests for Client.Python-003: stream timeout-kwarg fallback.
`stream_events_raw` and `query_active_alarms` must tolerate a fake/older stub `stream_events_raw` and `stream_alarms` must tolerate a fake/older stub
that does not accept a ``timeout`` keyword argument, matching the fallback that does not accept a ``timeout`` keyword argument, matching the fallback
already present in `galaxy.watch_deploy_events` and the unary `_unary` helper. already present in `galaxy.watch_deploy_events` and the unary `_unary` helper.
""" """
@@ -51,9 +51,9 @@ class _NoTimeoutStubStreamEvents:
self.StreamEvents = stream self.StreamEvents = stream
class _NoTimeoutStubQueryAlarms: class _NoTimeoutStubStreamAlarms:
def __init__(self, stream: _NoTimeoutStream) -> None: def __init__(self, stream: _NoTimeoutStream) -> None:
self.QueryActiveAlarms = stream self.StreamAlarms = stream
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -78,24 +78,30 @@ async def test_stream_events_raw_falls_back_when_stub_rejects_timeout() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_active_alarms_falls_back_when_stub_rejects_timeout() -> None: async def test_stream_alarms_falls_back_when_stub_rejects_timeout() -> None:
stream = _NoTimeoutStream( stream = _NoTimeoutStream(
[pb.ActiveAlarmSnapshot(alarm_full_reference="Tank01.Level.HiHi")], [
pb.AlarmFeedMessage(
active_alarm=pb.ActiveAlarmSnapshot(
alarm_full_reference="Tank01.Level.HiHi",
),
),
],
) )
client = await GatewayClient.connect( client = await GatewayClient.connect(
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0), ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0),
stub=_NoTimeoutStubQueryAlarms(stream), stub=_NoTimeoutStubStreamAlarms(stream),
) )
received = [ received = [
snapshot message
async for snapshot in client.query_active_alarms( async for message in client.stream_alarms(
pb.QueryActiveAlarmsRequest(session_id="session-1"), pb.StreamAlarmsRequest(),
) )
] ]
assert len(received) == 1 assert len(received) == 1
assert received[0].alarm_full_reference == "Tank01.Level.HiHi" assert received[0].active_alarm.alarm_full_reference == "Tank01.Level.HiHi"
@pytest.mark.asyncio @pytest.mark.asyncio
+19 -18
View File
@@ -16,9 +16,9 @@ use crate::auth::AuthInterceptor;
use crate::error::{ensure_command_success, ensure_protocol_success, Error}; use crate::error::{ensure_command_success, ensure_protocol_success, Error};
use crate::generated::mxaccess_gateway::v1::mx_access_gateway_client::MxAccessGatewayClient; use crate::generated::mxaccess_gateway::v1::mx_access_gateway_client::MxAccessGatewayClient;
use crate::generated::mxaccess_gateway::v1::{ use crate::generated::mxaccess_gateway::v1::{
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, CloseSessionReply, AcknowledgeAlarmReply, AcknowledgeAlarmRequest, AlarmFeedMessage, CloseSessionReply,
CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply, CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply,
OpenSessionRequest, QueryActiveAlarmsRequest, StreamEventsRequest, OpenSessionRequest, StreamAlarmsRequest, StreamEventsRequest,
}; };
use crate::options::ClientOptions; use crate::options::ClientOptions;
use crate::session::Session; use crate::session::Session;
@@ -33,11 +33,11 @@ pub type RawGatewayClient = MxAccessGatewayClient<InterceptedService<Channel, Au
pub type EventStream = pub type EventStream =
std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<MxEvent, Error>> + Send + 'static>>; std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<MxEvent, Error>> + Send + 'static>>;
/// Pinned, boxed [`ActiveAlarmSnapshot`] stream returned by /// Pinned, boxed [`AlarmFeedMessage`] stream returned by
/// [`GatewayClient::query_active_alarms`]. Errors are pre-mapped from /// [`GatewayClient::stream_alarms`]. Errors are pre-mapped from
/// `tonic::Status` to [`Error`]; dropping the stream cancels the call. /// `tonic::Status` to [`Error`]; dropping the stream cancels the call.
pub type ActiveAlarmStream = std::pin::Pin< pub type AlarmFeedStream = std::pin::Pin<
Box<dyn futures_core::Stream<Item = Result<ActiveAlarmSnapshot, Error>> + Send + 'static>, Box<dyn futures_core::Stream<Item = Result<AlarmFeedMessage, Error>> + Send + 'static>,
>; >;
/// Thin async wrapper around the generated gateway client. /// Thin async wrapper around the generated gateway client.
@@ -227,26 +227,27 @@ impl GatewayClient {
Ok(reply) Ok(reply)
} }
/// Open the server-streaming `QueryActiveAlarms` RPC — the gateway's /// Attach to the gateway's central `StreamAlarms` feed.
/// ConditionRefresh equivalent.
/// ///
/// The returned [`ActiveAlarmStream`] yields one [`ActiveAlarmSnapshot`] /// The returned [`AlarmFeedStream`] opens with one [`AlarmFeedMessage`]
/// per currently-active alarm. Dropping the stream cancels the gRPC call /// per currently-active alarm (the ConditionRefresh snapshot), then a
/// cooperatively. Optional alarm-reference prefix scoping /// single `snapshot_complete`, then a `transition` for every subsequent
/// (`request.alarm_filter_prefix`) limits the stream to a sub-tree. /// raise / acknowledge / clear. It is served by the gateway's always-on
/// alarm monitor — no worker session is opened — so any number of clients
/// may attach. Dropping the stream cancels the gRPC call cooperatively.
/// Optional alarm-reference prefix scoping (`request.alarm_filter_prefix`)
/// limits the stream to a sub-tree.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns the `tonic::Status` mapped through [`Error::from`] if the /// Returns the `tonic::Status` mapped through [`Error::from`] if the
/// server rejects the request. /// server rejects the request.
pub async fn query_active_alarms( pub async fn stream_alarms(
&self, &self,
request: QueryActiveAlarmsRequest, request: StreamAlarmsRequest,
) -> Result<ActiveAlarmStream, Error> { ) -> Result<AlarmFeedStream, Error> {
let mut client = self.inner.clone(); let mut client = self.inner.clone();
let response = client let response = client.stream_alarms(self.stream_request(request)).await?;
.query_active_alarms(self.stream_request(request))
.await?;
let stream = futures_util::StreamExt::map(response.into_inner(), |result| { let stream = futures_util::StreamExt::map(response.into_inner(), |result| {
result.map_err(Error::from) result.map_err(Error::from)
}); });
+39 -22
View File
@@ -8,6 +8,7 @@ use std::time::Duration;
use futures_core::Stream; use futures_core::Stream;
use futures_util::StreamExt; use futures_util::StreamExt;
use mxgateway_client::generated::mxaccess_gateway::v1::alarm_feed_message;
use mxgateway_client::generated::mxaccess_gateway::v1::mx_access_gateway_server::{ use mxgateway_client::generated::mxaccess_gateway::v1::mx_access_gateway_server::{
MxAccessGateway, MxAccessGatewayServer, MxAccessGateway, MxAccessGatewayServer,
}; };
@@ -16,12 +17,12 @@ use mxgateway_client::generated::mxaccess_gateway::v1::mx_command_reply;
use mxgateway_client::generated::mxaccess_gateway::v1::mx_value::Kind; use mxgateway_client::generated::mxaccess_gateway::v1::mx_value::Kind;
use mxgateway_client::generated::mxaccess_gateway::v1::{ use mxgateway_client::generated::mxaccess_gateway::v1::{
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AddItemReply, AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AddItemReply,
BulkReadReply, BulkReadResult, BulkSubscribeReply, BulkWriteReply, BulkWriteResult, AlarmFeedMessage, BulkReadReply, BulkReadResult, BulkSubscribeReply, BulkWriteReply,
CloseSessionReply, CloseSessionRequest, MxCommandKind, MxCommandReply, MxDataType, MxEvent, BulkWriteResult, CloseSessionReply, CloseSessionRequest, MxCommandKind, MxCommandReply,
MxEventFamily, MxStatusCategory, MxStatusProxy, MxStatusSource, MxValue, OpenSessionReply, MxDataType, MxEvent, MxEventFamily, MxStatusCategory, MxStatusProxy, MxStatusSource, MxValue,
OpenSessionRequest, ProtocolStatus, ProtocolStatusCode, QueryActiveAlarmsRequest, SessionState, OpenSessionReply, OpenSessionRequest, ProtocolStatus, ProtocolStatusCode, SessionState,
StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry, WriteSecured2BulkEntry, StreamAlarmsRequest, StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry,
WriteSecuredBulkEntry, WriteSecured2BulkEntry, WriteSecuredBulkEntry,
}; };
use mxgateway_client::{ use mxgateway_client::{
ApiKey, ClientOptions, CommandError, Error, GatewayClient, MxStatus, MxValue as ClientMxValue, ApiKey, ClientOptions, CommandError, Error, GatewayClient, MxStatus, MxValue as ClientMxValue,
@@ -208,7 +209,6 @@ async fn acknowledge_alarm_returns_reply_with_native_status() {
let reply = client let reply = client
.acknowledge_alarm(AcknowledgeAlarmRequest { .acknowledge_alarm(AcknowledgeAlarmRequest {
session_id: "session-fixture".to_owned(),
client_correlation_id: "corr-1".to_owned(), client_correlation_id: "corr-1".to_owned(),
alarm_full_reference: "Tank01.Level.HiHi".to_owned(), alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
comment: "investigating".to_owned(), comment: "investigating".to_owned(),
@@ -225,7 +225,7 @@ async fn acknowledge_alarm_returns_reply_with_native_status() {
} }
#[tokio::test] #[tokio::test]
async fn query_active_alarms_streams_snapshot_rows() { async fn stream_alarms_streams_snapshot_then_complete() {
let state = Arc::new(FakeState::default()); let state = Arc::new(FakeState::default());
let endpoint = spawn_fake_gateway(state.clone()).await; let endpoint = spawn_fake_gateway(state.clone()).await;
let client = GatewayClient::connect(ClientOptions::new(endpoint)) let client = GatewayClient::connect(ClientOptions::new(endpoint))
@@ -233,15 +233,23 @@ async fn query_active_alarms_streams_snapshot_rows() {
.unwrap(); .unwrap();
let mut stream = client let mut stream = client
.query_active_alarms(QueryActiveAlarmsRequest { .stream_alarms(StreamAlarmsRequest::default())
session_id: "session-fixture".to_owned(),
..QueryActiveAlarmsRequest::default()
})
.await .await
.unwrap(); .unwrap();
let first = stream.next().await.unwrap().unwrap(); let first = stream.next().await.unwrap().unwrap();
assert_eq!(first.alarm_full_reference, "Tank01.Level.HiHi"); match first.payload {
Some(alarm_feed_message::Payload::ActiveAlarm(snapshot)) => {
assert_eq!(snapshot.alarm_full_reference, "Tank01.Level.HiHi");
}
other => panic!("expected an active-alarm snapshot, got {other:?}"),
}
let second = stream.next().await.unwrap().unwrap();
assert_eq!(
second.payload,
Some(alarm_feed_message::Payload::SnapshotComplete(true)),
);
} }
#[test] #[test]
@@ -907,7 +915,6 @@ impl MxAccessGateway for FakeGateway {
_request: Request<AcknowledgeAlarmRequest>, _request: Request<AcknowledgeAlarmRequest>,
) -> Result<Response<AcknowledgeAlarmReply>, Status> { ) -> Result<Response<AcknowledgeAlarmReply>, Status> {
Ok(Response::new(AcknowledgeAlarmReply { Ok(Response::new(AcknowledgeAlarmReply {
session_id: "session-fixture".to_owned(),
correlation_id: "corr-1".to_owned(), correlation_id: "corr-1".to_owned(),
protocol_status: Some(ok_status("ack ok")), protocol_status: Some(ok_status("ack ok")),
status: Some(MxStatusProxy { status: Some(MxStatusProxy {
@@ -920,18 +927,28 @@ impl MxAccessGateway for FakeGateway {
})) }))
} }
type QueryActiveAlarmsStream = type StreamAlarmsStream =
Pin<Box<dyn Stream<Item = Result<ActiveAlarmSnapshot, Status>> + Send + 'static>>; Pin<Box<dyn Stream<Item = Result<AlarmFeedMessage, Status>> + Send + 'static>>;
async fn query_active_alarms( async fn stream_alarms(
&self, &self,
_request: Request<QueryActiveAlarmsRequest>, _request: Request<StreamAlarmsRequest>,
) -> Result<Response<Self::QueryActiveAlarmsStream>, Status> { ) -> Result<Response<Self::StreamAlarmsStream>, Status> {
let (sender, receiver) = mpsc::channel(4); let (sender, receiver) = mpsc::channel(4);
sender sender
.send(Ok(ActiveAlarmSnapshot { .send(Ok(AlarmFeedMessage {
alarm_full_reference: "Tank01.Level.HiHi".to_owned(), payload: Some(alarm_feed_message::Payload::ActiveAlarm(
..ActiveAlarmSnapshot::default() ActiveAlarmSnapshot {
alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
..ActiveAlarmSnapshot::default()
},
)),
}))
.await
.unwrap();
sender
.send(Ok(AlarmFeedMessage {
payload: Some(alarm_feed_message::Payload::SnapshotComplete(true)),
})) }))
.await .await
.unwrap(); .unwrap();
-828
View File
@@ -1,828 +0,0 @@
# aaAlarmManagedClient discovery — public surface, 2026-05-01
Result of running
`MxGateway.Worker.Tests.AlarmClientDiscoveryTests.DumpAlarmClientPublicSurface`
against the deployed AVEVA assembly:
- File:
`C:\Program Files (x86)\ArchestrA\Framework\Bin\ViewAppFramework\Content\MA\aaAlarmManagedClient.dll`
- Assembly identity: `aaAlarmManagedClient, Version=1.0.7368.41290,
Culture=neutral, PublicKeyToken=7ebd82b507d9e10c`
## Public types
- `aaAlarmManagedClient.AlarmClient` (class)
- `aaAlarmManagedClient.PriorityData` (class)
That's the entire exported surface — two types, no interfaces, no
delegates.
## `AlarmClient` events
**None.** The class has no public events at all. The reflection probe's
`GetEvents(BindingFlags.Public | Instance | Static)` returned an empty
list.
## `AlarmClient` methods (relevant subset)
- **Lifecycle:**
`RegisterConsumer(int hWnd, string szProductName, string
szApplicationName, string szVersion, bool bRetainHiddenAlarms) → int`,
`DeregisterConsumer() → int`,
`InitializeConsumer(string szApplicationName) → int`,
`UninitializeConsumer() → int`,
`Dispose()`.
- **Subscription:**
`Subscribe(string szSubscription, short wFromPri, short wToPri,
eQueryType QueryType, eSortFlags SortFlags, eAlarmFilterState
FilterMask, eAlarmFilterState FilterSpecification) → int`.
- **Change enumeration (pull on poke):**
`GetStatistics(out int lPercentQuery, out int lTotalAlarms, out int
lActiveAlarms, out int lSuppressedAlarms, out int lSuppressedFilters,
out int lNewAlarms, out int lChangesCount, out int[] ChangeCodes,
out int[] ChangePos, out int[] hAlarm) → int`.
- **Record fetch:**
`GetAlarmExtendedRec(int lIndex, out AlarmRecord almRec) → int`,
`GetAlarmExtendedRec2(...)`,
`GetHighPriAlarm(out AlarmRecord almRec) → int`.
- **Selection model** (used by ack-selected-* family):
`DeselectAll`, `SelectAlaramEntry(short select, int from, int to)`,
`SelectByGUID(Guid)`, `SelectAlarmCount(int from, int to)`.
- **Acknowledge:**
`AlarmAckByGUID(Guid alarmGuid, string ackComment, string ackOprName,
string ackOprNode, string ackOprDomain, string ackOprFullName) → int`
is the per-alarm full-fidelity native ack.
`AlarmAckSelected(string ackComment, string ackOprName, string
ackOprNode, string ackOprDomain, string ackOprFullName) → int`
acks whatever the selection model currently has selected.
Several `AckSelected*Group/Tag/Priority/All/Visible*Alarms_Ex(...)`
variants exist for bulk ack scoped to a group / tag / priority range.
- **Suppress / shelve:** `SupressSelected*` and `ShelveSelected*`
families plus `DoAlarmShelveAction(...)`. Out of scope for the v1
alarm path.
- **Snapshot/filter** (`SF*` prefix): `SFSetSortA / SFSetFilterA /
SFCreateSnapshot / SFGetListCount / SFDeleteSnapshot / SFRefreshAlarm /
SFGetStatistics`. Snapshot-style query API, distinct from the
consumer-subscription path. Not currently used.
## What this means
The architecture comment on
`src/MxGateway.Worker/MxAccess/AlarmClientConsumer.cs` (PR A.5) is
**wrong against this deployed assembly**:
> "The AVEVA alarm-manager surface (`IAlarmMgrDataProvider`) exposes
> the events we need as plain .NET events — no Windows message pump
> required."
There is no managed event surface. `AlarmClient.RegisterConsumer`
takes an `hWnd` because **WM_APP messaging is the actual notification
mechanism**: AVEVA's alarm provider WM_APP-pokes the registered window,
and the consumer is expected to call `GetStatistics` on each poke to
pull `ChangeCodes` / `ChangePos` / `hAlarm` arrays, then
`GetAlarmExtendedRec(pos, …)` per index to fetch each changed record.
`AlarmClientConsumer.AlarmRecordReceived` has no production callers as
a result — `RaiseAlarmRecordReceived` is `internal` for tests and
never gets invoked at runtime. Until A.2 lands a WM_APP pump,
`MX_EVENT_FAMILY_ON_ALARM_TRANSITION` cannot carry events.
## Live runtime probe — 2026-05-01
`MxGateway.Worker.Tests.AlarmClientWmProbeTests.ProbeAlarmClientWmMessages`
is a Skip-gated runtime probe that creates a real message-only
window, calls `AlarmClient.RegisterConsumer(hWnd, …)` +
`Subscribe(@"\Galaxy!", …)`, and pumps for 20s while logging every
window message that arrives. Run results below — this turned the
"WM_APP pump" design assumption upside down.
**`RegisterConsumer` and `Subscribe` both returned 0 (success).** The
calls are valid against the deployed assembly; no parameter pinning
needed.
**A registered-message-class WM (ID `0xC275` in this OS session)
fired every ~1s after `Subscribe` completed.** Constant
`wParam = 0x00001100`, constant `lParam = 0x079E46D8` (looks like a
stable pointer into AVEVA-internal state) for all 20 hits. The
constant payload across hits with no Galaxy alarm being fired
suggests this is a **heartbeat/keepalive**, not a per-change
notification.
**Critically: this WM is delivered to AVEVA's own internal window
(`hwnd=0x18032E`) — NOT to the consumer's `hWnd` we passed in.** The
consumer window's `WndProc` received only the standard creation
sequence (`WM_GETMINMAXINFO`, `WM_NCCREATE`, `WM_NCCALCSIZE`,
`WM_CREATE`) and the destruction sequence (`WM_NCDESTROY`,
`WM_DESTROY`, `WM_NCCALCSIZE`) — nothing in between. AVEVA's
notification path runs entirely against AVEVA's internal window;
it never forwards to the user-supplied hWnd.
The message ID itself is dynamic (a `RegisterWindowMessage`
allocation in the >= 0xC000 range), so it cannot be hard-coded —
each consumer process must call `RegisterWindowMessage` with the
correct *string* and use whatever ID the OS returns.
## What this means for A.2
The "WM_APP pump on the user hWnd" design — what the original plan
banner described and what the previous version of this doc
recommended — does not match how AVEVA actually delivers
notifications. The hWnd parameter to `RegisterConsumer` does not
appear to receive any of AVEVA's alarm traffic; it's likely used
only as a registration identity (and perhaps as a parent for modal
dialogs).
Two viable A.2 designs given the probe data:
1. **Polling.** Just call `GetStatistics` on a timer (e.g. every
500ms in the worker's STA) and react to the change set it
reports. No window plumbing needed. Trade-off: latency floor =
poll period; modest CPU floor because the call is cheap. Matches
the heartbeat-style WM 0xC275 semantics — AVEVA itself runs a
poll loop internally.
2. **Hook AVEVA's internal window.** Discover AVEVA's own window
(`hwnd=0x18032E` in the probe), `SetWindowsHookEx` or
`SetWindowSubclass` on it, and intercept WM 0xC275 on AVEVA's
thread. Higher fidelity, near-zero latency, but invasive,
fragile across AVEVA upgrades, and requires running on the same
process / thread as the AVEVA window. Probably a non-starter
without further AVEVA documentation.
**Recommendation:** the polling path (option 1) is cheaper to
implement, more robust against AVEVA-internal change, and
acceptable for a typical alarm cadence. The worker's existing STA
already provides a thread-affinitized timer surface. The unanswered
question is whether `GetStatistics` can be safely called outside
AVEVA's own message-pump thread — confirmable by extending the
probe to fire `GetStatistics` on its own thread and check the
result.
## Alarm-provider visibility — third probe run, 2026-05-01
Extended the probe to call `AlarmClient.GetProviders` after
`RegisterConsumer`. Result on this rig:
```
GetProviders -> rc=0 count=0 list=[]
```
**Zero alarm providers visible to the consumer process.** This
explains every preceding probe run: no providers means no alarm
events, regardless of how many times any value (including a
bool with an `$Alarm` extension) flips. `Subscribe(@"\Galaxy!")`
returns 0 (success) but matches nothing because the alarm-manager
chain that provides the matching feed doesn't expose any provider
to this consumer.
A System Platform script flipping `TestMachine_001.TestAlarm001`
every 10s during this probe run produced no observable
`GetStatistics` transitions, no `positions[]` / `handles[]`
entries, no change in any field — confirms the silence is not
about subscription-scope / message-pump but about provider
absence.
### Possible causes
1. **No `$Alarm` extension on the test bool.** If
`TestMachine_001.TestAlarm001` is a regular UDA without a
`BoolAlarm` extension wired to it, flipping the value just
writes a new value — no alarm fires.
2. **Alarm manager service not running.** AVEVA's `aaAlarmMgr`
(or the equivalent on this rig's Platform version) needs to
be running for providers to register.
3. **Process security context.** A consumer running under a
normal user account may not see providers that registered
under `LocalSystem` / a Platform service identity. The
gateway-worker installation runs under a service account
that may have access where `dotnet test` doesn't.
## InitializeConsumer required — fourth probe run, 2026-05-01
Adding `InitializeConsumer("AlarmProbe.Tests")` before
`RegisterConsumer` made `\Galaxy!` appear in `GetProviders`
(count=1, status 0 → 100 within 500ms). So #2 and #3 above are
NOT the cause — the consumer can see the alarm provider once it
calls Initialize. That's a missing API-call ordering, not a
permission or service issue.
```
InitializeConsumer -> 0
RegisterConsumer -> 0
GetProviders [after Register] -> rc=0 count=0 list=[]
Subscribe('\Galaxy!') -> 0
GetProviders [after Subscribe] -> rc=0 count=1 list=[ 0 \Galaxy!]
GetProviders [poll #1] -> rc=0 count=1 list=[100 \Galaxy!]
```
Despite the provider being visible at "100% query complete" for
the entire 60s window, `GetStatistics` continued to report
`total=0 active=0 codes=[7]` — no alarm transitions reached the
consumer even with a System Platform script flipping the test
boolean every 10s during the run.
That isolates the remaining unknown to whether the test bool's
alarm extension is actually generating MxAccess alarm-provider
events when its value flips. The probe has confirmed every link
in the consumer chain works (Initialize → Register → Subscribe →
provider visible at 100%) — what's missing is alarm traffic from
the producer side. ObjectViewer or another live consumer running
alongside the script is the next discriminator: does it visibly
see the alarm fire?
API-ordering finding: `InitializeConsumer` MUST precede
`RegisterConsumer` (or at least, must be called before
`GetProviders` returns anything). PR A.5's `AlarmClientConsumer`
omits `InitializeConsumer` entirely — that's a bug fix to apply
even before A.2 lands, since without it the provider chain never
becomes visible.
## Subscribe-parameter sweep — fifth probe run, 2026-05-01
Even with `InitializeConsumer` + provider visible at status 100,
no alarm transitions arrived during a 60s window with the user's
script flipping the test bool every 10s. Tried:
- `qtSummary` and `qtHistory` (the only `eQueryType` values).
- Priority 1..999 and 0..32767.
- `eAlarmFilterState.asNone` and `asAlarmActiveNow` for both
`FilterMask` and `FilterSpecification`.
`eAlarmFilterState` is single-state-valued (asNone=0,
asAlarmActiveNow=1, asAlarmAcked=2, asShelved=3), not flag bits.
None of these knobs surfaced any alarm activity.
User confirmation 2026-05-01: the test bool does have a
`BoolAlarm` extension on it; in `aaObjectViewer` the
`$Alarm.InAlarm` sub-attribute flips true/false in lockstep with
the script's writes. So the alarm extension is **evaluating**
its condition, just not visibly producing transitions on the
`aaAlarmManagedClient` consumer stream.
## Multi-channel + multi-subscription probe — sixth run, 2026-05-01
Extended the probe to try every consumer-side approach in
parallel:
- **Subscription expressions** (sequential): `\Galaxy!`,
`\Galaxy!*`, `\\Galaxy!`, `\Galaxy!TestArea`, `\\.\Galaxy!`.
All Subscribe calls returned rc=0; the last one
(`\\.\Galaxy!`) is reflected in `GetProviders` (count=1).
- **Read channels** polled at 500ms cadence: `GetStatistics`,
`GetHighPriAlarm`, `SFCreateSnapshot` + `SFGetStatistics`.
- **Filter+sort**: priority 0..32767, `qtSummary`,
state=`asAlarmActiveNow`, sort=`sfReturnNewestFirst`.
- **AlarmRecord init** (worked around `Not a valid Win32
FileTime` exception): all DateTime fields pre-set to FILETIME
epoch (1601-01-01 UTC) before the call, since
`default(DateTime)` is outside FILETIME range and trips the
interop marshaler.
Result of the 60s run with `TestMachine_001.TestAlarm001` being
flipped every 10s:
```
Subscribe('\Galaxy!') -> 0
Subscribe('\Galaxy!*') -> 0
Subscribe('\\Galaxy!') -> 0
Subscribe('\Galaxy!TestArea') -> 0
Subscribe('\\.\Galaxy!') -> 0
GetProviders [after Subscribe-multi] -> count=1 list=[ 0 \\.\Galaxy!]
GetStatistics #1: total=0 active=0 changes=1 codes=[7] positions=[] handles=[]
GetHighPriAlarm #1: rc=0 { }
SF channel #1: SFCreate=0 numAlarms=0 SFStats=0 unackRet=0 unackAlm=0 ackAlm=0 others=0 events=0 idxNewest=-1
```
**No further "(changed)" entries for the entire 60s window.**
Every read API returned the same empty result on every poll.
User confirms the alarm IS firing — `aaObjectViewer` sees
`$Alarm.InAlarm` flip in lockstep with the script. Historian
records exist (per user — needs verification by querying the
historian directly).
## Conclusion of consumer-side probing
`aaAlarmManagedClient.AlarmClient` is **not** the receive
surface AVEVA's alarm pipeline routes to in this Galaxy
configuration. The consumer chain is verified end-to-end:
- `InitializeConsumer` + `RegisterConsumer` + `Subscribe` all
succeed (rc=0).
- `GetProviders` finds `\Galaxy!` once Initialize is called.
- All read APIs (`GetStatistics`, `GetHighPriAlarm`,
`SFCreateSnapshot`/`SFGetStatistics`) return empty even with
every documented filter combination.
- The consumer's hWnd receives zero AVEVA messages between
`WM_CREATE` and `WM_DESTROY`; AVEVA's traffic goes to its own
internal hwnd.
The next investigation directions are not consumer-side:
1. **Inspect `aaObjectViewer`'s alarm SDK** to see what library
it uses to read alarms. If different from
`aaAlarmManagedClient`, switch the worker over.
2. **Query the historian directly** (`aahEventStorage` /
`aahEventSvc`) to confirm alarms are recorded — and use the
same path for v2 alarm capture.
3. **Inspect AVEVA's alarm-routing config** for this Galaxy in
System Platform IDE — area assignments, alarm provider
bindings, "publish alarm events to" settings on the platform.
For A.2 implementation: the `aaAlarmManagedClient` path the
gateway-worker is currently architected around may be a
dead-end on customer Galaxies configured this way. If the
alarms truly only flow through the historian event-storage path,
A.2 needs to consume from `aahEventStorage` instead — a
fundamental architecture pivot.
## BREAKTHROUGH — seventh probe run, 2026-05-01
Two changes finally produced a signal:
1. **Subscription scope:** `\\<MachineName>\Galaxy!<TopArea>` is the
canonical AlarmClient subscription format (per ArchestrA Alarm
Client docs at `archestra6.rssing.com/chan-12008125/article13.html`):
`\\Node\Provider!Area!Filter`, where Node is the *machine* name,
Provider is **literally `Galaxy`**, and Area is a hosted area
object. For this rig (`\\DESKTOP-6JL3KKO\Galaxy!DEV`) the DEV
area — the platform's primary area — is the right scope. Earlier
`\Galaxy!`, `\Galaxy!TestArea`, `\\.\Galaxy!`, etc., all returned
rc=0 but matched no traffic — they were not the canonical form.
2. **`InitializeConsumer` before `RegisterConsumer`** — already
discovered earlier; bug-fix for PR A.5's `AlarmClientConsumer`.
With both in place, `GetHighPriAlarm` returned a record on every
poll for 60s straight (117/117 calls), but threw
`ArgumentOutOfRangeException: Not a valid Win32 FileTime` instead
of returning successfully — the AlarmRecord struct contains five
DateTime fields (`ar_Time`, `ar_OrigTime`, `ar_AckTime`,
`ar_RtnTime`, `ar_SubTime`) and AVEVA writes sentinel/invalid
FILETIME values for unset ones (e.g., `ar_AckTime` for an
unacknowledged alarm). The .NET interop that AVEVA ships
(`aaAlarmManagedClient.dll`) auto-converts FILETIME→DateTime and
rejects out-of-range values.
`GetStatistics` continues to report `total=0 active=0` even with
GetHighPriAlarm returning records — those two API surfaces have
genuinely different views in AVEVA's data model.
So: **alarms flow through `aaAlarmManagedClient.AlarmClient` once
the subscription expression is canonical**. The blocking issue is
extracting the payload past the .NET interop's DateTime
auto-marshaling.
## Remaining work to capture alarm payloads
Define a custom COM interop that uses `long` (FILETIME-as-int64)
instead of `DateTime` for the timestamp fields. Approach options:
1. **Patch the AVEVA-shipped `aaAlarmManagedClient.dll`** — ildasm
the assembly, replace `DateTime` with `long` on AlarmRecord's
timestamp fields, ilasm back. Brittle across AVEVA upgrades.
2. **Write our own `[ComImport]` interface** — declare
`IRawAlarmConsumer` ourselves with safe-blittable types,
discover the underlying COM IID (via reflection on
`AlarmClient`'s `[Guid]` attribute), and `(IRawAlarmConsumer)
alarmClient` cast. Cleaner; requires the IID.
3. **Use `IDispatch` late binding** — dispatch-Invoke bypasses
strong-typed marshaling. Verbose but doesn't need IIDs.
For PR A.2's worker integration, option 2 is the least
disruptive. Once the interop is custom, `AlarmClient.Subscribe` +
`GetHighPriAlarm` + `GetAlarmExtendedRec` form a viable
polling-style alarm consumer.
**REVISED 2026-05-01 — option 1 not directly applicable.**
Reflection on `aaAlarmManagedClient.AlarmClient` shows it
implements only `IDisposable` (no `[ComImport]` interface, no
class GUID). It has a single field `CwwAlarmConsumer*
m_almUnmanaged` — meaning `AlarmClient` is a **C++/CLI managed
wrapper around a native C++ class**, NOT a COM-interop class.
The DateTime conversion happens inside the AVEVA wrapper's IL,
not at a .NET-to-COM marshaling boundary. There is no separate
COM interface IID we can QI to.
Revised approach options:
A. **Switch to `wnwrapConsumer.dll`** — a separate standalone
COM library AVEVA ships at
`C:\Program Files (x86)\Common Files\ArchestrA\wnwrapConsumer.dll`
exposing `WNWRAPCONSUMERLib.wwAlarmConsumerClass` with
`SetXmlAlarmQuery` / `GetXmlCurrentAlarms`. XML-string output
bypasses FILETIME marshaling entirely.
B. **Patch `aaAlarmManagedClient.dll` IL** — wrap the unsafe
`DateTime.FromFileTime` calls with a safe variant. Direct
fix but modifies a vendor binary.
C. **Reflect into `m_almUnmanaged` and call native vtable**
get the IntPtr, walk the MSVC C++ vtable, call
`__thiscall` methods via `Marshal.GetDelegateForFunctionPointer`.
Doable but requires reverse-engineering the C++ class layout.
Option A is the best fit: real COM-based, self-contained in
our code, conventional production-grade approach (the WIN-911
consumer pattern referenced in AVEVA support forums uses it).
The polling-vs-WM_APP-callback question from earlier is now
moot: `GetStatistics`'s `positions[]/handles[]` arrays remained
empty even when alarms were demonstrably present. The active
read API for current alarms is `GetHighPriAlarm`, not
`GetStatistics`'s change array.
### Implications for A.2 implementation
The A.2 PR's value is unmeasurable until at least one alarm
provider is visible. The choice between polling-via-`GetStatistics`
and the callback path can only be decided by observing what
populates first when a real alarm fires. Without a provider,
both paths return the same "nothing happening" answer.
Until that's resolved, A.2 implementation work is genuinely
blocked on a dev-rig configuration issue — not on architectural
choice or code structure.
## GetStatistics polling — second probe run, 2026-05-01
Extended the probe to call `GetStatistics` every ~2s alongside the
WM logger. Key findings:
- **`GetStatistics` is safely callable from the same thread that
did `RegisterConsumer` + `Subscribe`.** Every poll returned rc=0
with no exceptions over 9 polls / 20s window.
- **The deployed Galaxy currently has zero active alarms.** Every
poll reported `total=0 active=0 suppressed=0 newAlarms=0`. The
`positions[]` and `handles[]` arrays were empty.
- **`changes=1 codes=[7]` was constant across all polls**, matching
the constant 1 Hz WM 0xC275 cadence. Code 7 is consistent with a
"heartbeat / subscription healthy" sentinel — same semantics as
the WM but reported through the pull-side API.
- `percent=100` (query-complete percentage) was constant — the
subscription is steady-state.
This confirms the polling design (option 1 in the previous section)
is mechanically viable. The remaining open question is whether
`GetStatistics` populates `positions[] / handles[]` with real
entries when an alarm transition actually fires — proving that
requires firing an alarm.
## Open follow-up probes
Each can be added to `AlarmClientWmProbeTests` as a separate
Skip-gated test:
1. **Fire a real Galaxy alarm during the pump window.** The cleanest
programmatic trigger is an MxAccess write that flips a
`$Alarm`-extended boolean to true (alarm in) and back to false
(alarm out). Pinning the exact tag reference is pending — needs
either a documented test-fixture tag or an interactive selection
in System Platform IDE. Once the trigger fires, this resolves
whether AVEVA's pulled change set arrives via `GetStatistics`
`positions[] / handles[]` (per-change polling works) or only via
the AVEVA-internal window (callback path needed).
2. **Hook AVEVA's internal window** to log what WMs it actually
processes — only relevant if probe 1 shows `GetStatistics` does
NOT report per-change activity.
3. **Decompile `aaAlarmManagedClient.dll`'s IL** for the
`RegisterConsumer` method to find what `RegisterWindowMessage`
string is used and whether there's a callback-registration
surface on `WNAL_Register` that the managed client wraps. The
alarmlst.dll strings (`WNAL_CallBack`, "Invalid callbacks" error)
suggest the underlying C API takes callbacks, but the managed
wrapper exposes none of them.
PR A.5's `Subscribe` / `AcknowledgeByGuid` / `SnapshotActiveAlarms`
are correct — they're pull-style and don't depend on the
notification mechanism.
## Option A — captured, 2026-05-01
`wnwrapConsumer.dll` (`C:\Program Files (x86)\Common Files\
ArchestrA\wnwrapConsumer.dll`) hosts the standalone COM class
`WNWRAPCONSUMERLib.wwAlarmConsumerClass`. Type library imports
cleanly via `tlbimp` (output stored under `mxaccessgw/lib/
Interop.WNWRAPCONSUMERLib.dll`). The COM class is registered in
`HKLM:\SOFTWARE\WOW6432Node\Classes\CLSID\
{7AB52E5F-36B2-4A30-AE46-952A746F667C}` with `ThreadingModel=
Apartment` — `new wwAlarmConsumerClass()` succeeds via
`CoCreateInstance`.
The probe `MxGateway.Worker.Tests/WnWrapConsumerProbeTests.cs`
(Skip-gated, archival) drove the captured run. Lifecycle:
1. `new wwAlarmConsumerClass()` — instantiated.
2. `InitializeConsumer("MxGatewayProbe.WnWrap")` -> 0.
3. `RegisterConsumer(hWnd: 0, productName, applicationName,
version)` -> 0. **Note:** wnwrap's `RegisterConsumer` is
4-arg (no `bRetainHiddenAlarms`); `aaAlarmManagedClient`'s
is 5-arg. Different surface.
4. `Subscribe(@"\\<machine>\Galaxy!DEV", priLow=1, priHigh=999,
qtSummary, sfReturnNewestFirst, asAlarmActiveNow,
asAlarmActiveNow)` -> 0. Same canonical scope that worked
for `aaAlarmManagedClient`.
5. `SetXmlAlarmQuery(...)` was called too but the round-trip
`GetXmlAlarmQuery` returned a mangled echo (NODE became
`DESKTOP-6JL3KKO\Galaxy!DEV`, PROVIDER became `Galaxy!DEV`,
ALARM_STATE shortened to `All`, DISPLAY_MODE truncated to
`Sum`). The XML-query path looks broken in this build; rely
on `Subscribe` for the filter and skip `SetXmlAlarmQuery` in
production. Confirming "Subscribe alone is sufficient" is
one follow-up probe (call `Subscribe` and read XML, no
`SetXmlAlarmQuery`) — out of scope for the breakthrough run
but easy to verify.
### Captured XML (60 polls over 30s, 500ms cadence)
`GetXmlCurrentAlarms2(maxAlmCnt: 100, out vartCurrentXmlAlarms)`
returned BSTR XML cleanly on every call — 60/60 ok, zero throws.
`GetXmlCurrentAlarms` (the v1 method) returned identical content
on the same cadence; either method is viable.
Empty state:
```xml
<?xml version="1.0"?><ALARM_RECORDS COUNT="0"></ALARM_RECORDS>
```
With alarm active (`UNACK_ALM`, value=true after the flip
script set the bool true):
```xml
<?xml version="1.0"?>
<ALARM_RECORDS COUNT="1">
<ALARM>
<GUID>BCC4705395424D65BDAABCDEA6A32A73</GUID>
<DATE>2026/5/1</DATE>
<TIME>13:26:14.709</TIME>
<GMTOFFSET>240</GMTOFFSET>
<DSTADJUST>0</DSTADJUST>
<PROVIDER_NODE>DESKTOP-6JL3KKO</PROVIDER_NODE>
<PROVIDER_NAME>Galaxy</PROVIDER_NAME>
<GROUP>TestArea</GROUP>
<TAGNAME>TestMachine_001.TestAlarm001</TAGNAME>
<TYPE>DSC</TYPE>
<VALUE>true</VALUE>
<LIMIT>true</LIMIT>
<PRIORITY>500</PRIORITY>
<STATE>UNACK_ALM</STATE>
<OPERATOR_NODE></OPERATOR_NODE>
<OPERATOR_NAME></OPERATOR_NAME>
<ALARM_COMMENT>Test alarm #1</ALARM_COMMENT>
</ALARM>
</ALARM_RECORDS>
```
After the script set the bool false (`UNACK_RTN`, value=false):
```xml
<?xml version="1.0"?>
<ALARM_RECORDS COUNT="1">
<ALARM>
<GUID>BCC4705395424D65BDAABCDEA6A32A73</GUID>
<DATE>2026/5/1</DATE>
<TIME>13:26:24.710</TIME>
...
<VALUE>false</VALUE>
<STATE>UNACK_RTN</STATE>
...
</ALARM>
</ALARM_RECORDS>
```
The 10s cadence between transitions matches the System Platform
script's flip frequency exactly. **GUID is stable across the
in→out cycle** (`BCC4705…` carried through both states), so the
XML stream represents the alarm record's lifecycle, not separate
event records — this is "current alarms snapshot," not
"transition stream." For an OPC UA `AlarmConditionService`
adapter this is fine: condition-state changes per-snapshot is
the supported model.
`STATE` enum values observed: `UNACK_RTN` (the alarm has
returned to normal but is unacknowledged — i.e., visible in the
"current alarms" list because operator hasn't acked it yet) and
`UNACK_ALM` (the alarm is currently active and unacknowledged).
The other states from `eAlmState` (`ACK_RTN`, `ACK_ALM`) would
appear when an ack is performed — `wwAlarmConsumerClass.AlarmAckByGUID`
is the method to call.
### `GetStatistics` AV — unrelated quirk
Every `GetStatistics` call threw `AccessViolationException` in
the probe. Cause: the wnwrap interop signature uses `IntPtr` for
the three array out-parameters (`pChangeCode`, `pChangePos`,
`phAlarm`); passing `IntPtr.Zero` is wrong — the COM impl is
writing into the buffer pointer without null-checking. Pre-
allocate three int-arrays and pass pinned pointers (or use
`Marshal.AllocCoTaskMem`) to fix. Not required for the
production path — the XML methods give us everything we need.
### Implications for PR A.2 worker integration
Replacing `aaAlarmManagedClient.AlarmClient` with
`WNWRAPCONSUMERLib.wwAlarmConsumerClass` in the worker's
alarm-consumer surface unblocks A.2 fully. Outline:
1. **Reference path:** drop `aaAlarmManagedClient.dll` reference
from `MxGateway.Worker.csproj`; add `Interop.WNWRAPCONSUMERLib.dll`
reference from `mxaccessgw/lib/`. (Or commit the interop dll
in-tree under `lib/` and reference relatively.)
2. **`AlarmClientConsumer``WnWrapAlarmConsumer`:** rewrite
the consumer wrapper to:
- `new wwAlarmConsumerClass()` on the worker's STA thread.
- `InitializeConsumer(applicationName)` then
`RegisterConsumer(hWnd: 0, …)`.
- `Subscribe(@"\\<node>\Galaxy!<area>", …)` per configured
area. The `<node>` and `<area>` are configurable (default
`Environment.MachineName` + the platform's primary area).
- Poll `GetXmlCurrentAlarms2(maxAlmCnt, out xml)` on a
timer (500ms-1s cadence is comfortable). Parse XML
payload; diff against the previous snapshot (keyed by
`GUID`); emit `MX_EVENT_FAMILY_ON_ALARM_TRANSITION`
events for added/changed/removed records.
- `AlarmAckByGUID(VBGUID, comment, oprName, node, domain,
fullName)` for client-driven acknowledgements (matches
PR A.5's `AlarmAckCommand` payload).
- Lifecycle teardown: `DeregisterConsumer` +
`UninitializeConsumer` + `Marshal.FinalReleaseComObject`.
3. **Conversion layer:** map XML record fields to
`MxAlarmConditionRecord` proto:
- `GUID``condition_id` (canonicalize the no-dashes hex
to a UUID string).
- `STATE` enum → `inAlarm` + `acked` booleans
(`UNACK_ALM` → in_alarm=true, acked=false;
`UNACK_RTN` → in_alarm=false, acked=false;
`ACK_ALM` → in_alarm=true, acked=true;
`ACK_RTN` → in_alarm=false, acked=true).
- `DATE + TIME + GMTOFFSET + DSTADJUST` → reassemble UTC
timestamp; matches the worker's existing `Timestamp`
wire format.
- `PRIORITY` → severity (already 1-1000-ish range).
- `TAGNAME` → reference; `PROVIDER_NAME` + `GROUP` for
scope metadata.
4. **PR A.5 fix carry-over:** `InitializeConsumer` MUST be
called before `RegisterConsumer` (rediscovered with
`aaAlarmManagedClient`, also true here). The existing
`AlarmClientConsumer` skips Initialize entirely; the new
`WnWrapAlarmConsumer` includes it from day one.
5. **Test reuse:** PR A.5's snapshot/ack contract tests can
stay — they don't touch the underlying COM API. Add a new
integration test against the wnwrap surface (live-AVEVA-only,
Skip-gated like the probe).
### Settled API-ordering and surface knowledge
- `InitializeConsumer` first, then `RegisterConsumer` — both
on `aaAlarmManagedClient.AlarmClient` and
`wwAlarmConsumerClass`.
- `RegisterConsumer` arity differs:
`aaAlarmManagedClient.AlarmClient.RegisterConsumer(hWnd,
product, app, version, bRetainHiddenAlarms)` — 5 args;
`wwAlarmConsumerClass.RegisterConsumer(hWnd, product, app,
version)` — 4 args. The wnwrap class has no
`bRetainHiddenAlarms` parameter at all.
- Subscription expression format: `\\<machine>\Galaxy!<area>`
(literal `Galaxy` provider) for both libraries.
- Native ack: `AlarmAckByGUID(VBGUID guid, comment, oprName,
node, domain, fullName)` on the v2 surface; ID 5-arg
variant on the legacy `IwwAlarmConsumer` interface.
These findings retire the open follow-up probes from the
"polling-vs-pump" debate above — `wwAlarmConsumerClass` plus
poll-on-timer is the implementation.
## Live smoke-test discoveries — 2026-05-01
The Skip-gated `AlarmsLiveSmokeTests.Alarms_full_pipeline_round_trip`
ran the full
`WnWrapAlarmConsumer` + `AlarmDispatcher` + `MxAccessAlarmEventSink`
pipeline against the dev rig with the flip script running. End-to-end
verified: 6 real transitions captured on the 10s cadence, ack-by-name
returned rc=0, pipeline stayed healthy through 5 more transitions
afterwards. Three production-relevant quirks surfaced and were fixed
in the consumer:
### 1. `SetXmlAlarmQuery` is mandatory for reads despite the mangled echo
Without `SetXmlAlarmQuery`, the first `GetXmlCurrentAlarms2` call
fails with `E_FAIL` (HRESULT `0x80004005`). The discovery doc above
flagged the round-trip echo as mangled and recommended skipping the
call — that recommendation is **wrong**. The echo *is* mangled (AVEVA
parses NODE/PROVIDER/ALARM_STATE/DISPLAY_MODE incorrectly), but the
call itself is required as some kind of subscription enabler. Even
the Subscribe call setting the actual filter doesn't avoid the need
for `SetXmlAlarmQuery`.
`WnWrapAlarmConsumer.ComposeXmlAlarmQuery(subscription)` decomposes
the canonical `\\<machine>\Galaxy!<area>` form into the XML's
NODE/PROVIDER/GROUP fields. Mangled or not, the call enables reads.
### 2. Two consumers required: read-side vs. ack-side
`SetXmlAlarmQuery` enables reads but **breaks `AlarmAckByName` on
the same consumer instance**. With SetXml applied, AlarmAckByName
returns -55 even with valid name+provider+group+operator. Without
SetXml, AlarmAckByName succeeds with rc=0.
The production consumer therefore provisions **two** wnwrap COM
instances:
- Primary consumer (`client`): runs full lifecycle including
`SetXmlAlarmQuery` for `GetXmlCurrentAlarms2` polls.
- Ack-only consumer (`ackClient`): runs Initialize → Register →
Subscribe via the v1-prefixed methods, **no SetXmlAlarmQuery**.
All `AcknowledgeByName` calls dispatch through this instance.
Both consumers subscribe to the same expression. Disposal cleans up
both via a shared `ReleaseConsumerCom` helper.
### 3. `AlarmAckByName` v2 8-arg vs. v1 6-arg
`wwAlarmConsumerClass` exposes two `AlarmAckByName` overloads:
- `IwwAlarmConsumer2` v2: 8 args (`name, provider, group, comment,
oprName, node, domainName, oprFullName`).
- `IwwAlarmConsumer` v1: 6 args (no domain, no full-name).
The v2 8-arg method returns -55 on this AVEVA build regardless of
operator-identity inputs — looks like a stub. The v1 6-arg method
works. Production `WnWrapAlarmConsumer.AcknowledgeByName` calls the
6-arg overload and discards the proto's `domain` + `full_name` fields.
The proto contract keeps the 8 fields for forward compatibility if
AVEVA fixes the v2 method later.
### 4. `AlarmAckByGUID` is not implemented
The v2 `AlarmAckByGUID(VBGUID, …)` throws `NotImplementedException`
(COM `E_NOTIMPL`) on `wwAlarmConsumerClass` against this AVEVA
build. The reference→GUID lookup that we initially planned to wire
through `AlarmAckByGUID` is therefore not viable on wnwrap; all acks
must go through `AlarmAckByName`.
The proto `AcknowledgeAlarmCommand` (GUID-based) and the worker's
`MxAccessCommandExecutor.ExecuteAcknowledgeAlarm` switch arm remain
in the codebase for the forward-compat shape, but the gateway-side
`WorkerAlarmRpcDispatcher.AcknowledgeAsync` now always routes through
`AcknowledgeAlarmByName` when the public RPC supplies a recognizable
`Provider!Group.Tag` reference.
**Command/reply payload reuse.** `MxCommand.payload` has a dedicated
`acknowledge_alarm_by_name_command` field, but `MxCommandReply.payload`
intentionally has **no** by-name-specific case. The by-name ack carries
no outcome detail beyond the native return code, so the worker's
`ExecuteAcknowledgeAlarmByName` sets the same `acknowledge_alarm`
(`AcknowledgeAlarmReplyPayload`) reply case used by the GUID arm, with
`native_status` = the `AlarmAckByName` return code (also echoed into the
top-level `MxCommandReply.hresult`). Reply consumers must dispatch on
`MxCommandReply.kind` (`MX_COMMAND_KIND_ACKNOWLEDGE_ALARM` vs.
`MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME`), not on the payload oneof
case, to distinguish the two acks. `WorkerAlarmRpcDispatcher` reads only
the top-level `hresult`/`protocol_status`, so it handles both arms
without unpacking the payload.
**Worker `native_status` → public `AcknowledgeAlarmReply` mapping.** The
worker carries the ack outcome as a single `int32`
(`AcknowledgeAlarmReplyPayload.native_status`, the `AlarmAckByName` /
`AlarmAckByGUID` return code; `0` = success), also mirrored into the
worker `MxCommandReply.hresult`. The public `AcknowledgeAlarmReply` has
two outcome-shaped fields, but only one is populated:
- `AcknowledgeAlarmReply.hresult``WorkerAlarmRpcDispatcher` copies the
worker's `MxCommandReply.hresult` (the native return code) into this
field. **This is the authoritative ack-outcome field**; `0` means the
ack succeeded. It is absent only when the worker reply omitted the
value, which is a protocol violation surfaced in `protocol_status`.
- `AcknowledgeAlarmReply.status` (`MxStatusProxy`) — the worker by-name /
by-GUID ack path produces only the `int32` return code, never a
populated `MXSTATUS_PROXY` struct, so `WorkerAlarmRpcDispatcher` leaves
this field **unset on every reply**. It is reserved for a future
structured view of the ack outcome. Clients must not depend on it.
Client authors should therefore branch on `protocol_status` first (for
transport/session-level failures) and then on `hresult` (`0` = ack
accepted by MXAccess) — never on `status`.
### 5. STA / threading — production fix needed
The wnwrap COM is `ThreadingModel=Apartment`. The consumer's
internal `Timer` fires on threadpool threads and would block forever
on cross-apartment marshaling unless the host STA pumps Win32
messages. The smoke test sidesteps this by setting
`pollIntervalMilliseconds=0` (Timer disabled) and driving `PollOnce`
manually from the test's STA. Production hosting will route polls
through the worker's `StaRuntime` in a follow-up — the consumer's
`PollOnce` is `public` and idempotent so the wire-up is mechanical.
### Capture summary
```
Transition: kind=Clear ref='Galaxy!TestArea.TestMachine_001.TestAlarm001' …
Transition: kind=Raise ref='Galaxy!TestArea.TestMachine_001.TestAlarm001' …
SnapshotActiveAlarms count=1
active: ref='Galaxy!TestArea.TestMachine_001.TestAlarm001' state=Active
AcknowledgeByName(real identity) -> rc=0
Post-ack transition: kind=Clear …
+1: kind=Raise … (10s after ack)
+2: kind=Clear … (20s)
+3: kind=Raise … (30s)
+4: kind=Clear … (40s)
```
10s cadence held throughout; full proto fields populated correctly;
ack registered server-side without errors.
+3 -3
View File
@@ -103,7 +103,7 @@ public string ResolveRequiredScope(object request)
StreamEventsRequest => GatewayScopes.EventsRead, StreamEventsRequest => GatewayScopes.EventsRead,
MxCommandRequest commandRequest => ResolveCommandScope(commandRequest.Command?.Kind ?? MxCommandKind.Unspecified), MxCommandRequest commandRequest => ResolveCommandScope(commandRequest.Command?.Kind ?? MxCommandKind.Unspecified),
AcknowledgeAlarmRequest => GatewayScopes.InvokeWrite, AcknowledgeAlarmRequest => GatewayScopes.InvokeWrite,
QueryActiveAlarmsRequest => GatewayScopes.EventsRead, StreamAlarmsRequest => GatewayScopes.EventsRead,
TestConnectionRequest or TestConnectionRequest or
GetLastDeployTimeRequest or GetLastDeployTimeRequest or
DiscoverHierarchyRequest or DiscoverHierarchyRequest or
@@ -113,7 +113,7 @@ public string ResolveRequiredScope(object request)
} }
``` ```
The `_ => GatewayScopes.Admin` fallback is intentional: any future request type that the resolver does not recognize fails closed, requiring the strongest scope until the resolver is updated. `AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*` — and `QueryActiveAlarms` shares the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so it carries `events:read`. The `_ => GatewayScopes.Admin` fallback is intentional: any future request type that the resolver does not recognize fails closed, requiring the strongest scope until the resolver is updated. `AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*` — and `StreamAlarms` shares the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so it carries `events:read`. Both alarm RPCs are session-less: the scope check is the only authorization gate, since there is no per-session ownership to enforce.
`MxCommandRequest` is special because it multiplexes many MxAccess operations through a single RPC. The resolver inspects the embedded `MxCommandKind` so each operation gets its own scope: `MxCommandRequest` is special because it multiplexes many MxAccess operations through a single RPC. The resolver inspects the embedded `MxCommandKind` so each operation gets its own scope:
@@ -205,7 +205,7 @@ blocking constraint; secured values and raw credentials are never logged.
|----------|-------|--------------| |----------|-------|--------------|
| `SessionOpen` | `session:open` | `OpenSessionRequest` | | `SessionOpen` | `session:open` | `OpenSessionRequest` |
| `SessionClose` | `session:close` | `CloseSessionRequest` | | `SessionClose` | `session:close` | `CloseSessionRequest` |
| `EventsRead` | `events:read` | `StreamEventsRequest`, `QueryActiveAlarmsRequest`, `MxCommandKind.DrainEvents` | | `EventsRead` | `events:read` | `StreamEventsRequest`, `StreamAlarmsRequest`, `MxCommandKind.DrainEvents` |
| `InvokeRead` | `invoke:read` | `MxCommandRequest` for read-style command kinds (`Register`, `AddItem`, `Advise`, `ReadBulk`, and any kind not otherwise mapped) | | `InvokeRead` | `invoke:read` | `MxCommandRequest` for read-style command kinds (`Register`, `AddItem`, `Advise`, `ReadBulk`, and any kind not otherwise mapped) |
| `InvokeWrite` | `invoke:write` | `AcknowledgeAlarmRequest`, `MxCommandKind.Write`, `MxCommandKind.Write2`, `MxCommandKind.WriteBulk`, `MxCommandKind.Write2Bulk` | | `InvokeWrite` | `invoke:write` | `AcknowledgeAlarmRequest`, `MxCommandKind.Write`, `MxCommandKind.Write2`, `MxCommandKind.WriteBulk`, `MxCommandKind.Write2Bulk` |
| `InvokeSecure` | `invoke:secure` | `MxCommandKind.WriteSecured`, `MxCommandKind.WriteSecured2`, `MxCommandKind.WriteSecuredBulk`, `MxCommandKind.WriteSecured2Bulk`, `MxCommandKind.AuthenticateUser` | | `InvokeSecure` | `invoke:secure` | `MxCommandKind.WriteSecured`, `MxCommandKind.WriteSecured2`, `MxCommandKind.WriteSecuredBulk`, `MxCommandKind.WriteSecured2Bulk`, `MxCommandKind.AuthenticateUser` |
+18
View File
@@ -61,6 +61,12 @@ paths, timeouts, queue sizes, enum values, or protocol values are invalid.
"ConnectionString": "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;", "ConnectionString": "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;",
"CommandTimeoutSeconds": 60, "CommandTimeoutSeconds": 60,
"DashboardRefreshIntervalSeconds": 30 "DashboardRefreshIntervalSeconds": 30
},
"Alarms": {
"Enabled": false,
"SubscriptionExpression": "",
"DefaultArea": "",
"ReconcileIntervalSeconds": 30
} }
} }
} }
@@ -168,6 +174,18 @@ at startup.
See [Galaxy Repository Browse](./GalaxyRepository.md) for the RPC surface and See [Galaxy Repository Browse](./GalaxyRepository.md) for the RPC surface and
behavior. behavior.
## Alarm Options
| Option | Default | Description |
|--------|---------|-------------|
| `MxGateway:Alarms:Enabled` | `false` | Gates the gateway's always-on central alarm monitor. When `true`, the gateway opens one gateway-owned worker session dedicated to alarms, caches the active-alarm set, and fans it out to every client through the `StreamAlarms` RPC — no client opens its own session to see alarms. |
| `MxGateway:Alarms:SubscriptionExpression` | _(empty)_ | AVEVA alarm-subscription expression the monitor subscribes on startup, in canonical `\\<machine>\Galaxy!<area>` form. The literal `Galaxy` provider is correct regardless of the Galaxy database name. When empty and `Enabled` is `true`, the gateway falls back to `\\<MachineName>\Galaxy!<DefaultArea>` if `DefaultArea` is set. |
| `MxGateway:Alarms:DefaultArea` | _(empty)_ | Area name used to compose a default subscription when `SubscriptionExpression` is empty. If both are empty while `Enabled` is `true`, the monitor faults with a configuration diagnostic. |
| `MxGateway:Alarms:ReconcileIntervalSeconds` | `30` | How often the monitor reconciles its in-process alarm cache against the worker's authoritative active-alarm snapshot, catching transitions the live poll-and-diff feed missed. Floored at 5 seconds. |
The alarm monitor is independent of client sessions: `AcknowledgeAlarm` and
`StreamAlarms` are session-less RPCs served by the monitor.
## Related Documentation ## Related Documentation
- [Gateway Process Detailed Design](./GatewayProcessDesign.md) - [Gateway Process Detailed Design](./GatewayProcessDesign.md)
+22 -18
View File
@@ -274,28 +274,32 @@ diagnostic session/worker views.
### Alarms page ### Alarms page
`/dashboard/alarms` lists the alarms the dashboard session's worker currently `/dashboard/alarms` lists the alarms the gateway's central alarm monitor
reports as Active or ActiveAcked, refreshed every three seconds. It defaults to currently holds as Active or ActiveAcked, refreshed every three seconds. It
showing unacknowledged `Active` alarms; filters add acknowledged alarms and defaults to showing unacknowledged `Active` alarms; filters add acknowledged
narrow by area, severity range, and a reference/source/description text search. alarms and narrow by area, severity range, and a reference/source/description
Cleared alarms are not retained — the gateway holds no alarm-history store, so text search. Cleared alarms are not retained — the gateway holds no
the page reflects only the live active set. The page is read-only; it does not alarm-history store, so the page reflects only the live active set. The page is
acknowledge alarms. If `MxGateway:Alarms:Enabled` is false the session is never read-only; it does not acknowledge alarms. If `MxGateway:Alarms:Enabled` is
subscribed to an alarm provider, and the page says so instead of showing an false the central monitor never starts, and the page says so instead of showing
empty list with no explanation. an empty list with no explanation.
### Live data source ### Live data source
Both the Browse subscription panel and the Alarms page read live MXAccess data Both the Browse subscription panel and the Alarms page read live MXAccess data
through `IDashboardLiveDataService` (`DashboardLiveDataService`). It owns one through `IDashboardLiveDataService` (`DashboardLiveDataService`). For tag data
shared gateway session for the whole dashboard, opened lazily on first use via it owns one shared gateway session for the whole dashboard, opened lazily on
`ISessionManager` and re-opened transparently when it faults or its lease first use via `ISessionManager` and re-opened transparently when it faults or
expires. One session means one worker process backs every dashboard circuit; its lease expires. One session means one worker process backs every dashboard
all access is serialised so the worker sees one in-flight command at a time. circuit; all access is serialised so the worker sees one in-flight command at a
Tag reads go through `GatewaySession.SubscribeBulkAsync` / `ReadBulkAsync`; time. Tag reads go through `GatewaySession.SubscribeBulkAsync` / `ReadBulkAsync`.
alarm queries go through `IAlarmRpcDispatcher`. Alarm subscription is the
gateway's existing auto-subscribe-on-open hook, so the dashboard session is The Alarms page does **not** use the dashboard session: alarm data comes from
alarm-subscribed only when `MxGateway:Alarms:Enabled` is set. the gateway's always-on central monitor. `QueryAlarmsAsync` reads
`IGatewayAlarmService.CurrentAlarms` — the monitor's in-process cache — so the
dashboard sees the same active-alarm set as every `StreamAlarms` client, with
no per-dashboard alarm subscription. When `MxGateway:Alarms:Enabled` is false
the monitor never starts and the cache stays empty.
### API keys page ### API keys page
+6 -6
View File
@@ -29,7 +29,7 @@ A second gRPC service, `GalaxyRepositoryGrpcService`, is mapped alongside it. It
## RPC Handlers ## RPC Handlers
`MxAccessGatewayService` derives from the generated `MxAccessGateway.MxAccessGatewayBase` and implements every RPC declared in `mxaccess_gateway.proto` — six in total: `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`, `AcknowledgeAlarm`, and `QueryActiveAlarms`. The proto contract itself is documented in [Contracts](./Contracts.md); this section covers only what the server-side handler does on top of that contract. `MxAccessGatewayService` derives from the generated `MxAccessGateway.MxAccessGatewayBase` and implements every RPC declared in `mxaccess_gateway.proto` — six in total: `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`, `AcknowledgeAlarm`, and `StreamAlarms`. The proto contract itself is documented in [Contracts](./Contracts.md); this section covers only what the server-side handler does on top of that contract.
Public gRPC send and receive message sizes are configured from Public gRPC send and receive message sizes are configured from
`MxGateway:Protocol:MaxGrpcMessageBytes` (default 16 MiB). Official clients use `MxGateway:Protocol:MaxGrpcMessageBytes` (default 16 MiB). Official clients use
@@ -88,11 +88,11 @@ Carrying the enqueue timestamp into the worker layer is what lets queue-wait tim
### `AcknowledgeAlarm` ### `AcknowledgeAlarm`
`AcknowledgeAlarm` is a unary RPC that acknowledges a single alarm. The handler validates `session_id` and `alarm_full_reference` inline (it does not run through `MxAccessGrpcRequestValidator`, because the alarm surface routes through `IAlarmRpcDispatcher` rather than the generic `Invoke` path), resolves the session, then delegates to the registered `IAlarmRpcDispatcher`. The production `WorkerAlarmRpcDispatcher` routes the ack over the worker IPC by GUID (`AcknowledgeAlarmCommand`) when the reference parses as a canonical GUID, or by `Provider!Group.Tag` reference (`AcknowledgeAlarmByNameCommand`) otherwise. The handler-level RPC behaviour and the alarm contract itself are documented in [Alarm Client Discovery](./AlarmClientDiscovery.md). `AcknowledgeAlarm` is a unary, **session-less** RPC that acknowledges a single alarm. The handler validates `alarm_full_reference` inline (it does not run through `MxAccessGrpcRequestValidator`) and delegates to `IGatewayAlarmService.AcknowledgeAsync`. The always-on `GatewayAlarmMonitor` routes the ack over its own gateway-managed worker session — clients no longer open a session to acknowledge an alarm. A reference that parses as a canonical GUID forwards to `AcknowledgeAlarmCommand`; a `Provider!Group.Tag` reference forwards to `AcknowledgeAlarmByNameCommand`.
### `QueryActiveAlarms` ### `StreamAlarms`
`QueryActiveAlarms` is a server-streaming RPC that returns an `ActiveAlarmSnapshot` per currently active alarm. The handler validates `session_id` inline, resolves the session, and delegates to `IAlarmRpcDispatcher`; `WorkerAlarmRpcDispatcher` issues a `QueryActiveAlarmsCommand` over the worker IPC and streams each snapshot from the worker reply. `StreamAlarms` is a server-streaming, **session-less** RPC that attaches to the gateway's central alarm feed. The handler delegates to `IGatewayAlarmService.StreamAsync`. The stream opens with one `AlarmFeedMessage` carrying an `active_alarm` per currently-active alarm (the ConditionRefresh snapshot), then a single `snapshot_complete`, then a `transition` for every subsequent raise / acknowledge / clear. It is served by the always-on `GatewayAlarmMonitor`, which owns a single gateway-managed worker session and fans out to every attached client — clients no longer open a session of their own. `alarm_filter_prefix`, when set, scopes the stream to a sub-tree.
## Validation Rules ## Validation Rules
@@ -104,8 +104,8 @@ Carrying the enqueue timestamp into the worker layer is what lets queue-wait tim
| `CloseSession` | `session_id` must be non-empty. | `InvalidArgument` | | `CloseSession` | `session_id` must be non-empty. | `InvalidArgument` |
| `StreamEvents` | `session_id` must be non-empty. | `InvalidArgument` | | `StreamEvents` | `session_id` must be non-empty. | `InvalidArgument` |
| `Invoke` | `session_id` non-empty, `command` present, `kind` not `Unspecified`, payload oneof must match `kind`. | `InvalidArgument` | | `Invoke` | `session_id` non-empty, `command` present, `kind` not `Unspecified`, payload oneof must match `kind`. | `InvalidArgument` |
| `AcknowledgeAlarm` | `session_id` and `alarm_full_reference` must be non-empty. Validated inline in the handler, not by `MxAccessGrpcRequestValidator`. | `InvalidArgument` | | `AcknowledgeAlarm` | `alarm_full_reference` must be non-empty. Validated inline in the handler, not by `MxAccessGrpcRequestValidator`. | `InvalidArgument` |
| `QueryActiveAlarms` | `session_id` must be non-empty. Validated inline in the handler, not by `MxAccessGrpcRequestValidator`. | `InvalidArgument` | | `StreamAlarms` | No required fields — `alarm_filter_prefix` is optional. | — |
The payload-vs-kind check matters because the `MxCommand.payload` oneof is non-discriminated on the wire — a misaligned client could send `kind = Write` with a `Register` payload and silently confuse the worker. The validator turns that into a clear client error: The payload-vs-kind check matters because the `MxCommand.payload` oneof is non-discriminated on the wire — a misaligned client could send `kind = Write` with a `Register` payload and silently confuse the worker. The validator turns that into a clear client error: