75 lines
3.6 KiB
C#
75 lines
3.6 KiB
C#
using System.Runtime.CompilerServices;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// Transport-neutral <see cref="IAbCipTagEnumerator"/> that walks a Logix controller's symbol table
|
|
/// by reading the <c>@tags</c> listing through an injected reader + decoding the CIP Symbol Object
|
|
/// bytes with <see cref="CipSymbolObjectDecoder"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>The reader returns the raw Symbol Object bytes for a listing name (libplctag's
|
|
/// <c>Tag.GetBuffer()</c> for <c>@tags</c>, or the native paged Instance Attribute List). The decoder walks the concatenated entries + emits
|
|
/// <see cref="AbCipDiscoveredTag"/> records matching our driver surface.</para>
|
|
///
|
|
/// <para><see cref="EmptyAbCipTagEnumerator"/> remains available for tests that don't want
|
|
/// to touch the native library; the production factory default
|
|
/// (<see cref="LibplctagTagEnumeratorFactory"/>) wires this implementation in.</para>
|
|
/// </remarks>
|
|
internal sealed class SymbolListingEnumerator : IAbCipTagEnumerator
|
|
{
|
|
private readonly Func<AbCipTagCreateParams, string, CancellationToken, Task<byte[]>> _readListing;
|
|
|
|
/// <summary>Enumerator over a transport-specific listing reader.</summary>
|
|
/// <param name="readListing">Reads one symbol listing (<c>@tags</c> or <c>Program:X.@tags</c>) and
|
|
/// returns its raw Symbol Object bytes.</param>
|
|
public SymbolListingEnumerator(Func<AbCipTagCreateParams, string, CancellationToken, Task<byte[]>> readListing)
|
|
=> _readListing = readListing;
|
|
|
|
/// <inheritdoc />
|
|
/// <remarks>
|
|
/// A controller's <c>@tags</c> listing holds controller-scope tags plus one system entry per
|
|
/// program (<c>Program:UN01_Press</c>); the program's own tags are listed only by
|
|
/// <c>Program:<name>.@tags</c>. So the controller listing is read first, then each program's
|
|
/// listing, decoded with that program as the scope. A program whose listing cannot be read is
|
|
/// skipped — browse is best-effort and one unreadable program must not cost the others.
|
|
/// </remarks>
|
|
public async IAsyncEnumerable<AbCipDiscoveredTag> EnumerateAsync(
|
|
AbCipTagCreateParams deviceParams,
|
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
|
{
|
|
var buffer = await _readListing(deviceParams, "@tags", cancellationToken).ConfigureAwait(false);
|
|
|
|
// The decoder already surfaces each struct tag's CIP template instance id on
|
|
// AbCipDiscoveredTag.TemplateInstanceId (from the lower 12 bits of the symbol type), so
|
|
// re-yielding the decoded records as-is carries it through to DiscoverAsync's UDT fan-out.
|
|
var programs = new List<string>();
|
|
foreach (var tag in CipSymbolObjectDecoder.Decode(buffer))
|
|
{
|
|
if (CipSymbolObjectDecoder.ProgramNameOf(tag) is { } program) programs.Add(program);
|
|
yield return tag;
|
|
}
|
|
|
|
foreach (var program in programs)
|
|
{
|
|
byte[] programBuffer;
|
|
try
|
|
{
|
|
programBuffer = await _readListing(deviceParams, $"Program:{program}.@tags", cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException) { throw; }
|
|
catch (Exception)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var tag in CipSymbolObjectDecoder.Decode(programBuffer, programScope: program))
|
|
yield return tag;
|
|
}
|
|
}
|
|
|
|
/// <summary>Nothing to release — the reader owns any per-listing resources.</summary>
|
|
public void Dispose() { }
|
|
}
|