feat(runtime): route driver values to folder-scoped equipment NodeIds (live-value delivery)
v2-ci / build (push) Failing after 44s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 44s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
This commit is contained in:
@@ -11,6 +11,7 @@ using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Fleet;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Observability;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
@@ -85,6 +86,16 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
|
||||
private readonly Dictionary<string, ChildEntry> _children = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Driver live-value routing map: <c>(DriverInstanceId, FullName) → folder-scoped equipment
|
||||
/// NodeId(s)</c>. Rebuilt every apply by <see cref="PushDesiredSubscriptions"/> from the
|
||||
/// composition's <c>EquipmentTags</c> (mirroring <c>VirtualTagHostActor._nodeIdByVtag</c>), and
|
||||
/// resolved in <see cref="ForwardToMux"/> so a driver value published by wire-ref FullName lands
|
||||
/// on the variable's actual folder-scoped NodeId. A list because the same driver ref can back
|
||||
/// several equipment variables (e.g. identical machines sharing a register).
|
||||
/// </summary>
|
||||
private readonly Dictionary<(string DriverInstanceId, string FullName), List<string>> _nodeIdByDriverRef = new();
|
||||
|
||||
private sealed record ChildEntry(IActorRef Actor, DriverInstanceSpec Spec, bool Stubbed)
|
||||
{
|
||||
// Convenience accessors for sites that don't need the full spec.
|
||||
@@ -378,18 +389,32 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
|
||||
private void ForwardToMux(DriverInstanceActor.AttributeValuePublished msg)
|
||||
{
|
||||
// Pass driver-published values to the dependency mux when one is wired (VirtualTag inputs).
|
||||
// Without a mux, VirtualTagActor evaluation can't fire — that's the dev/Mac path (no virtual
|
||||
// tags registered); production binds the mux via the RuntimeActors extension.
|
||||
// Pass driver-published values to the dependency mux when one is wired (VirtualTag inputs,
|
||||
// keyed by FullReference). Without a mux, VirtualTagActor evaluation can't fire — that's the
|
||||
// dev/Mac path (no virtual tags registered); production binds the mux via the RuntimeActors
|
||||
// extension. KEEP this unchanged — VirtualTag inputs are still keyed by FullReference.
|
||||
_dependencyMux?.Tell(msg);
|
||||
|
||||
// Also push the value to the OPC UA sink. NOTE: equipment-tag variables are materialised with
|
||||
// folder-scoped NodeIds (EquipmentId/FolderPath/Name), while the driver publishes keyed by
|
||||
// FullReference (the tag's FullName). The value therefore only lands on the variable once the
|
||||
// FullName→NodeId routing — the equipment-tag "live values" milestone — is wired; until then
|
||||
// the variable stays BadWaitingForInitialData.
|
||||
_opcUaPublishActor?.Tell(new ZB.MOM.WW.OtOpcUa.Runtime.OpcUa.OpcUaPublishActor.AttributeValueUpdate(
|
||||
msg.FullReference, msg.Value, msg.Quality, msg.TimestampUtc));
|
||||
if (_opcUaPublishActor is null) return;
|
||||
|
||||
// Route the value to the OPC UA sink at the variable's ACTUAL NodeId. Equipment-tag variables
|
||||
// are materialised with folder-scoped NodeIds (EquipmentId/FolderPath/Name), while the driver
|
||||
// publishes keyed by its wire-ref FullName (FullReference). The _nodeIdByDriverRef map — built
|
||||
// each apply from the composition's EquipmentTags — resolves (DriverInstanceId, FullName) to
|
||||
// the folder-scoped NodeId(s) the materialiser placed the variable(s) at, so the value lands
|
||||
// instead of leaving the variable at BadWaitingForInitialData. One driver ref can back several
|
||||
// equipment variables (identical machines sharing a register), hence the fan-out.
|
||||
if (_nodeIdByDriverRef.TryGetValue((msg.DriverInstanceId, msg.FullReference), out var nodeIds))
|
||||
{
|
||||
foreach (var nodeId in nodeIds)
|
||||
_opcUaPublishActor.Tell(new ZB.MOM.WW.OtOpcUa.Runtime.OpcUa.OpcUaPublishActor.AttributeValueUpdate(
|
||||
nodeId, msg.Value, msg.Quality, msg.TimestampUtc));
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Debug("DriverHost {Node}: no equipment-tag NodeId for ({Driver},{Ref}) — value dropped",
|
||||
_localNode, msg.DriverInstanceId, msg.FullReference);
|
||||
}
|
||||
}
|
||||
|
||||
private void Stale()
|
||||
@@ -592,6 +617,21 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
.ToArray(),
|
||||
StringComparer.Ordinal);
|
||||
|
||||
// Rebuild the driver live-value routing map from the SAME EquipmentTags pass (mirrors
|
||||
// VirtualTagHostActor._nodeIdByVtag): map each tag's (DriverInstanceId, FullName) wire-ref to
|
||||
// the folder-scoped equipment NodeId the materialiser placed its variable at, so ForwardToMux
|
||||
// can land driver values on the right node. Clear-and-repopulate every apply so renames
|
||||
// (Name/FolderPath/EquipmentId changes) and removals are reflected.
|
||||
_nodeIdByDriverRef.Clear();
|
||||
foreach (var t in composition.EquipmentTags)
|
||||
{
|
||||
var key = (t.DriverInstanceId, t.FullName);
|
||||
var nodeId = EquipmentNodeIds.Variable(t.EquipmentId, t.FolderPath, t.Name);
|
||||
if (!_nodeIdByDriverRef.TryGetValue(key, out var list))
|
||||
_nodeIdByDriverRef[key] = list = new List<string>();
|
||||
if (!list.Contains(nodeId)) list.Add(nodeId);
|
||||
}
|
||||
|
||||
var total = 0;
|
||||
foreach (var (driverId, entry) in _children)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user