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:
2025-07-08 20:19:06 +08:00
parent 07253aed65
commit 5dd0ed8e39
11 changed files with 96 additions and 20 deletions

13
.idea/.idea.PMSWPF/.idea/.gitignore generated vendored Normal file
View 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
View 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>

View 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
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -79,6 +79,12 @@ public class DbDevice
[SugarColumn(ColumnDataType = "varchar(20)", SqlParameterDbType = typeof(EnumToStringConvert))]
public ProtocolType ProtocolType { get; set; }
/// <summary>
/// OPC UA Endpoint URL。
/// </summary>
[SugarColumn(IsNullable = true)]
public string? OpcUaEndpointUrl { get; set; }
/// <summary>
/// 设备关联的变量表列表。
/// </summary>

View File

@@ -89,7 +89,7 @@ public class DeviceRepository
/// </summary>
/// <param name="id">设备ID。</param>
/// <returns>对应的DbDevice对象。</returns>
public async Task<DbDevice> GetById(int id)
public async Task<Device> GetById(int id)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
@@ -99,7 +99,7 @@ public class DeviceRepository
.FirstAsync(p => p.Id == id);
stopwatch.Stop();
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);
return addDevice.CopyTo<Device>();
}
}

View File

@@ -46,6 +46,8 @@ public class VarDataRepository
using (var _db = DbContext.GetInstance())
{
var result = await _db.Queryable<DbVariableData>()
.Includes(d => d.VariableTable)
.Includes(d => d.VariableTable.Device)
.ToListAsync();
stopwatch.Stop();
NlogHelper.Info($"获取所有VariableData耗时{stopwatch.ElapsedMilliseconds}ms");

View File

@@ -88,6 +88,12 @@ public partial class Device : ObservableObject
/// </summary>
public ProtocolType ProtocolType { get; set; }
/// <summary>
/// OPC UA Endpoint URL。
/// </summary>
[ObservableProperty]
private string? opcUaEndpointUrl;
/// <summary>
/// 设备关联的变量表列表。
/// </summary>

View File

@@ -154,6 +154,11 @@ public partial class VariableData : ObservableObject
/// </summary>
public int VariableTableId { get; set; }
/// <summary>
/// 关联的变量表实体。
/// </summary>
public VariableTable? VariableTable { get; set; }
/// <summary>
/// 关联的MQTT配置列表。
/// </summary>

View File

@@ -193,6 +193,16 @@ public partial class DataServices : ObservableRecipient, IRecipient<LoadMessage>
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>

View File

@@ -150,9 +150,14 @@ namespace PMSWPF.Services
foreach (var id in variablesToRemove)
{
if (_opcUaVariables.TryGetValue(id, out var variable))
{
// 获取关联的设备信息
var device = await _dataServices.GetDeviceByIdAsync(variable.VariableTable.DeviceId??0);
if (device != null)
{
// 断开与该变量相关的 OPC UA 会话。
await DisconnectOpcUaSession(variable.OpcUaEndpointUrl);
await DisconnectOpcUaSession(device.OpcUaEndpointUrl);
}
_opcUaVariables.Remove(id);
}
}
@@ -160,20 +165,28 @@ namespace PMSWPF.Services
// 处理新增或更新的变量。
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))
{
// 如果是新变量,则添加到字典并建立连接和订阅。
_opcUaVariables.Add(variable.Id, variable);
await ConnectAndSubscribeOpcUa(variable);
await ConnectAndSubscribeOpcUa(variable, device);
}
else
{
// 如果变量已存在,则更新其信息。
_opcUaVariables[variable.Id] = variable;
// 如果终结点 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 服务器并订阅指定的变量。
/// </summary>
/// <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 服务器...");
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.");
return;
@@ -194,9 +208,9 @@ namespace PMSWPF.Services
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
{
@@ -221,12 +235,12 @@ namespace PMSWPF.Services
}
// 4. 发现服务器提供的终结点。
DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(variable.OpcUaEndpointUrl));
EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { variable.OpcUaEndpointUrl });
DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(device.OpcUaEndpointUrl));
EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { device.OpcUaEndpointUrl });
// 简化处理:选择第一个无安全策略的终结点。在生产环境中应选择合适的安全策略。
// 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);
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
@@ -240,9 +254,9 @@ namespace PMSWPF.Services
new UserIdentity(new AnonymousIdentityToken()), // 使用匿名用户身份
null);
_opcUaSessions[variable.OpcUaEndpointUrl] = session;
NlogHelper.Info($"Connected to OPC UA server: {variable.OpcUaEndpointUrl}");
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {variable.OpcUaEndpointUrl}");
_opcUaSessions[device.OpcUaEndpointUrl] = session;
NlogHelper.Info($"Connected to OPC UA server: {device.OpcUaEndpointUrl}");
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {device.OpcUaEndpointUrl}");
// 6. 创建订阅。
Subscription subscription = new Subscription(session.DefaultSubscription);
@@ -250,7 +264,7 @@ namespace PMSWPF.Services
session.AddSubscription(subscription);
subscription.Create();
_opcUaSubscriptions[variable.OpcUaEndpointUrl] = subscription;
_opcUaSubscriptions[device.OpcUaEndpointUrl] = subscription;
// 7. 创建监控项并添加到订阅中。
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
@@ -268,8 +282,8 @@ namespace PMSWPF.Services
}
catch (Exception ex)
{
NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
}
}
}