@@ -2,6 +2,7 @@ using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using S7.Net;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.S7.SymbolImport;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.S7;
|
||||
|
||||
@@ -67,6 +68,14 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
private readonly Dictionary<string, S7TagDefinition> _tagsByName = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, S7ParsedAddress> _parsedByName = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// PR-S7-D2 — post-fan-out tag list, in declaration order. Mirrors
|
||||
/// <see cref="_tagsByName"/> as a list so <see cref="DiscoverAsync"/> can produce a
|
||||
/// stable browse-tree ordering. Always preserves the original declaration order;
|
||||
/// UDT tags are replaced in-place by their fanned-out leaf children.
|
||||
/// </summary>
|
||||
private readonly List<S7TagDefinition> _effectiveTags = new();
|
||||
|
||||
private readonly S7DriverOptions _options = options;
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
|
||||
@@ -141,8 +150,39 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
// story this lets the Admin UI's "Save" round-trip stay sub-second on bad input.
|
||||
_tagsByName.Clear();
|
||||
_parsedByName.Clear();
|
||||
_effectiveTags.Clear();
|
||||
foreach (var t in _options.Tags)
|
||||
{
|
||||
// PR-S7-D2 — UDT-typed tags are fanned out into N scalar leaf member tags
|
||||
// before any address parsing happens against the parent. Reads / writes /
|
||||
// subscribes never see the parent UDT tag; only its scalar children.
|
||||
if (!string.IsNullOrWhiteSpace(t.UdtName))
|
||||
{
|
||||
if (t.ElementCount is int udtElems && udtElems > 1)
|
||||
throw new FormatException(
|
||||
$"S7 tag '{t.Name}' is UDT-typed (UdtName='{t.UdtName}') and ElementCount > 1; " +
|
||||
"array-of-UDT must be expressed via array members inside the UDT layout, " +
|
||||
"not at the parent-tag level");
|
||||
|
||||
// Parse the parent-tag base address once so the fan-out can compute
|
||||
// member byte offsets relative to ByteOffset. Parser uses CpuType for
|
||||
// V-memory mapping on S7-200 / LOGO families.
|
||||
var parentParsed = S7AddressParser.Parse(t.Address, _options.CpuType);
|
||||
var leaves = S7UdtFanOut.Expand(t, _options.Udts, parentParsed);
|
||||
foreach (var leaf in leaves)
|
||||
{
|
||||
var leafParsed = S7AddressParser.Parse(leaf.Address, _options.CpuType);
|
||||
if (_tagsByName.ContainsKey(leaf.Name))
|
||||
throw new InvalidOperationException(
|
||||
$"S7 tag '{leaf.Name}' (fanned out from UDT '{t.UdtName}' on tag '{t.Name}') " +
|
||||
"collides with an existing tag name");
|
||||
_tagsByName[leaf.Name] = leaf;
|
||||
_parsedByName[leaf.Name] = leafParsed;
|
||||
_effectiveTags.Add(leaf);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass CpuType so V-memory addresses (S7-200 / S7-200 Smart / LOGO!) resolve
|
||||
// against the device's family-specific DB mapping.
|
||||
var parsed = S7AddressParser.Parse(t.Address, _options.CpuType); // throws FormatException
|
||||
@@ -161,6 +201,7 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
}
|
||||
_tagsByName[t.Name] = t;
|
||||
_parsedByName[t.Name] = parsed;
|
||||
_effectiveTags.Add(t);
|
||||
}
|
||||
|
||||
var plc = BuildPlc();
|
||||
@@ -244,6 +285,9 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
// Reset the snapshot so a post-shutdown diagnostics read doesn't display a stale
|
||||
// PDU size from the previous connection. Reinit will repopulate after OpenAsync.
|
||||
_negotiatedPduSize = 0;
|
||||
// PR-S7-D2 — drop the post-fan-out tag list so a Reinit can rebuild it cleanly
|
||||
// without the previous run's UDT leaves leaking into the new tag map.
|
||||
_effectiveTags.Clear();
|
||||
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -1019,7 +1063,11 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
var folder = builder.Folder("S7", "S7");
|
||||
foreach (var t in _options.Tags)
|
||||
// PR-S7-D2 — iterate the post-fan-out effective tag list so UDT leaves surface as
|
||||
// browseable variables and the original parent UDT tag (which is no longer in the
|
||||
// read/write pipeline) never appears in the address space.
|
||||
var sourceTags = _effectiveTags.Count > 0 ? (IEnumerable<S7TagDefinition>)_effectiveTags : _options.Tags;
|
||||
foreach (var t in sourceTags)
|
||||
{
|
||||
var isArr = t.ElementCount is int ec && ec > 1;
|
||||
folder.Variable(t.Name, t.Name, new DriverAttributeInfo(
|
||||
|
||||
Reference in New Issue
Block a user