feat(focas): per-axis auto-scale from cnc_getfigure figures (manual config = fallback)
This commit is contained in:
@@ -655,6 +655,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var sys = await client.GetSysInfoAsync(ct).ConfigureAwait(false);
|
||||
var axes = await client.GetAxisNamesAsync(ct).ConfigureAwait(false);
|
||||
|
||||
// Per-axis decimal-place figures (cnc_getfigure), fetched once. Auto figures win
|
||||
// over the manual PositionDecimalPlaces config at the publish seam; an empty list
|
||||
// (the managed wire backend today) makes every axis fall back to the config knob.
|
||||
// Defensive: a figure-read failure must NOT fault device init — default to empty.
|
||||
state.PositionFigures = await SafeProbe(() => client.GetPositionFiguresAsync(ct), []);
|
||||
|
||||
// Optional-API probes — each returns empty / throws when unsupported.
|
||||
var spindles = await SafeProbe(() => client.GetSpindleNamesAsync(ct), []);
|
||||
var spindleMaxRpms = await SafeProbe(() => client.GetSpindleMaxRpmsAsync(ct), []);
|
||||
@@ -718,7 +724,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var axisIndex = i + 1; // FOCAS uses 1-based axis indexing
|
||||
var axis = cache.Axes[i];
|
||||
var snap = await client.ReadDynamicAsync(axisIndex, ct).ConfigureAwait(false);
|
||||
PublishAxisSnapshot(state, axis, snap);
|
||||
PublishAxisSnapshot(state, axis, snap, i); // i = 0-based axis index → PositionFigures lookup
|
||||
if (i == 0) { firstAxisSnap = snap; PublishRateSnapshot(state, snap); }
|
||||
}
|
||||
|
||||
@@ -816,24 +822,41 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
/// <see cref="PollGroupEngine"/> → <see cref="ReadAsync"/> path, which hits
|
||||
/// <see cref="TryReadFixedTree"/> and returns these cached values.
|
||||
/// </summary>
|
||||
private static void PublishAxisSnapshot(DeviceState state, FocasAxisName axis, FocasDynamicSnapshot snap)
|
||||
private static void PublishAxisSnapshot(DeviceState state, FocasAxisName axis, FocasDynamicSnapshot snap, int axisIndex)
|
||||
{
|
||||
var host = state.Options.HostAddress;
|
||||
// cnc_rddynamic2 returns positions as scaled integers; divide by
|
||||
// 10^PositionDecimalPlaces so they surface in engineering units on the Float64
|
||||
// axis nodes. PositionDecimalPlaces is clamped non-negative at config parse, and a
|
||||
// value of 0 yields factor 1.0 — i.e. the integer widened to double, byte-identical
|
||||
// to legacy behaviour (12345 / 1.0 == 12345.0). FeedRate / SpindleSpeed (rate
|
||||
// snapshot) and ServoLoad are NOT position-scaled and are published elsewhere.
|
||||
var factor = state.Options.PositionDecimalPlaces > 0
|
||||
? Math.Pow(10, state.Options.PositionDecimalPlaces)
|
||||
: 1.0;
|
||||
// cnc_rddynamic2 returns positions as scaled integers; divide by 10^figures so they
|
||||
// surface in engineering units on the Float64 axis nodes. The figure is per-axis:
|
||||
// an auto cnc_getfigure figure WINS, and the configured PositionDecimalPlaces is the
|
||||
// fallback when the CNC didn't report one for that axis (see AxisFactor). A figure of
|
||||
// 0 yields factor 1.0 — i.e. the integer widened to double, byte-identical to legacy
|
||||
// behaviour (12345 / 1.0 == 12345.0). CAVEAT: the managed WireFocasClient returns no
|
||||
// figures today, so the REAL backend always uses the manual fallback; live auto-fetch
|
||||
// lands when a FocasWireClient cnc_getfigure wire command is added. FeedRate /
|
||||
// SpindleSpeed (rate snapshot) and ServoLoad are NOT position-scaled (published elsewhere).
|
||||
var factor = AxisFactor(state, axisIndex);
|
||||
state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/AbsolutePosition")] = snap.AbsolutePosition / factor;
|
||||
state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/MachinePosition")] = snap.MachinePosition / factor;
|
||||
state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/RelativePosition")] = snap.RelativePosition / factor;
|
||||
state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/DistanceToGo")] = snap.DistanceToGo / factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the position-scale factor (10^figure) for a single axis. Auto
|
||||
/// (<c>cnc_getfigure</c>) wins per-axis; manual <c>PositionDecimalPlaces</c> is the
|
||||
/// fallback when the CNC didn't report a figure for that axis. Both clamp non-negative;
|
||||
/// 0 ⇒ factor 1.0 (legacy byte-identical). The managed wire backend returns no figures
|
||||
/// today, so the real backend always takes the manual-fallback branch.
|
||||
/// </summary>
|
||||
private static double AxisFactor(DeviceState state, int axisIndex)
|
||||
{
|
||||
var figures = state.PositionFigures;
|
||||
var dp = axisIndex >= 0 && axisIndex < figures.Count && figures[axisIndex] >= 0
|
||||
? figures[axisIndex]
|
||||
: Math.Max(0, state.Options.PositionDecimalPlaces);
|
||||
return dp > 0 ? Math.Pow(10, dp) : 1.0;
|
||||
}
|
||||
|
||||
private static void PublishRateSnapshot(DeviceState state, FocasDynamicSnapshot snap)
|
||||
{
|
||||
var host = state.Options.HostAddress;
|
||||
@@ -1176,6 +1199,13 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public Dictionary<string, double> LastServoLoads { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
/// <summary>Gets the last spindle load percentages by spindle index.</summary>
|
||||
public Dictionary<int, int> LastSpindleLoads { get; } = [];
|
||||
/// <summary>
|
||||
/// Gets or sets the per-axis position decimal-place figures fetched once at init via
|
||||
/// <c>cnc_getfigure</c> (parallel to the axis-name list; index = axis). An auto figure
|
||||
/// for an axis WINS over the configured <c>PositionDecimalPlaces</c>; an empty list (the
|
||||
/// managed wire backend's behaviour today) makes every axis fall back to that config knob.
|
||||
/// </summary>
|
||||
public IReadOnlyList<int> PositionFigures { get; set; } = [];
|
||||
|
||||
/// <summary>Disposes the FOCAS client instance.</summary>
|
||||
public void DisposeClient()
|
||||
|
||||
Reference in New Issue
Block a user