fix(driver-modbus-cli): resolve Low code-review findings (Driver.Modbus.Cli-003,004,005,006,007,008)
- Driver.Modbus.Cli-003: ModbusCommandBase.ValidateEndpoint rejects --port outside 1..65535, non-positive --timeout-ms, and --unit-id outside 1..247. - Driver.Modbus.Cli-004: wrapped SubscribeCommand's OnDataChange handler body in a try/catch (warn-and-swallow) and serialised the console write through a lock. - Driver.Modbus.Cli-005: Probe / Read / Write now catch the cancellation-during-init OperationCanceledException and print 'Cancelled.' instead of dumping a stack trace. - Driver.Modbus.Cli-006: ProbeCommand.ComputeVerdict derives the headline from BOTH the driver state and the probe snapshot's OPC UA quality class so the headline can't disagree with the wire result. - Driver.Modbus.Cli-007: docs/Driver.Modbus.Cli.md carries an explicit 'CLI scope' callout — the address-string grammar is a DriverConfig JSON feature; the CLI takes the structured triple only. - Driver.Modbus.Cli-008: pinned BuildOptions, ValidateEndpoint, the region-validation guards, ComputeVerdict, and the cancellation-during- initialize paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Infrastructure;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Commands;
|
||||
@@ -21,6 +22,7 @@ public sealed class ProbeCommand : ModbusCommandBase
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
ValidateEndpoint();
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
// Build with one probe tag + Probe.Enabled=false so InitializeAsync connects the
|
||||
@@ -39,17 +41,59 @@ public sealed class ProbeCommand : ModbusCommandBase
|
||||
var snapshot = await driver.ReadAsync(["__probe"], ct);
|
||||
var health = driver.GetHealth();
|
||||
|
||||
// Driver.Modbus.Cli-006: derive the headline verdict from BOTH the driver state
|
||||
// AND the probe-read StatusCode so the operator never sees the previous
|
||||
// contradictory pair (`Health: Healthy` over a Bad snapshot line). The bare
|
||||
// driver state is still printed below for diagnostics, but the verdict is what
|
||||
// the operator scans first.
|
||||
var verdict = ComputeVerdict(health.State, snapshot[0].StatusCode);
|
||||
|
||||
await console.Output.WriteLineAsync($"Host: {Host}:{Port} (unit {UnitId})");
|
||||
await console.Output.WriteLineAsync($"Health: {health.State}");
|
||||
await console.Output.WriteLineAsync($"Verdict: {verdict}");
|
||||
await console.Output.WriteLineAsync($"Driver state: {health.State}");
|
||||
if (health.LastError is { } err)
|
||||
await console.Output.WriteLineAsync($"Last error: {err}");
|
||||
await console.Output.WriteLineAsync();
|
||||
await console.Output.WriteLineAsync(
|
||||
SnapshotFormatter.Format($"HR[{ProbeAddress}]", snapshot[0]));
|
||||
}
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||
{
|
||||
// Driver.Modbus.Cli-005: Ctrl+C during InitializeAsync — exit quietly so CliFx
|
||||
// does not render a full stack trace for a user-initiated cancellation.
|
||||
await console.Output.WriteLineAsync("Cancelled.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await driver.ShutdownAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Driver.Modbus.Cli-006: combine the driver-side <see cref="DriverState"/> with the
|
||||
/// probe snapshot's OPC UA <c>StatusCode</c> into a single headline verdict. Order
|
||||
/// of precedence:
|
||||
/// <list type="number">
|
||||
/// <item><c>FAIL</c> — driver did not reach <see cref="DriverState.Healthy"/>
|
||||
/// (Faulted / Reconnecting / Unknown) OR the snapshot reports Bad quality.</item>
|
||||
/// <item><c>DEGRADED</c> — driver Healthy but the snapshot quality is Uncertain.</item>
|
||||
/// <item><c>OK</c> — driver Healthy and snapshot Good.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static string ComputeVerdict(DriverState state, uint statusCode)
|
||||
{
|
||||
// OPC UA StatusCode top 2 bits encode the quality class:
|
||||
// 0x00xxxxxx → Good, 0x40xxxxxx → Uncertain, 0x80xxxxxx / 0xC0xxxxxx → Bad.
|
||||
var qualityClass = statusCode & 0xC0000000u;
|
||||
var snapshotGood = qualityClass == 0x00000000u;
|
||||
var snapshotUncertain = qualityClass == 0x40000000u;
|
||||
|
||||
if (state != DriverState.Healthy || !snapshotGood && !snapshotUncertain)
|
||||
return $"FAIL (driver={state}, probe={SnapshotFormatter.FormatStatus(statusCode)})";
|
||||
|
||||
if (snapshotUncertain)
|
||||
return $"DEGRADED (driver={state}, probe={SnapshotFormatter.FormatStatus(statusCode)})";
|
||||
|
||||
return $"OK (driver={state}, probe={SnapshotFormatter.FormatStatus(statusCode)})";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user