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:
Joseph Doherty
2026-04-30 15:34:35 -04:00
parent ddad573b75
commit 0f88a953d7
11 changed files with 3159 additions and 154 deletions
@@ -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;