proto: alarm-transition family + ack/query RPCs (PR A.1) #104
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"fixtureSet": "mxaccess-gateway-client-behavior",
|
||||
"contractName": "mxaccess-gateway",
|
||||
"gatewayProtocolVersion": 2,
|
||||
"gatewayProtocolVersion": 3,
|
||||
"workerProtocolVersion": 1,
|
||||
"protoInputManifest": "clients/proto/proto-inputs.json",
|
||||
"fixtures": [
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"backendName": "mxaccess-worker",
|
||||
"workerProcessId": 1234,
|
||||
"workerProtocolVersion": 1,
|
||||
"gatewayProtocolVersion": 2,
|
||||
"gatewayProtocolVersion": 3,
|
||||
"capabilities": [
|
||||
"unary-open-session",
|
||||
"unary-close-session",
|
||||
"unary-invoke",
|
||||
"server-stream-events"
|
||||
"server-stream-events",
|
||||
"unary-acknowledge-alarm",
|
||||
"server-stream-active-alarms"
|
||||
],
|
||||
"defaultCommandTimeout": "30s",
|
||||
"protocolStatus": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"fixtureSet": "mxaccess-gateway-parity-fixture-matrix",
|
||||
"contractName": "mxaccess-gateway",
|
||||
"gatewayProtocolVersion": 2,
|
||||
"gatewayProtocolVersion": 3,
|
||||
"workerProtocolVersion": 1,
|
||||
"sourceCaptureRoot": "C:/Users/dohertj2/Desktop/mxaccess/captures",
|
||||
"sourceDocs": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"contractName": "mxaccess-gateway",
|
||||
"gatewayProtocolVersion": 2,
|
||||
"gatewayProtocolVersion": 3,
|
||||
"workerProtocolVersion": 1,
|
||||
"protoRoot": "src/MxGateway.Contracts/Protos",
|
||||
"sourceFiles": [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,11 +11,11 @@ public sealed class GatewayContractInfoTests
|
||||
Assert.Equal("mxaccess-worker", GatewayContractInfo.DefaultBackendName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the gateway protocol version starts at version one.</summary>
|
||||
/// <summary>Verifies that the gateway protocol version is bumped to three after the alarm proto extension.</summary>
|
||||
[Fact]
|
||||
public void GatewayProtocolVersion_IsVersionTwo()
|
||||
public void GatewayProtocolVersion_IsVersionThree()
|
||||
{
|
||||
Assert.Equal(2u, GatewayContractInfo.GatewayProtocolVersion);
|
||||
Assert.Equal(3u, GatewayContractInfo.GatewayProtocolVersion);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the worker protocol version starts at version one.</summary>
|
||||
|
||||
@@ -19,6 +19,8 @@ public sealed class ProtobufContractRoundTripTests
|
||||
Assert.Contains(service.Methods, method => method.Name == "CloseSession");
|
||||
Assert.Contains(service.Methods, method => method.Name == "Invoke");
|
||||
Assert.Contains(service.Methods, method => method.Name == "StreamEvents");
|
||||
Assert.Contains(service.Methods, method => method.Name == "AcknowledgeAlarm");
|
||||
Assert.Contains(service.Methods, method => method.Name == "QueryActiveAlarms");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that worker envelope descriptor contains required correlation fields.</summary>
|
||||
@@ -198,4 +200,193 @@ public sealed class ProtobufContractRoundTripTests
|
||||
Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerCommand, parsed.BodyCase);
|
||||
Assert.Equal(MxCommand.PayloadOneofCase.Advise, parsed.WorkerCommand.Command.PayloadCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an OnAlarmTransition event round-trips with full payload.</summary>
|
||||
[Fact]
|
||||
public void Event_RoundTripsOnAlarmTransitionWithFullPayload()
|
||||
{
|
||||
var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
|
||||
var ack = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 30, DateTimeKind.Utc));
|
||||
var original = new MxEvent
|
||||
{
|
||||
Family = MxEventFamily.OnAlarmTransition,
|
||||
SessionId = "session-1",
|
||||
WorkerSequence = 99,
|
||||
WorkerTimestamp = ack,
|
||||
GatewayReceiveTimestamp = ack,
|
||||
OnAlarmTransition = new OnAlarmTransitionEvent
|
||||
{
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
SourceObjectReference = "Tank01",
|
||||
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
||||
TransitionKind = AlarmTransitionKind.Acknowledge,
|
||||
Severity = 750,
|
||||
OriginalRaiseTimestamp = raise,
|
||||
TransitionTimestamp = ack,
|
||||
OperatorUser = "operator1",
|
||||
OperatorComment = "investigating",
|
||||
Category = "Process",
|
||||
Description = "Tank 01 high-high level",
|
||||
CurrentValue = new MxValue
|
||||
{
|
||||
DataType = MxDataType.Float,
|
||||
FloatValue = 95.4f,
|
||||
VariantType = "VT_R4",
|
||||
},
|
||||
LimitValue = new MxValue
|
||||
{
|
||||
DataType = MxDataType.Float,
|
||||
FloatValue = 90.0f,
|
||||
VariantType = "VT_R4",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
Assert.Equal(MxEvent.BodyOneofCase.OnAlarmTransition, parsed.BodyCase);
|
||||
Assert.Equal(AlarmTransitionKind.Acknowledge, parsed.OnAlarmTransition.TransitionKind);
|
||||
Assert.Equal(raise, parsed.OnAlarmTransition.OriginalRaiseTimestamp);
|
||||
Assert.Equal("operator1", parsed.OnAlarmTransition.OperatorUser);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an OnAlarmTransition event round-trips with only the required fields populated.</summary>
|
||||
[Fact]
|
||||
public void Event_RoundTripsOnAlarmTransitionWithOptionalFieldsEmpty()
|
||||
{
|
||||
var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
|
||||
var original = new MxEvent
|
||||
{
|
||||
Family = MxEventFamily.OnAlarmTransition,
|
||||
SessionId = "session-1",
|
||||
WorkerSequence = 100,
|
||||
OnAlarmTransition = new OnAlarmTransitionEvent
|
||||
{
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
||||
TransitionKind = AlarmTransitionKind.Raise,
|
||||
Severity = 750,
|
||||
TransitionTimestamp = raise,
|
||||
},
|
||||
};
|
||||
|
||||
var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
Assert.Equal(string.Empty, parsed.OnAlarmTransition.OperatorUser);
|
||||
Assert.Equal(string.Empty, parsed.OnAlarmTransition.OperatorComment);
|
||||
Assert.Null(parsed.OnAlarmTransition.OriginalRaiseTimestamp);
|
||||
Assert.Null(parsed.OnAlarmTransition.CurrentValue);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an MxEvent body oneof rejects multiple bodies — last write wins per proto3 semantics.</summary>
|
||||
[Fact]
|
||||
public void Event_OneofGuard_LastBodyWins()
|
||||
{
|
||||
var ev = new MxEvent
|
||||
{
|
||||
Family = MxEventFamily.OnAlarmTransition,
|
||||
OnDataChange = new OnDataChangeEvent(),
|
||||
OnAlarmTransition = new OnAlarmTransitionEvent
|
||||
{
|
||||
AlarmFullReference = "X",
|
||||
TransitionKind = AlarmTransitionKind.Raise,
|
||||
},
|
||||
};
|
||||
|
||||
Assert.Equal(MxEvent.BodyOneofCase.OnAlarmTransition, ev.BodyCase);
|
||||
Assert.Null(ev.OnDataChange);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAlarmRequest round-trips through serialization.</summary>
|
||||
[Fact]
|
||||
public void AcknowledgeAlarmRequest_RoundTripsAllFields()
|
||||
{
|
||||
var original = new AcknowledgeAlarmRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
ClientCorrelationId = "client-correlation-7",
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
Comment = "shift handover",
|
||||
OperatorUser = "operator2",
|
||||
};
|
||||
|
||||
var parsed = AcknowledgeAlarmRequest.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAlarmReply round-trips with status, hresult, and diagnostics.</summary>
|
||||
[Fact]
|
||||
public void AcknowledgeAlarmReply_RoundTripsStatusAndHresult()
|
||||
{
|
||||
var original = new AcknowledgeAlarmReply
|
||||
{
|
||||
SessionId = "session-1",
|
||||
CorrelationId = "gateway-correlation-7",
|
||||
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
|
||||
Hresult = 0,
|
||||
Status = new MxStatusProxy
|
||||
{
|
||||
Success = 1,
|
||||
Category = MxStatusCategory.Ok,
|
||||
DetectedBy = MxStatusSource.RespondingLmx,
|
||||
},
|
||||
DiagnosticMessage = "ack accepted",
|
||||
};
|
||||
|
||||
var parsed = AcknowledgeAlarmReply.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
Assert.True(parsed.HasHresult);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ActiveAlarmSnapshot round-trips with current state and operator metadata.</summary>
|
||||
[Fact]
|
||||
public void ActiveAlarmSnapshot_RoundTripsAllFields()
|
||||
{
|
||||
var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
|
||||
var ack = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 30, DateTimeKind.Utc));
|
||||
var original = new ActiveAlarmSnapshot
|
||||
{
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
SourceObjectReference = "Tank01",
|
||||
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
||||
Severity = 750,
|
||||
OriginalRaiseTimestamp = raise,
|
||||
CurrentState = AlarmConditionState.ActiveAcked,
|
||||
Category = "Process",
|
||||
Description = "Tank 01 high-high level",
|
||||
LastTransitionTimestamp = ack,
|
||||
OperatorUser = "operator2",
|
||||
OperatorComment = "investigating",
|
||||
};
|
||||
|
||||
var parsed = ActiveAlarmSnapshot.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
Assert.Equal(AlarmConditionState.ActiveAcked, parsed.CurrentState);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that QueryActiveAlarmsRequest round-trips empty filter prefix.</summary>
|
||||
[Fact]
|
||||
public void QueryActiveAlarmsRequest_RoundTripsWithAndWithoutFilter()
|
||||
{
|
||||
var withoutFilter = new QueryActiveAlarmsRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
ClientCorrelationId = "client-correlation-8",
|
||||
};
|
||||
|
||||
var withFilter = new QueryActiveAlarmsRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
ClientCorrelationId = "client-correlation-9",
|
||||
AlarmFilterPrefix = "Tank01.",
|
||||
};
|
||||
|
||||
Assert.Equal(withoutFilter, QueryActiveAlarmsRequest.Parser.ParseFrom(withoutFilter.ToByteArray()));
|
||||
Assert.Equal(withFilter, QueryActiveAlarmsRequest.Parser.ParseFrom(withFilter.ToByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user