namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// /// Documented-API capability matrix — per CNC series, what ranges each /// supports. Authoritative source for the driver's /// pre-flight validation in . /// /// /// Ranges come from the Fanuc FOCAS Developer Kit documentation matrix /// (see docs/v2/focas-version-matrix.md for the authoritative copy with /// per-function citations). Numbers chosen to match what the FOCAS library /// accepts — a read against an address outside the documented range returns /// EW_NUMBER or EW_PARAM at the wire, which this driver maps to /// BadOutOfRange. Catching at init time surfaces the mismatch as a config /// error before any session is opened. /// is treated permissively: every /// address passes validation. Pre-matrix configs don't break on upgrade; new /// deployments are encouraged to declare a series in the device options. /// public static class FocasCapabilityMatrix { /// /// Check whether is accepted by a CNC of /// . Returns null on pass + a failure reason /// on reject — the driver surfaces the reason string verbatim when failing /// InitializeAsync so operators see the specific out-of-range without /// guessing. /// public static string? Validate(FocasCncSeries series, FocasAddress address) { if (series == FocasCncSeries.Unknown) return null; return address.Kind switch { FocasAreaKind.Macro => ValidateMacro(series, address.Number), FocasAreaKind.Parameter => ValidateParameter(series, address.Number), FocasAreaKind.Pmc => ValidatePmc(series, address.PmcLetter, address.Number), FocasAreaKind.Diagnostic => ValidateDiagnostic(series, address.Number), _ => null, }; } /// Macro variable number accepted by a CNC series. Cites /// cnc_rdmacro/cnc_wrmacro in the Developer Kit. internal static (int min, int max) MacroRange(FocasCncSeries series) => series switch { // Common macros 1-33 + 100-199 + 500-999 universally; extended 10000+ only on // higher-end series. Using the extended ceiling per series per DevKit notes. FocasCncSeries.Sixteen_i => (0, 999), FocasCncSeries.Zero_i_D => (0, 999), FocasCncSeries.Zero_i_F or FocasCncSeries.Zero_i_MF or FocasCncSeries.Zero_i_TF => (0, 9999), FocasCncSeries.Thirty_i or FocasCncSeries.ThirtyOne_i or FocasCncSeries.ThirtyTwo_i => (0, 99999), FocasCncSeries.PowerMotion_i => (0, 999), _ => (0, int.MaxValue), }; /// Parameter number accepted; from cnc_rdparam/cnc_wrparam. /// Ranges reflect the highest-numbered parameter documented per series. internal static (int min, int max) ParameterRange(FocasCncSeries series) => series switch { FocasCncSeries.Sixteen_i => (0, 9999), FocasCncSeries.Zero_i_D or FocasCncSeries.Zero_i_F or FocasCncSeries.Zero_i_MF or FocasCncSeries.Zero_i_TF => (0, 14999), FocasCncSeries.Thirty_i or FocasCncSeries.ThirtyOne_i or FocasCncSeries.ThirtyTwo_i => (0, 29999), FocasCncSeries.PowerMotion_i => (0, 29999), _ => (0, int.MaxValue), }; /// /// CNC diagnostic number range accepted by a series; from cnc_rddiag /// (and cnc_rddiagdgn for axis-scoped reads). Returning null /// means the series doesn't support cnc_rddiag at all — the driver /// rejects every DIAG: address on that series. Conservative ceilings /// per the FOCAS Developer Kit: legacy 16i-family caps at 499; modern 0i-F /// family at 999; 30i / 31i / 32i extend to 1023. Power Motion i has a /// narrow diagnostic surface (0..255). /// internal static (int min, int max)? DiagnosticRange(FocasCncSeries series) => series switch { FocasCncSeries.Sixteen_i => (0, 499), FocasCncSeries.Zero_i_D => (0, 499), FocasCncSeries.Zero_i_F or FocasCncSeries.Zero_i_MF or FocasCncSeries.Zero_i_TF => (0, 999), FocasCncSeries.Thirty_i or FocasCncSeries.ThirtyOne_i or FocasCncSeries.ThirtyTwo_i => (0, 1023), FocasCncSeries.PowerMotion_i => (0, 255), _ => (0, int.MaxValue), }; /// PMC letters accepted per series. Legacy controllers omit F/M/C /// signal groups that 30i-family ladder programs use. internal static IReadOnlySet PmcLetters(FocasCncSeries series) => series switch { FocasCncSeries.Sixteen_i => new HashSet(StringComparer.OrdinalIgnoreCase) { "X", "Y", "R", "D" }, FocasCncSeries.Zero_i_D => new HashSet(StringComparer.OrdinalIgnoreCase) { "X", "Y", "R", "D", "E", "A" }, FocasCncSeries.Zero_i_F or FocasCncSeries.Zero_i_MF or FocasCncSeries.Zero_i_TF => new HashSet(StringComparer.OrdinalIgnoreCase) { "X", "Y", "F", "G", "R", "D", "E", "A", "M", "C" }, FocasCncSeries.Thirty_i or FocasCncSeries.ThirtyOne_i or FocasCncSeries.ThirtyTwo_i => new HashSet(StringComparer.OrdinalIgnoreCase) { "X", "Y", "F", "G", "R", "D", "E", "A", "M", "C", "K", "T" }, FocasCncSeries.PowerMotion_i => new HashSet(StringComparer.OrdinalIgnoreCase) { "X", "Y", "R", "D" }, _ => new HashSet(StringComparer.OrdinalIgnoreCase), }; /// PMC address-number ceiling per series. Multiplied by 8 to get bit /// count since PMC addresses are byte-addressed on read + bit-addressed on /// write — FocasAddress carries the bit separately. internal static int PmcMaxNumber(FocasCncSeries series) => series switch { FocasCncSeries.Sixteen_i => 999, FocasCncSeries.Zero_i_D => 1999, FocasCncSeries.Zero_i_F or FocasCncSeries.Zero_i_MF or FocasCncSeries.Zero_i_TF => 9999, FocasCncSeries.Thirty_i or FocasCncSeries.ThirtyOne_i or FocasCncSeries.ThirtyTwo_i => 59999, FocasCncSeries.PowerMotion_i => 1999, _ => int.MaxValue, }; /// /// Whether the FOCAS driver should expose the per-device Tooling/ /// fixed-tree subfolder for a given . Backed by /// cnc_rdtnum, which is documented for every modern Fanuc series /// (0i / 16i / 30i families) — defaulting to true. The capability /// hook exists so a future controller without cnc_rdtnum can opt /// out without touching the driver. /// stays permissive (matches the modal / override fixed-tree precedent in /// issue #259). Issue #260. /// public static bool SupportsTooling(FocasCncSeries series) => true; /// /// Whether the FOCAS driver should expose the per-device Offsets/ /// fixed-tree subfolder for a given . Backed by /// cnc_rdzofs(n=1..6) for the standard G54..G59 surfaces; extended /// G54.1 P1..P48 surfaces are deferred to a follow-up. Same permissive /// policy as . Issue #260. /// public static bool SupportsWorkOffsets(FocasCncSeries series) => true; private static string? ValidateMacro(FocasCncSeries series, int number) { var (min, max) = MacroRange(series); return (number < min || number > max) ? $"Macro variable #{number} is outside the documented range [{min}, {max}] for {series}." : null; } private static string? ValidateParameter(FocasCncSeries series, int number) { var (min, max) = ParameterRange(series); return (number < min || number > max) ? $"Parameter #{number} is outside the documented range [{min}, {max}] for {series}." : null; } private static string? ValidateDiagnostic(FocasCncSeries series, int number) { if (DiagnosticRange(series) is not { } range) return $"Diagnostic addresses are not supported on {series} (no documented cnc_rddiag range)."; var (min, max) = range; return (number < min || number > max) ? $"Diagnostic #{number} is outside the documented range [{min}, {max}] for {series}." : null; } private static string? ValidatePmc(FocasCncSeries series, string? letter, int number) { if (string.IsNullOrEmpty(letter)) return "PMC address is missing its letter prefix."; var letters = PmcLetters(series); if (!letters.Contains(letter)) { var letterList = string.Join(", ", letters); return $"PMC letter '{letter}' is not supported on {series}. Accepted: {{{letterList}}}."; } var max = PmcMaxNumber(series); return number > max ? $"PMC address {letter}{number} is outside the documented range [0, {max}] for {series}." : null; } }