8b2c3fa04c
- Rename FocasEquipmentTagParser -> FocasTagDefinitionFactory; TryParse ->
FromTagConfig(tagConfig, rawPath, out def). Drop leading-{ heuristic + the
deviceHostAddress blob key; def.Name = rawPath. Add inverse ToTagConfig.
writable: absent -> read-only, explicit true honoured (note preserved).
- FocasDriverOptions.Tags -> RawTags (IReadOnlyList<RawTagEntry>).
- FocasDriver: _tagsByRawPath (Ordinal); byName-only resolver; build table from
RawTags via FromTagConfig; multi-device routing threads RawTagEntry.DeviceName
-> ResolveDeviceHost match against options.Devices -> def.DeviceHostAddress
(TODO v3 WaveC: live host from Device row DeviceConfig). Tolerant per-tag
skip+log (mapper/address/matrix miss -> BadNodeIdUnknown); unknown device
fails init fast.
- Factory: bind List<RawTagEntry> RawTags; retire FocasTagDto/ParseDataType.
- CLI FocasCommandBase.BuildOptions -> RawTags via ToTagConfig + DeviceName.
- Tests: FocasRawTags helper; migrate Tags= -> RawTags=; rename parser tests to
FromTagConfig; rewrite retired blob-ref tests (equipment-tag/capability-gate/
resolve-host) onto authored RawPath. 272 driver + 52 CLI green.
93 lines
4.5 KiB
C#
93 lines
4.5 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Base for every FOCAS CLI command. Carries the CNC endpoint options
|
|
/// (host / port / series) + exposes <see cref="BuildOptions"/> so each command
|
|
/// can synthesise a <see cref="FocasDriverOptions"/> with one device + one tag.
|
|
/// </summary>
|
|
public abstract class FocasCommandBase : DriverCommandBase
|
|
{
|
|
/// <summary>Gets the CNC IP address or hostname.</summary>
|
|
[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!;
|
|
|
|
/// <summary>Gets the FOCAS TCP port.</summary>
|
|
[CommandOption("cnc-port", 'p', Description = "FOCAS TCP port (default 8193).")]
|
|
public int CncPort { get; init; } = 8193;
|
|
|
|
/// <summary>Gets the CNC series.</summary>
|
|
[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;
|
|
|
|
/// <summary>Gets the per-operation timeout in milliseconds.</summary>
|
|
[CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 2000).")]
|
|
public int TimeoutMs { get; init; } = 2000;
|
|
|
|
/// <inheritdoc />
|
|
public override TimeSpan Timeout
|
|
{
|
|
get => TimeSpan.FromMilliseconds(TimeoutMs);
|
|
init { /* driven by TimeoutMs */ }
|
|
}
|
|
|
|
/// <summary>Canonical FOCAS host-address string, shape <c>focas://host:port</c>.</summary>
|
|
protected string HostAddress => $"focas://{CncHost}:{CncPort}";
|
|
|
|
/// <summary>
|
|
/// Build a <see cref="FocasDriverOptions"/> 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 <c>BadCommunicationError</c>.
|
|
/// </summary>
|
|
/// <param name="tags">The tag definitions to include in the driver options.</param>
|
|
/// <returns>A <see cref="FocasDriverOptions"/> configured with a single device and the supplied tags.</returns>
|
|
protected FocasDriverOptions BuildOptions(IReadOnlyList<FocasTagDefinition> 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 },
|
|
};
|
|
|
|
/// <summary>Gets the driver instance ID.</summary>
|
|
protected string DriverInstanceId => $"focas-cli-{CncHost}:{CncPort}";
|
|
|
|
/// <summary>
|
|
/// Validates numeric option ranges at the CLI boundary so a
|
|
/// zero/negative <c>--cnc-port</c>, <c>--timeout-ms</c>, or <c>--interval-ms</c>
|
|
/// surfaces as a clean <see cref="CliFx.Exceptions.CommandException"/> rather than
|
|
/// either an opaque downstream exception (invalid <c>focas://host:<n></c> /
|
|
/// zero <c>TimeSpan</c>) or a tight-spinning poll loop. The <c>--interval-ms</c>
|
|
/// option is subscribe-only — pass <c>null</c> for probe/read/write so this
|
|
/// helper can be a single shared validator.
|
|
/// </summary>
|
|
/// <param name="intervalMs">The interval in milliseconds, or null if not applicable.</param>
|
|
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}).");
|
|
}
|
|
}
|