1 feat: 将 OpcUaEndpointUrl 移动到 Device 并更新 OpcUaBackgroundService
2 将 OpcUaEndpointUrl 属性从 VariableData 和 DbVariableData 移动到 Device 和 DbDevice。 3 更新 OpcUaBackgroundService 以从关联的 Device 对象中检索 OpcUaEndpointUrl。 4 确保 DataServices 和 VarDataRepository 正确加载关联的 VariableTable 和 Device 数据。 5 在 VariableData 模型中添加 VariableTable 属性以正确解析。
This commit is contained in:
13
.idea/.idea.PMSWPF/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.PMSWPF/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider 忽略的文件
|
||||||
|
/.idea.PMSWPF.iml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
4
.idea/.idea.PMSWPF/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.PMSWPF/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/.idea.PMSWPF/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.PMSWPF/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.PMSWPF/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.PMSWPF/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -79,6 +79,12 @@ public class DbDevice
|
|||||||
[SugarColumn(ColumnDataType = "varchar(20)", SqlParameterDbType = typeof(EnumToStringConvert))]
|
[SugarColumn(ColumnDataType = "varchar(20)", SqlParameterDbType = typeof(EnumToStringConvert))]
|
||||||
public ProtocolType ProtocolType { get; set; }
|
public ProtocolType ProtocolType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OPC UA Endpoint URL。
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsNullable = true)]
|
||||||
|
public string? OpcUaEndpointUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备关联的变量表列表。
|
/// 设备关联的变量表列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class DeviceRepository
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">设备ID。</param>
|
/// <param name="id">设备ID。</param>
|
||||||
/// <returns>对应的DbDevice对象。</returns>
|
/// <returns>对应的DbDevice对象。</returns>
|
||||||
public async Task<DbDevice> GetById(int id)
|
public async Task<Device> GetById(int id)
|
||||||
{
|
{
|
||||||
Stopwatch stopwatch = new Stopwatch();
|
Stopwatch stopwatch = new Stopwatch();
|
||||||
stopwatch.Start();
|
stopwatch.Start();
|
||||||
@@ -99,7 +99,7 @@ public class DeviceRepository
|
|||||||
.FirstAsync(p => p.Id == id);
|
.FirstAsync(p => p.Id == id);
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
NlogHelper.Info($"根据ID '{id}' 获取设备耗时:{stopwatch.ElapsedMilliseconds}ms");
|
NlogHelper.Info($"根据ID '{id}' 获取设备耗时:{stopwatch.ElapsedMilliseconds}ms");
|
||||||
return result;
|
return result.CopyTo<Device>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,4 +252,6 @@ public class DeviceRepository
|
|||||||
await _menuRepository.AddVarTableMenu(addDevice, addDeviceMenuId, db);
|
await _menuRepository.AddVarTableMenu(addDevice, addDeviceMenuId, db);
|
||||||
return addDevice.CopyTo<Device>();
|
return addDevice.CopyTo<Device>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,8 @@ public class VarDataRepository
|
|||||||
using (var _db = DbContext.GetInstance())
|
using (var _db = DbContext.GetInstance())
|
||||||
{
|
{
|
||||||
var result = await _db.Queryable<DbVariableData>()
|
var result = await _db.Queryable<DbVariableData>()
|
||||||
|
.Includes(d => d.VariableTable)
|
||||||
|
.Includes(d => d.VariableTable.Device)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
NlogHelper.Info($"获取所有VariableData耗时:{stopwatch.ElapsedMilliseconds}ms");
|
NlogHelper.Info($"获取所有VariableData耗时:{stopwatch.ElapsedMilliseconds}ms");
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ public partial class Device : ObservableObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ProtocolType ProtocolType { get; set; }
|
public ProtocolType ProtocolType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OPC UA Endpoint URL。
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? opcUaEndpointUrl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备关联的变量表列表。
|
/// 设备关联的变量表列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ public partial class VariableData : ObservableObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int VariableTableId { get; set; }
|
public int VariableTableId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联的变量表实体。
|
||||||
|
/// </summary>
|
||||||
|
public VariableTable? VariableTable { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 关联的MQTT配置列表。
|
/// 关联的MQTT配置列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -193,6 +193,16 @@ public partial class DataServices : ObservableRecipient, IRecipient<LoadMessage>
|
|||||||
return await _varDataRepository.GetAllAsync();
|
return await _varDataRepository.GetAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步根据ID获取设备数据。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备ID。</param>
|
||||||
|
/// <returns>设备对象,如果不存在则为null。</returns>
|
||||||
|
public async Task<Device> GetDeviceByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _deviceRepository.GetById(id);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步加载变量数据。
|
/// 异步加载变量数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -151,8 +151,13 @@ namespace PMSWPF.Services
|
|||||||
{
|
{
|
||||||
if (_opcUaVariables.TryGetValue(id, out var variable))
|
if (_opcUaVariables.TryGetValue(id, out var variable))
|
||||||
{
|
{
|
||||||
// 断开与该变量相关的 OPC UA 会话。
|
// 获取关联的设备信息
|
||||||
await DisconnectOpcUaSession(variable.OpcUaEndpointUrl);
|
var device = await _dataServices.GetDeviceByIdAsync(variable.VariableTable.DeviceId??0);
|
||||||
|
if (device != null)
|
||||||
|
{
|
||||||
|
// 断开与该变量相关的 OPC UA 会话。
|
||||||
|
await DisconnectOpcUaSession(device.OpcUaEndpointUrl);
|
||||||
|
}
|
||||||
_opcUaVariables.Remove(id);
|
_opcUaVariables.Remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,20 +165,28 @@ namespace PMSWPF.Services
|
|||||||
// 处理新增或更新的变量。
|
// 处理新增或更新的变量。
|
||||||
foreach (var variable in opcUaVariables)
|
foreach (var variable in opcUaVariables)
|
||||||
{
|
{
|
||||||
|
// 获取关联的设备信息
|
||||||
|
var device = await _dataServices.GetDeviceByIdAsync(variable.VariableTable.DeviceId??0);
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
NlogHelper.Warn($"变量 '{variable.Name}' (ID: {variable.Id}) 关联的设备不存在。");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_opcUaVariables.ContainsKey(variable.Id))
|
if (!_opcUaVariables.ContainsKey(variable.Id))
|
||||||
{
|
{
|
||||||
// 如果是新变量,则添加到字典并建立连接和订阅。
|
// 如果是新变量,则添加到字典并建立连接和订阅。
|
||||||
_opcUaVariables.Add(variable.Id, variable);
|
_opcUaVariables.Add(variable.Id, variable);
|
||||||
await ConnectAndSubscribeOpcUa(variable);
|
await ConnectAndSubscribeOpcUa(variable, device);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 如果变量已存在,则更新其信息。
|
// 如果变量已存在,则更新其信息。
|
||||||
_opcUaVariables[variable.Id] = variable;
|
_opcUaVariables[variable.Id] = variable;
|
||||||
// 如果终结点 URL 对应的会话已断开,则尝试重新连接。
|
// 如果终结点 URL 对应的会话已断开,则尝试重新连接。
|
||||||
if (_opcUaSessions.ContainsKey(variable.OpcUaEndpointUrl) && !_opcUaSessions[variable.OpcUaEndpointUrl].Connected)
|
if (_opcUaSessions.ContainsKey(device.OpcUaEndpointUrl) && !_opcUaSessions[device.OpcUaEndpointUrl].Connected)
|
||||||
{
|
{
|
||||||
await ConnectAndSubscribeOpcUa(variable);
|
await ConnectAndSubscribeOpcUa(variable, device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,10 +196,11 @@ namespace PMSWPF.Services
|
|||||||
/// 连接到 OPC UA 服务器并订阅指定的变量。
|
/// 连接到 OPC UA 服务器并订阅指定的变量。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="variable">要订阅的变量信息。</param>
|
/// <param name="variable">要订阅的变量信息。</param>
|
||||||
private async Task ConnectAndSubscribeOpcUa(VariableData variable)
|
/// <param name="device">变量所属的设备信息。</param>
|
||||||
|
private async Task ConnectAndSubscribeOpcUa(VariableData variable, Device device)
|
||||||
{
|
{
|
||||||
NlogHelper.Info($"正在为变量 '{variable.Name}' 连接和订阅 OPC UA 服务器...");
|
NlogHelper.Info($"正在为变量 '{variable.Name}' 连接和订阅 OPC UA 服务器...");
|
||||||
if (string.IsNullOrEmpty(variable.OpcUaEndpointUrl) || string.IsNullOrEmpty(variable.OpcUaNodeId))
|
if (string.IsNullOrEmpty(device.OpcUaEndpointUrl) || string.IsNullOrEmpty(variable.OpcUaNodeId))
|
||||||
{
|
{
|
||||||
NlogHelper.Warn($"OPC UA variable {variable.Name} has invalid EndpointUrl or NodeId.");
|
NlogHelper.Warn($"OPC UA variable {variable.Name} has invalid EndpointUrl or NodeId.");
|
||||||
return;
|
return;
|
||||||
@@ -194,9 +208,9 @@ namespace PMSWPF.Services
|
|||||||
|
|
||||||
Session session = null;
|
Session session = null;
|
||||||
// 检查是否已存在到该终结点的活动会话。
|
// 检查是否已存在到该终结点的活动会话。
|
||||||
if (_opcUaSessions.TryGetValue(variable.OpcUaEndpointUrl, out session) && session.Connected)
|
if (_opcUaSessions.TryGetValue(device.OpcUaEndpointUrl, out session) && session.Connected)
|
||||||
{
|
{
|
||||||
NlogHelper.Info($"Already connected to OPC UA endpoint: {variable.OpcUaEndpointUrl}");
|
NlogHelper.Info($"Already connected to OPC UA endpoint: {device.OpcUaEndpointUrl}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -221,12 +235,12 @@ namespace PMSWPF.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 发现服务器提供的终结点。
|
// 4. 发现服务器提供的终结点。
|
||||||
DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(variable.OpcUaEndpointUrl));
|
DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(device.OpcUaEndpointUrl));
|
||||||
EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { variable.OpcUaEndpointUrl });
|
EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { device.OpcUaEndpointUrl });
|
||||||
|
|
||||||
// 简化处理:选择第一个无安全策略的终结点。在生产环境中应选择合适的安全策略。
|
// 简化处理:选择第一个无安全策略的终结点。在生产环境中应选择合适的安全策略。
|
||||||
// ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, endpoints.First(e => e.SecurityMode == MessageSecurityMode.None), config);
|
// ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, endpoints.First(e => e.SecurityMode == MessageSecurityMode.None), config);
|
||||||
EndpointDescription selectedEndpoint = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, variable.OpcUaEndpointUrl, false);
|
EndpointDescription selectedEndpoint = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, device.OpcUaEndpointUrl, false);
|
||||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(application.ApplicationConfiguration);
|
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(application.ApplicationConfiguration);
|
||||||
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
|
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
|
||||||
|
|
||||||
@@ -240,9 +254,9 @@ namespace PMSWPF.Services
|
|||||||
new UserIdentity(new AnonymousIdentityToken()), // 使用匿名用户身份
|
new UserIdentity(new AnonymousIdentityToken()), // 使用匿名用户身份
|
||||||
null);
|
null);
|
||||||
|
|
||||||
_opcUaSessions[variable.OpcUaEndpointUrl] = session;
|
_opcUaSessions[device.OpcUaEndpointUrl] = session;
|
||||||
NlogHelper.Info($"Connected to OPC UA server: {variable.OpcUaEndpointUrl}");
|
NlogHelper.Info($"Connected to OPC UA server: {device.OpcUaEndpointUrl}");
|
||||||
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {variable.OpcUaEndpointUrl}");
|
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {device.OpcUaEndpointUrl}");
|
||||||
|
|
||||||
// 6. 创建订阅。
|
// 6. 创建订阅。
|
||||||
Subscription subscription = new Subscription(session.DefaultSubscription);
|
Subscription subscription = new Subscription(session.DefaultSubscription);
|
||||||
@@ -250,7 +264,7 @@ namespace PMSWPF.Services
|
|||||||
session.AddSubscription(subscription);
|
session.AddSubscription(subscription);
|
||||||
subscription.Create();
|
subscription.Create();
|
||||||
|
|
||||||
_opcUaSubscriptions[variable.OpcUaEndpointUrl] = subscription;
|
_opcUaSubscriptions[device.OpcUaEndpointUrl] = subscription;
|
||||||
|
|
||||||
// 7. 创建监控项并添加到订阅中。
|
// 7. 创建监控项并添加到订阅中。
|
||||||
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
|
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
|
||||||
@@ -268,8 +282,8 @@ namespace PMSWPF.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
|
NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
|
||||||
NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
|
NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user