feat: add JoeAppEngine OPC UA nodes, fix DCL auto-reconnect and quality push

- Add JoeAppEngine folder to OPC UA nodes.json (BTCS, AlarmCntsBySeverity, Scheduler/ScanTime)
- Fix DataConnectionActor: capture Self in PreStart for use from non-actor threads,
  preventing Self.Tell failure in Disconnected event handler
- Implement InstanceActor.HandleConnectionQualityChanged to mark attributes Bad on disconnect
- Fix LmxFakeProxy TagMapper to serialize arrays as JSON instead of "System.Int32[]"
- Allow DataType and DataSourceReference updates in TemplateService.UpdateAttributeAsync
- Update test_infra_opcua.md with JoeAppEngine documentation
This commit is contained in:
Joseph Doherty
2026-03-19 13:27:54 -04:00
parent ffdda51990
commit 7740a3bcf9
70 changed files with 2684 additions and 541 deletions

View File

@@ -22,4 +22,11 @@ public interface IDataConnection : IAsyncDisposable
Task<IReadOnlyDictionary<string, WriteResult>> WriteBatchAsync(IDictionary<string, object?> values, CancellationToken cancellationToken = default);
Task<bool> WriteBatchAndWaitAsync(IDictionary<string, object?> values, string flagPath, object? flagValue, string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default);
ConnectionHealth Status { get; }
/// <summary>
/// Raised when the adapter detects an unexpected connection loss (e.g., gRPC stream error,
/// network timeout). The DataConnectionActor listens for this to trigger reconnection
/// and push bad quality to all subscribed tags.
/// </summary>
event Action? Disconnected;
}

View File

@@ -14,4 +14,5 @@ public record SiteHealthReport(
int DeadLetterCount,
int DeployedInstanceCount,
int EnabledInstanceCount,
int DisabledInstanceCount);
int DisabledInstanceCount,
string NodeRole = "Unknown");

View File

@@ -2,3 +2,4 @@ namespace ScadaLink.Commons.Messages.Management;
public record MgmtDeployArtifactsCommand(int? SiteId = null);
public record QueryDeploymentsCommand(int? InstanceId = null, string? Status = null, int Page = 1, int PageSize = 50);
public record GetDeploymentDiffCommand(int InstanceId);

View File

@@ -8,3 +8,5 @@ public record MgmtEnableInstanceCommand(int InstanceId);
public record MgmtDisableInstanceCommand(int InstanceId);
public record MgmtDeleteInstanceCommand(int InstanceId);
public record SetConnectionBindingsCommand(int InstanceId, IReadOnlyList<(string AttributeName, int DataConnectionId)> Bindings);
public record SetInstanceOverridesCommand(int InstanceId, IReadOnlyDictionary<string, string?> Overrides);
public record SetInstanceAreaCommand(int InstanceId, int? AreaId);

View File

@@ -1,4 +1,6 @@
namespace ScadaLink.Commons.Messages.Management;
public record QueryEventLogsCommand(string SiteIdentifier, string? EventType = null, string? Severity = null, string? Keyword = null, DateTimeOffset? From = null, DateTimeOffset? To = null, int Page = 1, int PageSize = 50);
public record QueryEventLogsCommand(string SiteIdentifier, string? EventType = null, string? Severity = null, string? Keyword = null, DateTimeOffset? From = null, DateTimeOffset? To = null, int Page = 1, int PageSize = 50, string? InstanceName = null);
public record QueryParkedMessagesCommand(string SiteIdentifier, int Page = 1, int PageSize = 50);
public record RetryParkedMessageCommand(string SiteIdentifier, string MessageId);
public record DiscardParkedMessageCommand(string SiteIdentifier, string MessageId);

View File

@@ -11,3 +11,4 @@ public record UpdateApiKeyCommand(int ApiKeyId, bool IsEnabled);
public record ListScopeRulesCommand(int MappingId);
public record AddScopeRuleCommand(int MappingId, int SiteId);
public record DeleteScopeRuleCommand(int ScopeRuleId);
public record ResolveRolesCommand(IReadOnlyList<string> LdapGroups);

View File

@@ -0,0 +1,18 @@
namespace ScadaLink.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to permanently discard a parked message at a site.
/// </summary>
public record ParkedMessageDiscardRequest(
string CorrelationId,
string SiteId,
string MessageId,
DateTimeOffset Timestamp);
/// <summary>
/// Response from discarding a parked message.
/// </summary>
public record ParkedMessageDiscardResponse(
string CorrelationId,
bool Success,
string? ErrorMessage = null);

View File

@@ -0,0 +1,18 @@
namespace ScadaLink.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to retry a parked message at a site (move back to pending queue).
/// </summary>
public record ParkedMessageRetryRequest(
string CorrelationId,
string SiteId,
string MessageId,
DateTimeOffset Timestamp);
/// <summary>
/// Response from retrying a parked message.
/// </summary>
public record ParkedMessageRetryResponse(
string CorrelationId,
bool Success,
string? ErrorMessage = null);