fix(focas): a missing O-number or refused program sub-read goes Bad, not a Good 0
WireFocasClient.GetProgramInfoAsync defaulted every field the CNC did not report: ONumber ?? 0 when the cnc_exeprgname2 payload was too short to carry it, and Name '' / BlockCount 0 / Mode 0 when the sub-read was refused. 0 is a genuine O-number (a 31i reports 0 for a path-executed program -- live Z-34184), a genuine block count, and Mode 0 is MDI, a real operating mode. FocasProgramInfo fields are now nullable; MapProgramInfo leaves unreported fields null and the fixed tree publishes them BadNoData while the bundle's other fields stay Good. Ported from the sister ignitionoee FOCAS driver (101a3b1, 2026-08-12).
This commit is contained in:
@@ -1307,8 +1307,8 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
"Program/MainNumber" => state.LastProgramAxisRef?.MainProgramNumber,
|
||||
"Program/Sequence" => state.LastProgramAxisRef?.SequenceNumber,
|
||||
"OperationMode/Mode" => state.LastProgramInfo?.Mode,
|
||||
"OperationMode/ModeText" => state.LastProgramInfo is { } pi
|
||||
? FocasOpMode.ToText(pi.Mode) : null,
|
||||
"OperationMode/ModeText" => state.LastProgramInfo?.Mode is { } mode
|
||||
? FocasOpMode.ToText(mode) : null,
|
||||
"Timers/PowerOnSeconds" => TimerValue(state, FocasTimerKind.PowerOn),
|
||||
"Timers/OperatingSeconds" => TimerValue(state, FocasTimerKind.Operating),
|
||||
"Timers/CuttingSeconds" => TimerValue(state, FocasTimerKind.Cutting),
|
||||
@@ -1317,6 +1317,18 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
};
|
||||
if (value is not null)
|
||||
return new DataValueSnapshot(value, status, sourceUtc, now);
|
||||
|
||||
// The program bundle has been read, but the CNC did not report this field (a refused
|
||||
// sub-read, or no O-number in the payload). That is Bad — never a fabricated 0, and
|
||||
// not "no such node" either.
|
||||
if (state.LastProgramInfo is not null
|
||||
&& path is "Program/Name" or "Program/ONumber" or "Program/BlockCount"
|
||||
or "OperationMode/Mode" or "OperationMode/ModeText")
|
||||
return new DataValueSnapshot(
|
||||
null,
|
||||
status == FocasStatusMapper.Good ? FocasStatusMapper.BadNoData : status,
|
||||
sourceUtc,
|
||||
now);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -238,12 +238,15 @@ public sealed record FocasSpindleName(string Name, string Suffix1, string Suffix
|
||||
/// Program + operation-mode snapshot. Name is the currently-executing
|
||||
/// program filename (e.g. "O0001.NC"); ONumber is its Fanuc O-number (1-9999).
|
||||
/// Mode is the numeric code from <c>cnc_rdopmode</c> — see <see cref="FocasOpMode"/>.
|
||||
/// <b>A null field means the CNC did not report it</b> (its sub-read was refused, or the
|
||||
/// payload was too short to carry it); the driver publishes that node Bad. It is never
|
||||
/// defaulted to <c>0</c>, which is a genuine O-number, block count, and mode (MDI).
|
||||
/// </summary>
|
||||
public sealed record FocasProgramInfo(
|
||||
string Name,
|
||||
int ONumber,
|
||||
int BlockCount,
|
||||
int Mode);
|
||||
string? Name,
|
||||
int? ONumber,
|
||||
int? BlockCount,
|
||||
int? Mode);
|
||||
|
||||
/// <summary>Human-readable text for the <see cref="FocasProgramInfo.Mode"/> integer.</summary>
|
||||
public static class FocasOpMode
|
||||
|
||||
@@ -217,12 +217,29 @@ public sealed class WireFocasClient : IFocasClient
|
||||
throw new InvalidOperationException(
|
||||
$"cnc_exeprgname2 failed EW_{nameResult.Rc} and cnc_rdopmode failed EW_{modeResult.Rc}.");
|
||||
|
||||
var wireName = nameResult.Value;
|
||||
return MapProgramInfo(nameResult, blkResult, modeResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine the three program sub-reads into a <see cref="FocasProgramInfo"/>, leaving any
|
||||
/// field the CNC did not report null. A refused read, or an <c>cnc_exeprgname2</c> payload
|
||||
/// too short to carry the trailing O-number, used to publish a fabricated <c>0</c> at Good
|
||||
/// quality — indistinguishable from a real 0 (a 31i reports O-number 0 for a program run
|
||||
/// by path) and, for the mode, from MDI. Ported from the sister ignitionoee driver (101a3b1).
|
||||
/// </summary>
|
||||
/// <param name="name">The <c>cnc_exeprgname2</c> result.</param>
|
||||
/// <param name="blockCount">The <c>cnc_rdblkcount</c> result.</param>
|
||||
/// <param name="mode">The <c>cnc_rdopmode</c> result.</param>
|
||||
/// <returns>The snapshot, null where the CNC did not say.</returns>
|
||||
internal static FocasProgramInfo MapProgramInfo(
|
||||
FocasResult<WireProgramName> name, FocasResult<int> blockCount, FocasResult<short> mode)
|
||||
{
|
||||
var wireName = name.IsOk ? name.Value : null;
|
||||
return new FocasProgramInfo(
|
||||
Name: wireName?.Name ?? string.Empty,
|
||||
ONumber: wireName?.ONumber ?? 0,
|
||||
BlockCount: blkResult.IsOk ? blkResult.Value : 0,
|
||||
Mode: modeResult.IsOk ? modeResult.Value : 0);
|
||||
Name: wireName?.Name,
|
||||
ONumber: wireName?.ONumber,
|
||||
BlockCount: blockCount.IsOk ? blockCount.Value : null,
|
||||
Mode: mode.IsOk ? mode.Value : null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user