From eae7387d5c2bf623086f972ccf5738bf55ef3fed Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 19:43:47 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.OpcDa/IOpcDaServer.cs | 18 ++++++++++ .../BootstrapBlazor.OpcDa/OpcDaServer.cs | 33 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs index 26f015b0..893e641f 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ +using Opc.Da; + namespace BootstrapBlazor.OpcDa; /// @@ -59,4 +61,20 @@ public interface IOpcDaServer : IDisposable /// /// HashSet Write(params HashSet items); + + /// + /// 浏览 OPC Server 中的位号 (即数据项或者标签) + /// + /// + /// + /// + /// + BrowseElement[] Browser(string name, BrowseFilters filters, out BrowsePosition position); + + /// + /// 浏览 OPC Server 中的位号 (即数据项或者标签) + /// + /// + /// + BrowseElement[] BrowserNext(ref BrowsePosition position); } diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs index a1cc46e5..cc81b086 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs @@ -48,6 +48,39 @@ public bool Connect(string serverName) return IsConnected; } + /// + /// + /// + /// + /// + /// + /// + public BrowseElement[] Browser(string name, BrowseFilters filters, out BrowsePosition position) + { + if (_server is not { IsConnected: true }) + { + throw new InvalidOperationException("OPC Server is not connected."); + } + + return _server.Browse(new ItemIdentifier(name), filters, out position); + } + + /// + /// + /// + /// + /// + public BrowseElement[] BrowserNext(ref BrowsePosition position) + { + if (_server is not { IsConnected: true }) + { + throw new InvalidOperationException("OPC Server is not connected."); + } + + + return _server.BrowseNext(ref position); + } + /// /// 断开连接方法 /// From d14ad08ad6c992c00b1fd632bb951840184560ab Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 19:43:56 +0800 Subject: [PATCH 2/9] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTestOpcDa/UnitTest1.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/UnitTestOpcDa/UnitTest1.cs b/test/UnitTestOpcDa/UnitTest1.cs index 1e3e5580..097ab7de 100644 --- a/test/UnitTestOpcDa/UnitTest1.cs +++ b/test/UnitTestOpcDa/UnitTest1.cs @@ -80,4 +80,17 @@ public async Task Subscription_Ok() server.Disconnect(); server.Dispose(); } + + [Fact] + public void Browser_Ok() + { + var sc = new ServiceCollection(); + sc.AddOpcDaServer(); + + var sp = sc.BuildServiceProvider(); + var server = sp.GetRequiredService(); + server.Connect("opcda://localhost/Kepware.KEPServerEX.V6"); + + server.Browser("Simulation Examples", new BrowseFilters(), out var position); + } } From 012c7efee8e53f22e472bbf5e41622e2ca04bd4d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:08:57 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/Extensions.cs | 11 ++++++ .../BootstrapBlazor.OpcDa/IOpcDaServer.cs | 4 +-- .../Mock/MockOpcDaServer.cs | 25 +++++++++++++ .../BootstrapBlazor.OpcDa/OpcBrowseElement.cs | 35 +++++++++++++++++++ .../BootstrapBlazor.OpcDa/OpcBrowseFilters.cs | 31 ++++++++++++++++ .../OpcBrowsePosition.cs | 18 ++++++++++ .../BootstrapBlazor.OpcDa/OpcDaServer.cs | 12 ++++--- 7 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs create mode 100644 src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs create mode 100644 src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs diff --git a/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs b/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs index 1e29ec9a..45ed1957 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs @@ -24,4 +24,15 @@ public static IOpcSubscription ToOpcSubscription(this ISubscription subscription } public static ISubscription CreateSubscription(this Server server, string name, int updateRate = 1000, bool active = true) => server.CreateSubscription(new SubscriptionState { Name = name, Deadband = 0, UpdateRate = updateRate, Active = active }); + + public static BrowseFilters ToFilters(this OpcBrowseFilters filtes) + { + return new BrowseFilters + { + ReturnAllProperties = filtes.ReturnAllProperties, + ReturnPropertyValues = filtes.ReturnPropertyValues, + MaxElementsReturned = filtes.MaxElementsReturned, + ElementNameFilter = filtes.ElementNameFilter + }; + } } diff --git a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs index 893e641f..dbe0a031 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs @@ -69,12 +69,12 @@ public interface IOpcDaServer : IDisposable /// /// /// - BrowseElement[] Browser(string name, BrowseFilters filters, out BrowsePosition position); + OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition position); /// /// 浏览 OPC Server 中的位号 (即数据项或者标签) /// /// /// - BrowseElement[] BrowserNext(ref BrowsePosition position); + OpcBrowseElement[] BrowseNext(OpcBrowsePosition position); } diff --git a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs index 1af8d610..5cc6d3f3 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ +using Opc.Da; + namespace BootstrapBlazor.OpcDa; /// @@ -61,6 +63,29 @@ public HashSet Write(params HashSet items) .ToHashSet(OpcItemEqualityComparer.Default); } + /// + /// 浏览 OPC Server 中的位号 (即数据项或者标签) + /// + /// + /// + /// + /// + public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position) + { + position = null; + return []; + } + + /// + /// 浏览 OPC Server 中的位号 (即数据项或者标签) + /// + /// + /// + public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position) + { + return []; + } + public void Dispose() { diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs new file mode 100644 index 00000000..29bb62f0 --- /dev/null +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs @@ -0,0 +1,35 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Opc.Da; + +namespace BootstrapBlazor.OpcDa; + +/// +/// 对 OpcDataServer BrowseElement 的封装类 +/// +public class OpcBrowseElement +{ + /// + /// 获得/设置 节点名称 + /// + public string Name => _element.Name; + + /// + /// 获得/设置 是否是数据项 + /// + public bool IsItem => _element.IsItem; + + /// + /// 获得/设置 是否有子节点 + /// + public bool HasChildren => _element.HasChildren; + + internal OpcBrowseElement(BrowseElement element) + { + _element = element; + } + + private readonly BrowseElement _element; +} diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs new file mode 100644 index 00000000..44f257a9 --- /dev/null +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs @@ -0,0 +1,31 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.OpcDa; + +/// +/// 对 OpcDataServer BrowseFilters 的封装类 +/// +public class OpcBrowseFilters +{ + /// + /// 获得/设置 最大返回节点数量 + /// + public int MaxElementsReturned { get; set; } + + /// + /// 获得/设置 元素名称过滤器 + /// + public string? ElementNameFilter { get; set; } + + /// + /// 获得/设置 是否返回所有属性 + /// + public bool ReturnAllProperties { get; set; } + + /// + /// 获得/设置 是否返回属性值 + /// + public bool ReturnPropertyValues { get; set; } +} diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs new file mode 100644 index 00000000..8d19f7f1 --- /dev/null +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs @@ -0,0 +1,18 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.OpcDa; + +/// +/// 对 OpcDataServer BrowsePosition 的封装类 +/// +public class OpcBrowsePosition +{ + internal OpcBrowsePosition(Opc.Da.BrowsePosition position) + { + Position = position; + } + + internal Opc.Da.BrowsePosition Position { get; set; } +} diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs index cc81b086..6b20ccd7 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs @@ -55,14 +55,16 @@ public bool Connect(string serverName) /// /// /// - public BrowseElement[] Browser(string name, BrowseFilters filters, out BrowsePosition position) + public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition position) { if (_server is not { IsConnected: true }) { throw new InvalidOperationException("OPC Server is not connected."); } - return _server.Browse(new ItemIdentifier(name), filters, out position); + var results = _server.Browse(new ItemIdentifier(name), filters.ToFilters(), out var pos); + position = new OpcBrowsePosition(pos); + return results.Select(element => new OpcBrowseElement(element)).ToArray(); } /// @@ -70,15 +72,15 @@ public BrowseElement[] Browser(string name, BrowseFilters filters, out BrowsePos /// /// /// - public BrowseElement[] BrowserNext(ref BrowsePosition position) + public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position) { if (_server is not { IsConnected: true }) { throw new InvalidOperationException("OPC Server is not connected."); } - - return _server.BrowseNext(ref position); + var pos = position.Position; + return _server.BrowseNext(ref pos).Select(element => new OpcBrowseElement(element)).ToArray(); } /// From 6052edb0bfe444844de1536e765216916b6fd0c0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:09:07 +0800 Subject: [PATCH 4/9] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTestOpcDa/UnitTest1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UnitTestOpcDa/UnitTest1.cs b/test/UnitTestOpcDa/UnitTest1.cs index 097ab7de..edcb17c1 100644 --- a/test/UnitTestOpcDa/UnitTest1.cs +++ b/test/UnitTestOpcDa/UnitTest1.cs @@ -91,6 +91,6 @@ public void Browser_Ok() var server = sp.GetRequiredService(); server.Connect("opcda://localhost/Kepware.KEPServerEX.V6"); - server.Browser("Simulation Examples", new BrowseFilters(), out var position); + server.Browser(); } } From ccb76af8ae599138795fd8f1c750b60a8e8cb26c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:10:27 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=E7=B2=BE=E7=AE=80=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs index 6b20ccd7..6c291f86 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs @@ -4,7 +4,6 @@ using Opc; using Opc.Da; -using System.Collections.Concurrent; using System.Runtime.Versioning; namespace BootstrapBlazor.OpcDa; @@ -16,7 +15,6 @@ namespace BootstrapBlazor.OpcDa; sealed class OpcDaServer : IOpcDaServer { private Opc.Da.Server? _server = null; - private readonly ConcurrentDictionary> _valuesCache = []; /// /// 获得 OPC Server 名称 From 38cdd4ec11758d6f496ea9bee4ad6868a046c07b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:32:16 +0800 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20=E5=AE=8C=E5=96=84=20Browse=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.OpcDa/IOpcDaServer.cs | 2 +- .../Mock/MockOpcDaServer.cs | 15 ++++- .../BootstrapBlazor.OpcDa/OpcBrowseElement.cs | 20 ++++-- .../BootstrapBlazor.OpcDa/OpcDaServer.cs | 62 ++++++++----------- 4 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs index dbe0a031..79fdb5d6 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs @@ -69,7 +69,7 @@ public interface IOpcDaServer : IDisposable /// /// /// - OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition position); + OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position); /// /// 浏览 OPC Server 中的位号 (即数据项或者标签) diff --git a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs index 5cc6d3f3..46c3d692 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs @@ -73,7 +73,20 @@ public HashSet Write(params HashSet items) public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position) { position = null; - return []; + return [ + new OpcBrowseElement() + { + Name ="Channel1", + IsItem = false, + HasChildren = true + }, + new OpcBrowseElement() + { + Name ="Channel2", + IsItem = false, + HasChildren = true + } + ]; } /// diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs index 29bb62f0..e2e5339a 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs @@ -14,22 +14,30 @@ public class OpcBrowseElement /// /// 获得/设置 节点名称 /// - public string Name => _element.Name; + public string Name { get; set; } /// /// 获得/设置 是否是数据项 /// - public bool IsItem => _element.IsItem; + public bool IsItem { get; set; } /// /// 获得/设置 是否有子节点 /// - public bool HasChildren => _element.HasChildren; + public bool HasChildren { get; set; } - internal OpcBrowseElement(BrowseElement element) + /// + /// 构造函数 + /// + public OpcBrowseElement() { - _element = element; + Name = ""; } - private readonly BrowseElement _element; + internal OpcBrowseElement(BrowseElement element) + { + Name = element.Name; + IsItem = element.IsItem; + HasChildren = element.HasChildren; + } } diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs index 6c291f86..3c7872d7 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs @@ -46,41 +46,6 @@ public bool Connect(string serverName) return IsConnected; } - /// - /// - /// - /// - /// - /// - /// - public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition position) - { - if (_server is not { IsConnected: true }) - { - throw new InvalidOperationException("OPC Server is not connected."); - } - - var results = _server.Browse(new ItemIdentifier(name), filters.ToFilters(), out var pos); - position = new OpcBrowsePosition(pos); - return results.Select(element => new OpcBrowseElement(element)).ToArray(); - } - - /// - /// - /// - /// - /// - public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position) - { - if (_server is not { IsConnected: true }) - { - throw new InvalidOperationException("OPC Server is not connected."); - } - - var pos = position.Position; - return _server.BrowseNext(ref pos).Select(element => new OpcBrowseElement(element)).ToArray(); - } - /// /// 断开连接方法 /// @@ -176,6 +141,33 @@ private Opc.Da.Server GetOpcServer() return _server; } + /// + /// + /// + /// + /// + /// + /// + public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position) + { + var server = GetOpcServer(); + var results = server.Browse(new ItemIdentifier(name), filters.ToFilters(), out var pos); + position = new OpcBrowsePosition(pos); + return results.Select(element => new OpcBrowseElement(element)).ToArray(); + } + + /// + /// + /// + /// + /// + public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position) + { + var server = GetOpcServer(); + var pos = position.Position; + return server.BrowseNext(ref pos).Select(element => new OpcBrowseElement(element)).ToArray(); + } + /// /// Dispose 方法 /// From 59eda3a3c8cf8815e192619df0ea45818f5e93b4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:56:17 +0800 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20OpcBrowseFilte?= =?UTF-8?q?rType=20=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/Extensions.cs | 21 ++++-- .../Mock/MockOpcDaServer.cs | 69 +++++++++++++++---- .../BootstrapBlazor.OpcDa/OpcBrowseElement.cs | 7 ++ .../OpcBrowseFilterType.cs | 26 +++++++ .../BootstrapBlazor.OpcDa/OpcBrowseFilters.cs | 5 ++ .../OpcBrowsePosition.cs | 4 +- .../BootstrapBlazor.OpcDa/OpcDaServer.cs | 9 +-- 7 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilterType.cs diff --git a/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs b/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs index 45ed1957..df6a4425 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs @@ -25,14 +25,25 @@ public static IOpcSubscription ToOpcSubscription(this ISubscription subscription public static ISubscription CreateSubscription(this Server server, string name, int updateRate = 1000, bool active = true) => server.CreateSubscription(new SubscriptionState { Name = name, Deadband = 0, UpdateRate = updateRate, Active = active }); - public static BrowseFilters ToFilters(this OpcBrowseFilters filtes) + public static BrowseFilters ToFilters(this OpcBrowseFilters filters) { return new BrowseFilters { - ReturnAllProperties = filtes.ReturnAllProperties, - ReturnPropertyValues = filtes.ReturnPropertyValues, - MaxElementsReturned = filtes.MaxElementsReturned, - ElementNameFilter = filtes.ElementNameFilter + ReturnAllProperties = filters.ReturnAllProperties, + ReturnPropertyValues = filters.ReturnPropertyValues, + MaxElementsReturned = filters.MaxElementsReturned, + ElementNameFilter = filters.ElementNameFilter, + BrowseFilter = filters.BrowseFilter.ToBrowseFilter() + }; + } + + public static browseFilter ToBrowseFilter(this OpcBrowseFilterType filterType) + { + return filterType switch + { + OpcBrowseFilterType.All => browseFilter.all, + OpcBrowseFilterType.Branch => browseFilter.branch, + _ => browseFilter.item }; } } diff --git a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs index 46c3d692..04e7df62 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs @@ -3,6 +3,7 @@ // Website: https://www.blazor.zone or https://argozhang.github.io/ using Opc.Da; +using System.Net.Http.Headers; namespace BootstrapBlazor.OpcDa; @@ -73,20 +74,60 @@ public HashSet Write(params HashSet items) public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position) { position = null; - return [ - new OpcBrowseElement() - { - Name ="Channel1", - IsItem = false, - HasChildren = true - }, - new OpcBrowseElement() - { - Name ="Channel2", - IsItem = false, - HasChildren = true - } - ]; + if (string.IsNullOrEmpty(name)) + { + return [ + new OpcBrowseElement() + { + Name ="Channel1", + ItemName = "Channel1", + IsItem = false, + HasChildren = true + }, + new OpcBrowseElement() + { + Name ="Channel2", + ItemName = "Channel2", + IsItem = false, + HasChildren = true + } + ]; + } + + if (name == "Channel1") + { + return [ + new OpcBrowseElement() + { + Name ="Device1", + ItemName = "Channel1.Device1", + IsItem = false, + HasChildren = true + } + ]; + } + + if (name == "Channel1.Device1") + { + return [ + new OpcBrowseElement() + { + Name ="Tag1", + ItemName = "Channel1.Device1.Tag1", + IsItem = true, + HasChildren = false + }, + new OpcBrowseElement() + { + Name ="Tag2", + ItemName = "Channel1.Device1.Tag2", + IsItem = true, + HasChildren = false + } + ]; + } + + return []; } /// diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs index e2e5339a..02cad814 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs @@ -16,6 +16,11 @@ public class OpcBrowseElement /// public string Name { get; set; } + /// + /// 获得/设置 Item 名称 + /// + public string ItemName { get; set; } + /// /// 获得/设置 是否是数据项 /// @@ -32,11 +37,13 @@ public class OpcBrowseElement public OpcBrowseElement() { Name = ""; + ItemName = ""; } internal OpcBrowseElement(BrowseElement element) { Name = element.Name; + ItemName = element.ItemName; IsItem = element.IsItem; HasChildren = element.HasChildren; } diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilterType.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilterType.cs new file mode 100644 index 00000000..b41e28b2 --- /dev/null +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilterType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.OpcDa; + +/// +/// OpcDa 浏览过滤器类型枚举 +/// +public enum OpcBrowseFilterType +{ + /// + /// 全部 + /// + All, + + /// + /// 分支 + /// + Branch, + + /// + /// 数据项 + /// + Item +} diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs index 44f257a9..165f9381 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs @@ -28,4 +28,9 @@ public class OpcBrowseFilters /// 获得/设置 是否返回属性值 /// public bool ReturnPropertyValues { get; set; } + + /// + /// 获得/设置 浏览过滤器类型 + /// + public OpcBrowseFilterType BrowseFilter { get; set; } } diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs index 8d19f7f1..f2404848 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs @@ -9,10 +9,10 @@ namespace BootstrapBlazor.OpcDa; /// public class OpcBrowsePosition { - internal OpcBrowsePosition(Opc.Da.BrowsePosition position) + internal OpcBrowsePosition(Opc.Da.BrowsePosition? position) { Position = position; } - internal Opc.Da.BrowsePosition Position { get; set; } + internal Opc.Da.BrowsePosition? Position { get; set; } } diff --git a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs index 3c7872d7..150e61b0 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs +++ b/src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs @@ -151,9 +151,9 @@ private Opc.Da.Server GetOpcServer() public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position) { var server = GetOpcServer(); - var results = server.Browse(new ItemIdentifier(name), filters.ToFilters(), out var pos); - position = new OpcBrowsePosition(pos); - return results.Select(element => new OpcBrowseElement(element)).ToArray(); + var results = server.Browse(new ItemIdentifier(name), filters.ToFilters(), out var pos) ?? []; + position = pos == null ? null : new OpcBrowsePosition(pos); + return [.. results.Select(element => new OpcBrowseElement(element))]; } /// @@ -165,7 +165,8 @@ public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position) { var server = GetOpcServer(); var pos = position.Position; - return server.BrowseNext(ref pos).Select(element => new OpcBrowseElement(element)).ToArray(); + var results = server.BrowseNext(ref pos) ?? []; + return [.. results.Select(element => new OpcBrowseElement(element))]; } /// From ddfeecf6ce4d65961e4db0373b488f2c6d966c28 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 6 Aug 2025 22:59:03 +0800 Subject: [PATCH 8/9] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTestOpcDa/UnitTest1.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/UnitTestOpcDa/UnitTest1.cs b/test/UnitTestOpcDa/UnitTest1.cs index edcb17c1..45b98aa0 100644 --- a/test/UnitTestOpcDa/UnitTest1.cs +++ b/test/UnitTestOpcDa/UnitTest1.cs @@ -91,6 +91,8 @@ public void Browser_Ok() var server = sp.GetRequiredService(); server.Connect("opcda://localhost/Kepware.KEPServerEX.V6"); - server.Browser(); + var elements = server.Browse("Channel1", new OpcBrowseFilters(), out var position); + Assert.Equal(3, elements.Length); + Assert.Null(position); } } From 2e6bec6e7872a7600b306620dfca20470ff02faa Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 7 Aug 2025 09:37:16 +0800 Subject: [PATCH 9/9] chore: bump version 9.0.1 --- .../BootstrapBlazor.OpcDa/BootstrapBlazor.OpcDa.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/BootstrapBlazor.OpcDa/BootstrapBlazor.OpcDa.csproj b/src/extensions/BootstrapBlazor.OpcDa/BootstrapBlazor.OpcDa.csproj index 6b2d13dc..66f7b903 100644 --- a/src/extensions/BootstrapBlazor.OpcDa/BootstrapBlazor.OpcDa.csproj +++ b/src/extensions/BootstrapBlazor.OpcDa/BootstrapBlazor.OpcDa.csproj @@ -1,7 +1,7 @@  - 9.0.0 + 9.0.1 BootstrapBlazor.OpcDa