feat(runtime): carry DriverInstanceId on AttributeValuePublished (live-value routing key)

This commit is contained in:
Joseph Doherty
2026-06-13 06:27:52 -04:00
parent 26816fd17e
commit da1accceff
2 changed files with 14 additions and 14 deletions
@@ -62,7 +62,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
public sealed record ForceReconnect; public sealed record ForceReconnect;
/// <summary>Published to the actor's parent whenever the subscribed IDriver fires /// <summary>Published to the actor's parent whenever the subscribed IDriver fires
/// <see cref="ISubscribable.OnDataChange"/>. The parent forwards to OpcUaPublishActor.</summary> /// <see cref="ISubscribable.OnDataChange"/>. The parent forwards to OpcUaPublishActor.</summary>
public sealed record AttributeValuePublished(string FullReference, object? Value, OpcUaQuality Quality, DateTime TimestampUtc); public sealed record AttributeValuePublished(string DriverInstanceId, string FullReference, object? Value, OpcUaQuality Quality, DateTime TimestampUtc);
private sealed record DataChangeForward(string FullReference, DataValueSnapshot Snapshot); private sealed record DataChangeForward(string FullReference, DataValueSnapshot Snapshot);
public sealed class RetryConnect public sealed class RetryConnect
{ {
@@ -446,7 +446,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
{ {
var quality = QualityFromStatus(msg.Snapshot.StatusCode); var quality = QualityFromStatus(msg.Snapshot.StatusCode);
var ts = msg.Snapshot.SourceTimestampUtc ?? msg.Snapshot.ServerTimestampUtc; var ts = msg.Snapshot.SourceTimestampUtc ?? msg.Snapshot.ServerTimestampUtc;
Context.Parent.Tell(new AttributeValuePublished(msg.FullReference, msg.Snapshot.Value, quality, ts)); Context.Parent.Tell(new AttributeValuePublished(_driverInstanceId, msg.FullReference, msg.Snapshot.Value, quality, ts));
} }
/// <summary>Translate an OPC UA status code to the 3-state <see cref="OpcUaQuality"/> projection /// <summary>Translate an OPC UA status code to the 3-state <see cref="OpcUaQuality"/> projection
@@ -21,9 +21,9 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1", "tag-2" }, subA.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1", "tag-2" }, subA.Ref));
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-2", "tag-3" }, subB.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-2", "tag-3" }, subB.Ref));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-3", 30, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-3", 30, OpcUaQuality.Good, DateTime.UtcNow));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-2", 20, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-2", 20, OpcUaQuality.Good, DateTime.UtcNow));
// subA hears tag-1 + tag-2. // subA hears tag-1 + tag-2.
var aMsgs = new[] var aMsgs = new[]
@@ -53,7 +53,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
var sub = CreateTestProbe(); var sub = CreateTestProbe();
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("nobody-cares", 99, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "nobody-cares", 99, OpcUaQuality.Good, DateTime.UtcNow));
sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
} }
@@ -66,11 +66,11 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
var sub = CreateTestProbe(); var sub = CreateTestProbe();
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow));
sub.ExpectMsg<VirtualTagActor.DependencyValueChanged>(); sub.ExpectMsg<VirtualTagActor.DependencyValueChanged>();
mux.Tell(new DependencyMuxActor.UnregisterInterest(sub.Ref)); mux.Tell(new DependencyMuxActor.UnregisterInterest(sub.Ref));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-1", 20, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-1", 20, OpcUaQuality.Good, DateTime.UtcNow));
sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
} }
@@ -84,10 +84,10 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref));
mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-2" }, sub.Ref)); // replaces tag-1 mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-2" }, sub.Ref)); // replaces tag-1
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-1", 10, OpcUaQuality.Good, DateTime.UtcNow));
sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
mux.Tell(new DriverInstanceActor.AttributeValuePublished("tag-2", 20, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "tag-2", 20, OpcUaQuality.Good, DateTime.UtcNow));
sub.ExpectMsg<VirtualTagActor.DependencyValueChanged>().TagId.ShouldBe("tag-2"); sub.ExpectMsg<VirtualTagActor.DependencyValueChanged>().TagId.ShouldBe("tag-2");
} }
@@ -110,17 +110,17 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
AwaitAssert(() => AwaitAssert(() =>
{ {
mux.Tell(new DriverInstanceActor.AttributeValuePublished( mux.Tell(new DriverInstanceActor.AttributeValuePublished(
"ref-a", 10, OpcUaQuality.Good, DateTime.UtcNow)); "driver-1", "ref-a", 10, OpcUaQuality.Good, DateTime.UtcNow));
parent.ExpectMsg<VirtualTagActor.EvaluationResult>(TimeSpan.FromMilliseconds(200)) parent.ExpectMsg<VirtualTagActor.EvaluationResult>(TimeSpan.FromMilliseconds(200))
.Value.ShouldBe(10); .Value.ShouldBe(10);
}, duration: TimeSpan.FromSeconds(3)); }, duration: TimeSpan.FromSeconds(3));
// From here the actor is wired — second publish drives the sum. // From here the actor is wired — second publish drives the sum.
mux.Tell(new DriverInstanceActor.AttributeValuePublished("ref-b", 32, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "ref-b", 32, OpcUaQuality.Good, DateTime.UtcNow));
parent.ExpectMsg<VirtualTagActor.EvaluationResult>(TimeSpan.FromSeconds(2)).Value.ShouldBe(42); parent.ExpectMsg<VirtualTagActor.EvaluationResult>(TimeSpan.FromSeconds(2)).Value.ShouldBe(42);
// Unrelated ref shouldn't fire eval. // Unrelated ref shouldn't fire eval.
mux.Tell(new DriverInstanceActor.AttributeValuePublished("ref-unrelated", 99, OpcUaQuality.Good, DateTime.UtcNow)); mux.Tell(new DriverInstanceActor.AttributeValuePublished("driver-1", "ref-unrelated", 99, OpcUaQuality.Good, DateTime.UtcNow));
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
} }
@@ -144,7 +144,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase
// Tell the host an AttributeValuePublished — it should fan out to the mux + subscriber. // Tell the host an AttributeValuePublished — it should fan out to the mux + subscriber.
hostActor.Tell(new DriverInstanceActor.AttributeValuePublished( hostActor.Tell(new DriverInstanceActor.AttributeValuePublished(
"ref-1", 42, OpcUaQuality.Good, DateTime.UtcNow)); "driver-1", "ref-1", 42, OpcUaQuality.Good, DateTime.UtcNow));
subscriber.ExpectMsg<VirtualTagActor.DependencyValueChanged>().TagId.ShouldBe("ref-1"); subscriber.ExpectMsg<VirtualTagActor.DependencyValueChanged>().TagId.ShouldBe("ref-1");
} }