From 16e193557eb76a81f9eb52ec5b2133a38ae28910 Mon Sep 17 00:00:00 2001 From: wenjy Date: Tue, 23 Sep 2025 10:55:19 +0800 Subject: [PATCH] =?UTF-8?q?change=20-=20ua=20=E9=80=9A=E8=AE=AF=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 6148 bytes .../DeviceCollectionBusiness.cs | 165 +++++++++-- .../Sln.Imm.Daemon.Business.csproj | 15 + .../BaseDeviceInfoCacheService.cs | 2 +- Sln.Imm.Daemon.Config/.DS_Store | Bin 0 -> 6148 bytes .../Sln.Imm.Daemon.Model.csproj | 8 +- .../dto/DeviceParamValueDto.cs | 96 ++++-- Sln.Imm.Daemon.Model/dto/OpcItemValue.cs | 47 +-- Sln.Imm.Daemon.Model/dto/OpcNode.cs | 45 +-- Sln.Imm.Daemon.Opc/IOpcService.cs | 46 +-- Sln.Imm.Daemon.Opc/Impl/OpcDaService.cs | 230 +++++++++++++-- Sln.Imm.Daemon.Opc/Impl/OpcUaService.cs | 274 ++++++++++++++++-- Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj | 10 +- .../service/Impl/BaseDeviceInfoServiceImpl.cs | 9 +- Sln.Imm.Daemon.sln | 12 + Sln.Imm.Daemon/ConsoleHelper.cs | 31 -- Sln.Imm.Daemon/Program.cs | 27 +- Sln.Imm.Daemon/Sln.Imm.Daemon.csproj | 2 + 18 files changed, 831 insertions(+), 188 deletions(-) create mode 100644 Sln.Imm.Daemon.Business/Sln.Imm.Daemon.Business.csproj create mode 100644 Sln.Imm.Daemon.Config/.DS_Store delete mode 100644 Sln.Imm.Daemon/ConsoleHelper.cs diff --git a/.DS_Store b/.DS_Store index 14e71e92f659628650cfb3d621d6fc0f5f935381..032308d7c80d9688d091d7165ba54f5d7f9a38fc 100644 GIT binary patch delta 237 zcmZoMXfc=|#>B!ku~2NHo+2ar#(>?7iwl^U7}+QDFzGXjP8ML2QDV?zaAwG7$YV%j z$Ye;*NjD5m&d)7i00BnBN+1bVriQ94H{Zo2DJMS(D9y3xm-7Y})8mdfm8RfSie`C1 y29o7W0YC#8L_lub9Kp1nX)`+qKL^k=n-iJ8Gf(ChapYiRU|<5N*&HFVh8X~cjWdP- delta 72 zcmZoMXfc=|#>B)qu~2NHo+2aj#(>?7jLegHSo9f1CJV60Y>r@E&$O_Cc{4i)KL=3V aW> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Business -* 唯一标识:2152B53A-F33E-47F0-9112-21E05DFC903D -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-11 09:42:39 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Business + * 唯一标识:2152B53A-F33E-47F0-9112-21E05DFC903D + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-11 09:42:39 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> +using Newtonsoft.Json; +using Sln.Imm.Daemon.Cache; +using Sln.Imm.Daemon.Model.dao; +using Sln.Imm.Daemon.Model.dto; +using Sln.Imm.Daemon.Opc; +using Sln.Imm.Daemon.Opc.Impl; +using Sln.Imm.Daemon.Serilog; + namespace Sln.Imm.Daemon.Business; public class DeviceCollectionBusiness { - + private readonly SerilogHelper _serilog; + + private readonly BaseDeviceInfoCacheService _cacheService; + + private readonly IOpcService _opcUaService; + + public DeviceCollectionBusiness(SerilogHelper serilogHelper, BaseDeviceInfoCacheService cacheService,OpcUaService opcUaService) + { + _serilog = serilogHelper; + _cacheService = cacheService; + _opcUaService = opcUaService; + + this.Handle(); + } + + public async Task Handle() + { + bool isFalg = true; + + var deviceInfos = await _cacheService.GetValueAsync("BaseDeviceInfoCache"); + + do + { + lock (string.Empty) + { + foreach (var item in deviceInfos) + { + Task.Run(async () => + { + try + { + _serilog.Info($"开始采集{item.deviceName},设备数据"); + var opcItemValues = await this.ReadParam(item); + + this.SaveParam(item, opcItemValues, out List paramValues); + + _serilog.Info($"{item.deviceName}数据采集完成:{JsonConvert.SerializeObject(paramValues)}"); + } + catch (Exception e) + { + _serilog.Info($"{item.deviceName}数据读取异常:{e.Message}"); + } + }); + } + + Task.Delay(1000 * 10).Wait(); + } + } while (isFalg); + } + + /// + /// 读取设备参数 + /// + /// + public async Task> ReadParam(BaseDeviceInfo device) + { + try + { + if (device == null) + { + throw new ArgumentNullException($"设备信息不允许为空"); + } + + List deviceParams = device.deviceParams.Select(x => x.paramAddr).ToList(); + + bool result = await _opcUaService.ConnectAsync(device.networkAddress); + + if (!result) + { + throw new ArgumentNullException($"设备未连接"); + + } + + List infos = await _opcUaService.ReadNodeAsync(deviceParams); + + //var infos = _opcService.BrowseNodesAsync("ns=2;s=Devices/分厂一/车间一/测试空设备"); + + return infos; + + } + catch (Exception e) + { + throw new InvalidOperationException($"设备参数读取异常:{e.Message}"); + } + } + + /// + /// + /// + /// + /// + public void SaveParam(BaseDeviceInfo device, List opcItemValues, + out List paramValues) + { + var deviceParams = device.deviceParams.ToList(); + + paramValues = deviceParams + .GroupJoin(opcItemValues, + param => param.paramName, + value => value.NodeId, + (param, values) => new { Param = param, Values = values }) + .SelectMany( + x => x.Values.DefaultIfEmpty(), + (x, value) => new DeviceParamValueDto + { + deviceCode = x.Param.deviceCode, + paramCode = x.Param.paramCode, + paramName = x.Param.paramName, + netWork = x.Param.netWork, + paramAddr = x.Param.paramAddr, + paramType = x.Param.paramType, + isFlag = x.Param.isFlag, + paramValue = value?.Value.ToString(), + }) + .ToList(); + + //保存数据库 + } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Business/Sln.Imm.Daemon.Business.csproj b/Sln.Imm.Daemon.Business/Sln.Imm.Daemon.Business.csproj new file mode 100644 index 0000000..a352da5 --- /dev/null +++ b/Sln.Imm.Daemon.Business/Sln.Imm.Daemon.Business.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/Sln.Imm.Daemon.Cache/BaseDeviceInfoCacheService.cs b/Sln.Imm.Daemon.Cache/BaseDeviceInfoCacheService.cs index 201dac8..c23f621 100644 --- a/Sln.Imm.Daemon.Cache/BaseDeviceInfoCacheService.cs +++ b/Sln.Imm.Daemon.Cache/BaseDeviceInfoCacheService.cs @@ -62,7 +62,7 @@ public class BaseDeviceInfoCacheService { var value = _service.GetDeviceInfosByNavigate(); //将值存入缓存,设置过期时间等 - await _fusionCache.SetAsync(key, value, TimeSpan.FromMinutes(5)).ConfigureAwait(false); + await _fusionCache.SetAsync(key, value, TimeSpan.FromSeconds(30)).ConfigureAwait(false); _logger.Info($"通过ORM获取设备数据:{value.Count};条"); return value; } diff --git a/Sln.Imm.Daemon.Config/.DS_Store b/Sln.Imm.Daemon.Config/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a185a25f876c819dbf4e7256e7c692c6b64ddef8 GIT binary patch literal 6148 zcmeHKy-ve05WXWFBC&Kt2qC{h2li0H%+MWGp;8NKa2NKx1Wy4E!N$nSXzA}kQrP@smgwHU17&)C${E;t@6Hk;Zz;@Lvkms z6n%CEoPnHyLbp@7|F7}Mj5hgsi0_;MXW*YPz>|7W&+$@rw;sHn+_eE?i6J6>oh%?& lXFmbh$T@P7jA{?EhF@jaDM}X6r*NSE2qZ#$at3~ZfiHspKMw!^ literal 0 HcmV?d00001 diff --git a/Sln.Imm.Daemon.Model/Sln.Imm.Daemon.Model.csproj b/Sln.Imm.Daemon.Model/Sln.Imm.Daemon.Model.csproj index e9f3ad4..92fc415 100644 --- a/Sln.Imm.Daemon.Model/Sln.Imm.Daemon.Model.csproj +++ b/Sln.Imm.Daemon.Model/Sln.Imm.Daemon.Model.csproj @@ -7,15 +7,11 @@ - + - - - - - + diff --git a/Sln.Imm.Daemon.Model/dto/DeviceParamValueDto.cs b/Sln.Imm.Daemon.Model/dto/DeviceParamValueDto.cs index 23df3df..1ac05f4 100644 --- a/Sln.Imm.Daemon.Model/dto/DeviceParamValueDto.cs +++ b/Sln.Imm.Daemon.Model/dto/DeviceParamValueDto.cs @@ -1,31 +1,85 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Model.dto -* 唯一标识:30BE0131-C20C-4C85-BCC0-747E45D78D0E -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-11 10:47:21 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Model.dto + * 唯一标识:30BE0131-C20C-4C85-BCC0-747E45D78D0E + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-11 10:47:21 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> namespace Sln.Imm.Daemon.Model.dto; -public class DeviceParamValue +public class DeviceParamValueDto { - + /// + /// Desc:设备编号 + /// Default: + /// Nullable:False + /// + public string deviceCode { get; set; } + + /// + /// Desc:参数编号 + /// Default: + /// Nullable:False + /// + public string paramCode { get; set; } + + /// + /// Desc:参数名称 + /// Default: + /// Nullable:False + /// + public string paramName { get; set; } + + /// + /// Desc:网络地址 + /// Default: + /// Nullable:False + /// + public string netWork { get; set; } + + /// + /// Desc: 参数地址 + /// Default: + /// Nullable:False + /// + public string paramAddr { get; set; } + + /// + /// Desc: 参数类型 + /// Default: + /// Nullable:False + /// + public string paramType { get; set; } + + /// + /// Desc: 参数值 + /// Default: + /// Nullable:False + /// + public string paramValue { get; set; } + + /// + /// Desc: 是否启用 + /// Default: + /// Nullable:False + /// + public int isFlag { get; set; } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Model/dto/OpcItemValue.cs b/Sln.Imm.Daemon.Model/dto/OpcItemValue.cs index adfd14a..4da9850 100644 --- a/Sln.Imm.Daemon.Model/dto/OpcItemValue.cs +++ b/Sln.Imm.Daemon.Model/dto/OpcItemValue.cs @@ -1,25 +1,25 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Model.dto -* 唯一标识:B9DF55E9-5666-4C26-927B-206A96CAE127 -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 16:33:16 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Model.dto + * 唯一标识:B9DF55E9-5666-4C26-927B-206A96CAE127 + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-22 16:33:16 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> @@ -27,5 +27,12 @@ namespace Sln.Imm.Daemon.Model.dto; public class OpcItemValue { - + public string ItemId { get; set; } + + public string GroupName { get; set; } + public object Value { get; set; } + + public string Quality { get; set; } + + public DateTime Timestamp { get; set; } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Model/dto/OpcNode.cs b/Sln.Imm.Daemon.Model/dto/OpcNode.cs index 9e2c9c1..75dd21b 100644 --- a/Sln.Imm.Daemon.Model/dto/OpcNode.cs +++ b/Sln.Imm.Daemon.Model/dto/OpcNode.cs @@ -1,25 +1,25 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Model.dto -* 唯一标识:AD0BDBDD-F957-4589-A6AE-934E5A1FBE0F -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 17:24:21 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Model.dto + * 唯一标识:AD0BDBDD-F957-4589-A6AE-934E5A1FBE0F + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-22 17:24:21 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> @@ -27,5 +27,10 @@ namespace Sln.Imm.Daemon.Model.dto; public class OpcNode { - + public string NodeId { get; set; } + public string DisplayName { get; set; } + public object Value { get; set; } + public DateTimeOffset SourceTimestamp { get; set; } + public string DataType { get; set; } + public string AccessLevel { get; set; } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Opc/IOpcService.cs b/Sln.Imm.Daemon.Opc/IOpcService.cs index 73ff33d..2d2b277 100644 --- a/Sln.Imm.Daemon.Opc/IOpcService.cs +++ b/Sln.Imm.Daemon.Opc/IOpcService.cs @@ -1,31 +1,37 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Opc -* 唯一标识:5A00C633-A1C6-4D33-A338-6F4F303CB565 -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 17:18:26 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Opc + * 唯一标识:5A00C633-A1C6-4D33-A338-6F4F303CB565 + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-22 17:18:26 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> +using Sln.Imm.Daemon.Model.dto; + namespace Sln.Imm.Daemon.Opc; public interface IOpcService { - + Task ConnectAsync(string serverUrl); + Task DisconnectAsync(); + Task> ReadNodeAsync(List nodeId); + Task WriteNodeAsync(string nodeId, object value); + Task> BrowseNodesAsync(string startingNodeId = null); } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Opc/Impl/OpcDaService.cs b/Sln.Imm.Daemon.Opc/Impl/OpcDaService.cs index 745032b..9443aa0 100644 --- a/Sln.Imm.Daemon.Opc/Impl/OpcDaService.cs +++ b/Sln.Imm.Daemon.Opc/Impl/OpcDaService.cs @@ -1,31 +1,219 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Opc.Impl -* 唯一标识:3143E6AC-55C1-46DA-BDF8-8A19682A8A26 -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 17:25:33 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Opc.Impl + * 唯一标识:3143E6AC-55C1-46DA-BDF8-8A19682A8A26 + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-22 17:25:33 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> +using Sln.Imm.Daemon.Model.dto; +using TitaniumAS.Opc.Client; +using TitaniumAS.Opc.Client.Common; +using TitaniumAS.Opc.Client.Da; +using TitaniumAS.Opc.Client.Da.Browsing; + namespace Sln.Imm.Daemon.Opc.Impl; -public class OpcDaService +public class OpcDaService : IOpcService, IDisposable { - + private OpcDaServer _server; + private bool _disposed = false; + + public OpcDaService() + { + // 初始化 TitaniumAS.Opc.Client 库 + Bootstrap.Initialize(); + } + + public async Task ConnectAsync(string serverUrl) + { + try + { + // 构建服务器URL + Uri url = UrlBuilder.Build(serverUrl); + + // 创建服务器实例并连接 + _server = new OpcDaServer(url); + _server.Connect(); + + return _server.IsConnected; + } + catch (Exception ex) + { + Console.WriteLine($"连接到 OPC DA 服务器失败: {ex.Message}"); + return false; + } + } + + public async Task DisconnectAsync() + { + if (_server != null && _server.IsConnected) + { + _server.Disconnect(); + _server.Dispose(); + _server = null; + } + } + + public async Task> ReadNodeAsync(List nodeId) + { + if (_server == null || !_server.IsConnected) + throw new Exception("未连接到 OPC DA 服务器"); + + try + { + // 创建临时组和项进行读取 + using (var group = _server.AddGroup("TempReadGroup")) + { + group.IsActive = true; + + OpcDaItemDefinition[] definitions = new OpcDaItemDefinition[nodeId.Count]; + + for (int i = 0; i < nodeId.Count; i++) + { + var definition = new OpcDaItemDefinition + { + ItemId = nodeId[i], + IsActive = true + }; + + definitions[i] = definition; + } + + // 添加要读取的项 + + + var results = group.AddItems(definitions); + + if (results[0].Error.Failed) + throw new Exception($"添加项失败: {results[0].Error}"); + + // 读取项值 + var values = await group.ReadAsync(group.Items); + + if (values[0].Error.Failed) + throw new Exception($"读取失败: {values[0].Error}"); + + //return new OpcNode + //{ + // NodeId = nodeId, + // DisplayName = nodeId, + // Value = values[0].Value, + // SourceTimestamp = values[0].Timestamp, + // DataType = values[0].Value?.GetType().Name ?? "Unknown" + //}; + + return null; + } + } + catch (Exception ex) + { + Console.WriteLine($"读取节点时出错: {ex.Message}"); + throw; + } + } + + public async Task WriteNodeAsync(string nodeId, object value) + { + if (_server == null || !_server.IsConnected) + throw new Exception("未连接到 OPC DA 服务器"); + + try + { + // 创建临时组和项进行写入 + using (var group = _server.AddGroup("TempWriteGroup")) + { + group.IsActive = true; + + // 添加要写入的项 + var definition = new OpcDaItemDefinition + { + ItemId = nodeId, + IsActive = true + }; + + var results = group.AddItems(new[] { definition }); + + if (results[0].Error.Failed) + throw new Exception($"添加项失败: {results[0].Error}"); + + // 写入值 + var item = results[0].Item; + var writeResult = await group.WriteAsync(new[] { item }, new[] { value }); + + if (writeResult[0].Failed) + throw new Exception($"写入失败: {writeResult[0]}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"写入节点时出错: {ex.Message}"); + throw; + } + } + + public async Task> BrowseNodesAsync(string startingNodeId = null) + { + if (_server == null || !_server.IsConnected) + throw new Exception("未连接到 OPC DA 服务器"); + + var nodes = new List(); + + try + { + // 使用浏览功能浏览服务器地址空间 + var browser = new OpcDaBrowserAuto(_server); + var elements = browser.GetElements(startingNodeId); + + foreach (var element in elements) + { + nodes.Add(new OpcNode + { + NodeId = element.ItemId, + DisplayName = element.Name, + DataType = "Unknown" // OPC DA 浏览不直接提供数据类型 + }); + + // 如果元素有子元素,递归浏览 + if (element.HasChildren) + { + var childNodes = await BrowseNodesAsync(element.ItemId); + nodes.AddRange(childNodes); + } + } + + return nodes; + } + catch (Exception ex) + { + Console.WriteLine($"浏览节点时出错: {ex.Message}"); + throw; + } + } + + public void Dispose() + { + if (!_disposed) + { + DisconnectAsync().Wait(); + _disposed = true; + } + } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Opc/Impl/OpcUaService.cs b/Sln.Imm.Daemon.Opc/Impl/OpcUaService.cs index 857309b..835e5c3 100644 --- a/Sln.Imm.Daemon.Opc/Impl/OpcUaService.cs +++ b/Sln.Imm.Daemon.Opc/Impl/OpcUaService.cs @@ -1,31 +1,263 @@ #region << 版 本 注 释 >> /*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon.Opc.Impl -* 唯一标识:AAD72EB2-53B5-43B2-AD71-77FE72AC816E -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 17:24:43 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ + * 版权所有 (c) 2025 WenJY 保留所有权利。 + * CLR版本:4.0.30319.42000 + * 机器名称:Mr.Wen's MacBook Pro + * 命名空间:Sln.Imm.Daemon.Opc.Impl + * 唯一标识:AAD72EB2-53B5-43B2-AD71-77FE72AC816E + * + * 创建者:WenJY + * 电子邮箱: + * 创建时间:2025-09-22 17:24:43 + * 版本:V1.0.0 + * 描述: + * + *-------------------------------------------------------------------- + * 修改人: + * 时间: + * 修改说明: + * + * 版本:V1.0.0 + *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> +using Opc.Ua; +using Opc.Ua.Client; +using Sln.Imm.Daemon.Model.dto; + namespace Sln.Imm.Daemon.Opc.Impl; -public class OpcUaService +public class OpcUaService : IOpcService, IDisposable { - + private ApplicationConfiguration _config; + private Session _session; + private bool _disposed = false; + + public OpcUaService() + { + _config = new ApplicationConfiguration() + { + ApplicationName = "OPC UA Client", + ApplicationType = ApplicationType.Client, + SecurityConfiguration = new SecurityConfiguration + { + // 使用最小化的证书配置 + ApplicationCertificate = new CertificateIdentifier + { + StoreType = "Directory", + StorePath = "./pki/own" // 使用相对路径 + }, + TrustedPeerCertificates = new CertificateTrustList + { + StoreType = "Directory", + StorePath = "./pki/trusted" // 使用相对路径 + }, + TrustedIssuerCertificates = new CertificateTrustList + { + StoreType = "Directory", + StorePath = "./pki/issuers" // 使用相对路径 + }, + RejectedCertificateStore = new CertificateTrustList + { + StoreType = "Directory", + StorePath = "./pki/rejected" // 使用相对路径 + }, + AutoAcceptUntrustedCertificates = true // 自动接受证书(仅测试环境) + }, + TransportConfigurations = new TransportConfigurationCollection(), + ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 } + }; + + _config.Validate(ApplicationType.Client).Wait(); + } + + public async Task ConnectAsync(string serverUrl) + { + try + { + var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, false); + var endpointConfiguration = EndpointConfiguration.Create(_config); + var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); + + _session = await Session.Create( + _config, + endpoint, + false, + false, + _config.ApplicationName, + 60000, + new UserIdentity(), + null); + + return _session != null && _session.Connected; + } + catch (Exception ex) + { + throw new InvalidOperationException($"连接到 OPC UA 服务器失败: {ex.Message}"); + } + } + + public async Task DisconnectAsync() + { + if (_session != null && _session.Connected) + { + _session.Close(); + _session.Dispose(); + _session = null; + } + + await Task.CompletedTask; + } + + public async Task> ReadNodeAsync(List nodeId) + { + if (_session == null || !_session.Connected) + throw new Exception("未连接到 OPC UA 服务器"); + + try + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + + nodeId.ForEach(x => + { + ReadValueId nodeToRead = new ReadValueId + { + NodeId = new NodeId(x), + AttributeId = Attributes.Value + }; + nodesToRead.Add(nodeToRead); + }); + + _session.Read( + null, + 0, + TimestampsToReturn.Both, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + if (results != null && results.Count > 0 && StatusCode.IsGood(results[0].StatusCode)) + { + var indexedNodeIds = nodeId.Select((value,index)=>new {Index = index, Value = value}); + + List result = new List(); + + foreach (var item in indexedNodeIds) + { + var dataValue = results[item.Index]; + result.Add(new OpcNode() + { + NodeId = item.Value, + DisplayName = item.Value, + Value = dataValue.Value, + SourceTimestamp = dataValue.SourceTimestamp, + DataType = dataValue.WrappedValue.TypeInfo?.BuiltInType.ToString() ?? "Unknown" + }); + } + return result; + } + + throw new Exception($"读取节点失败: {StatusCode.LookupSymbolicId((uint)results[0].StatusCode)}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"读取节点时出错:{ex.Message}"); + } + } + + public async Task WriteNodeAsync(string nodeId, object value) + { + if (_session == null || !_session.Connected) + throw new Exception("未连接到 OPC UA 服务器"); + + try + { + WriteValueCollection nodesToWrite = new WriteValueCollection(); + WriteValue nodeToWrite = new WriteValue + { + NodeId = new NodeId(nodeId), + AttributeId = Attributes.Value, + Value = new DataValue(new Variant(value)) + }; + nodesToWrite.Add(nodeToWrite); + + _session.Write( + null, + nodesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + if (results == null || results.Count == 0 || !StatusCode.IsGood(results[0])) + { + throw new Exception($"写入节点失败: {StatusCode.LookupSymbolicId((uint)results[0])}"); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"写入节点时出错:{ex.Message}"); + } + } + + public async Task> BrowseNodesAsync(string startingNodeId = null) + { + if (_session == null || !_session.Connected) + throw new Exception("未连接到 OPC UA 服务器"); + + var nodes = new List(); + NodeId startingNode = startingNodeId != null ? new NodeId(startingNodeId) : ObjectIds.ObjectsFolder; + + try + { + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); + BrowseDescription nodeToBrowse = new BrowseDescription + { + NodeId = startingNode, + BrowseDirection = BrowseDirection.Forward, + ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, + IncludeSubtypes = true, + NodeClassMask = (uint)(NodeClass.Variable | NodeClass.Object), + ResultMask = (uint)BrowseResultMask.All + }; + nodesToBrowse.Add(nodeToBrowse); + + _session.Browse( + null, + null, + 0, + nodesToBrowse, + out BrowseResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + if (results != null && results.Count > 0) + { + foreach (var reference in results[0].References) + { + nodes.Add(new OpcNode + { + NodeId = reference.NodeId.ToString(), + DisplayName = reference.DisplayName.Text, + DataType = reference.NodeClass.ToString() + }); + } + } + + return nodes; + } + catch (Exception ex) + { + Console.WriteLine($"浏览节点时出错: {ex.Message}"); + throw; + } + } + + public void Dispose() + { + if (!_disposed) + { + DisconnectAsync().Wait(); + _disposed = true; + } + } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj b/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj index ded4bbd..eeb416a 100644 --- a/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj +++ b/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj @@ -7,9 +7,13 @@ - - - + + + + + + + diff --git a/Sln.Imm.Daemon.Repository/service/Impl/BaseDeviceInfoServiceImpl.cs b/Sln.Imm.Daemon.Repository/service/Impl/BaseDeviceInfoServiceImpl.cs index 40c0d3e..7a91ff9 100644 --- a/Sln.Imm.Daemon.Repository/service/Impl/BaseDeviceInfoServiceImpl.cs +++ b/Sln.Imm.Daemon.Repository/service/Impl/BaseDeviceInfoServiceImpl.cs @@ -44,8 +44,13 @@ public class BaseDeviceInfoServiceImpl : BaseServiceImpl, IBaseD { try { - return _rep.Context.Queryable().Includes(x => x.deviceParams.Where(x=>x.isFlag == 1).ToList()) - .Where(x => x.isFlag == 1) + // return _rep.Context.Queryable().Includes(x => x.deviceParams.Where(x=>x.isFlag == 1).ToList()) + // .Where(x => x.isFlag == 1) + // .ToList(); + + return _rep.Context.Queryable() + .Includes(x => x.deviceParams.Where(p => p.isFlag == 1).ToList()) + .Where(x => SqlFunc.MappingColumn(x.isFlag, "IS_FLAG") == 1) .ToList(); } catch (Exception ex) diff --git a/Sln.Imm.Daemon.sln b/Sln.Imm.Daemon.sln index 84ce5f8..54c8b01 100644 --- a/Sln.Imm.Daemon.sln +++ b/Sln.Imm.Daemon.sln @@ -17,6 +17,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Repository", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Cache", "Sln.Imm.Daemon.Cache\Sln.Imm.Daemon.Cache.csproj", "{143CC5C4-661C-42A9-BEA0-3C6653347DE5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Business", "Sln.Imm.Daemon.Business\Sln.Imm.Daemon.Business.csproj", "{0D16FB81-A5E4-4599-8EF8-A0B130F12B7B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Opc", "Sln.Imm.Daemon.Opc\Sln.Imm.Daemon.Opc.csproj", "{05C0F145-6166-4939-9576-D208F2A69D1A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +55,14 @@ Global {143CC5C4-661C-42A9-BEA0-3C6653347DE5}.Debug|Any CPU.Build.0 = Debug|Any CPU {143CC5C4-661C-42A9-BEA0-3C6653347DE5}.Release|Any CPU.ActiveCfg = Release|Any CPU {143CC5C4-661C-42A9-BEA0-3C6653347DE5}.Release|Any CPU.Build.0 = Release|Any CPU + {0D16FB81-A5E4-4599-8EF8-A0B130F12B7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D16FB81-A5E4-4599-8EF8-A0B130F12B7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D16FB81-A5E4-4599-8EF8-A0B130F12B7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D16FB81-A5E4-4599-8EF8-A0B130F12B7B}.Release|Any CPU.Build.0 = Release|Any CPU + {05C0F145-6166-4939-9576-D208F2A69D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C0F145-6166-4939-9576-D208F2A69D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C0F145-6166-4939-9576-D208F2A69D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C0F145-6166-4939-9576-D208F2A69D1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Sln.Imm.Daemon/ConsoleHelper.cs b/Sln.Imm.Daemon/ConsoleHelper.cs deleted file mode 100644 index ec5c7fb..0000000 --- a/Sln.Imm.Daemon/ConsoleHelper.cs +++ /dev/null @@ -1,31 +0,0 @@ -#region << 版 本 注 释 >> - -/*-------------------------------------------------------------------- -* 版权所有 (c) 2025 WenJY 保留所有权利。 -* CLR版本:4.0.30319.42000 -* 机器名称:Mr.Wen's MacBook Pro -* 命名空间:Sln.Imm.Daemon -* 唯一标识:AF303F75-B268-4E12-8443-9792C001B764 -* -* 创建者:WenJY -* 电子邮箱: -* 创建时间:2025-09-22 17:28:48 -* 版本:V1.0.0 -* 描述: -* -*-------------------------------------------------------------------- -* 修改人: -* 时间: -* 修改说明: -* -* 版本:V1.0.0 -*--------------------------------------------------------------------*/ - -#endregion << 版 本 注 释 >> - -namespace Sln.Imm.Daemon; - -public class ConsoleHelper -{ - -} \ No newline at end of file diff --git a/Sln.Imm.Daemon/Program.cs b/Sln.Imm.Daemon/Program.cs index 0be3b6d..8037112 100644 --- a/Sln.Imm.Daemon/Program.cs +++ b/Sln.Imm.Daemon/Program.cs @@ -2,8 +2,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NeoSmart.Caching.Sqlite; +using Sln.Imm.Daemon.Business; using Sln.Imm.Daemon.Cache; using Sln.Imm.Daemon.Config; +using Sln.Imm.Daemon.Opc; +using Sln.Imm.Daemon.Opc.Impl; using Sln.Imm.Daemon.Repository; using Sln.Imm.Daemon.Serilog; using ZiggyCreatures.Caching.Fusion; @@ -26,6 +29,25 @@ namespace Sln.Imm.Daemon var log = ServiceProvider.GetService(); log.Info($"系统启动成功,日志存放位置:{appConfig.logPath}"); + var deviceCollectionBusiness = ServiceProvider.GetService(); + deviceCollectionBusiness?.Handle(); + + //IOpcService _opcService = ServiceProvider.GetService(); + //var result = _opcService.ConnectAsync("opc.tcp://192.168.0.100:62541/SharpNodeSettings/OpcUaServer").Result; + //if (result) + //{ + // while (true) + // { + // var info = _opcService.ReadNodeAsync(new List() + // { + // "ns=2;s=Devices/分厂一/车间一/测试空设备/压力", + // "ns=2;s=Devices/分厂一/车间一/测试空设备/温度", + // "ns=2;s=Devices/分厂一/车间一/测试空设备/条码" + // }); + // var infos = _opcService.BrowseNodesAsync("ns=2;s=Devices/分厂一/车间一/测试空设备"); + // Thread.Sleep(5000); + // } + //} Task.Delay(-1).Wait(); } @@ -46,7 +68,8 @@ namespace Sln.Imm.Daemon Assembly.LoadFrom("Sln.Imm.Daemon.Common.dll"), Assembly.LoadFrom("Sln.Imm.Daemon.Repository.dll"), Assembly.LoadFrom("Sln.Imm.Daemon.Cache.dll"), - // Assembly.LoadFrom("Sln.Iot.Socket.dll"), + Assembly.LoadFrom("Sln.Imm.Daemon.Business.dll"), + Assembly.LoadFrom("Sln.Imm.Daemon.Opc.dll"), // Assembly.LoadFrom("Sln.Iot.Business.dll"), }; @@ -67,7 +90,7 @@ namespace Sln.Imm.Daemon .WithDistributedCache( new SqliteCache(new SqliteCacheOptions { - CachePath = "/Users/wenxiansheng/Public/WorkSpace/Mesnac/项目资料/澳柯玛注塑车间MES项目/数据缓存/FusionCache.db" + CachePath = "F:\\桌面\\数据缓存\\FusionCache.db" })); } } diff --git a/Sln.Imm.Daemon/Sln.Imm.Daemon.csproj b/Sln.Imm.Daemon/Sln.Imm.Daemon.csproj index 32d6370..d8bfc1d 100644 --- a/Sln.Imm.Daemon/Sln.Imm.Daemon.csproj +++ b/Sln.Imm.Daemon/Sln.Imm.Daemon.csproj @@ -13,9 +13,11 @@ + +