Auto: focas-f2b — multi-path/multi-channel CNC
Adds optional `@N` path suffix to FocasAddress (PARAM:1815@2, R100@3.0, MACRO:500@2, DIAG:280@2/1) with PathId defaulting to 1 for back-compat. Per-device PathCount is discovered via cnc_rdpathnum at first connect and cached on DeviceState; reads with PathId>PathCount return BadOutOfRange. The driver issues cnc_setpath before each non-default-path read and tracks LastSetPath so repeat reads on the same path skip the wire call. Closes #264
This commit is contained in:
@@ -377,6 +377,20 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var client = await EnsureConnectedAsync(device, cancellationToken).ConfigureAwait(false);
|
||||
var parsed = FocasAddress.TryParse(def.Address)
|
||||
?? throw new InvalidOperationException($"FOCAS tag '{def.Name}' has malformed Address '{def.Address}'.");
|
||||
// Multi-path validation + dispatch (issue #264). PathId=1 is the back-compat
|
||||
// default and skips the cnc_setpath call entirely; non-default paths are
|
||||
// bounded against the device's cached PathCount and only switch the active
|
||||
// path when it differs from the last one set on the session.
|
||||
if (parsed.PathId > 1 && device.PathCount > 0 && parsed.PathId > device.PathCount)
|
||||
{
|
||||
results[i] = new DataValueSnapshot(null, FocasStatusMapper.BadOutOfRange, null, now);
|
||||
continue;
|
||||
}
|
||||
if (parsed.PathId != 1 && device.LastSetPath != parsed.PathId)
|
||||
{
|
||||
await client.SetPathAsync(parsed.PathId, cancellationToken).ConfigureAwait(false);
|
||||
device.LastSetPath = parsed.PathId;
|
||||
}
|
||||
var (value, status) = parsed.Kind == FocasAreaKind.Diagnostic
|
||||
? await client.ReadDiagnosticAsync(
|
||||
parsed.Number, parsed.BitIndex ?? 0, def.DataType, cancellationToken).ConfigureAwait(false)
|
||||
@@ -432,6 +446,16 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var client = await EnsureConnectedAsync(device, cancellationToken).ConfigureAwait(false);
|
||||
var parsed = FocasAddress.TryParse(def.Address)
|
||||
?? throw new InvalidOperationException($"FOCAS tag '{def.Name}' has malformed Address '{def.Address}'.");
|
||||
if (parsed.PathId > 1 && device.PathCount > 0 && parsed.PathId > device.PathCount)
|
||||
{
|
||||
results[i] = new WriteResult(FocasStatusMapper.BadOutOfRange);
|
||||
continue;
|
||||
}
|
||||
if (parsed.PathId != 1 && device.LastSetPath != parsed.PathId)
|
||||
{
|
||||
await client.SetPathAsync(parsed.PathId, cancellationToken).ConfigureAwait(false);
|
||||
device.LastSetPath = parsed.PathId;
|
||||
}
|
||||
var status = await client.WriteAsync(parsed, def.DataType, w.Value, cancellationToken).ConfigureAwait(false);
|
||||
results[i] = new WriteResult(status);
|
||||
}
|
||||
@@ -1089,6 +1113,19 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
device.Client = null;
|
||||
throw;
|
||||
}
|
||||
// Multi-path bootstrap (issue #264). cnc_rdpathnum runs once per session — the
|
||||
// controller's path topology is fixed at boot. A reconnect resets the wire
|
||||
// session's "last set path" so the next non-default-path read forces a fresh
|
||||
// cnc_setpath; that's why LastSetPath is reset alongside PathCount here.
|
||||
try
|
||||
{
|
||||
device.PathCount = await device.Client.GetPathCountAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
device.PathCount = 1;
|
||||
}
|
||||
device.LastSetPath = 0;
|
||||
return device.Client;
|
||||
}
|
||||
|
||||
@@ -1190,6 +1227,24 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public string? LastErrorMessage;
|
||||
public DateTime LastSuccessfulReadUtc;
|
||||
|
||||
/// <summary>
|
||||
/// CNC path topology cached at first successful connect via
|
||||
/// <c>cnc_rdpathnum</c> (issue #264). 1 for single-path controllers; 2..N for
|
||||
/// multi-path lathes / dual-turret machines. The driver validates per-tag
|
||||
/// <c>FocasAddress.PathId</c> against this count + rejects with
|
||||
/// <c>BadOutOfRange</c> when the tag points beyond what the CNC reports.
|
||||
/// </summary>
|
||||
public int PathCount { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Most recent path number set on the wire session via <c>cnc_setpath</c>,
|
||||
/// or <c>0</c> when no path has been set yet (fresh session). The driver
|
||||
/// skips redundant <c>cnc_setpath</c> calls when the tag's <c>PathId</c>
|
||||
/// matches the last-set value; reconnects reset this to <c>0</c> so the
|
||||
/// next non-default-path read forces a fresh switch (issue #264).
|
||||
/// </summary>
|
||||
public int LastSetPath { get; set; }
|
||||
|
||||
public void DisposeClient()
|
||||
{
|
||||
Client?.Dispose();
|
||||
|
||||
Reference in New Issue
Block a user