docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -46,6 +46,9 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
private readonly Task _dispatchLoop;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdsTwinCATClient"/> class.
|
||||
/// </summary>
|
||||
public AdsTwinCATClient()
|
||||
{
|
||||
_client.AdsNotificationEx += OnAdsNotificationEx;
|
||||
@@ -54,8 +57,14 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
|
||||
private readonly record struct PendingNotification(NotificationRegistration Registration, object? Value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the client is connected.
|
||||
/// </summary>
|
||||
public bool IsConnected => _client.IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the symbol version changes on the connected ADS target.
|
||||
/// </summary>
|
||||
public event EventHandler? OnSymbolVersionChanged;
|
||||
|
||||
/// <summary>Raise <see cref="OnSymbolVersionChanged"/> when <paramref name="adsError"/> is <c>DeviceSymbolVersionInvalid</c> (1809 / 0x0711).</summary>
|
||||
@@ -66,6 +75,13 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
return TwinCATStatusMapper.MapAdsError(adsError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the specified ADS target asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="address">The AMS address to connect to.</param>
|
||||
/// <param name="timeout">The connection timeout.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous connection operation.</returns>
|
||||
public Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_client.IsConnected) return Task.CompletedTask;
|
||||
@@ -75,6 +91,14 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value from the specified symbol path asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="symbolPath">The ADS symbol path to read from.</param>
|
||||
/// <param name="type">The TwinCAT data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for BOOL values within larger containers.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A tuple containing the value and OPC UA status code.</returns>
|
||||
public async Task<(object? value, uint status)> ReadValueAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
@@ -123,6 +147,15 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
: symbolPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to the specified symbol path asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="symbolPath">The ADS symbol path to write to.</param>
|
||||
/// <param name="type">The TwinCAT data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for BOOL values (not supported for writes).</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The OPC UA status code of the write operation.</returns>
|
||||
public async Task<uint> WriteValueAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
@@ -149,6 +182,11 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Probes the connection to verify the ADS target is reachable.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>True if the probe succeeds; otherwise false.</returns>
|
||||
public async Task<bool> ProbeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -162,6 +200,17 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a notification (subscription) for changes to the specified symbol.
|
||||
/// </summary>
|
||||
/// <param name="symbolPath">The ADS symbol path to monitor.</param>
|
||||
/// <param name="type">The TwinCAT data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for BOOL values.</param>
|
||||
/// <param name="cycleTime">The minimum cycle time between notifications.</param>
|
||||
/// <param name="maxDelayMs">The maximum delay before delivering a batched notification.</param>
|
||||
/// <param name="onChange">The callback to invoke when the value changes.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A handle to manage the notification subscription.</returns>
|
||||
public async Task<ITwinCATNotificationHandle> AddNotificationAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
@@ -228,6 +277,12 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a notification subscription by handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">The notification handle to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous delete operation.</returns>
|
||||
internal async Task DeleteNotificationAsync(uint handle, CancellationToken cancellationToken)
|
||||
{
|
||||
_notifications.TryRemove(handle, out _);
|
||||
@@ -235,6 +290,11 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
catch { /* best-effort tear-down; target may already be gone */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Browses all available symbols on the connected ADS target asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An async enumerable of discovered symbols.</returns>
|
||||
public async IAsyncEnumerable<TwinCATDiscoveredSymbol> BrowseSymbolsAsync(
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -299,6 +359,9 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the client and releases all resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
|
||||
@@ -323,11 +386,29 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
AdsTwinCATClient owner,
|
||||
uint handle) : ITwinCATNotificationHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the symbol path being monitored.
|
||||
/// </summary>
|
||||
public string SymbolPath { get; } = symbolPath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TwinCAT data type of the symbol.
|
||||
/// </summary>
|
||||
public TwinCATDataType Type { get; } = type;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the optional bit index for BOOL values.
|
||||
/// </summary>
|
||||
public int? BitIndex { get; } = bitIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the callback to invoke when the value changes.
|
||||
/// </summary>
|
||||
public Action<string, object?> OnChange { get; } = onChange;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the notification subscription.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Fire-and-forget AMS call — caller has already committed to the tear-down.
|
||||
@@ -388,5 +469,9 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
/// <summary>Default <see cref="ITwinCATClientFactory"/> — one <see cref="AdsTwinCATClient"/> per call.</summary>
|
||||
internal sealed class AdsTwinCATClientFactory : ITwinCATClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AdsTwinCATClient"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="AdsTwinCATClient"/> instance.</returns>
|
||||
public ITwinCATClient Create() => new AdsTwinCATClient();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||
public interface ITwinCATClient : IDisposable
|
||||
{
|
||||
/// <summary>Establish the AMS connection. Idempotent — subsequent calls are no-ops when already connected.</summary>
|
||||
/// <param name="address">The target AMS address.</param>
|
||||
/// <param name="timeout">The connection timeout.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the connection attempt.</param>
|
||||
Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>True when the AMS router + target both accept commands.</summary>
|
||||
@@ -34,6 +37,10 @@ public interface ITwinCATClient : IDisposable
|
||||
/// <paramref name="type"/>, or <c>null</c> when the read produced no data; the
|
||||
/// <c>status</c> tuple member carries the mapped OPC UA status (0 = Good).
|
||||
/// </summary>
|
||||
/// <param name="symbolPath">The ADS symbol path.</param>
|
||||
/// <param name="type">The target data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for bit extraction within a word.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
Task<(object? value, uint status)> ReadValueAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
@@ -44,6 +51,11 @@ public interface ITwinCATClient : IDisposable
|
||||
/// Write a symbolic value. Returns the mapped OPC UA status for the operation
|
||||
/// (0 = Good, non-zero = error mapped via <see cref="TwinCATStatusMapper"/>).
|
||||
/// </summary>
|
||||
/// <param name="symbolPath">The ADS symbol path.</param>
|
||||
/// <param name="type">The data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for bit manipulation within a word.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the write operation.</param>
|
||||
Task<uint> WriteValueAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
@@ -55,6 +67,7 @@ public interface ITwinCATClient : IDisposable
|
||||
/// Cheap health probe — returns <c>true</c> when the target's AMS state is reachable.
|
||||
/// Used by <see cref="Core.Abstractions.IHostConnectivityProbe"/>'s probe loop.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the probe operation.</param>
|
||||
Task<bool> ProbeAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -88,6 +101,7 @@ public interface ITwinCATClient : IDisposable
|
||||
/// UDT / function-block typed symbols surface with <c>DataType = null</c> so callers can
|
||||
/// decide whether to drill in via their own walker.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the enumeration operation.</param>
|
||||
IAsyncEnumerable<TwinCATDiscoveredSymbol> BrowseSymbolsAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -110,5 +124,6 @@ public sealed record TwinCATDiscoveredSymbol(
|
||||
/// <summary>Factory for <see cref="ITwinCATClient"/>s. One client per device.</summary>
|
||||
public interface ITwinCATClientFactory
|
||||
{
|
||||
/// <summary>Creates a new TwinCAT client instance.</summary>
|
||||
ITwinCATClient Create();
|
||||
}
|
||||
|
||||
@@ -22,10 +22,14 @@ public sealed record TwinCATAmsAddress(string NetId, int Port)
|
||||
/// <summary>Default AMS port — TC3 PLC runtime 1.</summary>
|
||||
public const int DefaultPlcPort = 851;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Port == DefaultPlcPort
|
||||
? $"ads://{NetId}"
|
||||
: $"ads://{NetId}:{Port}";
|
||||
|
||||
/// <summary>Parses a TwinCAT AMS address from a string.</summary>
|
||||
/// <param name="value">The address string to parse.</param>
|
||||
/// <returns>The parsed AMS address, or null if parsing fails.</returns>
|
||||
public static TwinCATAmsAddress? TryParse(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
|
||||
@@ -35,8 +35,12 @@ public enum TwinCATDataType
|
||||
Structure,
|
||||
}
|
||||
|
||||
/// <summary>Extension methods for TwinCATDataType.</summary>
|
||||
public static class TwinCATDataTypeExtensions
|
||||
{
|
||||
/// <summary>Maps a TwinCAT data type to the equivalent driver data type.</summary>
|
||||
/// <param name="t">The TwinCAT data type to convert.</param>
|
||||
/// <returns>The corresponding driver data type.</returns>
|
||||
public static DriverDataType ToDriverDataType(this TwinCATDataType t) => t switch
|
||||
{
|
||||
TwinCATDataType.Bool => DriverDataType.Boolean,
|
||||
|
||||
@@ -29,10 +29,18 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private DriverHealth _health = new(DriverState.Unknown, null, null);
|
||||
|
||||
/// <summary>Occurs when a subscribed tag value changes.</summary>
|
||||
public event EventHandler<DataChangeEventArgs>? OnDataChange;
|
||||
/// <summary>Occurs when a device host connectivity status changes.</summary>
|
||||
public event EventHandler<HostStatusChangedEventArgs>? OnHostStatusChanged;
|
||||
/// <summary>Occurs when the Galaxy object hierarchy or TwinCAT symbol table is rediscovered.</summary>
|
||||
public event EventHandler<RediscoveryEventArgs>? OnRediscoveryNeeded;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="TwinCATDriver"/> class.</summary>
|
||||
/// <param name="options">Driver configuration options.</param>
|
||||
/// <param name="driverInstanceId">Unique driver instance identifier.</param>
|
||||
/// <param name="clientFactory">Optional ADS client factory; defaults to <see cref="AdsTwinCATClientFactory"/>.</param>
|
||||
/// <param name="logger">Optional logger; defaults to <see cref="NullLogger{TwinCATDriver}"/>.</param>
|
||||
public TwinCATDriver(TwinCATDriverOptions options, string driverInstanceId,
|
||||
ITwinCATClientFactory? clientFactory = null,
|
||||
ILogger<TwinCATDriver>? logger = null)
|
||||
@@ -48,9 +56,15 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot)));
|
||||
}
|
||||
|
||||
/// <summary>Gets the unique driver instance identifier.</summary>
|
||||
public string DriverInstanceId => _driverInstanceId;
|
||||
/// <summary>Gets the driver type name.</summary>
|
||||
public string DriverType => "TwinCAT";
|
||||
|
||||
/// <summary>Initializes the driver with configuration and establishes device connections.</summary>
|
||||
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
_health = new DriverHealth(DriverState.Initializing, null, null);
|
||||
@@ -95,12 +109,19 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Reinitializes the driver by shutting down and reinitializing with new configuration.</summary>
|
||||
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
await ShutdownAsync(cancellationToken).ConfigureAwait(false);
|
||||
await InitializeAsync(driverConfigJson, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Shuts down the driver and releases all device connections and subscriptions.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public async Task ShutdownAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Native subs first — disposing the handles is cheap + lets the client close its
|
||||
@@ -133,6 +154,8 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
|
||||
}
|
||||
|
||||
/// <summary>Gets the current driver health status.</summary>
|
||||
/// <returns>Driver health information.</returns>
|
||||
public DriverHealth GetHealth() => _health;
|
||||
|
||||
/// <summary>
|
||||
@@ -150,14 +173,24 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
/// every <see cref="DiscoverAsync"/> call. This is a no-op but is deliberately present
|
||||
/// so Core's cache-budget enforcement sees a compliant Tier-A driver.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <summary>Gets the count of configured devices.</summary>
|
||||
internal int DeviceCount => _devices.Count;
|
||||
/// <summary>Gets the device state for the specified host address.</summary>
|
||||
/// <param name="hostAddress">The ADS host address.</param>
|
||||
/// <returns>Device state or null if not found.</returns>
|
||||
internal DeviceState? GetDeviceState(string hostAddress) =>
|
||||
_devices.TryGetValue(hostAddress, out var s) ? s : null;
|
||||
|
||||
// ---- IReadable ----
|
||||
|
||||
/// <summary>Reads values for the specified tag references from ADS devices.</summary>
|
||||
/// <param name="fullReferences">The full tag references to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Data value snapshots for each reference.</returns>
|
||||
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
|
||||
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -212,6 +245,10 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
|
||||
// ---- IWritable ----
|
||||
|
||||
/// <summary>Writes values to the specified tags on ADS devices.</summary>
|
||||
/// <param name="writes">The write requests to execute.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Write results for each request.</returns>
|
||||
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -272,6 +309,10 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
|
||||
// ---- ITagDiscovery ----
|
||||
|
||||
/// <summary>Discovers devices and tags from ADS configuration and optionally controller symbols.</summary>
|
||||
/// <param name="builder">Address space builder for adding discovered nodes.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public async Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
@@ -352,6 +393,10 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
/// target's PLC runtime — the PLC pushes changes on its own cycle so we skip the poll
|
||||
/// loop entirely. Unsub path disposes the handles.
|
||||
/// </summary>
|
||||
/// <param name="fullReferences">The full tag references to subscribe to.</param>
|
||||
/// <param name="publishingInterval">The publishing interval for updates.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Subscription handle for managing the subscription.</returns>
|
||||
public async Task<ISubscriptionHandle> SubscribeAsync(
|
||||
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -402,6 +447,10 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from a native or poll-based subscription.</summary>
|
||||
/// <param name="handle">The subscription handle to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completion task.</returns>
|
||||
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
|
||||
{
|
||||
if (handle is NativeSubscriptionHandle native && _nativeSubs.TryRemove(native.Id, out var sub))
|
||||
@@ -415,6 +464,7 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
|
||||
private sealed record NativeSubscriptionHandle(long Id) : ISubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets the diagnostic identifier for the subscription.</summary>
|
||||
public string DiagnosticId => $"twincat-native-sub-{Id}";
|
||||
}
|
||||
|
||||
@@ -424,6 +474,8 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
|
||||
// ---- IHostConnectivityProbe ----
|
||||
|
||||
/// <summary>Gets the connectivity status for all configured devices.</summary>
|
||||
/// <returns>List of host connectivity statuses.</returns>
|
||||
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses() =>
|
||||
[.. _devices.Values.Select(s => new HostConnectivityStatus(s.Options.HostAddress, s.HostState, s.HostStateChangedUtc))];
|
||||
|
||||
@@ -484,6 +536,9 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
/// </summary>
|
||||
public const string UnresolvedHostSentinel = "";
|
||||
|
||||
/// <summary>Resolves the device host address for the specified tag reference.</summary>
|
||||
/// <param name="fullReference">The full tag reference.</param>
|
||||
/// <returns>The host address or <see cref="UnresolvedHostSentinel"/> if not found.</returns>
|
||||
public string ResolveHost(string fullReference)
|
||||
{
|
||||
if (_tagsByName.TryGetValue(fullReference, out var def))
|
||||
@@ -573,6 +628,7 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
/// cancel tokens, wait on task handles with a hard timeout, dispose clients — so a
|
||||
/// synchronous path does the right thing without re-entering the scheduler.
|
||||
/// </summary>
|
||||
/// <summary>Synchronously disposes driver resources without awaiting async operations.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose native subscriptions first — handle disposal is sync.
|
||||
@@ -602,32 +658,43 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
||||
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
|
||||
}
|
||||
|
||||
/// <summary>Asynchronously disposes driver resources.</summary>
|
||||
/// <returns>Completion task.</returns>
|
||||
public async ValueTask DisposeAsync() => await ShutdownAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
internal sealed class DeviceState(TwinCATAmsAddress parsedAddress, TwinCATDeviceOptions options)
|
||||
{
|
||||
/// <summary>Gets the parsed AMS address for the device.</summary>
|
||||
public TwinCATAmsAddress ParsedAddress { get; } = parsedAddress;
|
||||
/// <summary>Gets the device configuration options.</summary>
|
||||
public TwinCATDeviceOptions Options { get; } = options;
|
||||
/// <summary>Gets or sets the active ADS client for this device.</summary>
|
||||
public ITwinCATClient? Client { get; set; }
|
||||
|
||||
/// <summary>Serializes connect / reconnect so concurrent callers never race a client
|
||||
/// create-or-dispose for this device (Driver.TwinCAT-007).</summary>
|
||||
public SemaphoreSlim ConnectGate { get; } = new(1, 1);
|
||||
|
||||
/// <summary>Gets the lock object for synchronizing host state transitions.</summary>
|
||||
public object ProbeLock { get; } = new();
|
||||
/// <summary>Gets or sets the current host connectivity state.</summary>
|
||||
public HostState HostState { get; set; } = HostState.Unknown;
|
||||
/// <summary>Gets or sets the UTC timestamp of the last host state change.</summary>
|
||||
public DateTime HostStateChangedUtc { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>Gets or sets the cancellation token source for the probe loop.</summary>
|
||||
public CancellationTokenSource? ProbeCts { get; set; }
|
||||
/// <summary>The running probe-loop task — awaited by <see cref="TwinCATDriver.ShutdownAsync"/>
|
||||
/// so the loop cannot touch a disposed client (Driver.TwinCAT-009).</summary>
|
||||
public Task? ProbeTask { get; set; }
|
||||
|
||||
/// <summary>Disposes the active ADS client if any.</summary>
|
||||
public void DisposeClient()
|
||||
{
|
||||
Client?.Dispose();
|
||||
Client = null;
|
||||
}
|
||||
|
||||
/// <summary>Disposes the connection gate semaphore.</summary>
|
||||
public void DisposeGate() => ConnectGate.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,17 @@ public static class TwinCATDriverFactoryExtensions
|
||||
{
|
||||
public const string DriverTypeName = "TwinCAT";
|
||||
|
||||
/// <summary>Registers the TwinCAT driver factory with the provided registry.</summary>
|
||||
/// <param name="registry">The driver factory registry to register with.</param>
|
||||
public static void Register(DriverFactoryRegistry registry)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
registry.Register(DriverTypeName, CreateInstance);
|
||||
}
|
||||
|
||||
/// <summary>Creates a TwinCAT driver instance from the provided configuration.</summary>
|
||||
/// <param name="driverInstanceId">The driver instance identifier.</param>
|
||||
/// <param name="driverConfigJson">The driver configuration as JSON.</param>
|
||||
internal static TwinCATDriver CreateInstance(string driverInstanceId, string driverConfigJson)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
|
||||
@@ -32,6 +37,8 @@ public static class TwinCATDriverFactoryExtensions
|
||||
/// <see cref="TwinCATDriver.InitializeAsync"/> / <see cref="TwinCATDriver.ReinitializeAsync"/>
|
||||
/// so a config generation pushed via Reinitialize is actually applied (Driver.TwinCAT-001).
|
||||
/// </summary>
|
||||
/// <param name="driverConfigJson">The JSON configuration string.</param>
|
||||
/// <param name="driverInstanceId">The driver instance identifier.</param>
|
||||
internal static TwinCATDriverOptions ParseOptions(string driverConfigJson, string driverInstanceId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
|
||||
@@ -70,6 +77,8 @@ public static class TwinCATDriverFactoryExtensions
|
||||
/// fields like <c>NotificationMaxDelayMs</c> and <c>Structure</c>-tag rejection are
|
||||
/// honored end-to-end.
|
||||
/// </summary>
|
||||
/// <param name="driverConfigJson">The JSON configuration string.</param>
|
||||
/// <param name="driverInstanceId">The driver instance identifier.</param>
|
||||
public static TwinCATDriverOptions ParseOptionsForTests(string driverConfigJson, string driverInstanceId)
|
||||
=> ParseOptions(driverConfigJson, driverInstanceId);
|
||||
|
||||
@@ -124,35 +133,67 @@ public static class TwinCATDriverFactoryExtensions
|
||||
|
||||
internal sealed class TwinCATDriverConfigDto
|
||||
{
|
||||
/// <summary>Gets or sets the timeout in milliseconds.</summary>
|
||||
public int? TimeoutMs { get; init; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether to use native notifications.</summary>
|
||||
public bool? UseNativeNotifications { get; init; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether to enable controller browsing.</summary>
|
||||
public bool? EnableControllerBrowse { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the maximum notification delay in milliseconds.</summary>
|
||||
public int? NotificationMaxDelayMs { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the list of configured devices.</summary>
|
||||
public List<TwinCATDeviceDto>? Devices { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the list of configured tags.</summary>
|
||||
public List<TwinCATTagDto>? Tags { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the probe configuration.</summary>
|
||||
public TwinCATProbeDto? Probe { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class TwinCATDeviceDto
|
||||
{
|
||||
/// <summary>Gets or sets the host address.</summary>
|
||||
public string? HostAddress { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the device name.</summary>
|
||||
public string? DeviceName { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class TwinCATTagDto
|
||||
{
|
||||
/// <summary>Gets or sets the tag name.</summary>
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the device host address.</summary>
|
||||
public string? DeviceHostAddress { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the symbol path.</summary>
|
||||
public string? SymbolPath { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the data type.</summary>
|
||||
public string? DataType { get; init; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the tag is writable.</summary>
|
||||
public bool? Writable { get; init; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether writes are idempotent.</summary>
|
||||
public bool? WriteIdempotent { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class TwinCATProbeDto
|
||||
{
|
||||
/// <summary>Gets or sets a value indicating whether probing is enabled.</summary>
|
||||
public bool? Enabled { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the probe interval in milliseconds.</summary>
|
||||
public int? IntervalMs { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the probe timeout in milliseconds.</summary>
|
||||
public int? TimeoutMs { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,13 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||
/// </summary>
|
||||
public sealed class TwinCATDriverOptions
|
||||
{
|
||||
/// <summary>Gets the list of TwinCAT devices to connect to.</summary>
|
||||
public IReadOnlyList<TwinCATDeviceOptions> Devices { get; init; } = [];
|
||||
/// <summary>Gets the list of TwinCAT tag definitions.</summary>
|
||||
public IReadOnlyList<TwinCATTagDefinition> Tags { get; init; } = [];
|
||||
/// <summary>Gets the probe options for TwinCAT connection probing.</summary>
|
||||
public TwinCATProbeOptions Probe { get; init; } = new();
|
||||
/// <summary>Gets the default communication timeout.</summary>
|
||||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
@@ -64,9 +68,13 @@ public sealed record TwinCATTagDefinition(
|
||||
bool Writable = true,
|
||||
bool WriteIdempotent = false);
|
||||
|
||||
/// <summary>Probe options for TwinCAT connection monitoring.</summary>
|
||||
public sealed class TwinCATProbeOptions
|
||||
{
|
||||
/// <summary>Gets a value indicating whether probing is enabled.</summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
/// <summary>Gets the probe interval.</summary>
|
||||
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
|
||||
/// <summary>Gets the probe timeout.</summary>
|
||||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ public static class TwinCATStatusMapper
|
||||
public const uint AdsSymbolVersionChanged = 1809u; // DeviceSymbolVersionInvalid = 0x0711
|
||||
|
||||
/// <summary>True when <paramref name="adsError"/> is the symbol-version-changed code.</summary>
|
||||
/// <param name="adsError">The ADS error code to check.</param>
|
||||
/// <returns>True if the error code indicates symbol version changed.</returns>
|
||||
public static bool IsSymbolVersionChanged(uint adsError) => adsError == AdsSymbolVersionChanged;
|
||||
|
||||
/// <summary>
|
||||
@@ -47,6 +49,8 @@ public static class TwinCATStatusMapper
|
||||
/// StatusCode. 0 = success. Device-layer codes (0x0700–0x073F) cover the operations a
|
||||
/// driver actually encounters during normal runtime.
|
||||
/// </summary>
|
||||
/// <param name="adsError">The ADS error code to map.</param>
|
||||
/// <returns>The corresponding OPC UA status code.</returns>
|
||||
public static uint MapAdsError(uint adsError) => adsError switch
|
||||
{
|
||||
0 => Good,
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed record TwinCATSymbolPath(
|
||||
IReadOnlyList<TwinCATSymbolSegment> Segments,
|
||||
int? BitIndex)
|
||||
{
|
||||
/// <summary>Converts this path to an ADS symbol name.</summary>
|
||||
/// <returns>The ADS symbol name string.</returns>
|
||||
public string ToAdsSymbolName()
|
||||
{
|
||||
var buf = new System.Text.StringBuilder();
|
||||
@@ -31,6 +33,9 @@ public sealed record TwinCATSymbolPath(
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Attempts to parse a TwinCAT symbol path from the given string.</summary>
|
||||
/// <param name="value">The path string to parse.</param>
|
||||
/// <returns>A TwinCATSymbolPath if parsing succeeds; otherwise null.</returns>
|
||||
public static TwinCATSymbolPath? TryParse(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||
/// </summary>
|
||||
public static class TwinCATSystemSymbolFilter
|
||||
{
|
||||
/// <summary><c>true</c> when the symbol path matches a known system / infrastructure prefix.</summary>
|
||||
/// <summary>Gets a value indicating whether a symbol is a system or infrastructure symbol.</summary>
|
||||
/// <param name="instancePath">The symbol instance path to check.</param>
|
||||
public static bool IsSystemSymbol(string instancePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instancePath)) return true;
|
||||
|
||||
Reference in New Issue
Block a user