69e0a622f3
Every 0x0026 position block is self-describing: [i32 raw][u16 base][u16 exponent],
value = raw / base^exponent. The live 31i-B capture (ref-dynamic2-axis1.bin)
reports base 10 / exponent 4 on every block, so axis 1 is 280.1574 mm, not
2801574. The driver ignored the envelope and instead sent 'cnc_getfigure'
(0x00D3) -- a command that does not exist in the FOCAS wire protocol, served only
by the in-tree mock; the real control refuses it with rc=1, so on hardware every
position fell back to PositionDecimalPlaces (default 0) and published raw counts.
- ParsePositionBlock decodes the envelope into FocasPosition; the driver applies
raw / base^exponent at publish. PositionDecimalPlaces is now only the fallback
for a scale-less (4-byte) block.
- A block describing no position (0xFFFF vacant marker, base not 2/10, exponent
> 18 -- raw / 10^400 would read as a Good 0.0) publishes BadNoData on that node
alone; siblings are untouched.
- ReadPositionFiguresAsync / IFocasClient.GetPositionFiguresAsync and the
per-axis figure cache are deleted.
Ported from the sister ignitionoee FOCAS driver v0.17.0 (+ Focas.Wire-014);
live axis-scoped captures live-dynamic2-axis{1,2}-0x0026.bin (Z-34184 X 0.0374,
Y 370.177) added as fixtures.
153 lines
5.7 KiB
C#
153 lines
5.7 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire;
|
|
|
|
/// <summary>
|
|
/// Return envelope over a parsed wire response. <see cref="Rc"/> carries the FOCAS
|
|
/// <c>EW_*</c> code from the response block — <c>0</c> / <see cref="IsOk"/> means the
|
|
/// call succeeded and <see cref="Value"/> is populated; non-zero means the CNC rejected
|
|
/// the call and <see cref="Value"/> is <c>default</c>. Callers use the RC to distinguish
|
|
/// "feature missing on this series" (<c>EW_FUNC</c> / <c>EW_NOOPT</c>) from genuine
|
|
/// failures.
|
|
/// </summary>
|
|
public readonly record struct FocasResult<T>(short Rc, T? Value)
|
|
{
|
|
/// <summary>True when <see cref="Rc"/> is zero (<c>EW_OK</c>).</summary>
|
|
public bool IsOk => Rc == 0;
|
|
}
|
|
|
|
/// <summary>CNC identity payload returned by <c>cnc_sysinfo</c>.</summary>
|
|
public sealed record WireSysInfo(
|
|
short AddInfo,
|
|
short MaxAxis,
|
|
string CncType,
|
|
string MachineType,
|
|
string Series,
|
|
string Version,
|
|
string Axes);
|
|
|
|
/// <summary>Coarse CNC state bits returned by <c>cnc_statinfo</c> — the seven-word status block plus TM mode.</summary>
|
|
public sealed record WireStatus(
|
|
short Auto,
|
|
short Run,
|
|
short Motion,
|
|
short Mstb,
|
|
short Emergency,
|
|
short Alarm,
|
|
short Edit,
|
|
short TmMode);
|
|
|
|
/// <summary>
|
|
/// Four-slot position quadruple for one axis: absolute / machine / relative / distance-to-go,
|
|
/// each carrying the scale its own <c>0x0026</c> block reported.
|
|
/// </summary>
|
|
public sealed record WireAxisPosition(
|
|
FocasPosition Absolute,
|
|
FocasPosition Machine,
|
|
FocasPosition Relative,
|
|
FocasPosition Distance);
|
|
|
|
/// <summary>
|
|
/// Fast-poll bundle for one axis from <c>cnc_rddynamic2</c> — alarm flags, active program
|
|
/// numbers, sequence number, actual feed rate, actual spindle speed, and the position
|
|
/// quadruple.
|
|
/// </summary>
|
|
public sealed record WireDynamic(
|
|
int Alarm,
|
|
int ProgramNumber,
|
|
int MainProgramNumber,
|
|
int SequenceNumber,
|
|
int FeedRate,
|
|
int SpindleSpeed,
|
|
WireAxisPosition Axis);
|
|
|
|
/// <summary>One servo-meter entry from <c>cnc_rdsvmeter</c> — per-axis load percentage (scale by 10^<see cref="Decimal"/>).</summary>
|
|
public sealed record WireServoMeter(
|
|
short Index,
|
|
string Name,
|
|
int Value,
|
|
short Decimal,
|
|
short Unit);
|
|
|
|
/// <summary>One spindle metric slot from <c>cnc_rdspload</c> / <c>cnc_rdspmaxrpm</c>.</summary>
|
|
public sealed record WireSpindleMetric(
|
|
short Index,
|
|
int Value);
|
|
|
|
/// <summary>
|
|
/// One axis-name slot from <c>cnc_rdaxisname</c>. <see cref="Index"/> is the 1-based
|
|
/// axis index (preserved even when the name is empty so callers can pass it to
|
|
/// <c>cnc_rddynamic2</c>).
|
|
/// </summary>
|
|
public readonly record struct WireAxisRecord(short Index, string Name);
|
|
|
|
/// <summary>One spindle-name slot from <c>cnc_rdspdlname</c>.</summary>
|
|
public readonly record struct WireSpindleRecord(short Index, string Name);
|
|
|
|
/// <summary>Parameter value returned by <c>cnc_rdparam</c>, interpreted as a scalar Int32.</summary>
|
|
public sealed record WireParameter(
|
|
short DataNumber,
|
|
short Type,
|
|
int Value);
|
|
|
|
/// <summary>
|
|
/// Macro variable from <c>cnc_rdmacro</c>. Scaled decimal: the real value is
|
|
/// <c>Value / 10^Decimal</c> (<see cref="Scaled"/>). FANUC normalises every macro to a
|
|
/// 9-significant-digit mantissa, so <see cref="Value"/> alone is never a value to publish
|
|
/// (live: <c>#502 = 270</c> arrives as <c>270000000 / 10^6</c>).
|
|
/// </summary>
|
|
/// <param name="Number">Macro variable number (the request argument; not carried in the payload).</param>
|
|
/// <param name="Value">The 9-significant-digit mantissa.</param>
|
|
/// <param name="Decimal">Implied decimal places; negative (<c>0xFFFF</c> on the wire) = vacant.</param>
|
|
public sealed record WireMacro(
|
|
short Number,
|
|
int Value,
|
|
short Decimal)
|
|
{
|
|
/// <summary>
|
|
/// True when the CNC reported the variable as vacant (undefined / never set):
|
|
/// <c>dec_val == 0xFFFF</c>. A vacant macro's mantissa is 0, indistinguishable from a
|
|
/// genuine 0, so this must be tested before <see cref="Value"/> means anything. Any
|
|
/// negative decimal is treated as vacant — no other negative has ever been observed and
|
|
/// failing closed is the safe reading.
|
|
/// </summary>
|
|
public bool IsVacant => Decimal < 0;
|
|
|
|
/// <summary>The real value, <c>Value / 10^Decimal</c>; null when <see cref="IsVacant"/>.</summary>
|
|
public double? Scaled => IsVacant ? null : Decimal == 0 ? Value : Value / Math.Pow(10.0, Decimal);
|
|
}
|
|
|
|
/// <summary>PMC range read-back from <c>pmc_rdpmcrng</c>: one or more values of the requested width.</summary>
|
|
public sealed record WirePmcRange(
|
|
short Area,
|
|
short DataType,
|
|
ushort Start,
|
|
ushort End,
|
|
IReadOnlyList<long> Values);
|
|
|
|
/// <summary>
|
|
/// One active alarm from <c>cnc_rdalmmsg2</c>. Mirrors the vendor <c>ODBALMMSG2</c>
|
|
/// layout; <see cref="AlarmGroup"/> is populated when the wire responder carries it
|
|
/// (currently <c>null</c> for both the 76-byte vendor shape and the 80-byte legacy
|
|
/// shape).
|
|
/// </summary>
|
|
public sealed record WireAlarm(
|
|
int AlarmNumber,
|
|
short Type,
|
|
short Axis,
|
|
short MessageLength,
|
|
string Message,
|
|
int? AlarmGroup = null);
|
|
|
|
/// <summary>
|
|
/// Executing-program identity from <c>cnc_exeprgname2</c>: the NUL-terminated name and
|
|
/// the trailing 32-bit O-number (null when the wire responder omits the trailing int).
|
|
/// </summary>
|
|
public sealed record WireProgramName(
|
|
string Name,
|
|
int? ONumber);
|
|
|
|
/// <summary>One cumulative timer reading from <c>cnc_rdtimer</c> (minutes + fractional milliseconds).</summary>
|
|
public sealed record WireTimer(
|
|
short Type,
|
|
int Minutes,
|
|
int Milliseconds);
|