namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// /// Wire-layer abstraction over one FOCAS session to a CNC. The driver holds one per /// configured device; lifetime matches the device. /// /// /// The default implementation is — a pure-managed /// FOCAS/2 Ethernet client that speaks the wire protocol directly on TCP:8193. No /// P/Invoke, no native DLLs, no out-of-process isolation. /// /// is a scaffolding backend that /// throws on — selected by /// "Backend": "unimplemented" so a DriverInstance row can be seeded before the CNC /// endpoint is reachable without silently reading stale data. /// public interface IFocasClient : IDisposable { /// Open the FWLIB handle + TCP session. Idempotent. Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken); /// True when the FWLIB handle is valid + the socket is up. bool IsConnected { get; } /// /// Read the value at in the requested /// . Returns a boxed .NET value + the OPC UA status mapped /// through . /// Task<(object? value, uint status)> ReadAsync( FocasAddress address, FocasDataType type, CancellationToken cancellationToken); /// /// Write to . Returns the mapped /// OPC UA status (0 = Good). /// Task WriteAsync( FocasAddress address, FocasDataType type, object? value, CancellationToken cancellationToken); /// /// Cheap health probe — e.g. cnc_rdcncstat. Returns true when the CNC /// responds with any valid status. /// Task ProbeAsync(CancellationToken cancellationToken); /// /// Read active alarm messages from the CNC via cnc_rdalmmsg2. Returns /// zero-or-more active alarms. Null / empty list means "no alarms currently /// active". IAlarmSource projection polls this at a configurable interval + /// emits transitions (raise / clear) on the driver's OnAlarmEvent. /// Task> ReadAlarmsAsync(CancellationToken cancellationToken); // ---- Fixed-tree T1 (identity + axis discovery + fast-poll dynamic bundle) ---- /// /// Read CNC identity via cnc_sysinfo. Populates the Identity/* /// subtree of the fixed-node surface. Callable once at session open; the /// values don't change across the session. /// Task GetSysInfoAsync(CancellationToken cancellationToken); /// /// Read the CNC's configured axis names via cnc_rdaxisname. The driver /// uses these to build the Axes/{name}/ subtree and to index /// calls. /// Task> GetAxisNamesAsync(CancellationToken cancellationToken); /// /// Read the CNC's configured spindle names via cnc_rdspdlname. Drives /// the Spindle/{name}/ subtree. /// Task> GetSpindleNamesAsync(CancellationToken cancellationToken); /// /// Read the fast-poll dynamic bundle for one axis via cnc_rddynamic2. /// Returns the current position quadruple (absolute / machine / relative / /// distance-to-go) plus actual feed rate + actual spindle speed + alarm /// flags + program / sequence numbers — one network round-trip per call. /// Task ReadDynamicAsync(int axisIndex, CancellationToken cancellationToken); // ---- Fixed-tree T2 (program + operation mode) ---- /// /// Aggregate program + operation-mode snapshot. One wire round-trip per /// underlying FWLIB call — cnc_rdblkcount, cnc_exeprgname2, /// cnc_rdopmode. The driver polls this on a slower cadence than /// since program / mode transitions happen /// on human-operator timescales. /// Task GetProgramInfoAsync(CancellationToken cancellationToken); // ---- Fixed-tree T3 (timers) ---- /// /// Read one CNC cumulative timer. Kind selects PowerOn / Operating / Cutting / /// Cycle. Values are seconds — the managed side already converted the native /// minute+msec representation so downstream nodes display uniform units. /// Task GetTimerAsync(FocasTimerKind kind, CancellationToken cancellationToken); // ---- Fixed-tree T3.5 (servo meters) ---- /// /// Read the servo-load meter percentages across all configured axes. /// Values are percentages (scaled by 10^Dec). Empty list on a /// disconnected session or unsupported CNC. /// Task> GetServoLoadsAsync(CancellationToken cancellationToken); // ---- Fixed-tree T3.6 (spindle meters) ---- /// /// Read per-spindle load percentages. Result list index corresponds to /// spindle index from . Empty list on a /// disconnected session or when the CNC doesn't support the call (older /// series like 16i may return EW_FUNC). /// Task> GetSpindleLoadsAsync(CancellationToken cancellationToken); /// /// Read per-spindle maximum RPM values. Static configuration, fetched once at /// bootstrap. Index alignment as per . /// Task> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken); } /// One servo-meter entry — one axis's current load percentage. public sealed record FocasServoLoad(string AxisName, double LoadPercent); /// Which cumulative counter reads. public enum FocasTimerKind { /// Machine power-on hours — resets never. PowerOn = 0, /// Cycle operating time — resets when the operator clears the counter. Operating = 1, /// Cutting time — only counts while in cutting feed. Cutting = 2, /// Cycle time since the last program start. Cycle = 3, } /// One cumulative timer reading. is the canonical unit. public sealed record FocasTimer(FocasTimerKind Kind, int Minutes, int Milliseconds) { /// Cumulative time in seconds — Minutes * 60 + Milliseconds / 1000. public double TotalSeconds => Minutes * 60.0 + Milliseconds / 1000.0; } /// /// CNC identity snapshot from cnc_sysinfo. Strings are trimmed ASCII. /// public sealed record FocasSysInfo( int AddInfo, int MaxAxis, string CncType, // "M" (mill) / "T" (lathe) string MtType, string Series, // e.g. "30i" string Version, // e.g. "A1.0" int AxesCount); /// One configured axis name (e.g. "X", "X1"). public sealed record FocasAxisName(string Name, string Suffix) { /// /// Display name — name + suffix concatenated, trimmed. Empty suffix yields /// just the name (the common case on single-channel CNCs). /// public string Display => string.IsNullOrEmpty(Suffix) ? Name : $"{Name}{Suffix}"; } /// One configured spindle name (e.g. "S1"). public sealed record FocasSpindleName(string Name, string Suffix1, string Suffix2, string Suffix3) { public string Display { get { var s = Name + Suffix1 + Suffix2 + Suffix3; return s.TrimEnd('\0', ' '); } } } /// /// Fast-poll bundle for one axis. Position values are scaled integers; the caller /// divides by 10^DecimalPlaces to get the decimal value. DecimalPlaces is /// currently left to the caller to supply (via device config or a future /// cnc_getfigure path once that export lands). /// /// /// Program + operation-mode snapshot. Name is the currently-executing /// program filename (e.g. "O0001.NC"); ONumber is its Fanuc O-number (1-9999). /// Mode is the numeric code from cnc_rdopmode — see . /// public sealed record FocasProgramInfo( string Name, int ONumber, int BlockCount, int Mode); /// Human-readable text for the integer. public static class FocasOpMode { public static string ToText(int mode) => mode switch { 0 => "MDI", 1 => "AUTO", 2 => "TJOG", 3 => "EDIT", 4 => "HANDLE", 5 => "JOG", 6 => "TEACH_IN_HANDLE", 7 => "REFERENCE", 8 => "REMOTE", 9 => "TEST", _ => $"Mode{mode}", }; } public sealed record FocasDynamicSnapshot( int AxisIndex, int AlarmFlags, int ProgramNumber, int MainProgramNumber, int SequenceNumber, int ActualFeedRate, int ActualSpindleSpeed, int AbsolutePosition, int MachinePosition, int RelativePosition, int DistanceToGo); /// /// One active alarm surfaced by . Shape /// mirrors ODBALMMSG2 but normalises the message bytes to a .NET string. /// public sealed record FocasActiveAlarm( int AlarmNumber, short Type, short Axis, string Message); /// Factory for s. One client per configured device. public interface IFocasClientFactory { IFocasClient Create(); } /// /// Scaffolding factory — throws on so a DriverInstance row can be /// seeded ahead of the CNC endpoint being reachable without silently reading stale data. /// Select via "Backend": "unimplemented" in driver config. Flip to /// "Backend": "wire" once the CNC is provisioned. /// public sealed class UnimplementedFocasClientFactory : IFocasClientFactory { public IFocasClient Create() => throw new NotSupportedException( "FOCAS driver backend is 'unimplemented'. Switch to 'Backend: \"wire\"' in driver config " + "once the CNC is provisioned — see docs/drivers/FOCAS.md."); } /// /// Well-known FOCAS alarm types from fwlib32.h ALM_TYPE_*. Narrow subset — /// the full list is ~15 types per model; these cover the universally-present categories. /// /// /// Constants are typed so they match the wire field width on /// cnc_rdalmmsg2 (and so 's switch (short) /// statements compile against a matching type rather than relying on implicit int→short /// narrowing on the constants). /// public static class FocasAlarmType { /// Pass to -equivalent to mean "any type". public const short All = -1; public const short Parameter = 0; // ALM_P public const short PulseCode = 1; // ALM_Y (servo) public const short Overtravel = 2; // ALM_O public const short Overheat = 3; // ALM_H public const short Servo = 4; // ALM_S public const short DataIo = 5; // ALM_T public const short MemoryCheck = 6; // ALM_M public const short MacroAlarm = 13; // ALM_MC — used by #3006 etc. }