docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
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 41s
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>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
@@ -277,6 +277,6 @@ internal sealed class FocasAlarmProjection : IAsyncDisposable
|
||||
/// <summary>Handle returned by <see cref="FocasAlarmProjection.SubscribeAsync"/>.</summary>
|
||||
public sealed record FocasAlarmSubscriptionHandle(long Id) : IAlarmSubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets the diagnostic identifier for this subscription.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DiagnosticId => $"focas-alarm-sub-{Id}";
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
private readonly EquipmentTagRefResolver<FocasTagDefinition> _resolver;
|
||||
// Per-tag-name cache of the FocasAddress parsed once at InitializeAsync. ReadAsync /
|
||||
// WriteAsync look up the pre-parsed value instead of re-parsing tag.Address on every hot
|
||||
// call — resolves Driver.FOCAS-008. ConcurrentDictionary is required because the poll loop
|
||||
// call. ConcurrentDictionary is required because the poll loop
|
||||
// (ReadAsync) and the host write thread (WriteAsync) both mutate this cache when resolver-
|
||||
// produced equipment tags are encountered for the first time.
|
||||
private readonly ConcurrentDictionary<string, FocasAddress> _parsedAddressesByTagName =
|
||||
@@ -75,15 +75,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot)));
|
||||
}
|
||||
|
||||
/// <summary>Gets the driver instance identifier.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DriverInstanceId => _driverInstanceId;
|
||||
/// <summary>Gets the driver type name.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DriverType => "FOCAS";
|
||||
|
||||
/// <summary>Initializes the driver with configuration and prepares device connections and polling.</summary>
|
||||
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous initialization operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
Volatile.Write(ref _health, new DriverHealth(DriverState.Initializing, null, null));
|
||||
@@ -91,7 +88,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
{
|
||||
// Fail fast if the factory is a stub/unimplemented backend — the operator must
|
||||
// see an actionable error at init rather than a phantom-Healthy driver that fails
|
||||
// every read/write/subscribe silently (Driver.FOCAS-009).
|
||||
// every read/write/subscribe silently.
|
||||
_clientFactory.EnsureUsable();
|
||||
|
||||
foreach (var device in _options.Devices)
|
||||
@@ -122,7 +119,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
}
|
||||
_tagsByName[tag.Name] = tag;
|
||||
// Cache the parsed FocasAddress so ReadAsync / WriteAsync don't re-parse on every
|
||||
// hot-path call (Driver.FOCAS-008). The address string has already been validated
|
||||
// hot-path call. The address string has already been validated
|
||||
// by FocasAddress.TryParse above; reusing the parsed record avoids per-tick allocs
|
||||
// on subscription pollers.
|
||||
_parsedAddressesByTagName[tag.Name] = parsed;
|
||||
@@ -171,19 +168,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Reinitializes the driver by shutting down and restarting with new configuration.</summary>
|
||||
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous reinitialization operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
await ShutdownAsync(cancellationToken).ConfigureAwait(false);
|
||||
await InitializeAsync(driverConfigJson, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Shuts down the driver, cancelling all running operations and releasing resources.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous shutdown operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task ShutdownAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _poll.DisposeAsync().ConfigureAwait(false);
|
||||
@@ -195,8 +187,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
foreach (var state in _devices.Values)
|
||||
{
|
||||
// Cancel-then-dispose can race in tight shutdown loops; swallowing is intentional
|
||||
// but we now log the cause so a noisy shutdown leaves a Debug trace
|
||||
// (Driver.FOCAS-007).
|
||||
// but we now log the cause so a noisy shutdown leaves a Debug trace.
|
||||
try { state.ProbeCts?.Cancel(); }
|
||||
catch (Exception ex) { _logger.LogDebug(ex, "Cancelling probe CTS for {Host} failed", state.Options.HostAddress); }
|
||||
state.ProbeCts?.Dispose();
|
||||
@@ -218,13 +209,11 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
Volatile.Write(ref _health, new DriverHealth(DriverState.Unknown, Volatile.Read(ref _health).LastSuccessfulRead, null));
|
||||
}
|
||||
|
||||
/// <summary>Gets the current health status of the driver.</summary>
|
||||
/// <inheritdoc />
|
||||
public DriverHealth GetHealth() => Volatile.Read(ref _health);
|
||||
/// <summary>Gets the current memory footprint of the driver.</summary>
|
||||
/// <inheritdoc />
|
||||
public long GetMemoryFootprint() => 0;
|
||||
/// <summary>Flushes optional internal caches.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous cache flush operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <summary>Gets the number of configured devices.</summary>
|
||||
@@ -240,12 +229,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
/// <summary>Test seam — returns true when a parsed <see cref="FocasAddress"/> for the given
|
||||
/// reference is present in the address cache (both authored and equipment-tag paths).</summary>
|
||||
/// <param name="reference">The tag or equipment-tag reference to check.</param>
|
||||
/// <returns><see langword="true"/> if a parsed address is cached for the reference; otherwise <see langword="false"/>.</returns>
|
||||
internal bool IsParsedAddressCached(string reference) =>
|
||||
_parsedAddressesByTagName.ContainsKey(reference);
|
||||
|
||||
// Resolves a tag definition to its parsed FocasAddress, caching the result so that
|
||||
// equipment tags (resolver-produced, not seeded at InitializeAsync) don't re-parse the
|
||||
// address string on every ReadAsync / WriteAsync hot-path call (Driver.FOCAS-008).
|
||||
// address string on every ReadAsync / WriteAsync hot-path call.
|
||||
// Throwing inside the GetOrAdd factory propagates the exception to the caller and does
|
||||
// NOT store anything in the dictionary — consistent with the existing "fail fast on a
|
||||
// malformed address" behaviour from the init-time validation of authored tags.
|
||||
@@ -257,10 +248,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- IReadable ----
|
||||
|
||||
/// <summary>Reads values from one or more tags asynchronously.</summary>
|
||||
/// <param name="fullReferences">A read-only list of tag references to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous read operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
|
||||
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -329,10 +317,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- IWritable ----
|
||||
|
||||
/// <summary>Writes values to one or more tags asynchronously.</summary>
|
||||
/// <param name="writes">A read-only list of write requests.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous write operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -401,18 +386,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- ITagDiscovery ----
|
||||
|
||||
/// <summary>
|
||||
/// Retry-until-stable: the FixedTree subtree is filled in asynchronously by
|
||||
/// <see cref="FixedTreeLoopAsync"/> a couple of seconds AFTER connect, so the first
|
||||
/// post-connect <see cref="DiscoverAsync"/> pass would miss it — the host must re-run
|
||||
/// discovery until the captured node set is non-empty and stable.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
// Retry-until-stable: the FixedTree subtree is filled in asynchronously by
|
||||
// FixedTreeLoopAsync a couple of seconds AFTER connect, so the first post-connect
|
||||
// DiscoverAsync pass would miss it — the host must re-run discovery until the captured
|
||||
// node set is non-empty and stable.
|
||||
public DiscoveryRediscoverPolicy RediscoverPolicy => DiscoveryRediscoverPolicy.UntilStable;
|
||||
|
||||
/// <summary>Discovers tags and builds the OPC UA address space asynchronously.</summary>
|
||||
/// <param name="builder">The address space builder for constructing the OPC UA namespace.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous discovery operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
@@ -589,19 +570,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- ISubscribable (polling overlay via shared engine) ----
|
||||
|
||||
/// <summary>Subscribes to data changes on one or more tags.</summary>
|
||||
/// <param name="fullReferences">A read-only list of tag references to subscribe to.</param>
|
||||
/// <param name="publishingInterval">The interval at which to publish data changes.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous subscription operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<ISubscriptionHandle> SubscribeAsync(
|
||||
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(_poll.Subscribe(fullReferences, publishingInterval));
|
||||
|
||||
/// <summary>Unsubscribes from a previous subscription.</summary>
|
||||
/// <param name="handle">The subscription handle to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous unsubscription operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
|
||||
{
|
||||
_poll.Unsubscribe(handle);
|
||||
@@ -610,7 +584,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- IHostConnectivityProbe ----
|
||||
|
||||
/// <summary>Gets the connectivity status of all configured devices.</summary>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses() =>
|
||||
[.. _devices.Values.Select(s => new HostConnectivityStatus(s.Options.HostAddress, s.HostState, s.HostStateChangedUtc))];
|
||||
|
||||
@@ -623,7 +597,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
{
|
||||
var client = await EnsureConnectedAsync(state, ct).ConfigureAwait(false);
|
||||
// Apply Probe.Timeout so a hung CNC socket gets cancelled at the configured
|
||||
// budget rather than blocking until the OS TCP timeout (Driver.FOCAS-009).
|
||||
// budget rather than blocking until the OS TCP timeout.
|
||||
// TimeSpan.Zero / negative means "no per-probe timeout" — fall back to the loop
|
||||
// cancellation token unmodified.
|
||||
var probeTimeout = _options.Probe.Timeout;
|
||||
@@ -1048,10 +1022,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- IAlarmSource ----
|
||||
|
||||
/// <summary>Subscribes to alarm events from the driver.</summary>
|
||||
/// <param name="sourceNodeIds">A read-only list of source node IDs to subscribe to.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous subscription operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(
|
||||
IReadOnlyList<string> sourceNodeIds, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1061,17 +1032,11 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
return _alarmProjection.SubscribeAsync(sourceNodeIds, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from a previous alarm subscription.</summary>
|
||||
/// <param name="handle">The alarm subscription handle to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous unsubscription operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken) =>
|
||||
_alarmProjection is { } p ? p.UnsubscribeAsync(handle, cancellationToken) : Task.CompletedTask;
|
||||
|
||||
/// <summary>Acknowledges one or more alarms.</summary>
|
||||
/// <param name="acknowledgements">A read-only list of alarm acknowledgement requests.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the asynchronous acknowledgement operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task AcknowledgeAsync(
|
||||
IReadOnlyList<AlarmAcknowledgeRequest> acknowledgements, CancellationToken cancellationToken) =>
|
||||
_alarmProjection is { } p ? p.AcknowledgeAsync(acknowledgements, cancellationToken) : Task.CompletedTask;
|
||||
@@ -1113,9 +1078,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
|
||||
// ---- IPerCallHostResolver ----
|
||||
|
||||
/// <summary>Resolves the host address for a given tag reference.</summary>
|
||||
/// <param name="fullReference">The full reference of the tag.</param>
|
||||
/// <returns>The host address for the tag reference.</returns>
|
||||
/// <inheritdoc />
|
||||
public string ResolveHost(string fullReference)
|
||||
{
|
||||
if (_tagsByName.TryGetValue(fullReference, out var def))
|
||||
@@ -1159,6 +1122,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
/// <summary>Disposes the driver and releases all resources synchronously.</summary>
|
||||
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
/// <summary>Disposes the driver and releases all resources asynchronously.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync() => await ShutdownAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -205,6 +205,7 @@ public static class FocasDriverFactoryExtensions
|
||||
/// </summary>
|
||||
internal sealed class FlexibleStringConverter : JsonConverter<string?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
reader.TokenType switch
|
||||
{
|
||||
@@ -216,6 +217,7 @@ public static class FocasDriverFactoryExtensions
|
||||
_ => throw new JsonException($"Expected string, number, or null but got {reader.TokenType}."),
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null) writer.WriteNullValue();
|
||||
|
||||
@@ -20,6 +20,7 @@ public interface IFocasClient : IDisposable
|
||||
/// <param name="address">The CNC host address and port.</param>
|
||||
/// <param name="timeout">The connection timeout duration.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>True when the FWLIB handle is valid + the socket is up.</summary>
|
||||
@@ -33,6 +34,7 @@ public interface IFocasClient : IDisposable
|
||||
/// <param name="address">The CNC memory address to read from.</param>
|
||||
/// <param name="type">The FOCAS data type to read.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A tuple of the boxed value read and the mapped OPC UA status code.</returns>
|
||||
Task<(object? value, uint status)> ReadAsync(
|
||||
FocasAddress address,
|
||||
FocasDataType type,
|
||||
@@ -46,6 +48,7 @@ public interface IFocasClient : IDisposable
|
||||
/// <param name="type">The FOCAS data type to write.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The mapped OPC UA status code (0 = Good).</returns>
|
||||
Task<uint> WriteAsync(
|
||||
FocasAddress address,
|
||||
FocasDataType type,
|
||||
@@ -57,6 +60,7 @@ public interface IFocasClient : IDisposable
|
||||
/// responds with any valid status.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns><c>true</c> when the CNC responds with a valid status; otherwise <c>false</c>.</returns>
|
||||
Task<bool> ProbeAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -66,6 +70,7 @@ public interface IFocasClient : IDisposable
|
||||
/// emits transitions (raise / clear) on the driver's <c>OnAlarmEvent</c>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The list of currently active alarms; empty when none are active.</returns>
|
||||
Task<IReadOnlyList<FocasActiveAlarm>> ReadAlarmsAsync(CancellationToken cancellationToken);
|
||||
|
||||
// ---- Fixed-tree T1 (identity + axis discovery + fast-poll dynamic bundle) ----
|
||||
@@ -76,6 +81,7 @@ public interface IFocasClient : IDisposable
|
||||
/// values don't change across the session.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The CNC identity snapshot.</returns>
|
||||
Task<FocasSysInfo> GetSysInfoAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -84,6 +90,7 @@ public interface IFocasClient : IDisposable
|
||||
/// <see cref="ReadDynamicAsync"/> calls.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The CNC's configured axis names.</returns>
|
||||
Task<IReadOnlyList<FocasAxisName>> GetAxisNamesAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -91,6 +98,7 @@ public interface IFocasClient : IDisposable
|
||||
/// the <c>Spindle/{name}/</c> subtree.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The CNC's configured spindle names.</returns>
|
||||
Task<IReadOnlyList<FocasSpindleName>> GetSpindleNamesAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -101,6 +109,7 @@ public interface IFocasClient : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="axisIndex">The axis index to read dynamics for.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The fast-poll dynamic bundle for the requested axis.</returns>
|
||||
Task<FocasDynamicSnapshot> ReadDynamicAsync(int axisIndex, CancellationToken cancellationToken);
|
||||
|
||||
// ---- Fixed-tree T2 (program + operation mode) ----
|
||||
@@ -113,6 +122,7 @@ public interface IFocasClient : IDisposable
|
||||
/// on human-operator timescales.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The aggregate program + operation-mode snapshot.</returns>
|
||||
Task<FocasProgramInfo> GetProgramInfoAsync(CancellationToken cancellationToken);
|
||||
|
||||
// ---- Fixed-tree T3 (timers) ----
|
||||
@@ -124,6 +134,7 @@ public interface IFocasClient : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="kind">The timer kind to read.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The requested cumulative timer reading.</returns>
|
||||
Task<FocasTimer> GetTimerAsync(FocasTimerKind kind, CancellationToken cancellationToken);
|
||||
|
||||
// ---- Fixed-tree T3.5 (servo meters) ----
|
||||
@@ -134,6 +145,7 @@ public interface IFocasClient : IDisposable
|
||||
/// disconnected session or unsupported CNC.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The servo-load meter percentages across all configured axes.</returns>
|
||||
Task<IReadOnlyList<FocasServoLoad>> GetServoLoadsAsync(CancellationToken cancellationToken);
|
||||
|
||||
// ---- Fixed-tree T3.6 (spindle meters) ----
|
||||
@@ -145,6 +157,7 @@ public interface IFocasClient : IDisposable
|
||||
/// series like 16i may return EW_FUNC).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Per-spindle load percentages, indexed as per <see cref="GetSpindleNamesAsync"/>.</returns>
|
||||
Task<IReadOnlyList<int>> GetSpindleLoadsAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -152,6 +165,7 @@ public interface IFocasClient : IDisposable
|
||||
/// bootstrap. Index alignment as per <see cref="GetSpindleLoadsAsync"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Per-spindle maximum RPM values, indexed as per <see cref="GetSpindleLoadsAsync"/>.</returns>
|
||||
Task<IReadOnlyList<int>> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -161,6 +175,7 @@ public interface IFocasClient : IDisposable
|
||||
/// <c>PositionDecimalPlaces</c>. Values are clamped non-negative.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Per-axis position decimal-place figures; empty when the CNC/backend doesn't report them.</returns>
|
||||
Task<IReadOnlyList<int>> GetPositionFiguresAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -317,17 +332,10 @@ public sealed class UnimplementedFocasClientFactory : IFocasClientFactory
|
||||
"FOCAS driver backend is 'unimplemented'. Switch to 'Backend: \"wire\"' in driver config " +
|
||||
"once the CNC is provisioned — see docs/drivers/FOCAS.md.";
|
||||
|
||||
/// <summary>
|
||||
/// Config-time probe — throws immediately so <see cref="FocasDriver.InitializeAsync"/>
|
||||
/// faults the driver before any background loops start. This prevents the footgun where
|
||||
/// the driver appears Healthy at init but every read/write/subscribe fails.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Always thrown.</exception>
|
||||
/// <inheritdoc />
|
||||
public void EnsureUsable() => throw new NotSupportedException(Message);
|
||||
|
||||
/// <summary>Creates a new client instance (always throws NotSupportedException).</summary>
|
||||
/// <returns>Never returns; always throws NotSupportedException.</returns>
|
||||
/// <exception cref="NotSupportedException">Always thrown to indicate backend is not yet provisioned.</exception>
|
||||
/// <inheritdoc />
|
||||
public IFocasClient Create() => throw new NotSupportedException(Message);
|
||||
}
|
||||
|
||||
@@ -352,5 +360,5 @@ public static class FocasAlarmType
|
||||
public const short Servo = 4; // ALM_S
|
||||
public const short DataIo = 5; // ALM_T
|
||||
public const short MemoryCheck = 6; // ALM_M
|
||||
public const short MacroAlarm = 13; // ALM_MC — used by #3006 etc.
|
||||
public const short MacroAlarm = 13; // ALM_MC
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public sealed class SynchronizedFocasClient : IFocasClient
|
||||
public Task<IReadOnlyList<int>> GetPositionFiguresAsync(CancellationToken cancellationToken) =>
|
||||
RunBoundedAsync(ct => _inner.GetPositionFiguresAsync(ct), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Disposes the inner client and the call gate.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_inner.Dispose();
|
||||
|
||||
@@ -61,7 +61,7 @@ public static class FocasOperationModeExtensions
|
||||
/// <c>"EDIT"</c>). Delegates to <see cref="FocasOpMode.ToText"/> so the wire layer
|
||||
/// and the fixed-tree projection render identical labels — historically these two
|
||||
/// surfaces diverged ("TJOG" vs "T-JOG", "TEACH_IN_HANDLE" vs "TEACH-IN-HANDLE",
|
||||
/// and different unknown-code fallbacks). Resolved by Driver.FOCAS-010.
|
||||
/// and different unknown-code fallbacks).
|
||||
/// </summary>
|
||||
/// <param name="mode">The operation mode.</param>
|
||||
/// <returns>The operator-facing label.</returns>
|
||||
|
||||
@@ -70,6 +70,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="port">The FOCAS/2 TCP port (typically 8193).</param>
|
||||
/// <param name="timeoutSeconds">Connection timeout in seconds; zero or negative disables the timeout.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the connect operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task ConnectAsync(
|
||||
string host,
|
||||
int port,
|
||||
@@ -90,6 +91,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="port">The FOCAS/2 TCP port (typically 8193).</param>
|
||||
/// <param name="timeout">Connection timeout duration; <see cref="TimeSpan.Zero"/> disables the timeout.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the connect operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task ConnectAsync(
|
||||
string host,
|
||||
int port,
|
||||
@@ -183,6 +185,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// Async dispose — sends the close PDU when connected and tears down both sockets.
|
||||
/// Idempotent.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _lifetimeGate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
@@ -218,6 +221,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The CNC identity/sysinfo, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<WireSysInfo>> ReadSysInfoAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -233,6 +237,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The aggregated CNC status bits, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<WireStatus>> ReadStatusAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -272,6 +277,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The configured axis name records, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<IReadOnlyList<WireAxisRecord>>> ReadAxisNamesAsync(
|
||||
short maxCount = 32,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -288,6 +294,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The configured spindle name records, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<IReadOnlyList<WireSpindleRecord>>> ReadSpindleNamesAsync(
|
||||
short maxCount = 8,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -308,6 +315,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The fast-poll dynamic snapshot for the axis, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<WireDynamic>> ReadDynamic2Async(
|
||||
short axis = 1,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -353,6 +361,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The servo-meter load percentages, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<IReadOnlyList<WireServoMeter>>> ReadServoMeterAsync(
|
||||
short maxCount = 32,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -387,6 +396,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The per-axis position decimal-place figures, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<IReadOnlyList<int>>> ReadPositionFiguresAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -413,6 +423,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The per-spindle load percentages, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<IReadOnlyList<WireSpindleMetric>>> ReadSpindleLoadAsync(
|
||||
short spindleSelector = -1,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -425,6 +436,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The per-spindle maximum RPMs, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<IReadOnlyList<WireSpindleMetric>>> ReadSpindleMaxRpmAsync(
|
||||
short spindleSelector = -1,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -442,6 +454,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The raw parameter payload bytes, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<byte[]>> ReadParameterBytesAsync(
|
||||
short dataNumber,
|
||||
short axis = 0,
|
||||
@@ -461,6 +474,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<WireParameter>> ReadParameterAsync(
|
||||
short dataNumber,
|
||||
short type = 0,
|
||||
@@ -481,6 +495,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed 8-bit parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<byte>> ReadParameterByteAsync(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null)
|
||||
{
|
||||
var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false);
|
||||
@@ -495,6 +510,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed 16-bit parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<short>> ReadParameterInt16Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null)
|
||||
{
|
||||
var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false);
|
||||
@@ -509,6 +525,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed 32-bit parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<int>> ReadParameterInt32Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null)
|
||||
{
|
||||
var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false);
|
||||
@@ -523,6 +540,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed single-precision parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<float>> ReadParameterFloat32Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null)
|
||||
{
|
||||
var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false);
|
||||
@@ -537,6 +555,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The typed double-precision parameter value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<double>> ReadParameterFloat64Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null)
|
||||
{
|
||||
var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false);
|
||||
@@ -550,6 +569,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The macro variable value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<WireMacro>> ReadMacroAsync(
|
||||
short number,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -573,6 +593,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The decoded PMC range values, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<WirePmcRange>> ReadPmcRangeAsync(
|
||||
short area,
|
||||
short dataType,
|
||||
@@ -601,6 +622,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The decoded PMC range values, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<WirePmcRange>> ReadPmcRangeAsync(
|
||||
FocasPmcArea area,
|
||||
FocasPmcDataType dataType,
|
||||
@@ -621,6 +643,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The active alarm list, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public async Task<FocasResult<IReadOnlyList<WireAlarm>>> ReadAlarmsAsync(
|
||||
short type = -1,
|
||||
short count = 32,
|
||||
@@ -640,6 +663,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The current operation mode, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<FocasOperationMode>> ReadOperationModeAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -657,6 +681,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The raw operation mode code, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<short>> ReadOperationModeCodeAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -670,6 +695,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The executing program name and O-number, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<WireProgramName>> ReadExecutingProgramNameAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -680,6 +706,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The executed block count, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<int>> ReadBlockCountAsync(
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? timeout = null,
|
||||
@@ -697,6 +724,7 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <param name="timeout">Optional per-call timeout override.</param>
|
||||
/// <param name="pathId">Optional path ID override; defaults to <see cref="PathId"/>.</param>
|
||||
/// <returns>The cumulative timer value, wrapped in a <see cref="FocasResult{T}"/>.</returns>
|
||||
public Task<FocasResult<WireTimer>> ReadTimerAsync(
|
||||
short type,
|
||||
CancellationToken cancellationToken = default,
|
||||
@@ -895,6 +923,9 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// cutting-time payload <c>ac f2 10 00 90 a3 00 00</c> → minute=1110188, msec=41872. See
|
||||
/// <c>docs/plans/2026-06-25-focas-pdu-v3-30i-b-support.md</c>.
|
||||
/// </summary>
|
||||
/// <param name="type">Timer type selector, carried through into the returned <see cref="WireTimer"/>.</param>
|
||||
/// <param name="payload">The raw 8-byte <c>cnc_rdtimer</c> response payload.</param>
|
||||
/// <returns>The decoded minute/msec timer value.</returns>
|
||||
internal static WireTimer ParseTimer(short type, byte[] payload) => new(
|
||||
type,
|
||||
payload.Length >= 4 ? BinaryPrimitives.ReadInt32LittleEndian(payload.AsSpan(0, 4)) : 0,
|
||||
@@ -914,6 +945,10 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// machine's servo-meter screen — confirm magnitude at commissioning. The alignment + name
|
||||
/// fix here is what removes the gross misaligned garbage.</para>
|
||||
/// </summary>
|
||||
/// <param name="svPayload">The raw <c>cnc_rdsvmeter</c> response payload.</param>
|
||||
/// <param name="axisNamePayload">The raw <c>cnc_rdaxisname</c> response payload used to correlate axis names by index.</param>
|
||||
/// <param name="maxCount">Maximum number of servo meter records to return.</param>
|
||||
/// <returns>The decoded servo-meter records.</returns>
|
||||
internal static IReadOnlyList<WireServoMeter> ParseServoMeters(byte[] svPayload, byte[] axisNamePayload, int maxCount)
|
||||
{
|
||||
var result = new List<WireServoMeter>();
|
||||
@@ -935,6 +970,8 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>Byte width of one PMC slot for a FOCAS data-type code: Byte=1, Word=2, Long/Real=4, Double=8.</summary>
|
||||
/// <param name="dataType">The PMC data width code.</param>
|
||||
/// <returns>The byte width of one slot for the given data-type code.</returns>
|
||||
internal static int PmcByteWidth(short dataType) => dataType switch
|
||||
{
|
||||
1 => 2,
|
||||
@@ -950,6 +987,12 @@ public sealed class FocasWireClient : IAsyncDisposable, IDisposable
|
||||
/// <c>WireFocasClient.ReadPmcAsync</c>) or a trailing partial slot is dropped — which on the
|
||||
/// 31i-B surfaced as a spurious <c>BadOutOfRange</c> for a single Word read. 2026-06-25.
|
||||
/// </summary>
|
||||
/// <param name="area">The PMC address-letter code numeric value.</param>
|
||||
/// <param name="dataType">The PMC data width code numeric value.</param>
|
||||
/// <param name="start">The starting address.</param>
|
||||
/// <param name="end">The ending address.</param>
|
||||
/// <param name="payload">The raw <c>pmc_rdpmcrng</c> response payload.</param>
|
||||
/// <returns>The decoded PMC range values.</returns>
|
||||
internal static WirePmcRange ParsePmcRange(short area, short dataType, ushort start, ushort end, byte[] payload)
|
||||
{
|
||||
var width = PmcByteWidth(dataType);
|
||||
|
||||
@@ -32,6 +32,8 @@ internal static class FocasWireProtocol
|
||||
private static readonly ushort[] SupportedReadVersions = [1, 3];
|
||||
|
||||
/// <summary>True when <paramref name="version"/> is a PDU version this client can frame-parse.</summary>
|
||||
/// <param name="version">The PDU version from an inbound header.</param>
|
||||
/// <returns><c>true</c> if the version is one of the supported read PDU versions; otherwise, <c>false</c>.</returns>
|
||||
internal static bool IsSupportedReadVersion(ushort version) =>
|
||||
Array.IndexOf(SupportedReadVersions, version) >= 0;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
/// <summary>
|
||||
/// Construct with an optional logger. Threaded through to
|
||||
/// <see cref="FocasWireClient"/> so the per-response Debug entries actually reach
|
||||
/// the host's logging pipeline (Driver.FOCAS-007).
|
||||
/// the host's logging pipeline.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for debug output from wire client responses.</param>
|
||||
public WireFocasClient(ILogger<FocasWireClient>? logger)
|
||||
@@ -36,13 +36,10 @@ public sealed class WireFocasClient : IFocasClient
|
||||
_wire = new FocasWireClient(logger);
|
||||
}
|
||||
|
||||
/// <summary>Gets a value indicating whether the wire client is connected to the FOCAS host.</summary>
|
||||
/// <inheritdoc />
|
||||
public bool IsConnected => _wire.IsConnected;
|
||||
|
||||
/// <summary>Connects to a FOCAS host at the specified address.</summary>
|
||||
/// <param name="address">The host address containing the machine name and port.</param>
|
||||
/// <param name="timeout">The connection timeout; values less than or equal to zero are clamped to 1 second.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public async Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_wire.IsConnected) return;
|
||||
@@ -54,11 +51,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
await _wire.ConnectAsync(address.Host, address.Port, effective, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Reads a value from the specified FOCAS address.</summary>
|
||||
/// <param name="address">The FOCAS address to read from.</param>
|
||||
/// <param name="type">The FOCAS data type of the value.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A tuple containing the read value and FOCAS status code.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<(object? value, uint status)> ReadAsync(
|
||||
FocasAddress address, FocasDataType type, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -74,19 +67,12 @@ public sealed class WireFocasClient : IFocasClient
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Writes a value to a FOCAS address (always returns BadNotWritable as OtOpcUa is read-only).</summary>
|
||||
/// <param name="address">The FOCAS address to write to.</param>
|
||||
/// <param name="type">The FOCAS data type of the value.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that returns the BadNotWritable status code.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<uint> WriteAsync(
|
||||
FocasAddress address, FocasDataType type, object? value, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(FocasStatusMapper.BadNotWritable);
|
||||
|
||||
/// <summary>Probes the FOCAS host to verify connectivity.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>True if the probe succeeds; otherwise false.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> ProbeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return false;
|
||||
@@ -101,9 +87,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads all active alarms from the FOCAS host.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of active alarms; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<FocasActiveAlarm>> ReadAlarmsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return [];
|
||||
@@ -125,9 +109,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
Message: a.Message ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Gets system information from the FOCAS host.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The FOCAS system information.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<FocasSysInfo> GetSysInfoAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RequireConnected();
|
||||
@@ -147,9 +129,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
AxesCount: axesCount);
|
||||
}
|
||||
|
||||
/// <summary>Gets the names of all axes on the FOCAS host.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of axis names; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<FocasAxisName>> GetAxisNamesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return [];
|
||||
@@ -169,9 +149,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the names of all spindles on the FOCAS host.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of spindle names; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<FocasSpindleName>> GetSpindleNamesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return [];
|
||||
@@ -190,10 +168,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads the dynamic state of a specified axis.</summary>
|
||||
/// <param name="axisIndex">The index of the axis to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The dynamic snapshot of the axis.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<FocasDynamicSnapshot> ReadDynamicAsync(int axisIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
// FOCAS axes are 1-based; cnc_rddynamic2 with axis 0 returns EW_4 (live-confirmed on the
|
||||
@@ -220,9 +195,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
DistanceToGo: pos.Distance);
|
||||
}
|
||||
|
||||
/// <summary>Gets information about the currently executing program.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The current program information.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<FocasProgramInfo> GetProgramInfoAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RequireConnected();
|
||||
@@ -250,10 +223,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
Mode: modeResult.IsOk ? modeResult.Value : 0);
|
||||
}
|
||||
|
||||
/// <summary>Gets a timer value from the FOCAS host.</summary>
|
||||
/// <param name="kind">The kind of timer to read (run time, cutting time, etc.).</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The timer value.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<FocasTimer> GetTimerAsync(FocasTimerKind kind, CancellationToken cancellationToken)
|
||||
{
|
||||
RequireConnected();
|
||||
@@ -263,9 +233,7 @@ public sealed class WireFocasClient : IFocasClient
|
||||
return new FocasTimer(kind, t.Minutes, t.Milliseconds);
|
||||
}
|
||||
|
||||
/// <summary>Gets servo load information for all axes.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of servo load values for each axis; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<FocasServoLoad>> GetServoLoadsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return [];
|
||||
@@ -277,28 +245,18 @@ public sealed class WireFocasClient : IFocasClient
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>Gets spindle load information for all spindles.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of spindle load percentages; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<int>> GetSpindleLoadsAsync(CancellationToken cancellationToken) =>
|
||||
ReadSpindleMetricAsync((sel, ct) => _wire.ReadSpindleLoadAsync(sel, ct), cancellationToken);
|
||||
|
||||
/// <summary>Gets maximum RPM information for all spindles.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of maximum RPM values for each spindle; empty if read fails or not connected.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<int>> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken) =>
|
||||
ReadSpindleMetricAsync((sel, ct) => _wire.ReadSpindleMaxRpmAsync(sel, ct), cancellationToken);
|
||||
|
||||
/// <summary>Gets the per-axis position decimal-place figures via <c>cnc_getfigure</c>.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>
|
||||
/// The per-axis decimal-place figures read from the CNC via
|
||||
/// <see cref="FocasWireClient.ReadPositionFiguresAsync"/>, or an empty list when the read
|
||||
/// fails (non-zero RC — e.g. the series lacks <c>cnc_getfigure</c>) or is not connected.
|
||||
/// Per the <see cref="IFocasClient.GetPositionFiguresAsync"/> contract an empty list signals
|
||||
/// the driver to fall back to the configured <c>PositionDecimalPlaces</c>. Never throws —
|
||||
/// figure reads degrade to the config knob rather than faulting.
|
||||
/// </returns>
|
||||
/// <inheritdoc />
|
||||
// Reads via FocasWireClient.ReadPositionFiguresAsync; returns an empty list when the read
|
||||
// fails (non-zero RC — e.g. the series lacks cnc_getfigure) or is not connected. Never
|
||||
// throws — figure reads degrade to the configured PositionDecimalPlaces rather than faulting.
|
||||
public async Task<IReadOnlyList<int>> GetPositionFiguresAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_wire.IsConnected) return [];
|
||||
@@ -450,9 +408,8 @@ public sealed class WireFocasClientFactory : IFocasClientFactory
|
||||
|
||||
/// <summary>
|
||||
/// Construct the factory with a logger that every created <see cref="WireFocasClient"/>
|
||||
/// forwards to its <see cref="FocasWireClient"/>. Resolves Driver.FOCAS-007 — the wire
|
||||
/// client already emits Debug entries per FOCAS response, but the previous no-arg
|
||||
/// factory path discarded them.
|
||||
/// forwards to its <see cref="FocasWireClient"/> — the wire client already emits Debug
|
||||
/// entries per FOCAS response, but the previous no-arg factory path discarded them.
|
||||
/// </summary>
|
||||
/// <param name="logger">Optional logger for debug output from wire client responses.</param>
|
||||
public WireFocasClientFactory(ILogger<FocasWireClient>? logger)
|
||||
@@ -460,13 +417,10 @@ public sealed class WireFocasClientFactory : IFocasClientFactory
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op usability probe — the wire backend is always usable at config time.
|
||||
/// Implements <see cref="IFocasClientFactory.EnsureUsable"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
// No-op — the wire backend is always usable at config time.
|
||||
public void EnsureUsable() { }
|
||||
|
||||
/// <summary>Creates a new WireFocasClient instance.</summary>
|
||||
/// <returns>A new IFocasClient implementation.</returns>
|
||||
/// <inheritdoc />
|
||||
public IFocasClient Create() => new WireFocasClient(_logger);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user