using CliFx.Attributes; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli; /// /// Base for every FOCAS CLI command. Carries the CNC endpoint options /// (host / port / series) + exposes so each command /// can synthesise a with one device + one tag. /// public abstract class FocasCommandBase : DriverCommandBase { /// Gets the CNC IP address or hostname. [CommandOption("cnc-host", 'h', Description = "CNC IP address or hostname. FOCAS-over-EIP listens on port 8193 by default.", IsRequired = true)] public string CncHost { get; init; } = default!; /// Gets the FOCAS TCP port. [CommandOption("cnc-port", 'p', Description = "FOCAS TCP port (default 8193).")] public int CncPort { get; init; } = 8193; /// Gets the CNC series. [CommandOption("series", 's', Description = "CNC series: Unknown / Zero_i_D / Zero_i_F / Zero_i_MF / Zero_i_TF / Sixteen_i / " + "Thirty_i / ThirtyOne_i / ThirtyTwo_i / PowerMotion_i (default Unknown).")] public FocasCncSeries Series { get; init; } = FocasCncSeries.Unknown; /// Gets the per-operation timeout in milliseconds. [CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 2000).")] public int TimeoutMs { get; init; } = 2000; /// public override TimeSpan Timeout { get => TimeSpan.FromMilliseconds(TimeoutMs); init { /* driven by TimeoutMs */ } } /// Canonical FOCAS host-address string, shape focas://host:port. protected string HostAddress => $"focas://{CncHost}:{CncPort}"; /// /// Build a with the CNC target this base collected /// + the tag list a subclass supplies. Probe disabled; the driver's default managed /// wire client opens a TCP:8193 session to the CNC and surfaces unreachable endpoints /// as BadCommunicationError. /// /// The tag definitions to include in the driver options. /// A configured with a single device and the supplied tags. protected FocasDriverOptions BuildOptions(IReadOnlyList tags) => new() { Devices = [new FocasDeviceOptions( HostAddress: HostAddress, DeviceName: $"cli-{CncHost}:{CncPort}", Series: Series)], // v3: turn each typed def into the RawTagEntry delivery unit — TagConfig blob via // FocasTagDefinitionFactory.ToTagConfig, keyed by the def's synthesised name (RawPath), routed to // the device by the def's DeviceHostAddress (which the driver matches against Devices). RawTags = [.. tags.Select(t => new RawTagEntry( t.Name, FocasTagDefinitionFactory.ToTagConfig(t), t.WriteIdempotent, t.DeviceHostAddress))], Timeout = Timeout, Probe = new FocasProbeOptions { Enabled = false }, }; /// Gets the driver instance ID. protected string DriverInstanceId => $"focas-cli-{CncHost}:{CncPort}"; /// /// Validates numeric option ranges at the CLI boundary so a /// zero/negative --cnc-port, --timeout-ms, or --interval-ms /// surfaces as a clean rather than /// either an opaque downstream exception (invalid focas://host:<n> / /// zero TimeSpan) or a tight-spinning poll loop. The --interval-ms /// option is subscribe-only — pass null for probe/read/write so this /// helper can be a single shared validator. /// /// The interval in milliseconds, or null if not applicable. protected void ValidateOptions(int? intervalMs = null) { if (CncPort < 1 || CncPort > 65535) throw new CliFx.Exceptions.CommandException( $"--cnc-port must be in the range 1..65535 (got {CncPort})."); if (TimeoutMs <= 0) throw new CliFx.Exceptions.CommandException( $"--timeout-ms must be positive (got {TimeoutMs})."); if (intervalMs is { } iv && iv <= 0) throw new CliFx.Exceptions.CommandException( $"--interval-ms must be positive (got {iv})."); } }