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