namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; /// /// Parsed ab://gateway[:port]/cip-path host-address string for AB Legacy devices. /// Same format as AbCip — PCCC-over-EIP uses the same gateway + optional routing path as /// the CIP family (a PLC-5 bridged through a ControlLogix chassis takes the full CIP path; /// a direct-wired SLC 500 uses an empty path). /// /// /// Parser duplicated from AbCipHostAddress rather than shared because the two drivers ship /// independently + a shared helper would force a reference between them. If a third AB /// driver appears, extract into Core.Abstractions. /// public sealed record AbLegacyHostAddress(string Gateway, int Port, string CipPath) { public const int DefaultEipPort = 44818; public override string ToString() => Port == DefaultEipPort ? $"ab://{Gateway}/{CipPath}" : $"ab://{Gateway}:{Port}/{CipPath}"; public static AbLegacyHostAddress? TryParse(string? value) { if (string.IsNullOrWhiteSpace(value)) return null; const string prefix = "ab://"; if (!value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null; var remainder = value[prefix.Length..]; var slashIdx = remainder.IndexOf('/'); if (slashIdx < 0) return null; var authority = remainder[..slashIdx]; var cipPath = remainder[(slashIdx + 1)..]; if (string.IsNullOrEmpty(authority)) return null; var port = DefaultEipPort; var colonIdx = authority.LastIndexOf(':'); string gateway; if (colonIdx >= 0) { gateway = authority[..colonIdx]; if (!int.TryParse(authority[(colonIdx + 1)..], out port) || port is <= 0 or > 65535) return null; } else { gateway = authority; } if (string.IsNullOrEmpty(gateway)) return null; return new AbLegacyHostAddress(gateway, port, cipPath); } }