ba62022e88
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
95 lines
4.7 KiB
C#
95 lines
4.7 KiB
C#
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
|
|
|
|
/// <summary>
|
|
/// The remote server's <c>Server/ServerCapabilities/OperationLimits</c> that bound how many
|
|
/// items one service call may carry (#275). Every field is <c>0</c> = "no limit declared" (the
|
|
/// OPC UA convention), so <see cref="None"/> is the safe value when the server publishes nothing
|
|
/// or the read fails.
|
|
/// </summary>
|
|
/// <param name="MaxNodesPerRead">Items per Read.</param>
|
|
/// <param name="MaxNodesPerWrite">Items per Write.</param>
|
|
/// <param name="MaxNodesPerBrowse">Nodes per Browse.</param>
|
|
/// <param name="MaxMonitoredItemsPerCall">Items per CreateMonitoredItems / Modify / SetMonitoringMode.</param>
|
|
/// <param name="MaxNodesPerHistoryReadData">Nodes per HistoryRead (raw / processed / at-time).</param>
|
|
/// <param name="MaxNodesPerHistoryReadEvents">Nodes per HistoryRead (events).</param>
|
|
internal sealed record OpcUaClientOperationLimits(
|
|
uint MaxNodesPerRead,
|
|
uint MaxNodesPerWrite,
|
|
uint MaxNodesPerBrowse,
|
|
uint MaxMonitoredItemsPerCall,
|
|
uint MaxNodesPerHistoryReadData,
|
|
uint MaxNodesPerHistoryReadEvents)
|
|
{
|
|
/// <summary>No limits declared — every call goes out in one request.</summary>
|
|
public static OpcUaClientOperationLimits None { get; } = new(0, 0, 0, 0, 0, 0);
|
|
|
|
/// <summary>
|
|
/// Split <paramref name="total"/> items into consecutive <c>(Start, Count)</c> chunks of at most
|
|
/// <paramref name="limit"/> each. <c>limit == 0</c> (no limit) or a total that already fits yields a
|
|
/// single chunk; <c>total == 0</c> yields none.
|
|
/// </summary>
|
|
/// <param name="total">Number of items to send.</param>
|
|
/// <param name="limit">The server's per-call cap; 0 = unlimited.</param>
|
|
/// <returns>The chunk ranges, in order, covering <c>[0, total)</c> exactly once.</returns>
|
|
public static IEnumerable<(int Start, int Count)> Chunk(int total, uint limit)
|
|
{
|
|
if (total <= 0) yield break;
|
|
if (limit == 0 || limit >= (uint)total)
|
|
{
|
|
yield return (0, total);
|
|
yield break;
|
|
}
|
|
var size = (int)Math.Min(limit, int.MaxValue);
|
|
for (var start = 0; start < total; start += size)
|
|
yield return (start, Math.Min(size, total - start));
|
|
}
|
|
|
|
/// <summary>The standard variables read to populate the limits, in <see cref="FromValues"/> order.</summary>
|
|
internal static readonly NodeId[] LimitNodeIds =
|
|
[
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerRead,
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerWrite,
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerBrowse,
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxMonitoredItemsPerCall,
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerHistoryReadData,
|
|
VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerHistoryReadEvents,
|
|
];
|
|
|
|
/// <summary>
|
|
/// Build limits from the values read for <see cref="LimitNodeIds"/>. A Bad or non-<c>uint</c> value
|
|
/// (server doesn't publish that limit) is treated as 0 = unlimited.
|
|
/// </summary>
|
|
/// <param name="values">One value per <see cref="LimitNodeIds"/> entry, in order.</param>
|
|
/// <returns>The parsed limits.</returns>
|
|
public static OpcUaClientOperationLimits FromValues(IReadOnlyList<DataValue> values)
|
|
{
|
|
uint At(int i) =>
|
|
i < values.Count && values[i] is { } dv && StatusCode.IsGood(dv.StatusCode) && dv.Value is uint u ? u : 0;
|
|
return new OpcUaClientOperationLimits(At(0), At(1), At(2), At(3), At(4), At(5));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the server's declared limits. Best-effort: any failure (older server without the
|
|
/// capability object, transport error) yields <see cref="None"/> so connect never fails over this.
|
|
/// </summary>
|
|
/// <param name="session">The freshly connected session.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>The declared limits, or <see cref="None"/>.</returns>
|
|
public static async Task<OpcUaClientOperationLimits> ReadAsync(ISession session, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var nodes = new ReadValueIdCollection(LimitNodeIds.Length);
|
|
foreach (var id in LimitNodeIds)
|
|
nodes.Add(new ReadValueId { NodeId = id, AttributeId = Attributes.Value });
|
|
var resp = await session.ReadAsync(null, 0, TimestampsToReturn.Neither, nodes, ct).ConfigureAwait(false);
|
|
return FromValues(resp.Results);
|
|
}
|
|
catch (OperationCanceledException) { throw; }
|
|
catch { return None; }
|
|
}
|
|
}
|