Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientOperationLimits.cs
T

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; }
}
}