476 lines
20 KiB
C#
476 lines
20 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
|
|
|
/// <summary>
|
|
/// P/Invoke surface for Fanuc FWLIB (<c>Fwlib32.dll</c>). Declarations extracted from
|
|
/// <c>fwlib32.h</c> in the strangesast/fwlib repo; the licensed DLL itself is NOT shipped
|
|
/// with OtOpcUa — the deployment places <c>Fwlib32.dll</c> next to the server executable
|
|
/// or on <c>PATH</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Deliberately narrow — only the calls <see cref="FwlibFocasClient"/> actually makes.
|
|
/// FOCAS has 800+ functions in <c>fwlib32.h</c>; pulling in every one would bloat the
|
|
/// P/Invoke surface + signal more coverage than this driver provides. Expand as capabilities
|
|
/// are added.
|
|
/// </remarks>
|
|
internal static class FwlibNative
|
|
{
|
|
private const string Library = "Fwlib32.dll";
|
|
|
|
// ---- Handle lifetime ----
|
|
|
|
/// <summary>Open an Ethernet FWLIB handle. Returns EW_OK (0) on success; handle written out.</summary>
|
|
[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 ----
|
|
|
|
/// <summary>PMC range read. <paramref name="addrType"/> is the ADR_* enum; <paramref name="dataType"/> is 0 byte / 1 word / 2 long.</summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// <c>cnc_wrunlockparam</c> — 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 <c>EW_*</c> family — <c>EW_PASSWD</c> when the supplied bytes don't
|
|
/// match the configured password.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>P/Invoke shape kept narrow: the caller passes a 4-byte buffer. The
|
|
/// driver layer ASCII-encodes <c>FocasDeviceOptions.Password</c> into the
|
|
/// buffer (right-padded with <c>0x00</c>, truncated to 4 bytes) — that's the
|
|
/// shape every public Fanuc password example we've seen uses.</para>
|
|
/// </remarks>
|
|
[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 ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdtimer</c> — read CNC running timers. <paramref name="type"/>: 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.
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_rdtimer", ExactSpelling = true)]
|
|
public static extern short RdTimer(ushort handle, short type, ref IODBTMR buffer);
|
|
|
|
// ---- Modal codes ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_modal</c> — read modal information for one G-group or auxiliary code.
|
|
/// <paramref name="type"/>: 1..21 = G-group N (single group), 100 = M, 101 = S,
|
|
/// 102 = T, 103 = B (per Fanuc FOCAS reference). <paramref name="block"/>: 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 <c>ODBMDL</c> union
|
|
/// varies by group + series (issue #259).
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_modal", ExactSpelling = true)]
|
|
public static extern short Modal(ushort handle, short type, short block, ref ODBMDL buffer);
|
|
|
|
// ---- Tooling ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdtnum</c> — read the currently selected tool number. Returns
|
|
/// <c>EW_OK</c> + populates <see cref="IODBTNUM.Data"/> with the active T-code.
|
|
/// Tool life + current offset index reads (<c>cnc_rdtlinfo</c>/<c>cnc_rdtlsts</c>/
|
|
/// <c>cnc_rdtofs</c>) are deferred per the F1-d plan — those calls use ODBTLIFE*
|
|
/// unions whose shape varies per series.
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_rdtnum", ExactSpelling = true)]
|
|
public static extern short RdToolNumber(ushort handle, ref IODBTNUM buffer);
|
|
|
|
// ---- Work coordinate offsets ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdzofs</c> — read one work-coordinate offset slot. <paramref name="number"/>:
|
|
/// 1..6 = G54..G59 (standard). Extended <c>G54.1 P1..P48</c> use <c>cnc_rdzofsr</c>
|
|
/// and are deferred. <paramref name="axis"/>: -1 = all axes returned, 1..N = single
|
|
/// axis. <paramref name="length"/>: 12 + (N axes * 8) — we request -1 and let FWLIB
|
|
/// fill up to <see cref="IODBZOFS.Data"/>'s 8-axis ceiling.
|
|
/// </summary>
|
|
[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 ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdopmsg3</c> — read FANUC operator messages by class. <paramref name="type"/>:
|
|
/// 0 = OPMSG (op-msg ladder/macro), 1 = MACRO, 2 = EXTERN (external operator message),
|
|
/// 3 = REJ-EXT (rejected EXTERN). <paramref name="length"/>: per <c>fwlib32.h</c> the
|
|
/// buffer is <c>4 + 256 = 260</c> bytes per message slot — single-slot reads (length 260)
|
|
/// return the most-recent message in that class. Issue #261, plan PR F1-e.
|
|
/// </summary>
|
|
[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) ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_getfigure</c> — read per-axis figure info (decimal-place counts + units).
|
|
/// <paramref name="kind"/>: 0 = absolute / relative / machine position figures,
|
|
/// 1 = work-coord shift figures (per Fanuc reference). The reply struct holds
|
|
/// up to <see cref="MAX_AXIS"/> axis entries; the managed side reads the count
|
|
/// out via <paramref name="outCount"/>. Position values from <c>cnc_absolute</c>
|
|
/// / <c>cnc_machine</c> / <c>cnc_relative</c> / <c>cnc_distance</c> / <c>cnc_actf</c>
|
|
/// are scaled integers — divide by <c>10^figureinfo[axis].dec</c> for user units
|
|
/// (issue #262, plan PR F1-f).
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_getfigure", ExactSpelling = true)]
|
|
public static extern short GetFigure(
|
|
ushort handle,
|
|
short kind,
|
|
ref short outCount,
|
|
ref IODBAXIS figureinfo);
|
|
|
|
// ---- Diagnostics ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rddiag</c> — read a CNC diagnostic value. <paramref name="number"/> is the
|
|
/// diagnostic number (e.g. 1031 = current alarm cause); <paramref name="axis"/> is 0
|
|
/// for whole-CNC diagnostics or the 1-based axis index for per-axis diagnostics.
|
|
/// <paramref name="length"/> is sized like <see cref="RdParam"/> — 4-byte header +
|
|
/// widest payload (8 bytes for Float64). The shape of the payload depends on the
|
|
/// diagnostic; the managed side decodes via <see cref="FocasDataType"/> on the
|
|
/// configured tag (issue #263).
|
|
/// </summary>
|
|
[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 ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdpathnum</c> — 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
|
|
/// <see cref="ODBPATH.MaxPath"/> at connect and uses it to validate per-tag
|
|
/// <c>PathId</c> values (issue #264).
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_rdpathnum", ExactSpelling = true)]
|
|
public static extern short RdPathNum(ushort handle, ref ODBPATH buffer);
|
|
|
|
/// <summary>
|
|
/// <c>cnc_setpath</c> — switch the active CNC path (channel) for subsequent
|
|
/// calls. <paramref name="path"/> 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).
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_setpath", ExactSpelling = true)]
|
|
public static extern short SetPath(ushort handle, short path);
|
|
|
|
// ---- Currently-executing block ----
|
|
|
|
/// <summary>
|
|
/// <c>cnc_rdactpt</c> — 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.
|
|
/// </summary>
|
|
[DllImport(Library, EntryPoint = "cnc_rdactpt", CharSet = CharSet.Ansi, ExactSpelling = true)]
|
|
public static extern short RdActPt(ushort handle, ref ODBACTPT buffer);
|
|
|
|
// ---- Structs ----
|
|
|
|
/// <summary>
|
|
/// IODBPMC — PMC range I/O buffer. 8-byte header + 40-byte union. We marshal the union
|
|
/// as a fixed byte buffer + interpret per <see cref="FocasDataType"/> on the managed side.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>ODBM — macro variable read buffer. Value = <c>McrVal / 10^DecVal</c>.</summary>
|
|
[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
|
|
}
|
|
|
|
/// <summary>
|
|
/// IODBTMR — running-timer read buffer per <c>fwlib32.h</c>. Minute portion in
|
|
/// <see cref="Minute"/>; sub-minute remainder in milliseconds in <see cref="Msec"/>.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct IODBTMR
|
|
{
|
|
public int Minute;
|
|
public int Msec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <c>int aux_data</c> at offset 0; we read the first <c>short</c> for symmetry with
|
|
/// the FWLIB <c>g_modal.aux_data</c> width on G-group reads. The G-group decode
|
|
/// (type=1..21) is deferred — see <see cref="Modal"/> for context (issue #259).
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct ODBMDL
|
|
{
|
|
public short Datano;
|
|
public short Type;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
public byte[] Data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// IODBTNUM — current tool number read buffer. <see cref="Data"/> holds the active
|
|
/// T-code (Fanuc reference uses <c>long</c>; we narrow to <c>short</c> on the
|
|
/// managed side because <see cref="FocasToolingInfo.CurrentTool"/> surfaces as
|
|
/// <c>Int16</c>). Issue #260, F1-d.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct IODBTNUM
|
|
{
|
|
public short Datano;
|
|
public short Type;
|
|
public int Data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// IODBZOFS — work-coordinate offset read buffer. 4-byte header + per-axis
|
|
/// <c>OFSB</c> blocks (8 bytes each: 4-byte signed integer <c>data</c> + 2-byte
|
|
/// <c>dec</c> decimal-point count + 2-byte <c>unit</c> + 2-byte <c>disp</c>).
|
|
/// 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.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// OPMSG3 — single-slot operator-message read buffer per <c>fwlib32.h</c>. Per Fanuc
|
|
/// reference: <c>short datano</c> + <c>short type</c> + <c>char data[256]</c>. 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).
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct OPMSG3
|
|
{
|
|
public short Datano;
|
|
public short Type;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
|
|
public byte[] Data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ODBACTPT — current-block read buffer per <c>fwlib32.h</c>. Per Fanuc reference:
|
|
/// <c>long o_no</c> (currently active O-number) + <c>long n_no</c> (sequence) +
|
|
/// <c>char data[256]</c> (active block text). The text is null-terminated +
|
|
/// space-padded; trimmed before publishing for stable round-trip (issue #261).
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct ODBACTPT
|
|
{
|
|
public int ONo;
|
|
public int NNo;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
|
|
public byte[] Data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maximum axis count per the FWLIB <c>fwlib32.h</c> ceiling for figure-info reads.
|
|
/// Real Fanuc CNCs cap at 8 simultaneous axes for most series; we marshal an
|
|
/// 8-entry array (matches <see cref="IODBAXIS"/>) so the call completes regardless
|
|
/// of the deployment's axis count (issue #262).
|
|
/// </summary>
|
|
public const int MAX_AXIS = 8;
|
|
|
|
/// <summary>
|
|
/// IODBAXIS — per-axis figure info read buffer for <c>cnc_getfigure</c>. Each
|
|
/// axis entry carries the decimal-place count (<c>dec</c>) the CNC reports for
|
|
/// that axis's increment system + a unit code. The managed side reads the first
|
|
/// <c>outCount</c> entries returned by FWLIB; we marshal a fixed 8-entry ceiling
|
|
/// (issue #262, plan PR F1-f).
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ODBPATH — <c>cnc_rdpathnum</c> reply. <see cref="PathNo"/> is the currently-active
|
|
/// path (1-based); <see cref="MaxPath"/> is the controller's path count. We consume
|
|
/// <see cref="MaxPath"/> at bootstrap to validate per-tag PathId; runtime path
|
|
/// selection happens via <see cref="SetPath"/> (issue #264).
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct ODBPATH
|
|
{
|
|
public short PathNo;
|
|
public short MaxPath;
|
|
}
|
|
|
|
/// <summary>ODBST — CNC status info. Machine state, alarm flags, automatic / edit mode.</summary>
|
|
[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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// PMC address-letter → FOCAS <c>ADR_*</c> 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.
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
|
|
/// <summary>PMC data-type numeric codes per FOCAS/2: 0 = byte, 1 = word, 2 = long, 4 = float, 5 = double.</summary>
|
|
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,
|
|
};
|
|
}
|