Files
mxaccessgw/src/MxGateway.Contracts/Protos/mxaccess_gateway.proto
T
Joseph Doherty 40ca4b6908 Add gateway central alarm monitor and StreamAlarms feed
The gateway now monitors alarms continuously, independent of any client
session, and fans the feed out to every client.

GatewayAlarmMonitor is an always-on hosted service that owns one
gateway-managed worker session dedicated to alarms: it subscribes the
configured provider, caches the active-alarm set from the worker's
transition events (reconciled periodically against the worker's
authoritative snapshot), re-opens the session if the worker faults,
and broadcasts to all subscribers.

The new session-less StreamAlarms RPC opens with the current
active-alarm snapshot, then streams live transitions; any number of
clients fan out from the single monitor without opening a worker
session. AcknowledgeAlarm is now session-less and routes through the
monitor. The session-scoped QueryActiveAlarms RPC and the per-session
alarm auto-subscribe hook are removed, along with the now-dead
IAlarmRpcDispatcher trio; the dashboard Alarms tab reads the monitor's
in-process cache directly.

This intentionally reverses the v1 "no multi-subscriber fan-out"
decision for the alarm subsystem.

Contracts regenerated; gateway, dashboard and tests build clean,
94 alarm-affected tests pass, and the monitor is verified live.
Language-client stubs are regenerated in a follow-up change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:23:56 -04:00

1019 lines
32 KiB
Protocol Buffer

syntax = "proto3";
package mxaccess_gateway.v1;
option csharp_namespace = "MxGateway.Contracts.Proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
// Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
// 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
// (and `reserved` name) covering it in the same change so a future editor
// cannot accidentally reuse the retired tag.
// Public client API for MXAccess sessions hosted by the gateway.
service MxAccessGateway {
rpc OpenSession(OpenSessionRequest) returns (OpenSessionReply);
rpc CloseSession(CloseSessionRequest) returns (CloseSessionReply);
rpc Invoke(MxCommandRequest) returns (MxCommandReply);
rpc StreamEvents(StreamEventsRequest) returns (stream MxEvent);
rpc AcknowledgeAlarm(AcknowledgeAlarmRequest) returns (AcknowledgeAlarmReply);
// 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.
rpc StreamAlarms(StreamAlarmsRequest) returns (stream AlarmFeedMessage);
}
message OpenSessionRequest {
string requested_backend = 1;
string client_session_name = 2;
string client_correlation_id = 3;
google.protobuf.Duration command_timeout = 4;
}
message OpenSessionReply {
string session_id = 1;
string backend_name = 2;
int32 worker_process_id = 3;
uint32 worker_protocol_version = 4;
repeated string capabilities = 5;
google.protobuf.Duration default_command_timeout = 6;
ProtocolStatus protocol_status = 7;
// Public gateway contract version implemented by this endpoint. Clients use
// this value to reject incompatible generated-code inputs before issuing
// command-specific MXAccess calls.
uint32 gateway_protocol_version = 8;
}
message CloseSessionRequest {
string session_id = 1;
string client_correlation_id = 2;
}
message CloseSessionReply {
string session_id = 1;
SessionState final_state = 2;
ProtocolStatus protocol_status = 3;
}
message StreamEventsRequest {
string session_id = 1;
uint64 after_worker_sequence = 2;
}
message MxCommandRequest {
string session_id = 1;
string client_correlation_id = 2;
MxCommand command = 3;
}
message MxCommand {
MxCommandKind kind = 1;
oneof payload {
RegisterCommand register = 10;
UnregisterCommand unregister = 11;
AddItemCommand add_item = 12;
AddItem2Command add_item2 = 13;
RemoveItemCommand remove_item = 14;
AdviseCommand advise = 15;
UnAdviseCommand un_advise = 16;
AdviseSupervisoryCommand advise_supervisory = 17;
AddBufferedItemCommand add_buffered_item = 18;
SetBufferedUpdateIntervalCommand set_buffered_update_interval = 19;
SuspendCommand suspend = 20;
ActivateCommand activate = 21;
WriteCommand write = 22;
Write2Command write2 = 23;
WriteSecuredCommand write_secured = 24;
WriteSecured2Command write_secured2 = 25;
AuthenticateUserCommand authenticate_user = 26;
ArchestrAUserToIdCommand archestra_user_to_id = 27;
AddItemBulkCommand add_item_bulk = 28;
AdviseItemBulkCommand advise_item_bulk = 29;
RemoveItemBulkCommand remove_item_bulk = 30;
UnAdviseItemBulkCommand un_advise_item_bulk = 31;
SubscribeBulkCommand subscribe_bulk = 32;
UnsubscribeBulkCommand unsubscribe_bulk = 33;
SubscribeAlarmsCommand subscribe_alarms = 34;
UnsubscribeAlarmsCommand unsubscribe_alarms = 35;
AcknowledgeAlarmCommand acknowledge_alarm_command = 36;
QueryActiveAlarmsCommand query_active_alarms_command = 37;
AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38;
WriteBulkCommand write_bulk = 39;
Write2BulkCommand write2_bulk = 40;
WriteSecuredBulkCommand write_secured_bulk = 41;
WriteSecured2BulkCommand write_secured2_bulk = 42;
ReadBulkCommand read_bulk = 43;
PingCommand ping = 100;
GetSessionStateCommand get_session_state = 101;
GetWorkerInfoCommand get_worker_info = 102;
DrainEventsCommand drain_events = 103;
ShutdownWorkerCommand shutdown_worker = 104;
}
}
enum MxCommandKind {
MX_COMMAND_KIND_UNSPECIFIED = 0;
MX_COMMAND_KIND_REGISTER = 1;
MX_COMMAND_KIND_UNREGISTER = 2;
MX_COMMAND_KIND_ADD_ITEM = 3;
MX_COMMAND_KIND_ADD_ITEM2 = 4;
MX_COMMAND_KIND_REMOVE_ITEM = 5;
MX_COMMAND_KIND_ADVISE = 6;
MX_COMMAND_KIND_UN_ADVISE = 7;
MX_COMMAND_KIND_ADVISE_SUPERVISORY = 8;
MX_COMMAND_KIND_ADD_BUFFERED_ITEM = 9;
MX_COMMAND_KIND_SET_BUFFERED_UPDATE_INTERVAL = 10;
MX_COMMAND_KIND_SUSPEND = 11;
MX_COMMAND_KIND_ACTIVATE = 12;
MX_COMMAND_KIND_WRITE = 13;
MX_COMMAND_KIND_WRITE2 = 14;
MX_COMMAND_KIND_WRITE_SECURED = 15;
MX_COMMAND_KIND_WRITE_SECURED2 = 16;
MX_COMMAND_KIND_AUTHENTICATE_USER = 17;
MX_COMMAND_KIND_ARCHESTRA_USER_TO_ID = 18;
MX_COMMAND_KIND_ADD_ITEM_BULK = 19;
MX_COMMAND_KIND_ADVISE_ITEM_BULK = 20;
MX_COMMAND_KIND_REMOVE_ITEM_BULK = 21;
MX_COMMAND_KIND_UN_ADVISE_ITEM_BULK = 22;
MX_COMMAND_KIND_SUBSCRIBE_BULK = 23;
MX_COMMAND_KIND_UNSUBSCRIBE_BULK = 24;
MX_COMMAND_KIND_SUBSCRIBE_ALARMS = 25;
MX_COMMAND_KIND_UNSUBSCRIBE_ALARMS = 26;
MX_COMMAND_KIND_ACKNOWLEDGE_ALARM = 27;
MX_COMMAND_KIND_QUERY_ACTIVE_ALARMS = 28;
MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29;
MX_COMMAND_KIND_WRITE_BULK = 30;
MX_COMMAND_KIND_WRITE2_BULK = 31;
MX_COMMAND_KIND_WRITE_SECURED_BULK = 32;
MX_COMMAND_KIND_WRITE_SECURED2_BULK = 33;
MX_COMMAND_KIND_READ_BULK = 34;
MX_COMMAND_KIND_PING = 100;
MX_COMMAND_KIND_GET_SESSION_STATE = 101;
MX_COMMAND_KIND_GET_WORKER_INFO = 102;
MX_COMMAND_KIND_DRAIN_EVENTS = 103;
MX_COMMAND_KIND_SHUTDOWN_WORKER = 104;
}
message RegisterCommand {
string client_name = 1;
}
message UnregisterCommand {
int32 server_handle = 1;
}
message AddItemCommand {
int32 server_handle = 1;
string item_definition = 2;
}
message AddItem2Command {
int32 server_handle = 1;
string item_definition = 2;
string item_context = 3;
}
message RemoveItemCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message AdviseCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message UnAdviseCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message AdviseSupervisoryCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message AddBufferedItemCommand {
int32 server_handle = 1;
string item_definition = 2;
string item_context = 3;
}
message SetBufferedUpdateIntervalCommand {
int32 server_handle = 1;
int32 update_interval_milliseconds = 2;
}
message SuspendCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message ActivateCommand {
int32 server_handle = 1;
int32 item_handle = 2;
}
message WriteCommand {
int32 server_handle = 1;
int32 item_handle = 2;
MxValue value = 3;
int32 user_id = 4;
}
message Write2Command {
int32 server_handle = 1;
int32 item_handle = 2;
MxValue value = 3;
MxValue timestamp_value = 4;
int32 user_id = 5;
}
message WriteSecuredCommand {
int32 server_handle = 1;
int32 item_handle = 2;
int32 current_user_id = 3;
int32 verifier_user_id = 4;
// Credential-sensitive write value. Implementations must not log this field
// unless an explicit redacted value-logging path is enabled.
MxValue value = 5;
}
message WriteSecured2Command {
int32 server_handle = 1;
int32 item_handle = 2;
int32 current_user_id = 3;
int32 verifier_user_id = 4;
// Credential-sensitive write value. Implementations must not log this field
// unless an explicit redacted value-logging path is enabled.
MxValue value = 5;
MxValue timestamp_value = 6;
}
message AuthenticateUserCommand {
int32 server_handle = 1;
string verify_user = 2;
// Raw MXAccess credential. Implementations must keep this field out of logs,
// metrics labels, command lines, and diagnostics.
string verify_user_password = 3;
}
message ArchestrAUserToIdCommand {
int32 server_handle = 1;
string user_id_guid = 2;
}
message AddItemBulkCommand {
int32 server_handle = 1;
repeated string tag_addresses = 2;
}
message AdviseItemBulkCommand {
int32 server_handle = 1;
repeated int32 item_handles = 2;
}
message RemoveItemBulkCommand {
int32 server_handle = 1;
repeated int32 item_handles = 2;
}
message UnAdviseItemBulkCommand {
int32 server_handle = 1;
repeated int32 item_handles = 2;
}
message SubscribeBulkCommand {
int32 server_handle = 1;
repeated string tag_addresses = 2;
}
// Subscribe the worker's alarm consumer to an AVEVA alarm provider.
// Subscription expression follows the canonical
// `\\<machine>\Galaxy!<area>` format (literal "Galaxy" provider). The
// worker spins up a wnwrapConsumer-backed subscription on its STA on
// first call; subsequent calls are an error (use UnsubscribeAlarms then
// SubscribeAlarms to reconfigure).
message SubscribeAlarmsCommand {
string subscription_expression = 1;
}
// Tear down the worker's alarm consumer. No-op if no subscription is
// currently active.
message UnsubscribeAlarmsCommand {
}
// Acknowledge a single alarm by its GUID. Operator identity fields are
// recorded atomically with the ack transition in the alarm-history log.
// The reply's hresult / native_status surfaces AVEVA's
// AlarmAckByGUID return code.
message AcknowledgeAlarmCommand {
// Canonical 8-4-4-4-12 GUID string (e.g. "BCC47053-9542-4D65-BDAA-BCDEA6A32A73").
string alarm_guid = 1;
string comment = 2;
string operator_user = 3;
string operator_node = 4;
string operator_domain = 5;
string operator_full_name = 6;
}
// Snapshot the currently-active alarm set. Optional filter prefix scopes
// the snapshot to alarms whose alarm_full_reference starts with the
// supplied string (matches QueryActiveAlarmsRequest.alarm_filter_prefix).
message QueryActiveAlarmsCommand {
string alarm_filter_prefix = 1;
}
// Acknowledge a single alarm by its (name, provider, group) tuple. Used
// when the public RPC's AlarmFullReference (Provider!Group.Tag) cannot
// be resolved to a GUID directly. The worker invokes
// wwAlarmConsumerClass.AlarmAckByName which reaches the same alarm
// history path as AlarmAckByGUID.
message AcknowledgeAlarmByNameCommand {
// Tag/alarm name (e.g. "TestMachine_001.TestAlarm001"). Tag itself
// may contain dots; the gateway-side parser splits on the first dot
// after the '!' separator.
string alarm_name = 1;
// AVEVA alarm-provider name (literal "Galaxy" for ArchestrA Galaxies).
string provider_name = 2;
// Area/group name (e.g. "TestArea").
string group_name = 3;
string comment = 4;
string operator_user = 5;
string operator_node = 6;
string operator_domain = 7;
string operator_full_name = 8;
}
message UnsubscribeBulkCommand {
int32 server_handle = 1;
repeated int32 item_handles = 2;
}
// Bulk Write — sequential MXAccess Write per entry, on the worker's STA.
// MXAccess has no native bulk write; each entry round-trips through the same
// single-item Write path the gateway uses today. Per-item failures appear as
// BulkWriteResult entries with `was_successful = false` and never throw.
message WriteBulkCommand {
int32 server_handle = 1;
repeated WriteBulkEntry entries = 2;
}
message WriteBulkEntry {
int32 item_handle = 1;
MxValue value = 2;
int32 user_id = 3;
}
// Bulk Write2 — sequential MXAccess Write2 (timestamped) per entry.
message Write2BulkCommand {
int32 server_handle = 1;
repeated Write2BulkEntry entries = 2;
}
message Write2BulkEntry {
int32 item_handle = 1;
MxValue value = 2;
MxValue timestamp_value = 3;
int32 user_id = 4;
}
// Bulk WriteSecured — sequential MXAccess WriteSecured per entry.
// Credential-sensitive values (`value`) MUST be kept out of logs, metrics
// labels, command lines, and diagnostics — same redaction rules as the
// single-item WriteSecured contract.
message WriteSecuredBulkCommand {
int32 server_handle = 1;
repeated WriteSecuredBulkEntry entries = 2;
}
message WriteSecuredBulkEntry {
int32 item_handle = 1;
int32 current_user_id = 2;
int32 verifier_user_id = 3;
// Credential-sensitive write value. Implementations must not log this field
// unless an explicit redacted value-logging path is enabled.
MxValue value = 4;
}
// Bulk WriteSecured2 — sequential MXAccess WriteSecured2 (timestamped) per
// entry. Same redaction rules apply.
message WriteSecured2BulkCommand {
int32 server_handle = 1;
repeated WriteSecured2BulkEntry entries = 2;
}
message WriteSecured2BulkEntry {
int32 item_handle = 1;
int32 current_user_id = 2;
int32 verifier_user_id = 3;
// Credential-sensitive write value. Implementations must not log this field
// unless an explicit redacted value-logging path is enabled.
MxValue value = 4;
MxValue timestamp_value = 5;
}
// Bulk Read — snapshot the current value for each requested tag. MXAccess COM
// has no synchronous Read; the worker implements ReadBulk as:
//
// - 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
// reply returns the cached value WITHOUT modifying the existing
// subscription (was_cached = true).
// - Otherwise the worker takes the snapshot lifecycle itself: AddItem +
// Advise, wait up to `timeout_ms` for the first OnDataChange, then
// UnAdvise + RemoveItem before returning. The session is left exactly
// as it was before the call (was_cached = false).
//
// `timeout_ms == 0` uses the gateway-configured default (1000 ms).
message ReadBulkCommand {
int32 server_handle = 1;
repeated string tag_addresses = 2;
uint32 timeout_ms = 3;
}
message PingCommand {
string message = 1;
}
message GetSessionStateCommand {
}
message GetWorkerInfoCommand {
}
message DrainEventsCommand {
uint32 max_events = 1;
}
message ShutdownWorkerCommand {
google.protobuf.Duration grace_period = 1;
}
message MxCommandReply {
string session_id = 1;
string correlation_id = 2;
MxCommandKind kind = 3;
ProtocolStatus protocol_status = 4;
// HRESULT captured from MXAccess or a COM exception. This remains separate
// from gateway protocol status so MXAccess parity details are not hidden by
// transport failures.
optional int32 hresult = 5;
MxValue return_value = 6;
repeated MxStatusProxy statuses = 7;
string diagnostic_message = 8;
oneof payload {
RegisterReply register = 20;
AddItemReply add_item = 21;
AddItem2Reply add_item2 = 22;
AddBufferedItemReply add_buffered_item = 23;
SuspendReply suspend = 24;
ActivateReply activate = 25;
AuthenticateUserReply authenticate_user = 26;
ArchestrAUserToIdReply archestra_user_to_id = 27;
BulkSubscribeReply add_item_bulk = 28;
BulkSubscribeReply advise_item_bulk = 29;
BulkSubscribeReply remove_item_bulk = 30;
BulkSubscribeReply un_advise_item_bulk = 31;
BulkSubscribeReply subscribe_bulk = 32;
BulkSubscribeReply unsubscribe_bulk = 33;
// Reply payload for BOTH MX_COMMAND_KIND_ACKNOWLEDGE_ALARM (by GUID)
// and MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME. There is intentionally
// no by-name-specific reply case: the by-name ack carries no outcome
// detail beyond the native ack return code, so the worker reuses this
// `acknowledge_alarm` payload for both command kinds (the worker's
// MxAccessCommandExecutor sets `acknowledge_alarm` for the by-name arm
// too). Consumers must dispatch on MxCommandReply.kind, not on the
// payload case, to tell the two acks apart. The top-level `hresult`
// mirrors AcknowledgeAlarmReplyPayload.native_status and is preferred.
AcknowledgeAlarmReplyPayload acknowledge_alarm = 34;
QueryActiveAlarmsReplyPayload query_active_alarms = 35;
BulkWriteReply write_bulk = 36;
BulkWriteReply write2_bulk = 37;
BulkWriteReply write_secured_bulk = 38;
BulkWriteReply write_secured2_bulk = 39;
BulkReadReply read_bulk = 40;
SessionStateReply session_state = 100;
WorkerInfoReply worker_info = 101;
DrainEventsReply drain_events = 102;
}
}
message RegisterReply {
int32 server_handle = 1;
}
message AddItemReply {
int32 item_handle = 1;
}
message AddItem2Reply {
int32 item_handle = 1;
}
message AddBufferedItemReply {
int32 item_handle = 1;
}
message SuspendReply {
MxStatusProxy status = 1;
}
message ActivateReply {
MxStatusProxy status = 1;
}
message AuthenticateUserReply {
int32 user_id = 1;
}
message ArchestrAUserToIdReply {
int32 user_id = 1;
}
message SubscribeResult {
int32 server_handle = 1;
string tag_address = 2;
int32 item_handle = 3;
bool was_successful = 4;
string error_message = 5;
}
message BulkSubscribeReply {
repeated SubscribeResult results = 1;
}
// 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
// when the gateway's per-entry `IConstraintEnforcer.CheckWriteHandleAsync`
// filter (see `MxAccessGatewayService.ReplaceWriteBulkEntries` and
// `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.
message BulkWriteResult {
int32 server_handle = 1;
int32 item_handle = 2;
bool was_successful = 3;
optional int32 hresult = 4;
repeated MxStatusProxy statuses = 5;
string error_message = 6;
}
message BulkWriteReply {
repeated BulkWriteResult results = 1;
}
// Per-tag result for ReadBulk. `was_cached` is true when the value came from
// an existing live subscription's last OnDataChange (the worker did not touch
// the subscription); false when the worker took the AddItem + Advise + wait +
// 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.
message BulkReadResult {
int32 server_handle = 1;
string tag_address = 2;
int32 item_handle = 3;
bool was_successful = 4;
bool was_cached = 5;
MxValue value = 6;
int32 quality = 7;
google.protobuf.Timestamp source_timestamp = 8;
repeated MxStatusProxy statuses = 9;
string error_message = 10;
}
message BulkReadReply {
repeated BulkReadResult results = 1;
}
message SessionStateReply {
SessionState state = 1;
}
message WorkerInfoReply {
int32 worker_process_id = 1;
string worker_version = 2;
string mxaccess_progid = 3;
string mxaccess_clsid = 4;
}
message DrainEventsReply {
repeated MxEvent events = 1;
}
// Reply payload for AcknowledgeAlarmCommand AND
// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
// payload case (`MxCommandReply.acknowledge_alarm`); there is no
// dedicated by-name reply case. Surfaces AVEVA's native ack return
// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
// by-name arm); 0 means success. The MxCommandReply's hresult field
// carries the same value and is preferred for protocol consumers —
// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
// can echo native_status into AcknowledgeAlarmReply.hresult without
// unpacking the outer envelope.
message AcknowledgeAlarmReplyPayload {
int32 native_status = 1;
}
// Reply payload for QueryActiveAlarmsCommand. The worker walks
// IMxAccessAlarmConsumer.SnapshotActiveAlarms and packs each record as
// an ActiveAlarmSnapshot proto for the gateway-side ConditionRefresh
// stream.
message QueryActiveAlarmsReplyPayload {
repeated ActiveAlarmSnapshot snapshots = 1;
}
message MxEvent {
MxEventFamily family = 1;
string session_id = 2;
int32 server_handle = 3;
int32 item_handle = 4;
MxValue value = 5;
int32 quality = 6;
google.protobuf.Timestamp source_timestamp = 7;
repeated MxStatusProxy statuses = 8;
uint64 worker_sequence = 9;
google.protobuf.Timestamp worker_timestamp = 10;
google.protobuf.Timestamp gateway_receive_timestamp = 11;
optional int32 hresult = 12;
string raw_status = 13;
oneof body {
OnDataChangeEvent on_data_change = 20;
OnWriteCompleteEvent on_write_complete = 21;
OperationCompleteEvent operation_complete = 22;
OnBufferedDataChangeEvent on_buffered_data_change = 23;
OnAlarmTransitionEvent on_alarm_transition = 24;
}
}
enum MxEventFamily {
MX_EVENT_FAMILY_UNSPECIFIED = 0;
MX_EVENT_FAMILY_ON_DATA_CHANGE = 1;
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 {
}
message OnWriteCompleteEvent {
}
message OperationCompleteEvent {
}
message OnBufferedDataChangeEvent {
MxDataType data_type = 1;
MxArray quality_values = 2;
MxArray timestamp_values = 3;
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 {
// Retired: acknowledgement is session-less — it routes to the gateway's
// central alarm monitor, not a client worker session.
reserved 1;
reserved "session_id";
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 {
// Retired: see AcknowledgeAlarmRequest — acknowledgement is session-less.
reserved 1;
reserved "session_id";
string correlation_id = 2;
ProtocolStatus protocol_status = 3;
// Native ack return code echoed from the worker. The worker carries the
// ack outcome as a single int32 (AcknowledgeAlarmReplyPayload.native_status,
// = AlarmAckByName / AlarmAckByGUID return code; 0 = success); the gateway's
// WorkerAlarmRpcDispatcher copies that value here. This is the authoritative
// ack-outcome field for the public RPC. Absent only when the worker reply
// omitted the value entirely (a protocol violation).
optional int32 hresult = 4;
// Reserved for a structured MxStatusProxy view of the ack outcome. The
// worker by-name/by-GUID ack path produces only the int32 return code
// (see `hresult`), so the current gateway leaves this field UNSET on every
// reply. Clients must read `hresult` (and `protocol_status`) for the ack
// result and must not depend on `status` being populated.
MxStatusProxy status = 5;
string diagnostic_message = 6;
}
// Request to attach to the gateway's central alarm feed (StreamAlarms).
message StreamAlarmsRequest {
string client_correlation_id = 1;
// Optional alarm-reference prefix scoping the feed to an equipment
// sub-tree. Empty streams every active alarm.
string alarm_filter_prefix = 2;
}
// 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.
message AlarmFeedMessage {
oneof payload {
// Part of the initial active-alarm snapshot (ConditionRefresh).
ActiveAlarmSnapshot active_alarm = 1;
// Sentinel: the initial snapshot is fully delivered and `transition`
// messages follow. Always true when present.
bool snapshot_complete = 2;
// A live alarm state change (raise / acknowledge / clear).
OnAlarmTransitionEvent transition = 3;
}
}
message MxStatusProxy {
// Mirrors the `success` member of the MXAccess MXSTATUS_PROXY struct
// (a 16-bit signed value in the COM struct, widened to int32 on the
// wire). Despite the name it is NOT a boolean — it is the raw numeric
// indicator the worker reads off the COM struct without reinterpretation.
// It is carried verbatim for diagnostics; the authoritative success/
// failure of the operation is `category` (MX_STATUS_CATEGORY_OK marks
// success), with `detail`, `diagnostic_text`, `raw_category`, and
// `raw_detected_by` describing any non-OK outcome. Clients should branch
// on `category`, not on a specific `success` value.
int32 success = 1;
MxStatusCategory category = 2;
MxStatusSource detected_by = 3;
int32 detail = 4;
int32 raw_category = 5;
int32 raw_detected_by = 6;
string diagnostic_text = 7;
}
enum MxStatusCategory {
MX_STATUS_CATEGORY_UNSPECIFIED = 0;
MX_STATUS_CATEGORY_UNKNOWN = 1;
MX_STATUS_CATEGORY_OK = 2;
MX_STATUS_CATEGORY_PENDING = 3;
MX_STATUS_CATEGORY_WARNING = 4;
MX_STATUS_CATEGORY_COMMUNICATION_ERROR = 5;
MX_STATUS_CATEGORY_CONFIGURATION_ERROR = 6;
MX_STATUS_CATEGORY_OPERATIONAL_ERROR = 7;
MX_STATUS_CATEGORY_SECURITY_ERROR = 8;
MX_STATUS_CATEGORY_SOFTWARE_ERROR = 9;
MX_STATUS_CATEGORY_OTHER_ERROR = 10;
}
enum MxStatusSource {
MX_STATUS_SOURCE_UNSPECIFIED = 0;
MX_STATUS_SOURCE_UNKNOWN = 1;
MX_STATUS_SOURCE_REQUESTING_LMX = 2;
MX_STATUS_SOURCE_RESPONDING_LMX = 3;
MX_STATUS_SOURCE_REQUESTING_NMX = 4;
MX_STATUS_SOURCE_RESPONDING_NMX = 5;
MX_STATUS_SOURCE_REQUESTING_AUTOMATION_OBJECT = 6;
MX_STATUS_SOURCE_RESPONDING_AUTOMATION_OBJECT = 7;
}
message MxValue {
MxDataType data_type = 1;
string variant_type = 2;
bool is_null = 3;
string raw_diagnostic = 4;
int32 raw_data_type = 5;
oneof kind {
bool bool_value = 10;
int32 int32_value = 11;
int64 int64_value = 12;
float float_value = 13;
double double_value = 14;
string string_value = 15;
google.protobuf.Timestamp timestamp_value = 16;
MxArray array_value = 17;
bytes raw_value = 18;
}
}
message MxArray {
MxDataType element_data_type = 1;
string variant_type = 2;
repeated uint32 dimensions = 3;
string raw_diagnostic = 4;
int32 raw_element_data_type = 5;
oneof values {
BoolArray bool_values = 10;
Int32Array int32_values = 11;
Int64Array int64_values = 12;
FloatArray float_values = 13;
DoubleArray double_values = 14;
StringArray string_values = 15;
TimestampArray timestamp_values = 16;
RawArray raw_values = 17;
}
}
message BoolArray {
repeated bool values = 1;
}
message Int32Array {
repeated int32 values = 1;
}
message Int64Array {
repeated int64 values = 1;
}
message FloatArray {
repeated float values = 1;
}
message DoubleArray {
repeated double values = 1;
}
message StringArray {
repeated string values = 1;
}
message TimestampArray {
repeated google.protobuf.Timestamp values = 1;
}
message RawArray {
repeated bytes values = 1;
}
enum MxDataType {
MX_DATA_TYPE_UNSPECIFIED = 0;
MX_DATA_TYPE_UNKNOWN = 1;
MX_DATA_TYPE_NO_DATA = 2;
MX_DATA_TYPE_BOOLEAN = 3;
MX_DATA_TYPE_INTEGER = 4;
MX_DATA_TYPE_FLOAT = 5;
MX_DATA_TYPE_DOUBLE = 6;
MX_DATA_TYPE_STRING = 7;
MX_DATA_TYPE_TIME = 8;
MX_DATA_TYPE_ELAPSED_TIME = 9;
MX_DATA_TYPE_REFERENCE_TYPE = 10;
MX_DATA_TYPE_STATUS_TYPE = 11;
MX_DATA_TYPE_ENUM = 12;
MX_DATA_TYPE_SECURITY_CLASSIFICATION_ENUM = 13;
MX_DATA_TYPE_DATA_QUALITY_TYPE = 14;
MX_DATA_TYPE_QUALIFIED_ENUM = 15;
MX_DATA_TYPE_QUALIFIED_STRUCT = 16;
MX_DATA_TYPE_INTERNATIONALIZED_STRING = 17;
MX_DATA_TYPE_BIG_STRING = 18;
MX_DATA_TYPE_END = 19;
}
message ProtocolStatus {
ProtocolStatusCode code = 1;
string message = 2;
}
enum ProtocolStatusCode {
PROTOCOL_STATUS_CODE_UNSPECIFIED = 0;
PROTOCOL_STATUS_CODE_OK = 1;
PROTOCOL_STATUS_CODE_INVALID_REQUEST = 2;
PROTOCOL_STATUS_CODE_SESSION_NOT_FOUND = 3;
PROTOCOL_STATUS_CODE_SESSION_NOT_READY = 4;
PROTOCOL_STATUS_CODE_WORKER_UNAVAILABLE = 5;
PROTOCOL_STATUS_CODE_TIMEOUT = 6;
PROTOCOL_STATUS_CODE_CANCELED = 7;
PROTOCOL_STATUS_CODE_PROTOCOL_VIOLATION = 8;
PROTOCOL_STATUS_CODE_MXACCESS_FAILURE = 9;
}
enum SessionState {
SESSION_STATE_UNSPECIFIED = 0;
SESSION_STATE_CREATING = 1;
SESSION_STATE_STARTING_WORKER = 2;
SESSION_STATE_WAITING_FOR_PIPE = 3;
SESSION_STATE_HANDSHAKING = 4;
SESSION_STATE_INITIALIZING_WORKER = 5;
SESSION_STATE_READY = 6;
SESSION_STATE_CLOSING = 7;
SESSION_STATE_CLOSED = 8;
SESSION_STATE_FAULTED = 9;
}