Auto: abcip-2.3 — descriptions to OPC UA Description

Threads tag/UDT-member descriptions captured by the L5K (#346) and L5X
(#347) parsers through AbCipTagDefinition + AbCipStructureMember into
DriverAttributeInfo, so the address-space builder sets the OPC UA
Description attribute on each Variable node. L5kMember and L5xParser
also now capture per-member descriptions (via the (Description := "...")
attribute block on L5K and the <Description> child on L5X), and
L5kIngest forwards them. DriverNodeManager surfaces
DriverAttributeInfo.Description as the Variable's Description property.

Description is added as a trailing optional parameter on
DriverAttributeInfo (default null) so every other driver continues
to construct the record unchanged.

Closes #231
This commit is contained in:
Joseph Doherty
2026-04-25 18:23:31 -04:00
parent e5b192fcb3
commit e5299cda5a
8 changed files with 248 additions and 11 deletions

View File

@@ -55,7 +55,11 @@ public sealed class L5kIngest
var atomic = TryMapAtomic(m.DataType);
var memberType = atomic ?? AbCipDataType.Structure;
var writable = !IsReadOnly(m.ExternalAccess) && !IsAccessNone(m.ExternalAccess);
members.Add(new AbCipStructureMember(m.Name, memberType, writable));
members.Add(new AbCipStructureMember(
Name: m.Name,
DataType: memberType,
Writable: writable,
Description: m.Description));
}
udtIndex[dt.Name] = members;
}
@@ -101,7 +105,8 @@ public sealed class L5kIngest
TagPath: tagPath,
DataType: dataType,
Writable: writable,
Members: members));
Members: members,
Description: t.Description));
}
return new L5kIngestResult(tags, skippedAliases, skippedNoAccess);

View File

@@ -311,7 +311,8 @@ public static class L5kParser
if (typePart.Length == 0) return null;
var externalAccess = attributes.TryGetValue("ExternalAccess", out var ea) ? ea.Trim() : null;
return new L5kMember(name, typePart, arrayDim, externalAccess);
var description = attributes.TryGetValue("Description", out var d) ? Unquote(d) : null;
return new L5kMember(name, typePart, arrayDim, externalAccess, description);
}
// ---- helpers -----------------------------------------------------------
@@ -377,4 +378,9 @@ public sealed record L5kTag(
public sealed record L5kDataType(string Name, IReadOnlyList<L5kMember> Members);
/// <summary>One member line inside a UDT definition.</summary>
public sealed record L5kMember(string Name, string DataType, int? ArrayDim, string? ExternalAccess);
public sealed record L5kMember(
string Name,
string DataType,
int? ArrayDim,
string? ExternalAccess,
string? Description = null);

View File

@@ -163,11 +163,21 @@ public static class L5xParser
arrayDim = dim;
}
// Description child — same shape as on Tag nodes; sometimes wrapped in CDATA.
string? description = null;
var descNode = memberNode.SelectSingleNode("Description");
if (descNode is not null)
{
var raw = descNode.Value;
if (!string.IsNullOrEmpty(raw)) description = raw.Trim();
}
return new L5kMember(
Name: name,
DataType: dataType,
ArrayDim: arrayDim,
ExternalAccess: string.IsNullOrEmpty(externalAccess) ? null : externalAccess);
ExternalAccess: string.IsNullOrEmpty(externalAccess) ? null : externalAccess,
Description: description);
}
private static L5kDataType? ReadAddOnInstruction(XPathNavigator aoiNode)
@@ -200,11 +210,20 @@ public static class L5xParser
arrayDim = dim;
}
string? paramDescription = null;
var paramDescNode = paramNode.SelectSingleNode("Description");
if (paramDescNode is not null)
{
var raw = paramDescNode.Value;
if (!string.IsNullOrEmpty(raw)) paramDescription = raw.Trim();
}
members.Add(new L5kMember(
Name: paramName,
DataType: dataType,
ArrayDim: arrayDim,
ExternalAccess: string.IsNullOrEmpty(externalAccess) ? null : externalAccess));
ExternalAccess: string.IsNullOrEmpty(externalAccess) ? null : externalAccess,
Description: paramDescription));
}
return new L5kDataType(name, members);
}