Skip to content

Commit db208d3

Browse files
authored
feat(OpcDa): bump version 10.0.1 (#8070)
* refactor: 增加线程文化保持功能 * feat: 增加 AddMockOpcDaServer 方法 * doc: 完善多语言 * chore: 更新依赖到 10.0.1 * doc: 更新示例
1 parent 86babcf commit db208d3

8 files changed

Lines changed: 353 additions & 34 deletions

File tree

‎src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<PackageReference Include="BootstrapBlazor.OctIcon" Version="10.0.3" />
6565
<PackageReference Include="BootstrapBlazor.OfficeViewer" Version="10.0.0" />
6666
<PackageReference Include="BootstrapBlazor.OnScreenKeyboard" Version="10.0.0" />
67-
<PackageReference Include="BootstrapBlazor.OpcDa" Version="10.0.0" />
67+
<PackageReference Include="BootstrapBlazor.OpcDa" Version="10.0.1" />
6868
<PackageReference Include="BootstrapBlazor.PdfReader" Version="10.0.26" />
6969
<PackageReference Include="BootstrapBlazor.PdfViewer" Version="10.0.1" />
7070
<PackageReference Include="BootstrapBlazor.Player" Version="10.0.1" />

‎src/BootstrapBlazor.Server/Components/Samples/OpcDa.razor‎

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,91 @@
77

88
<PackageTips Name="BootstrapBlazor.OpcDa"/>
99

10+
<p class="code-label">@Localizer["RegisterServiceText"]</p>
11+
12+
<Pre>// 增加 BootstrapBlazor.OpcDa 服务
13+
if (OperatingSystem.IsWindows())
14+
{
15+
services.AddOpcDaServer();
16+
}</Pre>
17+
1018
<DemoBlock Title="@Localizer["OpcDaNormalTitle"]" Introduction="@Localizer["OpcDaNormalIntro"]" ShowCode="false" Name="Normal">
11-
<p class="code-label">1. 点击 <b>连接</b> 按钮与 <code>OpcDa</code> 服务器建立通讯连接</p>
19+
<p class="code-label">@Localizer["InjectServiceText"]</p>
20+
<Pre>[Inject]
21+
[NotNull]
22+
private IOpcDaServer? OpcDaServer { get; set; }</Pre>
23+
24+
<p class="code-label">@((MarkupString)Localizer["Step1Text"].Value)</p>
1225
<p>
1326
<BootstrapInputGroup>
14-
<BootstrapInputGroupLabel DisplayText="OpcDa Server"></BootstrapInputGroupLabel>
27+
<BootstrapInputGroupLabel DisplayText="@Localizer["ServerLabel"]"></BootstrapInputGroupLabel>
1528
<Display @bind-Value="@_serverName"></Display>
16-
<Button Text="连接" OnClick="OnConnect" IsDisabled="OpcDaServer.IsConnected"></Button>
17-
<Button Text="断开" OnClick="OnDisConnect" IsDisabled="!OpcDaServer.IsConnected"
29+
<Button Text="@Localizer["ConnectButtonText"]" OnClick="OnConnect" IsDisabled="OpcDaServer.IsConnected"></Button>
30+
<Button Text="@Localizer["DisconnectButtonText"]" OnClick="OnDisConnect" IsDisabled="!OpcDaServer.IsConnected"
1831
Color="Color.Danger"></Button>
1932
</BootstrapInputGroup>
2033
</p>
2134

22-
<p class="code-label">2. 点击 <b>读取</b> 按钮读取 <code>OpcDa</code> 服务器上的位号值</p>
35+
<p class="code-label">@((MarkupString)Localizer["Step2Text"].Value)</p>
2336
<div class="row g-3 mb-3">
2437
<div class="col-12 col-sm-6">
2538
<BootstrapInputGroup>
26-
<BootstrapInputGroupLabel DisplayText="转速"></BootstrapInputGroupLabel>
39+
<BootstrapInputGroupLabel DisplayText="@Localizer["SpeedLabel"]"></BootstrapInputGroupLabel>
2740
<BootstrapInputGroupLabel DisplayText="@Tag1"></BootstrapInputGroupLabel>
2841
<Display @bind-Value="@_tagValue1"></Display>
2942
</BootstrapInputGroup>
3043
</div>
3144
<div class="col-12 col-sm-6">
3245
<BootstrapInputGroup>
33-
<BootstrapInputGroupLabel DisplayText="流速"></BootstrapInputGroupLabel>
46+
<BootstrapInputGroupLabel DisplayText="@Localizer["FlowLabel"]"></BootstrapInputGroupLabel>
3447
<BootstrapInputGroupLabel DisplayText="@Tag2"></BootstrapInputGroupLabel>
3548
<Display @bind-Value="@_tagValue2"></Display>
3649
</BootstrapInputGroup>
3750
</div>
3851
<div class="col-12 col-sm-6">
39-
<Button OnClick="OnRead" Text="读取" IsDisabled="!OpcDaServer.IsConnected"></Button>
52+
<Button OnClick="OnRead" Text="@Localizer["ReadButtonText"]" IsDisabled="!OpcDaServer.IsConnected"></Button>
4053
</div>
4154
</div>
4255

43-
<p class="code-label">3. 订阅功能</p>
44-
<p>通过订阅可以监控一组 <b>位号</b> 数据改变情况,当数据改变时通过 <code>DataChanged</code> 回调方法通知订阅者</p>
56+
<p class="code-label">@Localizer["Step3Title"]</p>
57+
<p>@((MarkupString)Localizer["Step3Text"].Value)</p>
4558
<p class="row g-3">
4659
<div class="col-12 col-sm-6">
4760
<BootstrapInputGroup>
48-
<BootstrapInputGroupLabel DisplayText="订阅名称"></BootstrapInputGroupLabel>
61+
<BootstrapInputGroupLabel DisplayText="@Localizer["SubscriptionNameLabel"]"></BootstrapInputGroupLabel>
4962
<BootstrapInputGroupLabel DisplayText="Subscription1"></BootstrapInputGroupLabel>
5063
</BootstrapInputGroup>
5164
</div>
5265
<div class="col-12 col-sm-6">
5366
<BootstrapInputGroup>
54-
<BootstrapInputGroupLabel DisplayText="更新频率"></BootstrapInputGroupLabel>
67+
<BootstrapInputGroupLabel DisplayText="@Localizer["UpdateRateLabel"]"></BootstrapInputGroupLabel>
5568
<BootstrapInputGroupLabel DisplayText="1000"></BootstrapInputGroupLabel>
5669
</BootstrapInputGroup>
5770
</div>
5871
<div class="col-12 col-sm-6">
5972
<BootstrapInputGroup>
60-
<BootstrapInputGroupLabel DisplayText="转速"></BootstrapInputGroupLabel>
73+
<BootstrapInputGroupLabel DisplayText="@Localizer["SpeedLabel"]"></BootstrapInputGroupLabel>
6174
<BootstrapInputGroupLabel DisplayText="@Tag1"></BootstrapInputGroupLabel>
6275
<Display @bind-Value="@_tagValue3"></Display>
6376
</BootstrapInputGroup>
6477
</div>
6578
<div class="col-12 col-sm-6">
6679
<BootstrapInputGroup>
67-
<BootstrapInputGroupLabel DisplayText="流速"></BootstrapInputGroupLabel>
80+
<BootstrapInputGroupLabel DisplayText="@Localizer["FlowLabel"]"></BootstrapInputGroupLabel>
6881
<BootstrapInputGroupLabel DisplayText="@Tag2"></BootstrapInputGroupLabel>
6982
<Display @bind-Value="@_tagValue4"></Display>
7083
</BootstrapInputGroup>
7184
</div>
7285
<div class="col-12 col-sm-6">
73-
<Button OnClick="OnCreateSubscription" Text="订阅" IsDisabled="@(!OpcDaServer.IsConnected || _subscribed)"></Button>
74-
<Button OnClick="OnCancelSubscription" Text="取消" IsDisabled="!_subscribed"></Button>
86+
<Button OnClick="OnCreateSubscription" Text="@Localizer["SubscribeButtonText"]" IsDisabled="@(!OpcDaServer.IsConnected || _subscribed)"></Button>
87+
<Button OnClick="OnCancelSubscription" Text="@Localizer["CancelButtonText"]" IsDisabled="!_subscribed"></Button>
7588
</div>
7689
</p>
7790

78-
<p class="code-label">4. 浏览</p>
79-
<p>通过调用 <code>OpcDaServer</code> 的 <code>Browse</code> 方法可以构建一个节点树状结构</p>
91+
<p class="code-label">@Localizer["Step4Title"]</p>
92+
<p>@((MarkupString)Localizer["Step4Text"].Value)</p>
8093
<p>
81-
<Button OnClick="OnBrowse" Text="浏览" IsDisabled="!OpcDaServer.IsConnected"></Button>
94+
<Button OnClick="OnBrowse" Text="@Localizer["BrowseButtonText"]" IsDisabled="!OpcDaServer.IsConnected"></Button>
8295
</p>
8396
<p>
8497
<TreeView Items="_roots" AutoCheckChildren="true" AutoCheckParent="true" ShowIcon="true"

‎src/BootstrapBlazor.Server/Components/Samples/OpcDa.razor.cs‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
55

66
using BootstrapBlazor.OpcDa;
7+
using System.Globalization;
78

89
namespace BootstrapBlazor.Server.Components.Samples;
910

@@ -29,6 +30,8 @@ public partial class OpcDa : ComponentBase
2930
private IOpcSubscription? _subscription;
3031

3132
private bool _subscribed;
33+
private CultureInfo? _culture;
34+
private CultureInfo? _uiCulture;
3235

3336
private void OnConnect()
3437
{
@@ -62,6 +65,8 @@ private void OnRead()
6265
private void OnCreateSubscription()
6366
{
6467
_subscribed = true;
68+
_culture = CultureInfo.CurrentCulture;
69+
_uiCulture = CultureInfo.CurrentUICulture;
6570
_subscription = OpcDaServer.CreateSubscription("Subscription1", 1000, true);
6671
_subscription.DataChanged += UpdateValues;
6772
_subscription.AddItems([Tag1, Tag2]);
@@ -79,18 +84,30 @@ private void OnCancelSubscription()
7984

8085
private void UpdateValues(List<OpcReadItem> items)
8186
{
82-
var value1 = items.Find(i => i.Name == Tag1).Value;
83-
if (value1 != null)
84-
{
85-
_tagValue3 = value1.ToString();
86-
}
87-
var value2 = items.Find(i => i.Name == Tag2).Value;
88-
if (value2 != null)
87+
_ = InvokeAsync(() =>
8988
{
90-
_tagValue4 = value2.ToString();
91-
}
92-
93-
InvokeAsync(StateHasChanged);
89+
if (_culture != null)
90+
{
91+
CultureInfo.CurrentCulture = _culture;
92+
}
93+
if (_uiCulture != null)
94+
{
95+
CultureInfo.CurrentUICulture = _uiCulture;
96+
}
97+
98+
var value1 = items.Find(i => i.Name == Tag1).Value;
99+
if (value1 != null)
100+
{
101+
_tagValue3 = value1.ToString();
102+
}
103+
var value2 = items.Find(i => i.Name == Tag2).Value;
104+
if (value2 != null)
105+
{
106+
_tagValue4 = value2.ToString();
107+
}
108+
109+
StateHasChanged();
110+
});
94111
}
95112

96113
private List<TreeViewItem<OpcBrowseElement>> _roots = [];

‎src/BootstrapBlazor.Server/Locales/en-US.json‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3470,10 +3470,29 @@
34703470
"OnScreenKeyboardsTypeNumpadTitle": "Numpad"
34713471
},
34723472
"BootstrapBlazor.Server.Components.Samples.OpcDa": {
3473+
"BrowseButtonText": "Browse",
3474+
"CancelButtonText": "Cancel",
3475+
"ConnectButtonText": "Connect",
3476+
"DisconnectButtonText": "Disconnect",
3477+
"FlowLabel": "Flow",
3478+
"InjectServiceText": "Get the BootstrapBlazor.OpcDa service",
34733479
"OpcDaDescription": "Connect to OpcDa Server to obtain PLC real-time data",
34743480
"OpcDaNormalIntro": "Get an instance by injecting the service <code>IOpcDaServer</code> and call the <code>Read</code> method to get the PLC Tag value.",
34753481
"OpcDaNormalTitle": "Basic usage",
3476-
"OpcDaTitle": "OpcDa Server"
3482+
"OpcDaTitle": "OpcDa Server",
3483+
"ReadButtonText": "Read",
3484+
"RegisterServiceText": "Register the BootstrapBlazor.OpcDa service (this service only supports Windows; for cross-platform scenarios, use the OpcUa service instead)",
3485+
"ServerLabel": "OpcDa Server",
3486+
"SpeedLabel": "Speed",
3487+
"Step1Text": "1. Click the <b>Connect</b> button to establish communication with the <code>OpcDa</code> server",
3488+
"Step2Text": "2. Click the <b>Read</b> button to read tag values from the <code>OpcDa</code> server",
3489+
"Step3Text": "Through subscription, you can monitor changes in a group of <b>tag</b> data and notify subscribers through the <code>DataChanged</code> callback when data changes",
3490+
"Step3Title": "3. Subscription",
3491+
"Step4Text": "Call the <code>Browse</code> method of <code>OpcDaServer</code> to build a tree structure of nodes",
3492+
"Step4Title": "4. Browse",
3493+
"SubscribeButtonText": "Subscribe",
3494+
"SubscriptionNameLabel": "Subscription Name",
3495+
"UpdateRateLabel": "Update Rate"
34773496
},
34783497
"BootstrapBlazor.Server.Components.Samples.OtpInputs": {
34793498
"OtpInputsDescription": "A secure verification password box based on OTP (One-Time Password) that is limited to one-time use and has a time limit",

‎src/BootstrapBlazor.Server/Locales/zh-CN.json‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3470,10 +3470,29 @@
34703470
"OnScreenKeyboardsTypeNumpadTitle": "数字键盘"
34713471
},
34723472
"BootstrapBlazor.Server.Components.Samples.OpcDa": {
3473+
"BrowseButtonText": "浏览",
3474+
"CancelButtonText": "取消",
3475+
"ConnectButtonText": "连接",
3476+
"DisconnectButtonText": "断开",
3477+
"FlowLabel": "流速",
3478+
"InjectServiceText": "获得 BootstrapBlazor.OpcDa 服务",
34733479
"OpcDaDescription": "连接 OpcDa Server 获得 PLC 实时数据",
34743480
"OpcDaNormalIntro": "通过注入服务 <code>IOpcDaServer</code> 获得实例,调用 <code>Read</code> 方法获得 PLC 位号值",
34753481
"OpcDaNormalTitle": "基本用法",
3476-
"OpcDaTitle": "OpcDa Server 服务"
3482+
"OpcDaTitle": "OpcDa Server 服务",
3483+
"ReadButtonText": "读取",
3484+
"RegisterServiceText": "注册 BootstrapBlazor.OpcDa 服务(该服务仅支持 windows 平台,跨平台需求请转 OpcUa 服务)",
3485+
"ServerLabel": "OpcDa Server",
3486+
"SpeedLabel": "转速",
3487+
"Step1Text": "1. 点击 <b>连接</b> 按钮与 <code>OpcDa</code> 服务器建立通讯连接",
3488+
"Step2Text": "2. 点击 <b>读取</b> 按钮读取 <code>OpcDa</code> 服务器上的位号值",
3489+
"Step3Text": "通过订阅可以监控一组 <b>位号</b> 数据改变情况,当数据改变时通过 <code>DataChanged</code> 回调方法通知订阅者",
3490+
"Step3Title": "3. 订阅功能",
3491+
"Step4Text": "通过调用 <code>OpcDaServer</code> 的 <code>Browse</code> 方法可以构建一个节点树状结构",
3492+
"Step4Title": "4. 浏览",
3493+
"SubscribeButtonText": "订阅",
3494+
"SubscriptionNameLabel": "订阅名称",
3495+
"UpdateRateLabel": "更新频率"
34773496
},
34783497
"BootstrapBlazor.Server.Components.Samples.OtpInputs": {
34793498
"OtpInputsDescription": "基于 OTP(One-Time Password‌) 仅限单次使用且具备时效性的安全验证密码框",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.OpcDa;
7+
8+
/// <summary>
9+
/// 模拟 OpcDa Server 实现类
10+
/// </summary>
11+
sealed class MockOpcDaServer : IOpcDaServer
12+
{
13+
public bool IsConnected { get; set; }
14+
15+
public string? ServerName { get; set; }
16+
17+
private readonly Dictionary<string, IOpcSubscription> _subscriptions = [];
18+
19+
public bool Connect(string serverName)
20+
{
21+
ServerName = serverName;
22+
IsConnected = true;
23+
return true;
24+
}
25+
26+
public void Disconnect()
27+
{
28+
IsConnected = false;
29+
ServerName = null;
30+
}
31+
32+
public IOpcSubscription CreateSubscription(string name, int updateRate = 1000, bool active = true)
33+
{
34+
if (_subscriptions.TryGetValue(name, out var subscription))
35+
{
36+
CancelSubscription(subscription);
37+
}
38+
39+
subscription = new MockOpcDaSubscription(name, updateRate, active);
40+
_subscriptions.Add(name, subscription);
41+
return subscription;
42+
}
43+
44+
public void CancelSubscription(IOpcSubscription subscription)
45+
{
46+
_subscriptions.Remove(subscription.Name);
47+
if (subscription is IDisposable disposable)
48+
{
49+
disposable.Dispose();
50+
}
51+
}
52+
53+
public HashSet<OpcReadItem> Read(params HashSet<string> items)
54+
{
55+
return items.Select(i => new OpcReadItem(i, Quality.Good, DateTime.Now, Random.Shared.Next(1000, 2000)))
56+
.ToHashSet(OpcItemEqualityComparer<OpcReadItem>.Default);
57+
}
58+
59+
public HashSet<OpcWriteItem> Write(params HashSet<OpcWriteItem> items)
60+
{
61+
return items.Select(i => new OpcWriteItem(i.Name, i.Value) { Result = true })
62+
.ToHashSet(OpcItemEqualityComparer<OpcWriteItem>.Default);
63+
}
64+
65+
/// <summary>
66+
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
67+
/// </summary>
68+
/// <param name="name"></param>
69+
/// <param name="filters"></param>
70+
/// <param name="position"></param>
71+
/// <returns></returns>
72+
public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
73+
{
74+
position = null;
75+
if (string.IsNullOrEmpty(name))
76+
{
77+
return [
78+
new OpcBrowseElement()
79+
{
80+
Name ="Channel1",
81+
ItemName = "Channel1",
82+
IsItem = false,
83+
HasChildren = true
84+
},
85+
new OpcBrowseElement()
86+
{
87+
Name ="Channel2",
88+
ItemName = "Channel2",
89+
IsItem = false,
90+
HasChildren = true
91+
}
92+
];
93+
}
94+
95+
if (name == "Channel1")
96+
{
97+
return [
98+
new OpcBrowseElement()
99+
{
100+
Name ="Device1",
101+
ItemName = "Channel1.Device1",
102+
IsItem = false,
103+
HasChildren = true
104+
}
105+
];
106+
}
107+
108+
if (name == "Channel1.Device1")
109+
{
110+
return [
111+
new OpcBrowseElement()
112+
{
113+
Name ="Tag1",
114+
ItemName = "Channel1.Device1.Tag1",
115+
IsItem = true,
116+
HasChildren = false
117+
},
118+
new OpcBrowseElement()
119+
{
120+
Name ="Tag2",
121+
ItemName = "Channel1.Device1.Tag2",
122+
IsItem = true,
123+
HasChildren = false
124+
}
125+
];
126+
}
127+
128+
return [];
129+
}
130+
131+
/// <summary>
132+
/// 浏览 OPC Server 中的位号 (即数据项或者标签)
133+
/// </summary>
134+
/// <param name="position"></param>
135+
/// <returns></returns>
136+
public OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
137+
{
138+
return [];
139+
}
140+
141+
public void Dispose()
142+
{
143+
144+
}
145+
}

0 commit comments

Comments
 (0)