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}).");
}
}