feat: data-sourced attributes start with uncertain quality before first DCL value

Attributes bound to data connections now initialize with "Uncertain" quality,
distinguishing "never received a value" from "known good" or "connection lost."
Quality is tracked per attribute and included in GetAttributeResponse.
This commit is contained in:
Joseph Doherty
2026-03-17 18:25:39 -04:00
parent adc1af9f16
commit 775cb8084f
6 changed files with 76 additions and 6 deletions

View File

@@ -11,7 +11,9 @@ public record GetAttributeRequest(
DateTimeOffset Timestamp);
/// <summary>
/// Response containing the current value of an attribute.
/// Response containing the current value and quality of an attribute.
/// Quality is "Good", "Bad", or "Uncertain".
/// Data-sourced attributes start at "Uncertain" until the first DCL value update arrives.
/// </summary>
public record GetAttributeResponse(
string CorrelationId,
@@ -19,4 +21,5 @@ public record GetAttributeResponse(
string AttributeName,
object? Value,
bool Found,
string Quality,
DateTimeOffset Timestamp);

View File

@@ -38,6 +38,7 @@ public class InstanceActor : ReceiveActor
private readonly SiteRuntimeOptions _options;
private readonly ILogger _logger;
private readonly Dictionary<string, object?> _attributes = new();
private readonly Dictionary<string, string> _attributeQualities = new();
private readonly Dictionary<string, AlarmState> _alarmStates = new();
private readonly Dictionary<string, IActorRef> _scriptActors = new();
private readonly Dictionary<string, IActorRef> _alarmActors = new();
@@ -75,11 +76,15 @@ public class InstanceActor : ReceiveActor
_configuration = JsonSerializer.Deserialize<FlattenedConfiguration>(configJson);
// Load default attribute values from the flattened configuration
// Data-sourced attributes start with Uncertain quality until the first DCL value arrives.
// Static attributes start with Good quality.
if (_configuration != null)
{
foreach (var attr in _configuration.Attributes)
{
_attributes[attr.CanonicalName] = attr.Value;
_attributeQualities[attr.CanonicalName] =
string.IsNullOrEmpty(attr.DataSourceReference) ? "Good" : "Uncertain";
}
}
@@ -169,12 +174,14 @@ public class InstanceActor : ReceiveActor
private void HandleGetAttribute(GetAttributeRequest request)
{
var found = _attributes.TryGetValue(request.AttributeName, out var value);
_attributeQualities.TryGetValue(request.AttributeName, out var quality);
Sender.Tell(new GetAttributeResponse(
request.CorrelationId,
_instanceUniqueName,
request.AttributeName,
value,
found,
quality ?? "Good",
DateTimeOffset.UtcNow));
}
@@ -249,6 +256,7 @@ public class InstanceActor : ReceiveActor
{
// WP-24: State mutation serialized through this actor
_attributes[changed.AttributeName] = changed.Value;
_attributeQualities[changed.AttributeName] = changed.Quality;
PublishAndNotifyChildren(changed);
}
@@ -345,7 +353,7 @@ public class InstanceActor : ReceiveActor
kvp.Key,
kvp.Key,
kvp.Value,
"Good",
_attributeQualities.GetValueOrDefault(kvp.Key, "Good"),
DateTimeOffset.UtcNow)).ToList();
var alarmStates = _alarmStates.Select(kvp => new AlarmStateChanged(