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,
};
}