Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,26 @@ 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 filters)
{
return new BrowseFilters
{
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
};
}
}
18 changes: 18 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/IOpcDaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
Expand Down Expand Up @@ -59,4 +61,20 @@ public interface IOpcDaServer : IDisposable
/// <param name="items"></param>
/// <returns></returns>
HashSet<OpcWriteItem> Write(params HashSet<OpcWriteItem> items);

/// <summary>
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
/// </summary>
/// <param name="name"></param>
/// <param name="filters"></param>
/// <param name="position"></param>
/// <returns></returns>
OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position);

/// <summary>
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
OpcBrowseElement[] BrowseNext(OpcBrowsePosition position);
}
79 changes: 79 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// 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;
using System.Net.Http.Headers;

namespace BootstrapBlazor.OpcDa;

/// <summary>
Expand Down Expand Up @@ -61,6 +64,82 @@ public HashSet<OpcWriteItem> Write(params HashSet<OpcWriteItem> items)
.ToHashSet(OpcItemEqualityComparer<OpcWriteItem>.Default);
}

/// <summary>
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
/// </summary>
/// <param name="name"></param>
/// <param name="filters"></param>
/// <param name="position"></param>
/// <returns></returns>
public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
Comment thread
ArgoZhang marked this conversation as resolved.
{
position = null;
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 [];
}

/// <summary>
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
{
return [];
}

public void Dispose()
{

Expand Down
50 changes: 50 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/OpcBrowseElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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;

/// <summary>
/// 对 OpcDataServer BrowseElement 的封装类
/// </summary>
public class OpcBrowseElement
{
/// <summary>
/// 获得/设置 节点名称
/// </summary>
public string Name { get; set; }

/// <summary>
/// 获得/设置 Item 名称
/// </summary>
public string ItemName { get; set; }

/// <summary>
/// 获得/设置 是否是数据项
/// </summary>
public bool IsItem { get; set; }

/// <summary>
/// 获得/设置 是否有子节点
/// </summary>
public bool HasChildren { get; set; }

/// <summary>
/// 构造函数
/// </summary>
public OpcBrowseElement()
{
Name = "";
ItemName = "";
}

internal OpcBrowseElement(BrowseElement element)
{
Name = element.Name;
ItemName = element.ItemName;
IsItem = element.IsItem;
HasChildren = element.HasChildren;
}
}
26 changes: 26 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilterType.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// OpcDa 浏览过滤器类型枚举
/// </summary>
public enum OpcBrowseFilterType
{
/// <summary>
/// 全部
/// </summary>
All,

/// <summary>
/// 分支
/// </summary>
Branch,

/// <summary>
/// 数据项
/// </summary>
Item
}
36 changes: 36 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/OpcBrowseFilters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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;

/// <summary>
/// 对 OpcDataServer BrowseFilters 的封装类
/// </summary>
public class OpcBrowseFilters
{
/// <summary>
/// 获得/设置 最大返回节点数量
/// </summary>
public int MaxElementsReturned { get; set; }

/// <summary>
/// 获得/设置 元素名称过滤器
/// </summary>
public string? ElementNameFilter { get; set; }

/// <summary>
/// 获得/设置 是否返回所有属性
/// </summary>
public bool ReturnAllProperties { get; set; }

/// <summary>
/// 获得/设置 是否返回属性值
/// </summary>
public bool ReturnPropertyValues { get; set; }

/// <summary>
/// 获得/设置 浏览过滤器类型
/// </summary>
public OpcBrowseFilterType BrowseFilter { get; set; }
}
18 changes: 18 additions & 0 deletions src/extensions/BootstrapBlazor.OpcDa/OpcBrowsePosition.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 对 OpcDataServer BrowsePosition 的封装类
/// </summary>
public class OpcBrowsePosition
{
internal OpcBrowsePosition(Opc.Da.BrowsePosition? position)
{
Position = position;
}

internal Opc.Da.BrowsePosition? Position { get; set; }
}
30 changes: 28 additions & 2 deletions src/extensions/BootstrapBlazor.OpcDa/OpcDaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using Opc;
using Opc.Da;
using System.Collections.Concurrent;
using System.Runtime.Versioning;

namespace BootstrapBlazor.OpcDa;
Expand All @@ -16,7 +15,6 @@ namespace BootstrapBlazor.OpcDa;
sealed class OpcDaServer : IOpcDaServer
{
private Opc.Da.Server? _server = null;
private readonly ConcurrentDictionary<string, HashSet<OpcReadItem>> _valuesCache = [];

/// <summary>
/// 获得 OPC Server 名称
Expand Down Expand Up @@ -143,6 +141,34 @@ private Opc.Da.Server GetOpcServer()
return _server;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="name"></param>
/// <param name="filters"></param>
/// <param name="position"></param>
/// <returns></returns>
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 = pos == null ? null : new OpcBrowsePosition(pos);
return [.. results.Select(element => new OpcBrowseElement(element))];
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
{
var server = GetOpcServer();
var pos = position.Position;
var results = server.BrowseNext(ref pos) ?? [];
return [.. results.Select(element => new OpcBrowseElement(element))];
}

/// <summary>
/// Dispose 方法
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions test/UnitTestOpcDa/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,19 @@ 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<IOpcDaServer>();
server.Connect("opcda://localhost/Kepware.KEPServerEX.V6");

var elements = server.Browse("Channel1", new OpcBrowseFilters(), out var position);
Assert.Equal(3, elements.Length);
Assert.Null(position);
}
}