Wire DCL to Instance Actors for OPC UA tag value flow

- Add TagValueUpdate/ConnectionQualityChanged handlers to InstanceActor
- InstanceActor subscribes to DCL on PreStart based on DataSourceReference
- DeploymentManagerActor creates DCL connections on deploy and passes DCL ref
- AkkaHostedService creates DCL Manager Actor for tag subscriptions
- Move CreateConnectionCommand to Commons for cross-project access
- Add ConnectionConfig to FlattenedConfiguration for deployment packaging
This commit is contained in:
Joseph Doherty
2026-03-17 11:21:11 -04:00
parent 2798b91fe1
commit dfb809a909
6 changed files with 175 additions and 12 deletions

View File

@@ -29,6 +29,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
private readonly SiteStreamManager? _streamManager;
private readonly SiteRuntimeOptions _options;
private readonly ILogger<DeploymentManagerActor> _logger;
private readonly IActorRef? _dclManager;
private readonly Dictionary<string, IActorRef> _instanceActors = new();
public ITimerScheduler Timers { get; set; } = null!;
@@ -39,13 +40,15 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
SharedScriptLibrary sharedScriptLibrary,
SiteStreamManager? streamManager,
SiteRuntimeOptions options,
ILogger<DeploymentManagerActor> logger)
ILogger<DeploymentManagerActor> logger,
IActorRef? dclManager = null)
{
_storage = storage;
_compilationService = compilationService;
_sharedScriptLibrary = sharedScriptLibrary;
_streamManager = streamManager;
_options = options;
_dclManager = dclManager;
_logger = logger;
// Lifecycle commands
@@ -192,6 +195,9 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
return;
}
// Ensure DCL connections exist for any data-sourced attributes
EnsureDclConnections(command.FlattenedConfigurationJson);
// Create the Instance Actor immediately (no existing actor to replace)
CreateInstanceActor(instanceName, command.FlattenedConfigurationJson);
@@ -342,6 +348,54 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
_logger.LogInformation("Instance {Instance} deleted", instanceName);
}
// ── DCL connection management ──
private readonly HashSet<string> _createdConnections = new();
/// <summary>
/// Parses the flattened config to find bound data connections and ensures
/// the DCL Manager has corresponding connection actors created.
/// </summary>
private void EnsureDclConnections(string configJson)
{
if (_dclManager == null) return;
try
{
var config = System.Text.Json.JsonSerializer.Deserialize<Commons.Types.Flattening.FlattenedConfiguration>(configJson);
if (config?.Connections == null) return;
foreach (var (name, connConfig) in config.Connections)
{
if (_createdConnections.Contains(name))
continue;
var connectionDetails = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(connConfig.ConfigurationJson))
{
try
{
var parsed = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(connConfig.ConfigurationJson);
if (parsed != null) connectionDetails = parsed;
}
catch { /* Ignore parse errors */ }
}
_dclManager.Tell(new Commons.Messages.DataConnection.CreateConnectionCommand(
name, connConfig.Protocol, connectionDetails));
_createdConnections.Add(name);
_logger.LogInformation(
"Created DCL connection {Connection} (protocol={Protocol})",
name, connConfig.Protocol);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to parse flattened config for DCL connections");
}
}
// ── Debug View routing ──
private void RouteDebugViewSubscribe(SubscribeDebugViewRequest request)
@@ -456,7 +510,8 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
_sharedScriptLibrary,
_streamManager,
_options,
loggerFactory.CreateLogger<InstanceActor>()));
loggerFactory.CreateLogger<InstanceActor>(),
_dclManager));
var actorRef = Context.ActorOf(props, instanceName);
_instanceActors[instanceName] = actorRef;