feat(runtime): wire driver SubscribeBulk pass so tag values stream
v2-ci / build (push) Failing after 51s
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 51s
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
Materialised SystemPlatform/Galaxy variables previously stayed BadWaitingForInitialData because nothing told the driver to subscribe (OpcUaPublishActor TODO 'on a future SubscribeBulk pass') and published values were only forwarded to the VirtualTag mux, never the OPC UA sink. DriverHostActor now, after each apply, groups the deployment's galaxy tag MXAccess refs by driver and sends DriverInstanceActor.SetDesiredSubscriptions; the actor retains the set and (re)subscribes on every Connected entry, so values resume after reconnects/redeploys (closes the F8b/#113 gap). Published values are also forwarded to OpcUaPublishActor as AttributeValueUpdate (NodeId == galaxy MxAccessRef) so the materialised variable shows live data. Verified live in docker-dev: galaxy TestMachine_001 tags go Good with a changing TestChangingInt. +1 unit test.
This commit is contained in:
@@ -41,6 +41,15 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
public sealed record WriteAttribute(string TagId, object Value);
|
||||
public sealed record WriteAttributeResult(bool Success, string? Reason);
|
||||
public sealed record Subscribe(IReadOnlyList<string> FullReferences, TimeSpan PublishingInterval);
|
||||
/// <summary>
|
||||
/// Sets the set of references this driver should keep subscribed for the lifetime of the
|
||||
/// current deployment. Unlike the one-shot <see cref="Subscribe"/>, the desired set is
|
||||
/// retained and (re)established automatically every time the actor (re)enters
|
||||
/// <c>Connected</c> — closing the F8b/#113 "re-subscribe across reconnects" gap and giving
|
||||
/// <see cref="DriverHostActor"/> a single message to drive the SubscribeBulk pass after an
|
||||
/// apply. Sending an empty set clears the desired subscription.
|
||||
/// </summary>
|
||||
public sealed record SetDesiredSubscriptions(IReadOnlyList<string> FullReferences, TimeSpan PublishingInterval);
|
||||
public sealed record SubscriptionEstablished(string DiagnosticId, int ReferenceCount);
|
||||
public sealed record SubscriptionFailed(string Reason);
|
||||
public sealed record Unsubscribe;
|
||||
@@ -85,6 +94,11 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
private ISubscriptionHandle? _subscriptionHandle;
|
||||
private EventHandler<DataChangeEventArgs>? _dataChangeHandler;
|
||||
|
||||
/// <summary>The references the host wants kept subscribed (set by <see cref="SetDesiredSubscriptions"/>).
|
||||
/// Re-applied on every entry into <c>Connected</c> so values resume after a reconnect or redeploy.</summary>
|
||||
private IReadOnlyList<string> _desiredRefs = Array.Empty<string>();
|
||||
private TimeSpan _desiredInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timer scheduler for scheduling reconnection attempts.
|
||||
/// </summary>
|
||||
@@ -189,6 +203,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
Receive<WriteAttribute>(_ => Sender.Tell(new WriteAttributeResult(true, "stubbed")));
|
||||
Receive<DisconnectObserved>(_ => { /* stubbed drivers don't disconnect */ });
|
||||
Receive<ForceReconnect>(_ => { /* stubbed drivers don't reconnect */ });
|
||||
Receive<SetDesiredSubscriptions>(StoreDesiredSubscriptions);
|
||||
Receive<HealthPollTick>(_ => PublishHealthSnapshot());
|
||||
}
|
||||
|
||||
@@ -200,6 +215,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
_log.Info("DriverInstance {Id}: connected", _driverInstanceId);
|
||||
Become(Connected);
|
||||
PublishHealthSnapshot();
|
||||
ResubscribeDesired();
|
||||
});
|
||||
Receive<InitializeFailed>(msg =>
|
||||
{
|
||||
@@ -208,6 +224,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
Become(Reconnecting);
|
||||
PublishHealthSnapshot();
|
||||
});
|
||||
Receive<SetDesiredSubscriptions>(StoreDesiredSubscriptions);
|
||||
Receive<ForceReconnect>(_ => { /* already connecting — no-op */ });
|
||||
Receive<HealthPollTick>(_ => PublishHealthSnapshot());
|
||||
}
|
||||
@@ -234,6 +251,12 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
ReceiveAsync<WriteAttribute>(HandleWriteAsync);
|
||||
ReceiveAsync<Subscribe>(HandleSubscribeAsync);
|
||||
ReceiveAsync<Unsubscribe>(_ => UnsubscribeAsync());
|
||||
Receive<SetDesiredSubscriptions>(msg =>
|
||||
{
|
||||
StoreDesiredSubscriptions(msg);
|
||||
if (_desiredRefs.Count > 0) Self.Tell(new Subscribe(_desiredRefs, _desiredInterval));
|
||||
else if (_subscriptionHandle is not null) Self.Tell(new Unsubscribe());
|
||||
});
|
||||
Receive<DataChangeForward>(OnDataChangeForward);
|
||||
Receive<HealthPollTick>(_ => PublishHealthSnapshot());
|
||||
}
|
||||
@@ -247,8 +270,10 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
_log.Info("DriverInstance {Id}: reconnected", _driverInstanceId);
|
||||
Become(Connected);
|
||||
PublishHealthSnapshot();
|
||||
ResubscribeDesired();
|
||||
});
|
||||
Receive<InitializeFailed>(_ => { /* keep retrying via timer */ });
|
||||
Receive<SetDesiredSubscriptions>(StoreDesiredSubscriptions);
|
||||
Receive<ForceReconnect>(_ => { /* already reconnecting — no-op */ });
|
||||
Receive<HealthPollTick>(_ => PublishHealthSnapshot());
|
||||
Timers.StartPeriodicTimer("retry-connect", RetryConnect.Instance, _reconnectInterval);
|
||||
@@ -393,6 +418,25 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
_subscriptionHandle = null;
|
||||
}
|
||||
|
||||
/// <summary>Records the host's desired subscription set without touching the live subscription.
|
||||
/// The set is (re)applied by <see cref="ResubscribeDesired"/> on the next <c>Connected</c> entry.</summary>
|
||||
private void StoreDesiredSubscriptions(SetDesiredSubscriptions msg)
|
||||
{
|
||||
_desiredRefs = msg.FullReferences;
|
||||
_desiredInterval = msg.PublishingInterval;
|
||||
}
|
||||
|
||||
/// <summary>Re-establish the desired subscription after (re)connecting. Self-sends the one-shot
|
||||
/// <see cref="Subscribe"/> the Connected behaviour already handles (which drops any prior handle
|
||||
/// first), so values resume streaming after a reconnect or redeploy without host involvement.</summary>
|
||||
private void ResubscribeDesired()
|
||||
{
|
||||
if (_desiredRefs.Count > 0)
|
||||
{
|
||||
Self.Tell(new Subscribe(_desiredRefs, _desiredInterval));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataChangeForward(DataChangeForward msg)
|
||||
{
|
||||
var quality = QualityFromStatus(msg.Snapshot.StatusCode);
|
||||
|
||||
Reference in New Issue
Block a user