diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/VirtualTagActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/VirtualTagActor.cs new file mode 100644 index 0000000..ffa5adb --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/VirtualTagActor.cs @@ -0,0 +1,41 @@ +using Akka.Actor; +using Akka.Event; +using ZB.MOM.WW.OtOpcUa.Commons.Types; + +namespace ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags; + +/// +/// Wraps a single virtual-tag expression. Receives dependency-tag updates, recomputes the +/// expression, and publishes the result to OpcUaPublishActor. +/// +/// Engine wiring (compile expression via VirtualTagEngine, manage subscriptions, +/// emit AttributeValueUpdate) is staged for follow-up F8. This skeleton compiles + has +/// a basic message contract so DriverHostActor can spawn it as a child. +/// +public sealed class VirtualTagActor : ReceiveActor +{ + public sealed record DependencyValueChanged(string TagId, object? Value, DateTime TimestampUtc); + public sealed record EvaluationResult(string VirtualTagId, object? Value, DateTime TimestampUtc, CorrelationId Correlation); + + private readonly string _virtualTagId; + private readonly string _expression; + private readonly ILoggingAdapter _log = Context.GetLogger(); + private readonly Dictionary _dependencies = new(StringComparer.Ordinal); + + public static Props Props(string virtualTagId, string expression) => + Akka.Actor.Props.Create(() => new VirtualTagActor(virtualTagId, expression)); + + public VirtualTagActor(string virtualTagId, string expression) + { + _virtualTagId = virtualTagId; + _expression = expression; + + Receive(msg => + { + _dependencies[msg.TagId] = msg.Value; + // Engine wiring (F8): VirtualTagEngine.Evaluate(_expression, _dependencies) → publish. + _log.Debug("VirtualTag {Id}: dependency {Tag}={Value} buffered (eval staged for F8)", + _virtualTagId, msg.TagId, msg.Value); + }); + } +} diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs new file mode 100644 index 0000000..d75e760 --- /dev/null +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs @@ -0,0 +1,22 @@ +using Akka.Actor; +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness; +using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags; + +namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.VirtualTags; + +public sealed class VirtualTagActorTests : RuntimeActorTestBase +{ + [Fact] + public void DependencyValueChanged_is_accepted_and_actor_stays_alive() + { + var actor = Sys.ActorOf(VirtualTagActor.Props("vt-1", "a + b")); + Watch(actor); + actor.Tell(new VirtualTagActor.DependencyValueChanged("tag-a", 10, DateTime.UtcNow)); + actor.Tell(new VirtualTagActor.DependencyValueChanged("tag-b", 20, DateTime.UtcNow)); + + // No crash, no termination. + ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + } +}