feat(mtconnect): warn at Test Connect and deploy when SampleCount >= the Agent bufferSize (#825)
Parse the /probe Header bufferSize; MTConnectDriverProbe reads sampleCount (default 1000) and answers Ok with a WARNING message (amber chip in the Test Connect button); InitializeAsync logs the same Warning and exposes agentBufferSize on the diagnostics bag. Live-checked against the shared cppagent fixture (bufferSize 16384).
This commit is contained in:
@@ -55,6 +55,27 @@ public sealed partial class MTConnectDriver : IDriverDiagnostics
|
||||
/// <summary>Times a pump raised its count. Diagnostics only.</summary>
|
||||
private long _sampleCountRaises;
|
||||
|
||||
/// <summary>The Agent's <c>/probe</c> <c>bufferSize</c> as of the last initialize, or 0 when unknown. Diagnostics only.</summary>
|
||||
private long _agentBufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// lmxopcua#825 — the configuration warning for a <see cref="MTConnectDriverOptions.SampleCount"/> the Agent
|
||||
/// will refuse. An Agent answers <c>/sample?count=N</c> with <c>OUT_OF_RANGE</c> ("'count' must be less than
|
||||
/// bufferSize") when <c>N ≥ bufferSize</c>, so such a driver never streams: it re-baselines from
|
||||
/// <c>/current</c> and reports Degraded after three of them. Shared by <see cref="MTConnectDriverProbe"/>
|
||||
/// (Test Connect) and the driver's initialize (deploy), so both say the same thing.
|
||||
/// </summary>
|
||||
/// <param name="sampleCount">The configured count.</param>
|
||||
/// <param name="bufferSize">The Agent's buffer size, or null when its header did not say.</param>
|
||||
/// <returns>The warning text, or <see langword="null"/> when the count fits (or the buffer size is unknown).</returns>
|
||||
internal static string? SampleCountBufferWarning(int sampleCount, long? bufferSize) =>
|
||||
bufferSize is { } size && sampleCount >= size
|
||||
? $"SampleCount {sampleCount.ToString(CultureInfo.InvariantCulture)} is at or above the Agent's bufferSize "
|
||||
+ $"{size.ToString(CultureInfo.InvariantCulture)}: the Agent refuses every /sample stream (OUT_OF_RANGE), so "
|
||||
+ "values will only re-baseline from /current and the driver reports Degraded. Set SampleCount below "
|
||||
+ $"{size.ToString(CultureInfo.InvariantCulture)}."
|
||||
: null;
|
||||
|
||||
/// <summary>The ceiling a pump started with <paramref name="configured"/> may raise its count to.</summary>
|
||||
/// <param name="configured">The configured <see cref="MTConnectDriverOptions.SampleCount"/>.</param>
|
||||
/// <returns>The ceiling — never below <paramref name="configured"/>.</returns>
|
||||
@@ -77,6 +98,12 @@ public sealed partial class MTConnectDriver : IDriverDiagnostics
|
||||
bag["sampleLag"] = lag.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var bufferSize = Interlocked.Read(ref _agentBufferSize);
|
||||
if (bufferSize > 0)
|
||||
{
|
||||
bag["agentBufferSize"] = bufferSize.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
bag["sampleSaturatedChunks"] = Interlocked.Read(ref _saturatedChunks).ToString(CultureInfo.InvariantCulture);
|
||||
bag["sampleCountRaises"] = Interlocked.Read(ref _sampleCountRaises).ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
|
||||
@@ -3333,6 +3333,15 @@ public sealed partial class MTConnectDriver
|
||||
// ---- commit point: everything below is non-throwing bookkeeping ----
|
||||
Volatile.Write(ref _options, options);
|
||||
|
||||
// lmxopcua#825 — a SampleCount the Agent will refuse is a configuration error; say so at deploy rather than
|
||||
// leaving it to surface as three progress-free re-baselines and a Degraded stream.
|
||||
Interlocked.Exchange(ref _agentBufferSize, probe.BufferSize ?? 0);
|
||||
if (SampleCountBufferWarning(options.SampleCount, probe.BufferSize) is { } bufferWarning)
|
||||
{
|
||||
_logger.LogWarning("MTConnect driver {DriverInstanceId} ({AgentUri}): {Warning}",
|
||||
_driverInstanceId, AgentDisplay(options), bufferWarning);
|
||||
}
|
||||
|
||||
// Derived from the options AND the /probe model just fetched: a raw tag whose TagConfig
|
||||
// blob declares no type takes the type the Agent itself declares for that DataItem, so a
|
||||
// browse-committed tag (whose blob carries only the address) publishes as the number it
|
||||
|
||||
@@ -132,8 +132,18 @@ public sealed class MTConnectDriverProbe : IDriverProbe
|
||||
/// driver's tag/timing configuration — see the "reachable ≠ deployable" remarks on the type.
|
||||
/// </summary>
|
||||
public string? DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <c>/sample</c> <c>count</c> (lmxopcua#825). Not needed to reach the Agent, but read so Test Connect
|
||||
/// can warn when it is at or above the Agent's <c>bufferSize</c> — a config the Agent will refuse on every
|
||||
/// stream. Absent ⇒ the driver's own default.
|
||||
/// </summary>
|
||||
public int? SampleCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>The driver's default <c>SampleCount</c> (<c>MTConnectDriver.ParseOptions</c>) when the config omits it.</summary>
|
||||
private const int DefaultSampleCount = 1_000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DriverProbeResult> ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
|
||||
{
|
||||
@@ -195,10 +205,17 @@ public sealed class MTConnectDriverProbe : IDriverProbe
|
||||
try
|
||||
{
|
||||
using var client = new MTConnectAgentClient(options, _handler);
|
||||
_ = await client.ProbeAsync(cts.Token).ConfigureAwait(false);
|
||||
var model = await client.ProbeAsync(cts.Token).ConfigureAwait(false);
|
||||
sw.Stop();
|
||||
|
||||
return new(true, $"MTConnect /probe OK ({safeUri})", sw.Elapsed);
|
||||
// Reachable but misconfigured stays Ok (the endpoint answered — Test Connect's question), with the
|
||||
// warning in the message so the operator sees it before deploying (lmxopcua#825).
|
||||
var warning = MTConnectDriver.SampleCountBufferWarning(dto.SampleCount ?? DefaultSampleCount, model.BufferSize);
|
||||
return new(true,
|
||||
warning is null
|
||||
? $"MTConnect /probe OK ({safeUri})"
|
||||
: $"MTConnect /probe OK ({safeUri}) — WARNING: {warning}",
|
||||
sw.Elapsed);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,12 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
|
||||
/// <c>XElement</c>.
|
||||
/// </summary>
|
||||
/// <param name="Devices">Every device the Agent's probe response declares.</param>
|
||||
public sealed record MTConnectProbeModel(IReadOnlyList<MTConnectDevice> Devices);
|
||||
/// <param name="BufferSize">
|
||||
/// The Agent's observation ring-buffer size (the <c>/probe</c> <c>Header</c>'s <c>bufferSize</c>), or
|
||||
/// <see langword="null"/> when the header omits it or it is not a positive integer. A <c>/sample</c>
|
||||
/// <c>count</c> at or above it is refused <c>OUT_OF_RANGE</c> by the Agent (lmxopcua#825).
|
||||
/// </param>
|
||||
public sealed record MTConnectProbeModel(IReadOnlyList<MTConnectDevice> Devices, long? BufferSize = null);
|
||||
|
||||
/// <summary>
|
||||
/// Common shape shared by <see cref="MTConnectDevice"/> and <see cref="MTConnectComponent"/>:
|
||||
|
||||
@@ -132,7 +132,21 @@ internal static class MTConnectProbeParser
|
||||
"MTConnect /probe response declares no devices; refusing to report an empty device model as a successful probe.");
|
||||
}
|
||||
|
||||
return new MTConnectProbeModel(devices);
|
||||
return new MTConnectProbeModel(devices, ReadBufferSize(root));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Header</c>'s <c>bufferSize</c> (lmxopcua#825). Advisory only — a missing, malformed or non-positive
|
||||
/// value is <see langword="null"/>, never a parse failure, because nothing but a configuration warning reads it.
|
||||
/// </summary>
|
||||
private static long? ReadBufferSize(XElement root)
|
||||
{
|
||||
var header = MTConnectXml.ChildrenNamed(root, "Header").FirstOrDefault();
|
||||
var raw = header is null ? null : MTConnectXml.OptionalAttribute(header, "bufferSize");
|
||||
return long.TryParse(raw, System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out var size)
|
||||
&& size > 0
|
||||
? size
|
||||
: null;
|
||||
}
|
||||
|
||||
private static MTConnectDevice ReadDevice(XElement element) =>
|
||||
|
||||
Reference in New Issue
Block a user