diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs
index 7aa3fb61..ec2ddb37 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs
@@ -71,4 +71,18 @@ public record AlarmStateChanged(
/// Limit/threshold value for native limit alarms (display-only); empty otherwise.
public string LimitValue { get; init; } = string.Empty;
+
+ ///
+ /// Canonical name of the native alarm SOURCE BINDING this condition belongs to
+ /// (e.g. "Motor1.MotorAlarms"). Lets the Debug View nest live native conditions
+ /// under their configured binding node. Empty for computed alarms. Additive.
+ ///
+ public string NativeSourceCanonicalName { get; init; } = string.Empty;
+
+ ///
+ /// True when this row is a placeholder emitted for a CONFIGURED native source
+ /// binding that currently has no active conditions, so the Debug View tree can
+ /// show the binding node as "no active conditions". Additive; default false.
+ ///
+ public bool IsConfiguredPlaceholder { get; init; }
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs
index 4103640d..ffe6c6a0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs
@@ -84,7 +84,9 @@ public class StreamRelayActor : ReceiveActor
? Timestamp.FromDateTimeOffset(msg.OriginalRaiseTime.Value)
: null,
CurrentValue = msg.CurrentValue ?? string.Empty,
- LimitValue = msg.LimitValue ?? string.Empty
+ LimitValue = msg.LimitValue ?? string.Empty,
+ NativeSourceCanonicalName = msg.NativeSourceCanonicalName ?? string.Empty,
+ IsConfiguredPlaceholder = msg.IsConfiguredPlaceholder
}
};
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
index c6eae35f..8e57e851 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
@@ -253,7 +253,9 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
OperatorComment = evt.AlarmChanged.OperatorComment ?? string.Empty,
OriginalRaiseTime = evt.AlarmChanged.OriginalRaiseTime?.ToDateTimeOffset(),
CurrentValue = evt.AlarmChanged.CurrentValue ?? string.Empty,
- LimitValue = evt.AlarmChanged.LimitValue ?? string.Empty
+ LimitValue = evt.AlarmChanged.LimitValue ?? string.Empty,
+ NativeSourceCanonicalName = evt.AlarmChanged.NativeSourceCanonicalName ?? string.Empty,
+ IsConfiguredPlaceholder = evt.AlarmChanged.IsConfiguredPlaceholder
},
_ => null
};
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto b/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto
index df9ee7af..c76173b7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto
@@ -84,6 +84,8 @@ message AlarmStateUpdate {
google.protobuf.Timestamp original_raise_time = 19; // null when unknown
string current_value = 20;
string limit_value = 21;
+ string native_source_canonical_name = 22; // native binding canonical name; empty for computed
+ bool is_configured_placeholder = 23; // true for a quiet-binding placeholder row
}
// Audit Log (#23) telemetry: single lifecycle event ferried from a site SQLite
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteStreamGrpc/Sitestream.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteStreamGrpc/Sitestream.cs
index a0e79003..f81bf8e2 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteStreamGrpc/Sitestream.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteStreamGrpc/Sitestream.cs
@@ -36,7 +36,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
"KAkSFgoOYXR0cmlidXRlX3BhdGgYAiABKAkSFgoOYXR0cmlidXRlX25hbWUY",
"AyABKAkSDQoFdmFsdWUYBCABKAkSJAoHcXVhbGl0eRgFIAEoDjITLnNpdGVz",
"dHJlYW0uUXVhbGl0eRItCgl0aW1lc3RhbXAYBiABKAsyGi5nb29nbGUucHJv",
- "dG9idWYuVGltZXN0YW1wIrgEChBBbGFybVN0YXRlVXBkYXRlEhwKFGluc3Rh",
+ "dG9idWYuVGltZXN0YW1wIoEFChBBbGFybVN0YXRlVXBkYXRlEhwKFGluc3Rh",
"bmNlX3VuaXF1ZV9uYW1lGAEgASgJEhIKCmFsYXJtX25hbWUYAiABKAkSKQoF",
"c3RhdGUYAyABKA4yGi5zaXRlc3RyZWFtLkFsYXJtU3RhdGVFbnVtEhAKCHBy",
"aW9yaXR5GAQgASgFEi0KCXRpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90",
@@ -49,69 +49,70 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
"c2VyGBEgASgJEhgKEG9wZXJhdG9yX2NvbW1lbnQYEiABKAkSNwoTb3JpZ2lu",
"YWxfcmFpc2VfdGltZRgTIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3Rh",
"bXASFQoNY3VycmVudF92YWx1ZRgUIAEoCRITCgtsaW1pdF92YWx1ZRgVIAEo",
- "CSK9BAoNQXVkaXRFdmVudER0bxIQCghldmVudF9pZBgBIAEoCRIzCg9vY2N1",
- "cnJlZF9hdF91dGMYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w",
- "Eg8KB2NoYW5uZWwYAyABKAkSDAoEa2luZBgEIAEoCRIWCg5jb3JyZWxhdGlv",
- "bl9pZBgFIAEoCRIWCg5zb3VyY2Vfc2l0ZV9pZBgGIAEoCRIaChJzb3VyY2Vf",
- "aW5zdGFuY2VfaWQYByABKAkSFQoNc291cmNlX3NjcmlwdBgIIAEoCRINCgVh",
- "Y3RvchgJIAEoCRIOCgZ0YXJnZXQYCiABKAkSDgoGc3RhdHVzGAsgASgJEjAK",
- "C2h0dHBfc3RhdHVzGAwgASgLMhsuZ29vZ2xlLnByb3RvYnVmLkludDMyVmFs",
- "dWUSMAoLZHVyYXRpb25fbXMYDSABKAsyGy5nb29nbGUucHJvdG9idWYuSW50",
- "MzJWYWx1ZRIVCg1lcnJvcl9tZXNzYWdlGA4gASgJEhQKDGVycm9yX2RldGFp",
- "bBgPIAEoCRIXCg9yZXF1ZXN0X3N1bW1hcnkYECABKAkSGAoQcmVzcG9uc2Vf",
- "c3VtbWFyeRgRIAEoCRIZChFwYXlsb2FkX3RydW5jYXRlZBgSIAEoCBINCgVl",
- "eHRyYRgTIAEoCRIUCgxleGVjdXRpb25faWQYFCABKAkSGwoTcGFyZW50X2V4",
- "ZWN1dGlvbl9pZBgVIAEoCRITCgtzb3VyY2Vfbm9kZRgWIAEoCSI8Cg9BdWRp",
- "dEV2ZW50QmF0Y2gSKQoGZXZlbnRzGAEgAygLMhkuc2l0ZXN0cmVhbS5BdWRp",
- "dEV2ZW50RHRvIicKCUluZ2VzdEFjaxIaChJhY2NlcHRlZF9ldmVudF9pZHMY",
- "ASADKAkiiQMKFlNpdGVDYWxsT3BlcmF0aW9uYWxEdG8SHAoUdHJhY2tlZF9v",
- "cGVyYXRpb25faWQYASABKAkSDwoHY2hhbm5lbBgCIAEoCRIOCgZ0YXJnZXQY",
- "AyABKAkSEwoLc291cmNlX3NpdGUYBCABKAkSDgoGc3RhdHVzGAUgASgJEhMK",
- "C3JldHJ5X2NvdW50GAYgASgFEhIKCmxhc3RfZXJyb3IYByABKAkSMAoLaHR0",
- "cF9zdGF0dXMYCCABKAsyGy5nb29nbGUucHJvdG9idWYuSW50MzJWYWx1ZRIy",
- "Cg5jcmVhdGVkX2F0X3V0YxgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1l",
- "c3RhbXASMgoOdXBkYXRlZF9hdF91dGMYCiABKAsyGi5nb29nbGUucHJvdG9i",
- "dWYuVGltZXN0YW1wEjMKD3Rlcm1pbmFsX2F0X3V0YxgLIAEoCzIaLmdvb2ds",
- "ZS5wcm90b2J1Zi5UaW1lc3RhbXASEwoLc291cmNlX25vZGUYDCABKAkigAEK",
- "FUNhY2hlZFRlbGVtZXRyeVBhY2tldBIuCgthdWRpdF9ldmVudBgBIAEoCzIZ",
- "LnNpdGVzdHJlYW0uQXVkaXRFdmVudER0bxI3CgtvcGVyYXRpb25hbBgCIAEo",
- "CzIiLnNpdGVzdHJlYW0uU2l0ZUNhbGxPcGVyYXRpb25hbER0byJKChRDYWNo",
- "ZWRUZWxlbWV0cnlCYXRjaBIyCgdwYWNrZXRzGAEgAygLMiEuc2l0ZXN0cmVh",
- "bS5DYWNoZWRUZWxlbWV0cnlQYWNrZXQiWwoWUHVsbEF1ZGl0RXZlbnRzUmVx",
- "dWVzdBItCglzaW5jZV91dGMYASABKAsyGi5nb29nbGUucHJvdG9idWYuVGlt",
- "ZXN0YW1wEhIKCmJhdGNoX3NpemUYAiABKAUiXAoXUHVsbEF1ZGl0RXZlbnRz",
- "UmVzcG9uc2USKQoGZXZlbnRzGAEgAygLMhkuc2l0ZXN0cmVhbS5BdWRpdEV2",
- "ZW50RHRvEhYKDm1vcmVfYXZhaWxhYmxlGAIgASgIIlkKFFB1bGxTaXRlQ2Fs",
- "bHNSZXF1ZXN0Ei0KCXNpbmNlX3V0YxgBIAEoCzIaLmdvb2dsZS5wcm90b2J1",
- "Zi5UaW1lc3RhbXASEgoKYmF0Y2hfc2l6ZRgCIAEoBSJpChVQdWxsU2l0ZUNh",
- "bGxzUmVzcG9uc2USOAoMb3BlcmF0aW9uYWxzGAEgAygLMiIuc2l0ZXN0cmVh",
- "bS5TaXRlQ2FsbE9wZXJhdGlvbmFsRHRvEhYKDm1vcmVfYXZhaWxhYmxlGAIg",
- "ASgIKlwKB1F1YWxpdHkSFwoTUVVBTElUWV9VTlNQRUNJRklFRBAAEhAKDFFV",
- "QUxJVFlfR09PRBABEhUKEVFVQUxJVFlfVU5DRVJUQUlOEAISDwoLUVVBTElU",
- "WV9CQUQQAypdCg5BbGFybVN0YXRlRW51bRIbChdBTEFSTV9TVEFURV9VTlNQ",
- "RUNJRklFRBAAEhYKEkFMQVJNX1NUQVRFX05PUk1BTBABEhYKEkFMQVJNX1NU",
- "QVRFX0FDVElWRRACKoUBCg5BbGFybUxldmVsRW51bRIUChBBTEFSTV9MRVZF",
- "TF9OT05FEAASEwoPQUxBUk1fTEVWRUxfTE9XEAESFwoTQUxBUk1fTEVWRUxf",
- "TE9XX0xPVxACEhQKEEFMQVJNX0xFVkVMX0hJR0gQAxIZChVBTEFSTV9MRVZF",
- "TF9ISUdIX0hJR0gQBDK3AwoRU2l0ZVN0cmVhbVNlcnZpY2USVQoRU3Vic2Ny",
- "aWJlSW5zdGFuY2USIS5zaXRlc3RyZWFtLkluc3RhbmNlU3RyZWFtUmVxdWVz",
- "dBobLnNpdGVzdHJlYW0uU2l0ZVN0cmVhbUV2ZW50MAESRwoRSW5nZXN0QXVk",
- "aXRFdmVudHMSGy5zaXRlc3RyZWFtLkF1ZGl0RXZlbnRCYXRjaBoVLnNpdGVz",
- "dHJlYW0uSW5nZXN0QWNrElAKFUluZ2VzdENhY2hlZFRlbGVtZXRyeRIgLnNp",
- "dGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gaFS5zaXRlc3RyZWFtLklu",
- "Z2VzdEFjaxJaCg9QdWxsQXVkaXRFdmVudHMSIi5zaXRlc3RyZWFtLlB1bGxB",
- "dWRpdEV2ZW50c1JlcXVlc3QaIy5zaXRlc3RyZWFtLlB1bGxBdWRpdEV2ZW50",
- "c1Jlc3BvbnNlElQKDVB1bGxTaXRlQ2FsbHMSIC5zaXRlc3RyZWFtLlB1bGxT",
- "aXRlQ2FsbHNSZXF1ZXN0GiEuc2l0ZXN0cmVhbS5QdWxsU2l0ZUNhbGxzUmVz",
- "cG9uc2VCK6oCKFpCLk1PTS5XVy5TY2FkYUJyaWRnZS5Db21tdW5pY2F0aW9u",
- "LkdycGNiBnByb3RvMw=="));
+ "CRIkChxuYXRpdmVfc291cmNlX2Nhbm9uaWNhbF9uYW1lGBYgASgJEiEKGWlz",
+ "X2NvbmZpZ3VyZWRfcGxhY2Vob2xkZXIYFyABKAgivQQKDUF1ZGl0RXZlbnRE",
+ "dG8SEAoIZXZlbnRfaWQYASABKAkSMwoPb2NjdXJyZWRfYXRfdXRjGAIgASgL",
+ "MhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIPCgdjaGFubmVsGAMgASgJ",
+ "EgwKBGtpbmQYBCABKAkSFgoOY29ycmVsYXRpb25faWQYBSABKAkSFgoOc291",
+ "cmNlX3NpdGVfaWQYBiABKAkSGgoSc291cmNlX2luc3RhbmNlX2lkGAcgASgJ",
+ "EhUKDXNvdXJjZV9zY3JpcHQYCCABKAkSDQoFYWN0b3IYCSABKAkSDgoGdGFy",
+ "Z2V0GAogASgJEg4KBnN0YXR1cxgLIAEoCRIwCgtodHRwX3N0YXR1cxgMIAEo",
+ "CzIbLmdvb2dsZS5wcm90b2J1Zi5JbnQzMlZhbHVlEjAKC2R1cmF0aW9uX21z",
+ "GA0gASgLMhsuZ29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUSFQoNZXJyb3Jf",
+ "bWVzc2FnZRgOIAEoCRIUCgxlcnJvcl9kZXRhaWwYDyABKAkSFwoPcmVxdWVz",
+ "dF9zdW1tYXJ5GBAgASgJEhgKEHJlc3BvbnNlX3N1bW1hcnkYESABKAkSGQoR",
+ "cGF5bG9hZF90cnVuY2F0ZWQYEiABKAgSDQoFZXh0cmEYEyABKAkSFAoMZXhl",
+ "Y3V0aW9uX2lkGBQgASgJEhsKE3BhcmVudF9leGVjdXRpb25faWQYFSABKAkS",
+ "EwoLc291cmNlX25vZGUYFiABKAkiPAoPQXVkaXRFdmVudEJhdGNoEikKBmV2",
+ "ZW50cxgBIAMoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVudER0byInCglJbmdl",
+ "c3RBY2sSGgoSYWNjZXB0ZWRfZXZlbnRfaWRzGAEgAygJIokDChZTaXRlQ2Fs",
+ "bE9wZXJhdGlvbmFsRHRvEhwKFHRyYWNrZWRfb3BlcmF0aW9uX2lkGAEgASgJ",
+ "Eg8KB2NoYW5uZWwYAiABKAkSDgoGdGFyZ2V0GAMgASgJEhMKC3NvdXJjZV9z",
+ "aXRlGAQgASgJEg4KBnN0YXR1cxgFIAEoCRITCgtyZXRyeV9jb3VudBgGIAEo",
+ "BRISCgpsYXN0X2Vycm9yGAcgASgJEjAKC2h0dHBfc3RhdHVzGAggASgLMhsu",
+ "Z29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUSMgoOY3JlYXRlZF9hdF91dGMY",
+ "CSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDnVwZGF0ZWRf",
+ "YXRfdXRjGAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIzCg90",
+ "ZXJtaW5hbF9hdF91dGMYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0",
+ "YW1wEhMKC3NvdXJjZV9ub2RlGAwgASgJIoABChVDYWNoZWRUZWxlbWV0cnlQ",
+ "YWNrZXQSLgoLYXVkaXRfZXZlbnQYASABKAsyGS5zaXRlc3RyZWFtLkF1ZGl0",
+ "RXZlbnREdG8SNwoLb3BlcmF0aW9uYWwYAiABKAsyIi5zaXRlc3RyZWFtLlNp",
+ "dGVDYWxsT3BlcmF0aW9uYWxEdG8iSgoUQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gS",
+ "MgoHcGFja2V0cxgBIAMoCzIhLnNpdGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5",
+ "UGFja2V0IlsKFlB1bGxBdWRpdEV2ZW50c1JlcXVlc3QSLQoJc2luY2VfdXRj",
+ "GAEgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpiYXRjaF9z",
+ "aXplGAIgASgFIlwKF1B1bGxBdWRpdEV2ZW50c1Jlc3BvbnNlEikKBmV2ZW50",
+ "cxgBIAMoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVudER0bxIWCg5tb3JlX2F2",
+ "YWlsYWJsZRgCIAEoCCJZChRQdWxsU2l0ZUNhbGxzUmVxdWVzdBItCglzaW5j",
+ "ZV91dGMYASABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmJh",
+ "dGNoX3NpemUYAiABKAUiaQoVUHVsbFNpdGVDYWxsc1Jlc3BvbnNlEjgKDG9w",
+ "ZXJhdGlvbmFscxgBIAMoCzIiLnNpdGVzdHJlYW0uU2l0ZUNhbGxPcGVyYXRp",
+ "b25hbER0bxIWCg5tb3JlX2F2YWlsYWJsZRgCIAEoCCpcCgdRdWFsaXR5EhcK",
+ "E1FVQUxJVFlfVU5TUEVDSUZJRUQQABIQCgxRVUFMSVRZX0dPT0QQARIVChFR",
+ "VUFMSVRZX1VOQ0VSVEFJThACEg8KC1FVQUxJVFlfQkFEEAMqXQoOQWxhcm1T",
+ "dGF0ZUVudW0SGwoXQUxBUk1fU1RBVEVfVU5TUEVDSUZJRUQQABIWChJBTEFS",
+ "TV9TVEFURV9OT1JNQUwQARIWChJBTEFSTV9TVEFURV9BQ1RJVkUQAiqFAQoO",
+ "QWxhcm1MZXZlbEVudW0SFAoQQUxBUk1fTEVWRUxfTk9ORRAAEhMKD0FMQVJN",
+ "X0xFVkVMX0xPVxABEhcKE0FMQVJNX0xFVkVMX0xPV19MT1cQAhIUChBBTEFS",
+ "TV9MRVZFTF9ISUdIEAMSGQoVQUxBUk1fTEVWRUxfSElHSF9ISUdIEAQytwMK",
+ "EVNpdGVTdHJlYW1TZXJ2aWNlElUKEVN1YnNjcmliZUluc3RhbmNlEiEuc2l0",
+ "ZXN0cmVhbS5JbnN0YW5jZVN0cmVhbVJlcXVlc3QaGy5zaXRlc3RyZWFtLlNp",
+ "dGVTdHJlYW1FdmVudDABEkcKEUluZ2VzdEF1ZGl0RXZlbnRzEhsuc2l0ZXN0",
+ "cmVhbS5BdWRpdEV2ZW50QmF0Y2gaFS5zaXRlc3RyZWFtLkluZ2VzdEFjaxJQ",
+ "ChVJbmdlc3RDYWNoZWRUZWxlbWV0cnkSIC5zaXRlc3RyZWFtLkNhY2hlZFRl",
+ "bGVtZXRyeUJhdGNoGhUuc2l0ZXN0cmVhbS5Jbmdlc3RBY2sSWgoPUHVsbEF1",
+ "ZGl0RXZlbnRzEiIuc2l0ZXN0cmVhbS5QdWxsQXVkaXRFdmVudHNSZXF1ZXN0",
+ "GiMuc2l0ZXN0cmVhbS5QdWxsQXVkaXRFdmVudHNSZXNwb25zZRJUCg1QdWxs",
+ "U2l0ZUNhbGxzEiAuc2l0ZXN0cmVhbS5QdWxsU2l0ZUNhbGxzUmVxdWVzdBoh",
+ "LnNpdGVzdHJlYW0uUHVsbFNpdGVDYWxsc1Jlc3BvbnNlQiuqAihaQi5NT00u",
+ "V1cuU2NhZGFCcmlkZ2UuQ29tbXVuaWNhdGlvbi5HcnBjYgZwcm90bzM="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.Quality), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateEnum), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmLevelEnum), }, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.InstanceStreamRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.InstanceStreamRequest.Parser, new[]{ "CorrelationId", "InstanceUniqueName" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEvent), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEvent.Parser, new[]{ "CorrelationId", "AttributeChanged", "AlarmChanged" }, new[]{ "Event" }, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate.Parser, new[]{ "InstanceUniqueName", "AttributePath", "AttributeName", "Value", "Quality", "Timestamp" }, null, null, null, null),
- new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate.Parser, new[]{ "InstanceUniqueName", "AlarmName", "State", "Priority", "Timestamp", "Level", "Message", "Kind", "Active", "Acknowledged", "Confirmed", "ShelveState", "Suppressed", "SourceReference", "AlarmTypeName", "Category", "OperatorUser", "OperatorComment", "OriginalRaiseTime", "CurrentValue", "LimitValue" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate.Parser, new[]{ "InstanceUniqueName", "AlarmName", "State", "Priority", "Timestamp", "Level", "Message", "Kind", "Active", "Acknowledged", "Confirmed", "ShelveState", "Suppressed", "SourceReference", "AlarmTypeName", "Category", "OperatorUser", "OperatorComment", "OriginalRaiseTime", "CurrentValue", "LimitValue", "NativeSourceCanonicalName", "IsConfiguredPlaceholder" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto.Parser, new[]{ "EventId", "OccurredAtUtc", "Channel", "Kind", "CorrelationId", "SourceSiteId", "SourceInstanceId", "SourceScript", "Actor", "Target", "Status", "HttpStatus", "DurationMs", "ErrorMessage", "ErrorDetail", "RequestSummary", "ResponseSummary", "PayloadTruncated", "Extra", "ExecutionId", "ParentExecutionId", "SourceNode" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventBatch), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventBatch.Parser, new[]{ "Events" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.IngestAck), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.IngestAck.Parser, new[]{ "AcceptedEventIds" }, null, null, null, null),
@@ -1171,6 +1172,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
originalRaiseTime_ = other.originalRaiseTime_ != null ? other.originalRaiseTime_.Clone() : null;
currentValue_ = other.currentValue_;
limitValue_ = other.limitValue_;
+ nativeSourceCanonicalName_ = other.nativeSourceCanonicalName_;
+ isConfiguredPlaceholder_ = other.isConfiguredPlaceholder_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -1460,6 +1463,36 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
}
}
+ /// Field number for the "native_source_canonical_name" field.
+ public const int NativeSourceCanonicalNameFieldNumber = 22;
+ private string nativeSourceCanonicalName_ = "";
+ ///
+ /// native binding canonical name; empty for computed
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string NativeSourceCanonicalName {
+ get { return nativeSourceCanonicalName_; }
+ set {
+ nativeSourceCanonicalName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "is_configured_placeholder" field.
+ public const int IsConfiguredPlaceholderFieldNumber = 23;
+ private bool isConfiguredPlaceholder_;
+ ///
+ /// true for a quiet-binding placeholder row
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool IsConfiguredPlaceholder {
+ get { return isConfiguredPlaceholder_; }
+ set {
+ isConfiguredPlaceholder_ = value;
+ }
+ }
+
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -1496,6 +1529,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (!object.Equals(OriginalRaiseTime, other.OriginalRaiseTime)) return false;
if (CurrentValue != other.CurrentValue) return false;
if (LimitValue != other.LimitValue) return false;
+ if (NativeSourceCanonicalName != other.NativeSourceCanonicalName) return false;
+ if (IsConfiguredPlaceholder != other.IsConfiguredPlaceholder) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -1524,6 +1559,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (originalRaiseTime_ != null) hash ^= OriginalRaiseTime.GetHashCode();
if (CurrentValue.Length != 0) hash ^= CurrentValue.GetHashCode();
if (LimitValue.Length != 0) hash ^= LimitValue.GetHashCode();
+ if (NativeSourceCanonicalName.Length != 0) hash ^= NativeSourceCanonicalName.GetHashCode();
+ if (IsConfiguredPlaceholder != false) hash ^= IsConfiguredPlaceholder.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -1626,6 +1663,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteRawTag(170, 1);
output.WriteString(LimitValue);
}
+ if (NativeSourceCanonicalName.Length != 0) {
+ output.WriteRawTag(178, 1);
+ output.WriteString(NativeSourceCanonicalName);
+ }
+ if (IsConfiguredPlaceholder != false) {
+ output.WriteRawTag(184, 1);
+ output.WriteBool(IsConfiguredPlaceholder);
+ }
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -1720,6 +1765,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteRawTag(170, 1);
output.WriteString(LimitValue);
}
+ if (NativeSourceCanonicalName.Length != 0) {
+ output.WriteRawTag(178, 1);
+ output.WriteString(NativeSourceCanonicalName);
+ }
+ if (IsConfiguredPlaceholder != false) {
+ output.WriteRawTag(184, 1);
+ output.WriteBool(IsConfiguredPlaceholder);
+ }
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -1793,6 +1846,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (LimitValue.Length != 0) {
size += 2 + pb::CodedOutputStream.ComputeStringSize(LimitValue);
}
+ if (NativeSourceCanonicalName.Length != 0) {
+ size += 2 + pb::CodedOutputStream.ComputeStringSize(NativeSourceCanonicalName);
+ }
+ if (IsConfiguredPlaceholder != false) {
+ size += 2 + 1;
+ }
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -1874,6 +1933,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (other.LimitValue.Length != 0) {
LimitValue = other.LimitValue;
}
+ if (other.NativeSourceCanonicalName.Length != 0) {
+ NativeSourceCanonicalName = other.NativeSourceCanonicalName;
+ }
+ if (other.IsConfiguredPlaceholder != false) {
+ IsConfiguredPlaceholder = other.IsConfiguredPlaceholder;
+ }
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -1983,6 +2048,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
LimitValue = input.ReadString();
break;
}
+ case 178: {
+ NativeSourceCanonicalName = input.ReadString();
+ break;
+ }
+ case 184: {
+ IsConfiguredPlaceholder = input.ReadBool();
+ break;
+ }
}
}
#endif
@@ -2092,6 +2165,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
LimitValue = input.ReadString();
break;
}
+ case 178: {
+ NativeSourceCanonicalName = input.ReadString();
+ break;
+ }
+ case 184: {
+ IsConfiguredPlaceholder = input.ReadBool();
+ break;
+ }
}
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs
index 63a354ed..0a90be04 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs
@@ -330,7 +330,8 @@ public class NativeAlarmActor : ReceiveActor
OperatorComment = t.OperatorComment,
OriginalRaiseTime = t.OriginalRaiseTime,
CurrentValue = t.CurrentValue,
- LimitValue = t.LimitValue
+ LimitValue = t.LimitValue,
+ NativeSourceCanonicalName = _source.CanonicalName,
};
_instanceActor.Tell(change);
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/StreamRelayActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/StreamRelayActorTests.cs
index ba1fad29..cbfeab58 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/StreamRelayActorTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/StreamRelayActorTests.cs
@@ -113,6 +113,52 @@ public class StreamRelayActorTests : TestKit
Assert.Equal("90", alarm.LimitValue);
}
+ [Fact]
+ public void RelaysAlarmStateChanged_NativeBindingLinkage_SurvivesFullRoundTrip()
+ {
+ // DV-1: the native source-binding canonical name and the configured-placeholder
+ // flag must pack into AlarmStateUpdate (StreamRelayActor) and unpack back out
+ // (SiteStreamGrpcClient.ConvertToDomainEvent) — the full cross-process round trip.
+ var channel = Channel.CreateUnbounded();
+ var actor = Sys.ActorOf(Props.Create(() =>
+ new StreamRelayActor("corr-native-binding", channel.Writer)));
+
+ var timestamp = new DateTimeOffset(2026, 3, 21, 11, 0, 0, TimeSpan.Zero);
+ var domainEvent = new AlarmStateChanged(
+ "Site1.Motor01", "Motor1.MotorAlarms.Hi", AlarmState.Active, 900, timestamp)
+ {
+ Kind = AlarmKind.NativeOpcUa,
+ SourceReference = "Motor1.MotorAlarms.Hi",
+ NativeSourceCanonicalName = "Motor1.MotorAlarms",
+ IsConfiguredPlaceholder = true,
+ Condition = new AlarmConditionState(
+ Active: true, Acknowledged: false, Confirmed: null,
+ Shelve: AlarmShelveState.Unshelved, Suppressed: false, Severity: 900)
+ };
+
+ actor.Tell(domainEvent);
+
+ var success = channel.Reader.TryRead(out var protoEvent);
+ if (!success)
+ {
+ Thread.Sleep(500);
+ success = channel.Reader.TryRead(out protoEvent);
+ }
+
+ Assert.True(success, "Expected a proto event on the channel");
+ Assert.NotNull(protoEvent);
+
+ // Packed onto the wire frame by StreamRelayActor.
+ Assert.Equal("Motor1.MotorAlarms", protoEvent.AlarmChanged.NativeSourceCanonicalName);
+ Assert.True(protoEvent.AlarmChanged.IsConfiguredPlaceholder);
+
+ // Unpacked back into the domain record by SiteStreamGrpcClient.
+ var roundTripped = Assert.IsType(
+ SiteStreamGrpcClient.ConvertToDomainEvent(protoEvent));
+ Assert.Equal("Motor1.MotorAlarms", roundTripped.NativeSourceCanonicalName);
+ Assert.True(roundTripped.IsConfiguredPlaceholder);
+ }
+
[Fact]
public void SetsCorrelationId_OnAllEvents()
{
diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/NativeAlarmActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/NativeAlarmActorTests.cs
index abcd0766..3c3a45c8 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/NativeAlarmActorTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/NativeAlarmActorTests.cs
@@ -79,6 +79,27 @@ public class NativeAlarmActorTests : TestKit, IDisposable
Assert.Equal(800, emitted.Condition.Severity);
}
+ [Fact]
+ public void Raise_StampsNativeSourceCanonicalName_FromConfiguredBinding()
+ {
+ // DV-1: every emitted condition carries the canonical name of the source
+ // BINDING it belongs to, so the DebugView can nest live native conditions
+ // under their configured binding node. It must equal the binding's
+ // CanonicalName used to construct the actor (Source().CanonicalName).
+ var instance = CreateTestProbe();
+ var dcl = CreateTestProbe();
+ var actor = Spawn(instance.Ref, dcl.Ref);
+ dcl.ExpectMsg();
+
+ actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
+ "T01.Hi", AlarmTransitionKind.Raise,
+ new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800))));
+
+ var emitted = instance.ExpectMsg();
+ Assert.Equal(Source().CanonicalName, emitted.NativeSourceCanonicalName);
+ Assert.Equal("Pressure", emitted.NativeSourceCanonicalName);
+ }
+
[Fact]
public void SnapshotComplete_WithMissingPriorAlarm_EmitsReturnToNormal()
{