Task #253 — E2E CLI test scripts + FOCAS test-client CLI
The driver-layer integration tests confirm the driver sees the PLC, and
the Client.CLI tests confirm the client sees the server. Nothing glued
them end-to-end until this PR.
- scripts/e2e/_common.ps1: shared helpers — CLI invocation (published-
binary OR `dotnet run` fallback), Test-Probe / Test-DriverLoopback /
Test-ServerBridge (all return @{Passed;Reason} hashtables).
- scripts/e2e/test-<modbus|abcip|ablegacy|s7|focas|twincat>.ps1: per-
driver three-stage script (probe → driver-loopback → server-bridge).
AB Legacy / FOCAS / TwinCAT are gated behind *_TRUST_WIRE env vars
since they need real hardware (#222) or a licensed runtime (#221).
- scripts/e2e/test-phase7-virtualtags.ps1: writes a Modbus HR, reads
the server-side VirtualTag (VT = input * 2) back via OPC UA, triggers
+ clears a scripted alarm. Exercises the Phase 7 CachedTagUpstreamSource
+ ScriptedAlarmEngine path.
- scripts/e2e/test-all.ps1: reads e2e-config.json sidecar, runs each
present driver, prints a FINAL MATRIX (PASS/FAIL/SKIP). Missing
sections SKIP rather than fail hard.
- scripts/e2e/e2e-config.sample.json: commented sample — each dev's
NodeIds are local-seed-specific so e2e-config.json is .gitignore-d.
- scripts/e2e/README.md: full walkthrough — prereqs, three-stage design,
env-var gates, expected matrix, why this is separate from `dotnet test`.
Tasks #249-#251 shipped Modbus/AbCip/AbLegacy/S7/TwinCAT CLIs but left
FOCAS out. Since test-focas.ps1 needs it, the 6th CLI ships here:
- src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli: probe/read/write/subscribe
commands, AssemblyName `otopcua-focas-cli`. WriteCommand.ParseValue
handles the full FocasDataType enum (Bit/Byte/Int16/Int32/Float32/
Float64/String — no UInt variants; the FOCAS protocol exposes signed
PMC + Fanuc-Float only). Default DataType is Int16 to match the PMC
register convention.
Full-solution build clean (0 errors). FOCAS CLI wired into
ZB.MOM.WW.OtOpcUa.slnx. No .Tests project for the FOCAS CLI yet —
symmetric with how ProbeCommand has no unit-testable pure logic in the
other 5 CLIs either; WriteCommand.ParseValue parity will land in a
follow-up to keep this PR scoped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs
Normal file
58
src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using CliFx.Attributes;
|
||||
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
|
||||
{
|
||||
[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!;
|
||||
|
||||
[CommandOption("cnc-port", 'p', Description = "FOCAS TCP port (default 8193).")]
|
||||
public int CncPort { get; init; } = 8193;
|
||||
|
||||
[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;
|
||||
|
||||
[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 default
|
||||
/// <see cref="FwlibFocasClientFactory"/> attempts <c>Fwlib32.dll</c> P/Invoke, which
|
||||
/// throws <see cref="DllNotFoundException"/> at first call when the DLL is absent —
|
||||
/// surfaced through the driver as <c>BadCommunicationError</c>.
|
||||
/// </summary>
|
||||
protected FocasDriverOptions BuildOptions(IReadOnlyList<FocasTagDefinition> tags) => new()
|
||||
{
|
||||
Devices = [new FocasDeviceOptions(
|
||||
HostAddress: HostAddress,
|
||||
DeviceName: $"cli-{CncHost}:{CncPort}",
|
||||
Series: Series)],
|
||||
Tags = tags,
|
||||
Timeout = Timeout,
|
||||
Probe = new FocasProbeOptions { Enabled = false },
|
||||
};
|
||||
|
||||
protected string DriverInstanceId => $"focas-cli-{CncHost}:{CncPort}";
|
||||
}
|
||||
Reference in New Issue
Block a user