namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; /// /// Return envelope over a parsed wire response. carries the FOCAS /// EW_* code from the response block — 0 / means the /// call succeeded and is populated; non-zero means the CNC rejected /// the call and is default. Callers use the RC to distinguish /// "feature missing on this series" (EW_FUNC / EW_NOOPT) from genuine /// failures. /// public readonly record struct FocasResult(short Rc, T? Value) { /// True when is zero (EW_OK). public bool IsOk => Rc == 0; } /// CNC identity payload returned by cnc_sysinfo. public sealed record WireSysInfo( short AddInfo, short MaxAxis, string CncType, string MachineType, string Series, string Version, string Axes); /// Coarse CNC state bits returned by cnc_statinfo — the seven-word status block plus TM mode. public sealed record WireStatus( short Auto, short Run, short Motion, short Mstb, short Emergency, short Alarm, short Edit, short TmMode); /// /// Four-slot position quadruple for one axis: absolute / machine / relative / distance-to-go, /// each carrying the scale its own 0x0026 block reported. /// public sealed record WireAxisPosition( FocasPosition Absolute, FocasPosition Machine, FocasPosition Relative, FocasPosition Distance); /// /// Fast-poll bundle for one axis from cnc_rddynamic2 — alarm flags, active program /// numbers, sequence number, actual feed rate, actual spindle speed, and the position /// quadruple. /// public sealed record WireDynamic( int Alarm, int ProgramNumber, int MainProgramNumber, int SequenceNumber, int FeedRate, int SpindleSpeed, WireAxisPosition Axis); /// /// The self-describing (value, base, exponent) envelope shared by every value record on /// the FOCAS wire — cnc_rddynamic2 positions, servo meters, spindle meters: /// scaled = value / base^exponent, where only base 2 and base 10 are meaningful. /// internal static class FocasValueEnvelope { /// /// Largest exponent that can describe a real value. An i32 carries at most ten digits, so /// anything beyond is a misread record — and value / 10^400 would be /// value / Infinity = 0.0, a plausible Good zero. /// public const int MaxExponent = 18; /// /// True when the record describes a usable value. A fixed multi-slot buffer's slots past the /// configured axis/spindle count hold unrelated memory, which shows up exactly here: a base /// that is neither 2 nor 10, or an absurd exponent (incl. the 0xFFFF vacant marker). /// /// The record's base field. /// The record's exponent field. /// Whether the record may be published. public static bool IsValid(int @base, int exponent) => exponent <= MaxExponent && @base is 2 or 10; /// value / base^exponent, or null when is false. /// The raw value. /// The record's base field. /// The record's exponent field. /// The scaled value, or null. public static double? Scale(int value, int @base, int exponent) => !IsValid(@base, exponent) ? null : exponent == 0 ? value : value / Math.Pow(@base, exponent); } /// /// One servo-meter entry from cnc_rdsvmeter (0x0056): one axis's load. The 8-byte record /// is [+0..3] i32 value, [+4..5] u16 base, [+6..7] u16 exponent and the load /// is a direct percentage, = value / base^exponent. /// /// /// Offset 4 used to be read as a decimal count ("data / 10^dec"). It is the base — a /// steady 10 — so every load was divided by 10^10 and published ~0 (a real Makino cutting at /// 20,000 rpm read servo 0% on every axis). Found by the sister ignitionoee driver, /// Focas.Wire-019 (v0.17.2). /// /// 1-based axis slot. /// Axis name from the companion 0x0089 block (empty when none). /// Raw load value. /// Scale base (2 or 10 when meaningful). /// Scale exponent. public sealed record WireServoMeter( short Index, string Name, int Value, int Base, int Exponent) { /// False for a record whose envelope describes no usable value (buffer residue). public bool IsValid => FocasValueEnvelope.IsValid(Base, Exponent); /// The load percentage, Value / Base^Exponent; null when is false. public double? Scaled => FocasValueEnvelope.Scale(Value, Base, Exponent); } /// /// One spindle-meter slot from cnc_rdspmeter (0x0040; type 0 = load, type 1 = motor /// speed). Same 8-byte envelope as , but the scaled value is a /// fraction of full scale in 1/16384 units — see . /// /// 1-based spindle slot. /// Raw meter value, in 1/16384-of-full-scale units. /// Scale base. /// Scale exponent. public sealed record WireSpindleMetric( short Index, int Value, int Base, int Exponent) { /// /// The meter's full-scale count: 16384 = 2^14 = 100% of rated load (type 0) or of /// maximum spindle speed (type 1). Settled on the real Z-34184 by regressing the type-1 /// meter against cnc_rddynamic2 spindle speed over 596–20,000 rpm: /// rpm = 1.220749 × meter, 16384 × 1.220749 = 20000.75 (Focas.Wire-019). /// public const int FullScale = 1 << 14; /// False for a record whose envelope describes no usable value (buffer residue). public bool IsValid => FocasValueEnvelope.IsValid(Base, Exponent); /// /// The meter as a percentage — (Value / Base^Exponent) × 100 / 16384 — or null when /// is false. Values above 100 are real (short-term overload; the i16 /// record clamps one count short of 200%). /// public double? Percent => FocasValueEnvelope.Scale(Value, Base, Exponent) is { } scaled ? scaled * 100.0 / FullScale : null; } /// /// One axis-name slot from cnc_rdaxisname. is the 1-based /// axis index (preserved even when the name is empty so callers can pass it to /// cnc_rddynamic2). /// public readonly record struct WireAxisRecord(short Index, string Name); /// One spindle-name slot from cnc_rdspdlname. public readonly record struct WireSpindleRecord(short Index, string Name); /// Parameter value returned by cnc_rdparam, interpreted as a scalar Int32. public sealed record WireParameter( short DataNumber, short Type, int Value); /// /// Macro variable from cnc_rdmacro. Scaled decimal: the real value is /// Value / 10^Decimal (). FANUC normalises every macro to a /// 9-significant-digit mantissa, so alone is never a value to publish /// (live: #502 = 270 arrives as 270000000 / 10^6). /// /// Macro variable number (the request argument; not carried in the payload). /// The 9-significant-digit mantissa. /// Implied decimal places; negative (0xFFFF on the wire) = vacant. public sealed record WireMacro( short Number, int Value, short Decimal) { /// /// True when the CNC reported the variable as vacant (undefined / never set): /// dec_val == 0xFFFF. A vacant macro's mantissa is 0, indistinguishable from a /// genuine 0, so this must be tested before means anything. Any /// negative decimal is treated as vacant — no other negative has ever been observed and /// failing closed is the safe reading. /// public bool IsVacant => Decimal < 0; /// The real value, Value / 10^Decimal; null when . public double? Scaled => IsVacant ? null : Decimal == 0 ? Value : Value / Math.Pow(10.0, Decimal); } /// PMC range read-back from pmc_rdpmcrng: one or more values of the requested width. public sealed record WirePmcRange( short Area, short DataType, ushort Start, ushort End, IReadOnlyList Values); /// /// One active alarm from cnc_rdalmmsg2. Mirrors the vendor ODBALMMSG2 /// layout; is populated when the wire responder carries it /// (currently null for both the 76-byte vendor shape and the 80-byte legacy /// shape). /// public sealed record WireAlarm( int AlarmNumber, short Type, short Axis, short MessageLength, string Message, int? AlarmGroup = null); /// /// Executing-program identity from cnc_exeprgname2: the NUL-terminated name and /// the trailing 32-bit O-number (null when the wire responder omits the trailing int). /// public sealed record WireProgramName( string Name, int? ONumber); /// One cumulative timer reading from cnc_rdtimer (minutes + fractional milliseconds). public sealed record WireTimer( short Type, int Minutes, int Milliseconds);