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.
}