using System.Runtime.InteropServices; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// /// P/Invoke surface for Fanuc FWLIB (Fwlib32.dll). Declarations extracted from /// fwlib32.h in the strangesast/fwlib repo; the licensed DLL itself is NOT shipped /// with OtOpcUa — the deployment places Fwlib32.dll next to the server executable /// or on PATH. /// /// /// Deliberately narrow — only the calls actually makes. /// FOCAS has 800+ functions in fwlib32.h; pulling in every one would bloat the /// P/Invoke surface + signal more coverage than this driver provides. Expand as capabilities /// are added. /// internal static class FwlibNative { private const string Library = "Fwlib32.dll"; // ---- Handle lifetime ---- /// Open an Ethernet FWLIB handle. Returns EW_OK (0) on success; handle written out. [DllImport(Library, EntryPoint = "cnc_allclibhndl3", CharSet = CharSet.Ansi, ExactSpelling = true)] public static extern short AllcLibHndl3( [MarshalAs(UnmanagedType.LPStr)] string ipaddr, ushort port, int timeout, out ushort handle); [DllImport(Library, EntryPoint = "cnc_freelibhndl", ExactSpelling = true)] public static extern short FreeLibHndl(ushort handle); // ---- PMC ---- /// PMC range read. is the ADR_* enum; is 0 byte / 1 word / 2 long. [DllImport(Library, EntryPoint = "pmc_rdpmcrng", ExactSpelling = true)] public static extern short PmcRdPmcRng( ushort handle, short addrType, short dataType, ushort startNumber, ushort endNumber, ushort length, ref IODBPMC buffer); [DllImport(Library, EntryPoint = "pmc_wrpmcrng", ExactSpelling = true)] public static extern short PmcWrPmcRng( ushort handle, ushort length, ref IODBPMC buffer); // ---- Parameters ---- [DllImport(Library, EntryPoint = "cnc_rdparam", ExactSpelling = true)] public static extern short RdParam( ushort handle, ushort number, short axis, short length, ref IODBPSD buffer); [DllImport(Library, EntryPoint = "cnc_wrparam", ExactSpelling = true)] public static extern short WrParam( ushort handle, short length, ref IODBPSD buffer); /// /// cnc_wrunlockparam — emit the connection-level password that lifts /// the parameter-protect / read-protect gate on certain firmwares (issue #271, /// plan PR F4-d). The Fanuc FOCAS reference describes the password buffer as /// a 4-byte binary array (the controller compares byte-for-byte). Returns the /// usual EW_* family — EW_PASSWD when the supplied bytes don't /// match the configured password. /// /// /// P/Invoke shape kept narrow: the caller passes a 4-byte buffer. The /// driver layer ASCII-encodes FocasDeviceOptions.Password into the /// buffer (right-padded with 0x00, truncated to 4 bytes) — that's the /// shape every public Fanuc password example we've seen uses. /// [DllImport(Library, EntryPoint = "cnc_wrunlockparam", ExactSpelling = true)] public static extern short WrUnlockParam( ushort handle, [In] byte[] password); // ---- Macro variables ---- [DllImport(Library, EntryPoint = "cnc_rdmacro", ExactSpelling = true)] public static extern short RdMacro( ushort handle, short number, short length, ref ODBM buffer); [DllImport(Library, EntryPoint = "cnc_wrmacro", ExactSpelling = true)] public static extern short WrMacro( ushort handle, short number, short length, int macroValue, short decimalPointCount); // ---- Status ---- [DllImport(Library, EntryPoint = "cnc_statinfo", ExactSpelling = true)] public static extern short StatInfo(ushort handle, ref ODBST buffer); // ---- Timers ---- /// /// cnc_rdtimer — read CNC running timers. : 0 = power-on /// time (ms), 1 = operating time (ms), 2 = cycle time (ms), 3 = cutting time (ms). /// Only the cycle-time variant is consumed today (issue #258); the call is generic /// so the surface can grow without another P/Invoke. /// [DllImport(Library, EntryPoint = "cnc_rdtimer", ExactSpelling = true)] public static extern short RdTimer(ushort handle, short type, ref IODBTMR buffer); // ---- Modal codes ---- /// /// cnc_modal — read modal information for one G-group or auxiliary code. /// : 1..21 = G-group N (single group), 100 = M, 101 = S, /// 102 = T, 103 = B (per Fanuc FOCAS reference). : 0 = /// active modal commands. We only consume types 100..103 today (M/S/T/B); the /// G-group decode is deferred to a follow-up because the ODBMDL union /// varies by group + series (issue #259). /// [DllImport(Library, EntryPoint = "cnc_modal", ExactSpelling = true)] public static extern short Modal(ushort handle, short type, short block, ref ODBMDL buffer); // ---- Tooling ---- /// /// cnc_rdtnum — read the currently selected tool number. Returns /// EW_OK + populates with the active T-code. /// Tool life + current offset index reads (cnc_rdtlinfo/cnc_rdtlsts/ /// cnc_rdtofs) are deferred per the F1-d plan — those calls use ODBTLIFE* /// unions whose shape varies per series. /// [DllImport(Library, EntryPoint = "cnc_rdtnum", ExactSpelling = true)] public static extern short RdToolNumber(ushort handle, ref IODBTNUM buffer); // ---- Work coordinate offsets ---- /// /// cnc_rdzofs — read one work-coordinate offset slot. : /// 1..6 = G54..G59 (standard). Extended G54.1 P1..P48 use cnc_rdzofsr /// and are deferred. : -1 = all axes returned, 1..N = single /// axis. : 12 + (N axes * 8) — we request -1 and let FWLIB /// fill up to 's 8-axis ceiling. /// [DllImport(Library, EntryPoint = "cnc_rdzofs", ExactSpelling = true)] public static extern short RdWorkOffset( ushort handle, short number, short axis, short length, ref IODBZOFS buffer); // ---- Operator messages ---- /// /// cnc_rdopmsg3 — read FANUC operator messages by class. : /// 0 = OPMSG (op-msg ladder/macro), 1 = MACRO, 2 = EXTERN (external operator message), /// 3 = REJ-EXT (rejected EXTERN). : per fwlib32.h the /// buffer is 4 + 256 = 260 bytes per message slot — single-slot reads (length 260) /// return the most-recent message in that class. Issue #261, plan PR F1-e. /// [DllImport(Library, EntryPoint = "cnc_rdopmsg3", CharSet = CharSet.Ansi, ExactSpelling = true)] public static extern short RdOpMsg3( ushort handle, short type, short length, ref OPMSG3 buffer); // ---- Figure (per-axis decimal scaling) ---- /// /// cnc_getfigure — read per-axis figure info (decimal-place counts + units). /// : 0 = absolute / relative / machine position figures, /// 1 = work-coord shift figures (per Fanuc reference). The reply struct holds /// up to axis entries; the managed side reads the count /// out via . Position values from cnc_absolute /// / cnc_machine / cnc_relative / cnc_distance / cnc_actf /// are scaled integers — divide by 10^figureinfo[axis].dec for user units /// (issue #262, plan PR F1-f). /// [DllImport(Library, EntryPoint = "cnc_getfigure", ExactSpelling = true)] public static extern short GetFigure( ushort handle, short kind, ref short outCount, ref IODBAXIS figureinfo); // ---- Diagnostics ---- /// /// cnc_rddiag — read a CNC diagnostic value. is the /// diagnostic number (e.g. 1031 = current alarm cause); is 0 /// for whole-CNC diagnostics or the 1-based axis index for per-axis diagnostics. /// is sized like — 4-byte header + /// widest payload (8 bytes for Float64). The shape of the payload depends on the /// diagnostic; the managed side decodes via on the /// configured tag (issue #263). /// [DllImport(Library, EntryPoint = "cnc_rddiag", ExactSpelling = true)] public static extern short RdDiag( ushort handle, ushort number, short axis, short length, ref IODBPSD buffer); // ---- Multi-path / multi-channel ---- /// /// cnc_rdpathnum — read the number of CNC paths (channels) the controller /// exposes + the currently-active path. Multi-path CNCs (lathe + sub-spindle, /// dual-turret) return 2..N; single-path CNCs return 1. The driver caches /// at connect and uses it to validate per-tag /// PathId values (issue #264). /// [DllImport(Library, EntryPoint = "cnc_rdpathnum", ExactSpelling = true)] public static extern short RdPathNum(ushort handle, ref ODBPATH buffer); /// /// cnc_setpath — switch the active CNC path (channel) for subsequent /// calls. is 1-based. The driver issues this before /// every read whose path differs from the last one set on the session; /// single-path tags (PathId=1 only) skip the call entirely (issue #264). /// [DllImport(Library, EntryPoint = "cnc_setpath", ExactSpelling = true)] public static extern short SetPath(ushort handle, short path); // ---- Currently-executing block ---- /// /// cnc_rdactpt — read the currently-executing program block text. The /// reply struct holds the program / sequence numbers + the active block as a /// null-padded ASCII string. Issue #261, plan PR F1-e. /// [DllImport(Library, EntryPoint = "cnc_rdactpt", CharSet = CharSet.Ansi, ExactSpelling = true)] public static extern short RdActPt(ushort handle, ref ODBACTPT buffer); // ---- Structs ---- /// /// IODBPMC — PMC range I/O buffer. 8-byte header + 40-byte union. We marshal the union /// as a fixed byte buffer + interpret per on the managed side. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBPMC { public short TypeA; public short TypeD; public ushort DatanoS; public ushort DatanoE; // 40-byte union: cdata[5] / idata[5] / ldata[5] / fdata[5] / dbdata[5] — dbdata is the widest. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] public byte[] Data; } /// /// IODBPSD — CNC parameter I/O buffer. Axis-aware; for non-axis parameters pass axis=0. /// Union payload is bytes / shorts / longs — we marshal 32 bytes as the widest slot. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBPSD { public short Datano; public short Type; // axis index (0 for non-axis) [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] Data; } /// ODBM — macro variable read buffer. Value = McrVal / 10^DecVal. [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ODBM { public short Datano; public short Dummy; public int McrVal; // long in C; 32-bit signed public short DecVal; // decimal-point count } /// /// IODBTMR — running-timer read buffer per fwlib32.h. Minute portion in /// ; sub-minute remainder in milliseconds in . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBTMR { public int Minute; public int Msec; } /// /// ODBMDL — single-group modal read buffer. 4-byte header + a 4-byte union which we /// marshal as a fixed byte array. For type=100..103 (M/S/T/B) the union holds an /// int aux_data at offset 0; we read the first short for symmetry with /// the FWLIB g_modal.aux_data width on G-group reads. The G-group decode /// (type=1..21) is deferred — see for context (issue #259). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ODBMDL { public short Datano; public short Type; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] Data; } /// /// IODBTNUM — current tool number read buffer. holds the active /// T-code (Fanuc reference uses long; we narrow to short on the /// managed side because surfaces as /// Int16). Issue #260, F1-d. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBTNUM { public short Datano; public short Type; public int Data; } /// /// IODBZOFS — work-coordinate offset read buffer. 4-byte header + per-axis /// OFSB blocks (8 bytes each: 4-byte signed integer data + 2-byte /// dec decimal-point count + 2-byte unit + 2-byte disp). /// We marshal a fixed ceiling of 8 axes (= 64 bytes); the managed side reads /// only the first 3 (X / Y / Z) per the F1-d effort sizing. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBZOFS { public short Datano; public short Type; // Up to 8 axes * 8 bytes per OFSB = 64 bytes. Each block: int data, short dec, // short unit, short disp (10 bytes per fwlib32.h). We size for the worst case. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)] public byte[] Data; } /// /// OPMSG3 — single-slot operator-message read buffer per fwlib32.h. Per Fanuc /// reference: short datano + short type + char data[256]. The /// text is null-terminated + space-padded; the managed side trims trailing nulls / /// spaces before publishing. Length = 4 + 256 = 260 bytes; total 256 wide enough /// for the longest documented operator message body (issue #261). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct OPMSG3 { public short Datano; public short Type; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public byte[] Data; } /// /// ODBACTPT — current-block read buffer per fwlib32.h. Per Fanuc reference: /// long o_no (currently active O-number) + long n_no (sequence) + /// char data[256] (active block text). The text is null-terminated + /// space-padded; trimmed before publishing for stable round-trip (issue #261). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ODBACTPT { public int ONo; public int NNo; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public byte[] Data; } /// /// Maximum axis count per the FWLIB fwlib32.h ceiling for figure-info reads. /// Real Fanuc CNCs cap at 8 simultaneous axes for most series; we marshal an /// 8-entry array (matches ) so the call completes regardless /// of the deployment's axis count (issue #262). /// public const int MAX_AXIS = 8; /// /// IODBAXIS — per-axis figure info read buffer for cnc_getfigure. Each /// axis entry carries the decimal-place count (dec) the CNC reports for /// that axis's increment system + a unit code. The managed side reads the first /// outCount entries returned by FWLIB; we marshal a fixed 8-entry ceiling /// (issue #262, plan PR F1-f). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct IODBAXIS { // Each entry per fwlib32.h is { short dec, short unit, short reserved, short reserved2 } // = 8 bytes. 8 axes * 8 bytes = 64 bytes; we marshal a fixed byte buffer + decode on // the managed side so axis-count growth doesn't churn the P/Invoke surface. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8 * 8)] public byte[] Data; } /// /// ODBPATH — cnc_rdpathnum reply. is the currently-active /// path (1-based); is the controller's path count. We consume /// at bootstrap to validate per-tag PathId; runtime path /// selection happens via (issue #264). /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ODBPATH { public short PathNo; public short MaxPath; } /// ODBST — CNC status info. Machine state, alarm flags, automatic / edit mode. [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ODBST { public short Dummy; public short TmMode; public short Aut; public short Run; public short Motion; public short Mstb; public short Emergency; public short Alarm; public short Edit; } } /// /// PMC address-letter → FOCAS ADR_* numeric code. Per Fanuc FOCAS/2 spec the codes /// are: G=0, F=1, Y=2, X=3, A=4, R=5, T=6, K=7, C=8, D=9, E=10. Exposed internally + /// tested so the FwlibFocasClient translation is verifiable without the DLL loaded. /// internal static class FocasPmcAddrType { public static short? FromLetter(string letter) => letter.ToUpperInvariant() switch { "G" => 0, "F" => 1, "Y" => 2, "X" => 3, "A" => 4, "R" => 5, "T" => 6, "K" => 7, "C" => 8, "D" => 9, "E" => 10, _ => null, }; } /// PMC data-type numeric codes per FOCAS/2: 0 = byte, 1 = word, 2 = long, 4 = float, 5 = double. internal static class FocasPmcDataType { public const short Byte = 0; public const short Word = 1; public const short Long = 2; public const short Float = 4; public const short Double = 5; public static short FromFocasDataType(FocasDataType t) => t switch { FocasDataType.Bit or FocasDataType.Byte => Byte, FocasDataType.Int16 => Word, FocasDataType.Int32 => Long, FocasDataType.Float32 => Float, FocasDataType.Float64 => Double, _ => Byte, }; }