Publish default values for null static arrays

This commit is contained in:
Joseph Doherty
2026-03-25 14:12:37 -04:00
parent ed42b33512
commit bb0a89b2a1
2 changed files with 118 additions and 4 deletions

View File

@@ -27,10 +27,18 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
// Ref-counted MXAccess subscriptions
private readonly Dictionary<string, int> _subscriptionRefCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, BaseDataVariableState> _tagToVariableNode = new Dictionary<string, BaseDataVariableState>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, TagMetadata> _tagMetadata = new Dictionary<string, TagMetadata>(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new object();
private IDictionary<NodeId, IList<IReference>>? _externalReferences;
private sealed class TagMetadata
{
public int MxDataType { get; set; }
public bool IsArray { get; set; }
public int? ArrayDimension { get; set; }
}
/// <summary>
/// Gets the mapping from OPC UA node identifiers to the Galaxy tag references used for runtime I/O.
/// </summary>
@@ -91,6 +99,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
{
_nodeIdToTagReference.Clear();
_tagToVariableNode.Clear();
_tagMetadata.Clear();
VariableNodeCount = 0;
ObjectNodeCount = 0;
@@ -198,6 +207,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
PredefinedNodes.Clear();
_nodeIdToTagReference.Clear();
_tagToVariableNode.Clear();
_tagMetadata.Clear();
_subscriptionRefCounts.Clear();
// Rebuild
@@ -266,12 +276,19 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Value = NormalizePublishedValue(attr.FullTagReference, null);
variable.StatusCode = StatusCodes.BadWaitingForInitialData;
variable.Timestamp = DateTime.UtcNow;
AddPredefinedNode(SystemContext, variable);
_nodeIdToTagReference[nodeIdString] = attr.FullTagReference;
_tagToVariableNode[attr.FullTagReference] = variable;
_tagMetadata[attr.FullTagReference] = new TagMetadata
{
MxDataType = attr.MxDataType,
IsArray = attr.IsArray,
ArrayDimension = attr.ArrayDimension
};
VariableNodeCount++;
}
@@ -372,7 +389,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
try
{
var vtq = _mxAccessClient.ReadAsync(tagRef).GetAwaiter().GetResult();
results[i] = DataValueConverter.FromVtq(vtq);
results[i] = CreatePublishedDataValue(tagRef, vtq);
errors[i] = ServiceResult.Good;
}
catch (Exception ex)
@@ -446,7 +463,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
if (!int.TryParse(indexRange, out var index) || index < 0)
return false;
var currentValue = _mxAccessClient.ReadAsync(tagRef).GetAwaiter().GetResult().Value;
var currentValue = NormalizePublishedValue(tagRef, _mxAccessClient.ReadAsync(tagRef).GetAwaiter().GetResult().Value);
if (currentValue is not Array currentArray || currentArray.Rank != 1 || index >= currentArray.Length)
return false;
@@ -491,13 +508,47 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
if (!_tagToVariableNode.TryGetValue(tagRef, out var variable))
return;
var dataValue = DataValueConverter.FromVtq(Vtq.Good(value));
var dataValue = CreatePublishedDataValue(tagRef, Vtq.Good(value));
variable.Value = dataValue.Value;
variable.StatusCode = dataValue.StatusCode;
variable.Timestamp = dataValue.SourceTimestamp;
variable.ClearChangeMasks(SystemContext, false);
}
private DataValue CreatePublishedDataValue(string tagRef, Vtq vtq)
{
var normalizedValue = NormalizePublishedValue(tagRef, vtq.Value);
if (ReferenceEquals(normalizedValue, vtq.Value))
return DataValueConverter.FromVtq(vtq);
return DataValueConverter.FromVtq(new Vtq(normalizedValue, vtq.Timestamp, vtq.Quality));
}
private object? NormalizePublishedValue(string tagRef, object? value)
{
if (value != null)
return value;
if (!_tagMetadata.TryGetValue(tagRef, out var metadata) || !metadata.IsArray || !metadata.ArrayDimension.HasValue)
return null;
return CreateDefaultArrayValue(metadata);
}
private static Array CreateDefaultArrayValue(TagMetadata metadata)
{
var elementType = MxDataTypeMapper.MapToClrType(metadata.MxDataType);
var values = Array.CreateInstance(elementType, metadata.ArrayDimension!.Value);
if (elementType == typeof(string))
{
for (int i = 0; i < values.Length; i++)
values.SetValue(string.Empty, i);
}
return values;
}
#endregion
#region Subscription Delivery
@@ -588,7 +639,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
{
try
{
var dataValue = DataValueConverter.FromVtq(vtq);
var dataValue = CreatePublishedDataValue(address, vtq);
variable.Value = dataValue.Value;
variable.StatusCode = dataValue.StatusCode;
variable.Timestamp = dataValue.SourceTimestamp;