proto: add alarm-transition event family + ack/query RPCs (PR A.1)
First PR of the alarms-over-gateway epic (docs/plans/alarms-over-gateway.md in lmxopcua). Pure contract-surface change — no functional wiring yet. Worker-side subscription (A.2), gateway-side dispatch + ack handler (A.3), and ConditionRefresh (A.4) follow. mxaccess_gateway.proto: - Extend MxEventFamily with MX_EVENT_FAMILY_ON_ALARM_TRANSITION = 5. - Extend MxEvent.body oneof with OnAlarmTransitionEvent on_alarm_transition = 24. - Add OnAlarmTransitionEvent message carrying the full MxAccess alarm payload (full reference, source object, alarm-type-name, transition kind, raw severity, original raise timestamp, transition timestamp, operator user/comment, category, description, current/limit value). Mapping to OPC UA 0-1000 severity ladder happens server-side in lmxopcua's MxAccessSeverityMapper (B.1) — gateway preserves the native MxAccess scale. - Add AlarmTransitionKind enum (Raise / Acknowledge / Clear / Retrigger). - Add ActiveAlarmSnapshot + AlarmConditionState for the ConditionRefresh stream. - Add public RPCs AcknowledgeAlarm (unary) and QueryActiveAlarms (server-streaming) on MxAccessGateway service. - Add AcknowledgeAlarmRequest/Reply + QueryActiveAlarmsRequest. GatewayContractInfo.GatewayProtocolVersion bumps 2 -> 3. Fixture manifests (proto-inputs, behavior, parity, golden OpenSessionReply) and protoset descriptor regenerated. Tests: round-trip serialization for the new messages with all-fields-populated and empty-optional-fields cases; oneof last-write-wins guard between OnDataChange and OnAlarmTransition; descriptor service-method enumeration includes the two new RPCs. All 273 existing tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ namespace MxGateway.Contracts;
|
||||
/// </summary>
|
||||
public static class GatewayContractInfo
|
||||
{
|
||||
public const uint GatewayProtocolVersion = 2;
|
||||
public const uint GatewayProtocolVersion = 3;
|
||||
|
||||
public const uint WorkerProtocolVersion = 1;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,14 @@ namespace MxGateway.Contracts.Proto {
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.StreamEventsRequest> __Marshaller_mxaccess_gateway_v1_StreamEventsRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.StreamEventsRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.MxEvent> __Marshaller_mxaccess_gateway_v1_MxEvent = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.MxEvent.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest> __Marshaller_mxaccess_gateway_v1_AcknowledgeAlarmRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply> __Marshaller_mxaccess_gateway_v1_AcknowledgeAlarmReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest> __Marshaller_mxaccess_gateway_v1_QueryActiveAlarmsRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot> __Marshaller_mxaccess_gateway_v1_ActiveAlarmSnapshot = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot.Parser));
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::MxGateway.Contracts.Proto.OpenSessionRequest, global::MxGateway.Contracts.Proto.OpenSessionReply> __Method_OpenSession = new grpc::Method<global::MxGateway.Contracts.Proto.OpenSessionRequest, global::MxGateway.Contracts.Proto.OpenSessionReply>(
|
||||
@@ -97,6 +105,22 @@ namespace MxGateway.Contracts.Proto {
|
||||
__Marshaller_mxaccess_gateway_v1_StreamEventsRequest,
|
||||
__Marshaller_mxaccess_gateway_v1_MxEvent);
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest, global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply> __Method_AcknowledgeAlarm = new grpc::Method<global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest, global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply>(
|
||||
grpc::MethodType.Unary,
|
||||
__ServiceName,
|
||||
"AcknowledgeAlarm",
|
||||
__Marshaller_mxaccess_gateway_v1_AcknowledgeAlarmRequest,
|
||||
__Marshaller_mxaccess_gateway_v1_AcknowledgeAlarmReply);
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest, global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot> __Method_QueryActiveAlarms = new grpc::Method<global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest, global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot>(
|
||||
grpc::MethodType.ServerStreaming,
|
||||
__ServiceName,
|
||||
"QueryActiveAlarms",
|
||||
__Marshaller_mxaccess_gateway_v1_QueryActiveAlarmsRequest,
|
||||
__Marshaller_mxaccess_gateway_v1_ActiveAlarmSnapshot);
|
||||
|
||||
/// <summary>Service descriptor</summary>
|
||||
public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
|
||||
{
|
||||
@@ -131,6 +155,18 @@ namespace MxGateway.Contracts.Proto {
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task<global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply> AcknowledgeAlarm(global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest request, grpc::ServerCallContext context)
|
||||
{
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task QueryActiveAlarms(global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest request, grpc::IServerStreamWriter<global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot> responseStream, grpc::ServerCallContext context)
|
||||
{
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Client for MxAccessGateway</summary>
|
||||
@@ -230,6 +266,36 @@ namespace MxGateway.Contracts.Proto {
|
||||
{
|
||||
return CallInvoker.AsyncServerStreamingCall(__Method_StreamEvents, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply AcknowledgeAlarm(global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return AcknowledgeAlarm(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply AcknowledgeAlarm(global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.BlockingUnaryCall(__Method_AcknowledgeAlarm, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply> AcknowledgeAlarmAsync(global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return AcknowledgeAlarmAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply> AcknowledgeAlarmAsync(global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.AsyncUnaryCall(__Method_AcknowledgeAlarm, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncServerStreamingCall<global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot> QueryActiveAlarms(global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return QueryActiveAlarms(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncServerStreamingCall<global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot> QueryActiveAlarms(global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.AsyncServerStreamingCall(__Method_QueryActiveAlarms, null, options, request);
|
||||
}
|
||||
/// <summary>Creates a new instance of client from given <c>ClientBaseConfiguration</c>.</summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
protected override MxAccessGatewayClient NewInstance(ClientBaseConfiguration configuration)
|
||||
@@ -247,7 +313,9 @@ namespace MxGateway.Contracts.Proto {
|
||||
.AddMethod(__Method_OpenSession, serviceImpl.OpenSession)
|
||||
.AddMethod(__Method_CloseSession, serviceImpl.CloseSession)
|
||||
.AddMethod(__Method_Invoke, serviceImpl.Invoke)
|
||||
.AddMethod(__Method_StreamEvents, serviceImpl.StreamEvents).Build();
|
||||
.AddMethod(__Method_StreamEvents, serviceImpl.StreamEvents)
|
||||
.AddMethod(__Method_AcknowledgeAlarm, serviceImpl.AcknowledgeAlarm)
|
||||
.AddMethod(__Method_QueryActiveAlarms, serviceImpl.QueryActiveAlarms).Build();
|
||||
}
|
||||
|
||||
/// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic.
|
||||
@@ -261,6 +329,8 @@ namespace MxGateway.Contracts.Proto {
|
||||
serviceBinder.AddMethod(__Method_CloseSession, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::MxGateway.Contracts.Proto.CloseSessionRequest, global::MxGateway.Contracts.Proto.CloseSessionReply>(serviceImpl.CloseSession));
|
||||
serviceBinder.AddMethod(__Method_Invoke, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::MxGateway.Contracts.Proto.MxCommandRequest, global::MxGateway.Contracts.Proto.MxCommandReply>(serviceImpl.Invoke));
|
||||
serviceBinder.AddMethod(__Method_StreamEvents, serviceImpl == null ? null : new grpc::ServerStreamingServerMethod<global::MxGateway.Contracts.Proto.StreamEventsRequest, global::MxGateway.Contracts.Proto.MxEvent>(serviceImpl.StreamEvents));
|
||||
serviceBinder.AddMethod(__Method_AcknowledgeAlarm, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::MxGateway.Contracts.Proto.AcknowledgeAlarmRequest, global::MxGateway.Contracts.Proto.AcknowledgeAlarmReply>(serviceImpl.AcknowledgeAlarm));
|
||||
serviceBinder.AddMethod(__Method_QueryActiveAlarms, serviceImpl == null ? null : new grpc::ServerStreamingServerMethod<global::MxGateway.Contracts.Proto.QueryActiveAlarmsRequest, global::MxGateway.Contracts.Proto.ActiveAlarmSnapshot>(serviceImpl.QueryActiveAlarms));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ service MxAccessGateway {
|
||||
rpc CloseSession(CloseSessionRequest) returns (CloseSessionReply);
|
||||
rpc Invoke(MxCommandRequest) returns (MxCommandReply);
|
||||
rpc StreamEvents(StreamEventsRequest) returns (stream MxEvent);
|
||||
rpc AcknowledgeAlarm(AcknowledgeAlarmRequest) returns (AcknowledgeAlarmReply);
|
||||
rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot);
|
||||
}
|
||||
|
||||
message OpenSessionRequest {
|
||||
@@ -397,6 +399,7 @@ message MxEvent {
|
||||
OnWriteCompleteEvent on_write_complete = 21;
|
||||
OperationCompleteEvent operation_complete = 22;
|
||||
OnBufferedDataChangeEvent on_buffered_data_change = 23;
|
||||
OnAlarmTransitionEvent on_alarm_transition = 24;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +409,7 @@ enum MxEventFamily {
|
||||
MX_EVENT_FAMILY_ON_WRITE_COMPLETE = 2;
|
||||
MX_EVENT_FAMILY_OPERATION_COMPLETE = 3;
|
||||
MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE = 4;
|
||||
MX_EVENT_FAMILY_ON_ALARM_TRANSITION = 5;
|
||||
}
|
||||
|
||||
message OnDataChangeEvent {
|
||||
@@ -424,6 +428,134 @@ message OnBufferedDataChangeEvent {
|
||||
int32 raw_data_type = 4;
|
||||
}
|
||||
|
||||
// Carries a single MXAccess alarm transition (raise / acknowledge / clear /
|
||||
// re-trigger) in native MXAccess terms. The Part 9 state machine + ACL +
|
||||
// multi-source aggregation lives in lmxopcua's AlarmConditionService; the
|
||||
// gateway is UA-agnostic and forwards the raw payload.
|
||||
message OnAlarmTransitionEvent {
|
||||
// Fully-qualified alarm reference (e.g. "Tank01.Level.HiHi"). Stable across
|
||||
// transitions of the same condition; used by the lmxopcua side to correlate
|
||||
// raise/ack/clear into a single Part 9 condition.
|
||||
string alarm_full_reference = 1;
|
||||
|
||||
// Galaxy-side source object reference (e.g. "Tank01"). Empty for alarms
|
||||
// that do not bind to a Galaxy object.
|
||||
string source_object_reference = 2;
|
||||
|
||||
// MxAccess alarm-type qualifier (e.g. "AnalogLimitAlarm.HiHi", "DiscAlarm").
|
||||
string alarm_type_name = 3;
|
||||
|
||||
// What kind of state change this event represents.
|
||||
AlarmTransitionKind transition_kind = 4;
|
||||
|
||||
// Raw MXAccess severity value. Mapping to OPC UA 0-1000 happens server-side
|
||||
// in lmxopcua via MxAccessSeverityMapper; the gateway preserves the native
|
||||
// MXAccess scale.
|
||||
int32 severity = 5;
|
||||
|
||||
// When the alarm originally entered the active state. Preserved across
|
||||
// acknowledge transitions so the Part 9 condition keeps the original raise
|
||||
// time. Unset on retrigger from a previously-cleared condition.
|
||||
google.protobuf.Timestamp original_raise_timestamp = 6;
|
||||
|
||||
// When this specific transition occurred (raise time on Raise, ack time on
|
||||
// Acknowledge, clear time on Clear).
|
||||
google.protobuf.Timestamp transition_timestamp = 7;
|
||||
|
||||
// Operator principal recorded by MXAccess on Acknowledge transitions.
|
||||
// Empty on raise / clear.
|
||||
string operator_user = 8;
|
||||
|
||||
// Operator-supplied comment recorded by MXAccess on Acknowledge transitions.
|
||||
// Empty on raise / clear or when no comment was supplied.
|
||||
string operator_comment = 9;
|
||||
|
||||
// MxAccess alarm category (taxonomy bucket configured in the Galaxy
|
||||
// template, e.g. "Process", "Safety", "Diagnostics").
|
||||
string category = 10;
|
||||
|
||||
// Human-readable alarm description from the MxAccess alarm definition.
|
||||
string description = 11;
|
||||
|
||||
// Current alarm value (the value of the source attribute at the moment of
|
||||
// transition). Optional; populated when MxAccess surfaces it.
|
||||
MxValue current_value = 12;
|
||||
|
||||
// Limit/threshold value that triggered the transition for limit alarms.
|
||||
// Optional; populated for AnalogLimitAlarm-family transitions.
|
||||
MxValue limit_value = 13;
|
||||
}
|
||||
|
||||
enum AlarmTransitionKind {
|
||||
ALARM_TRANSITION_KIND_UNSPECIFIED = 0;
|
||||
ALARM_TRANSITION_KIND_RAISE = 1;
|
||||
ALARM_TRANSITION_KIND_ACKNOWLEDGE = 2;
|
||||
ALARM_TRANSITION_KIND_CLEAR = 3;
|
||||
ALARM_TRANSITION_KIND_RETRIGGER = 4;
|
||||
}
|
||||
|
||||
// Snapshot of a currently-active MXAccess alarm condition, returned from a
|
||||
// QueryActiveAlarms ConditionRefresh stream.
|
||||
message ActiveAlarmSnapshot {
|
||||
string alarm_full_reference = 1;
|
||||
string source_object_reference = 2;
|
||||
string alarm_type_name = 3;
|
||||
int32 severity = 4;
|
||||
google.protobuf.Timestamp original_raise_timestamp = 5;
|
||||
AlarmConditionState current_state = 6;
|
||||
string category = 7;
|
||||
string description = 8;
|
||||
// When the most recent state transition occurred (last raise, last ack,
|
||||
// last clear).
|
||||
google.protobuf.Timestamp last_transition_timestamp = 9;
|
||||
// Operator who acknowledged the alarm if the current state is ActiveAcked.
|
||||
// Empty otherwise.
|
||||
string operator_user = 10;
|
||||
// Operator comment recorded with the most recent acknowledge if the current
|
||||
// state is ActiveAcked. Empty otherwise.
|
||||
string operator_comment = 11;
|
||||
MxValue current_value = 12;
|
||||
MxValue limit_value = 13;
|
||||
}
|
||||
|
||||
enum AlarmConditionState {
|
||||
ALARM_CONDITION_STATE_UNSPECIFIED = 0;
|
||||
ALARM_CONDITION_STATE_ACTIVE = 1;
|
||||
ALARM_CONDITION_STATE_ACTIVE_ACKED = 2;
|
||||
ALARM_CONDITION_STATE_INACTIVE = 3;
|
||||
}
|
||||
|
||||
message AcknowledgeAlarmRequest {
|
||||
string session_id = 1;
|
||||
string client_correlation_id = 2;
|
||||
// Fully-qualified alarm reference matching OnAlarmTransitionEvent.alarm_full_reference.
|
||||
string alarm_full_reference = 3;
|
||||
// Operator-supplied comment forwarded to MXAccess.
|
||||
string comment = 4;
|
||||
// Operator principal performing the acknowledgement. The lmxopcua side
|
||||
// resolves this from the OPC UA session prior to invoking the RPC.
|
||||
string operator_user = 5;
|
||||
}
|
||||
|
||||
message AcknowledgeAlarmReply {
|
||||
string session_id = 1;
|
||||
string correlation_id = 2;
|
||||
ProtocolStatus protocol_status = 3;
|
||||
// HRESULT captured from MXAccess if the ack failed at the COM layer.
|
||||
optional int32 hresult = 4;
|
||||
// Native MxAccess status describing the outcome of the ack.
|
||||
MxStatusProxy status = 5;
|
||||
string diagnostic_message = 6;
|
||||
}
|
||||
|
||||
message QueryActiveAlarmsRequest {
|
||||
string session_id = 1;
|
||||
string client_correlation_id = 2;
|
||||
// Optional alarm-reference prefix used to scope a partial ConditionRefresh
|
||||
// (e.g. equipment sub-tree). Empty means full refresh.
|
||||
string alarm_filter_prefix = 3;
|
||||
}
|
||||
|
||||
message MxStatusProxy {
|
||||
int32 success = 1;
|
||||
MxStatusCategory category = 2;
|
||||
|
||||
Reference in New Issue
Block a user