using Opc.Ua;
using Opc.Ua.Client;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
///
/// The remote server's Server/ServerCapabilities/OperationLimits that bound how many
/// items one service call may carry (#275). Every field is 0 = "no limit declared" (the
/// OPC UA convention), so is the safe value when the server publishes nothing
/// or the read fails.
///
/// Items per Read.
/// Items per Write.
/// Nodes per Browse.
/// Items per CreateMonitoredItems / Modify / SetMonitoringMode.
/// Nodes per HistoryRead (raw / processed / at-time).
/// Nodes per HistoryRead (events).
internal sealed record OpcUaClientOperationLimits(
uint MaxNodesPerRead,
uint MaxNodesPerWrite,
uint MaxNodesPerBrowse,
uint MaxMonitoredItemsPerCall,
uint MaxNodesPerHistoryReadData,
uint MaxNodesPerHistoryReadEvents)
{
/// No limits declared — every call goes out in one request.
public static OpcUaClientOperationLimits None { get; } = new(0, 0, 0, 0, 0, 0);
///
/// Split items into consecutive (Start, Count) chunks of at most
/// each. limit == 0 (no limit) or a total that already fits yields a
/// single chunk; total == 0 yields none.
///
/// Number of items to send.
/// The server's per-call cap; 0 = unlimited.
/// The chunk ranges, in order, covering [0, total) exactly once.
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));
}
/// The standard variables read to populate the limits, in order.
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,
];
///
/// Build limits from the values read for . A Bad or non-uint value
/// (server doesn't publish that limit) is treated as 0 = unlimited.
///
/// One value per entry, in order.
/// The parsed limits.
public static OpcUaClientOperationLimits FromValues(IReadOnlyList 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));
}
///
/// Read the server's declared limits. Best-effort: any failure (older server without the
/// capability object, transport error) yields so connect never fails over this.
///
/// The freshly connected session.
/// Cancellation token.
/// The declared limits, or .
public static async Task 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; }
}
}