Auto: focas-f1c — Modal codes + overrides
Closes #259 Adds Modal/ + Override/ fixed-tree subfolders per FOCAS device, mirroring the pattern established by Status/ (#257) and Production/ (#258): cached snapshots refreshed on the probe tick, served from cache on read, no extra wire traffic on top of user-driven tag reads. Modal/ surfaces the four universally-present aux modal codes M/S/T/B from cnc_modal(type=100..103) as Int16. **G-group decoding (groups 1..21) is deferred to a follow-up** — the FWLIB ODBMDL union differs per series + group and the issue body explicitly permits this scoping. Adds the cnc_modal P/Invoke + ODBMDL struct + a generic int16 cnc_rdparam helper so the follow-up can add G-groups without further wire-level scaffolding. Override/ surfaces Feed/Rapid/Spindle/Jog from cnc_rdparam at MTB-specific parameter numbers (FocasDeviceOptions.OverrideParameters; defaults to 30i: 6010/6011/6014/6015). Per-field nullable params let a deployment hide overrides their MTB doesn't wire up; passing OverrideParameters=null suppresses the entire Override/ subfolder for that device. 6 unit tests cover discovery shape, omitted Override folder when unconfigured, partial Override field selection, cached-snapshot reads (Modal + Override), BadCommunicationError before first refresh, and the FwlibFocasClient disconnected short-circuit.
This commit is contained in:
@@ -187,6 +187,61 @@ internal sealed class FwlibFocasClient : IFocasClient
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadInt16Param(ushort number, out short value)
|
||||
{
|
||||
var buf = new FwlibNative.IODBPSD { Data = new byte[32] };
|
||||
var ret = FwlibNative.RdParam(_handle, number, axis: 0, length: 4 + 2, ref buf);
|
||||
if (ret != 0) { value = 0; return false; }
|
||||
value = BinaryPrimitives.ReadInt16LittleEndian(buf.Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<FocasModalInfo?> GetModalAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_connected) return Task.FromResult<FocasModalInfo?>(null);
|
||||
// type 100/101/102/103 = M/S/T/B (single auxiliary code, active modal block 0).
|
||||
// Best-effort — if any single read fails we still surface the others as 0; the
|
||||
// probe loop only updates the cache on a non-null return so a partial snapshot
|
||||
// is preferable to throwing away every successful field.
|
||||
return Task.FromResult<FocasModalInfo?>(new FocasModalInfo(
|
||||
MCode: ReadModalAux(type: 100),
|
||||
SCode: ReadModalAux(type: 101),
|
||||
TCode: ReadModalAux(type: 102),
|
||||
BCode: ReadModalAux(type: 103)));
|
||||
}
|
||||
|
||||
private short ReadModalAux(short type)
|
||||
{
|
||||
var buf = new FwlibNative.ODBMDL { Data = new byte[8] };
|
||||
var ret = FwlibNative.Modal(_handle, type, block: 0, ref buf);
|
||||
if (ret != 0) return 0;
|
||||
// For aux types (100..103) the union holds the code at offset 0 as a 2-byte
|
||||
// value (<c>aux_data</c>). Reading as Int16 keeps the surface identical to the
|
||||
// record contract; oversized values would have been truncated by FWLIB anyway.
|
||||
return BinaryPrimitives.ReadInt16LittleEndian(buf.Data);
|
||||
}
|
||||
|
||||
public Task<FocasOverrideInfo?> GetOverrideAsync(
|
||||
FocasOverrideParameters parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_connected) return Task.FromResult<FocasOverrideInfo?>(null);
|
||||
// Each parameter is independently nullable — a null parameter number keeps the
|
||||
// corresponding field at null + skips the wire call. A successful read on at
|
||||
// least one parameter is enough to publish a snapshot; this matches the
|
||||
// best-effort policy used by GetProductionAsync (issue #259).
|
||||
var feed = TryReadOverride(parameters.FeedParam);
|
||||
var rapid = TryReadOverride(parameters.RapidParam);
|
||||
var spindle = TryReadOverride(parameters.SpindleParam);
|
||||
var jog = TryReadOverride(parameters.JogParam);
|
||||
return Task.FromResult<FocasOverrideInfo?>(new FocasOverrideInfo(feed, rapid, spindle, jog));
|
||||
}
|
||||
|
||||
private short? TryReadOverride(ushort? param)
|
||||
{
|
||||
if (param is null) return null;
|
||||
return TryReadInt16Param(param.Value, out var v) ? v : null;
|
||||
}
|
||||
|
||||
// ---- PMC ----
|
||||
|
||||
private (object? value, uint status) ReadPmc(FocasAddress address, FocasDataType type)
|
||||
|
||||
Reference in New Issue
Block a user