diff --git a/Sln.Imm.Daemon.Business/DeviceCollectionBusiness.cs b/Sln.Imm.Daemon.Business/DeviceCollectionBusiness.cs index 9543fde..4463602 100644 --- a/Sln.Imm.Daemon.Business/DeviceCollectionBusiness.cs +++ b/Sln.Imm.Daemon.Business/DeviceCollectionBusiness.cs @@ -29,6 +29,7 @@ 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.Repository.service.@base; using Sln.Imm.Daemon.Serilog; namespace Sln.Imm.Daemon.Business; @@ -37,51 +38,82 @@ public class DeviceCollectionBusiness { private readonly SerilogHelper _serilog; - private readonly BaseDeviceInfoCacheService _cacheService; + private readonly BaseDeviceInfoCacheService _cacheService; - private readonly IOpcService _opcUaService; + private readonly List _opcs; - public DeviceCollectionBusiness(SerilogHelper serilogHelper, BaseDeviceInfoCacheService cacheService,OpcUaService opcUaService) + private readonly IBaseService _paramValService; + + public DeviceCollectionBusiness(SerilogHelper serilogHelper, BaseDeviceInfoCacheService cacheService, List opcs, IBaseService paramValService) { _serilog = serilogHelper; _cacheService = cacheService; - _opcUaService = opcUaService; + _opcs = opcs; + _paramValService = paramValService; - this.Handle(); + //this.Handle(); } - public async Task Handle() + public async Task Handle2() { bool isFalg = true; var deviceInfos = await _cacheService.GetValueAsync("BaseDeviceInfoCache"); + _opcs. + } + + public async Task Handle(IOpcService opcUa) + { + bool isFalg = true; + + var deviceInfos = await _cacheService.GetValueAsync("BaseDeviceInfoCache"); + int i = 0; do { - lock (string.Empty) + i++; + foreach (var item in deviceInfos) { - foreach (var item in deviceInfos) + //bool result = await _opcUaService.ConnectAsync(item.networkAddress); + + //if (!result) + //{ + // throw new ArgumentNullException($"设备未连接"); + + //} + //Task.Run(async () => + //{ + + // //_opcUaService.DisconnectAsync(); + //}); + + try { - Task.Run(async () => - { - try - { - _serilog.Info($"开始采集{item.deviceName},设备数据"); - var opcItemValues = await this.ReadParam(item); + _serilog.Info($"开始采集{item.deviceName},设备数据,第{i}次"); + var opcItemValues = await this.ReadParam(item, opcUa); - this.SaveParam(item, opcItemValues, out List paramValues); + //this.SaveParam(item, opcItemValues, out List paramValues); + + _serilog.Info($"{item.deviceName}数据采集完成:{JsonConvert.SerializeObject(opcItemValues)}"); - _serilog.Info($"{item.deviceName}数据采集完成:{JsonConvert.SerializeObject(paramValues)}"); - } - catch (Exception e) - { - _serilog.Info($"{item.deviceName}数据读取异常:{e.Message}"); - } - }); } - - Task.Delay(1000 * 1).Wait(); + catch (Exception e) + { + _serilog.Info($"{item.deviceName}数据读取异常:{e.Message}"); + } } + + if (i == 1000) + { + isFalg = false; + + await opcUa.DisconnectAsync(); + + _serilog.Info($"读取完成断开连接"); + } + + + await Task.Delay(1000 * 1); } while (isFalg); } @@ -89,7 +121,7 @@ public class DeviceCollectionBusiness /// 读取设备参数 /// /// - public async Task> ReadParam(BaseDeviceInfo device) + public async Task> ReadParam(BaseDeviceInfo device,IOpcService opcUa) { try { @@ -100,19 +132,19 @@ public class DeviceCollectionBusiness List deviceParams = device.deviceParams.Select(x => x.paramAddr).ToList(); - bool result = await _opcUaService.ConnectAsync(device.networkAddress); + //bool result = await _opcUaService.ConnectAsync(device.networkAddress); - if (!result) - { - throw new ArgumentNullException($"设备未连接"); + //if (!result) + //{ + // throw new ArgumentNullException($"设备未连接"); - } + //} - List infos = await _opcUaService.ReadNodeAsync(deviceParams); + List infos = await opcUa.ReadNodeAsync(deviceParams); //var infos = _opcService.BrowseNodesAsync("ns=2;s=Devices/分厂一/车间一/测试空设备"); - await _opcUaService.DisconnectAsync(); + //await _opcUaService.DisconnectAsync(); return infos; @@ -124,35 +156,73 @@ public class DeviceCollectionBusiness } /// - /// + /// 保存设备参数值到数据库 /// - /// - /// + /// 设备信息 + /// OPC节点值列表 + /// 输出参数值DTO列表 public void SaveParam(BaseDeviceInfo device, List opcItemValues, out List paramValues) { var deviceParams = device.deviceParams.ToList(); + var now = DateTime.Now; - paramValues = deviceParams - .GroupJoin(opcItemValues, - param => param.paramAddr, - 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(); + // 使用字典提高查找效率 + var opcValueDict = opcItemValues.ToDictionary(v => v.NodeId, v => v); - //保存数据库 + // 构建参数值列表和数据库实体列表 + var paramValueList = new List(); + var dbEntityList = new List(); + + foreach (var param in deviceParams) + { + // 查找对应的OPC值 + opcValueDict.TryGetValue(param.paramAddr, out var opcNode); + + var paramValue = opcNode?.Value?.ToString() ?? string.Empty; + + // 构建DTO + paramValueList.Add(new DeviceParamValueDto + { + deviceCode = param.deviceCode, + paramCode = param.paramCode, + paramName = param.paramName, + netWork = param.netWork, + paramAddr = param.paramAddr, + paramType = param.paramType, + isFlag = param.isFlag, + paramValue = paramValue, + }); + + // 构建数据库实体(批量插入) + dbEntityList.Add(new BaseDeviceParamVal + { + DEVICE_ID = device.objid, + DEVICE_CODE = device.deviceCode, + PARAM_CODE = param.paramCode, + PARAM_NAME = param.paramName, + PARAM_VALUE = paramValue, + COLLECT_TIME = now, + RECORD_TIME = now, + PARAM_TYPE = param.paramType, + }); + } + + paramValues = paramValueList; + + // 批量保存到数据库(最高效的方式) + if (dbEntityList.Count > 0) + { + try + { + _paramValService.Insert(dbEntityList); + _serilog.Info($"设备{device.deviceCode}成功保存{dbEntityList.Count}条参数值到数据库"); + } + catch (Exception ex) + { + _serilog.Info($"设备{device.deviceCode}保存参数值到数据库失败:{ex.Message}"); + throw; + } + } } } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Config/AppConfig.cs b/Sln.Imm.Daemon.Config/AppConfig.cs index d6e7ca8..a7d6327 100644 --- a/Sln.Imm.Daemon.Config/AppConfig.cs +++ b/Sln.Imm.Daemon.Config/AppConfig.cs @@ -38,6 +38,14 @@ public class AppConfig: IOptions /// Sql连接配置 /// public List sqlConfig { get; set; } + + /// + /// opc设备连接配置 + /// + /// + /// + /// + public List opcConfig { get;set; } public AppConfig Value => this; } \ No newline at end of file diff --git a/Sln.Imm.Daemon.Config/OpcConfig.cs b/Sln.Imm.Daemon.Config/OpcConfig.cs new file mode 100644 index 0000000..d8d2aaf --- /dev/null +++ b/Sln.Imm.Daemon.Config/OpcConfig.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Imm.Daemon.Config +{ + public class OpcConfig + { + /// + /// opc设备编号 + /// + public string opcDeviceCode { get; set; } + + /// + /// opc设备IP + /// + public string opcDeviceIp { get; set; } + + /// + /// 是否启用 + /// + public bool isFlag { get; set; } + } +} diff --git a/Sln.Imm.Daemon.Model/dao/BaseDeviceInfo.cs b/Sln.Imm.Daemon.Model/dao/BaseDeviceInfo.cs index a0e9d2a..e33b088 100644 --- a/Sln.Imm.Daemon.Model/dao/BaseDeviceInfo.cs +++ b/Sln.Imm.Daemon.Model/dao/BaseDeviceInfo.cs @@ -65,6 +65,12 @@ public class BaseDeviceInfo [SugarColumn(ColumnName = "DEVICE_MODEL")] public string deviceModel { get; set; } + /// + /// 设备厂商 + /// + [SugarColumn(ColumnName = "MANUFACTURER")] + public string deviceFacture { get; set; } + /// /// Desc:网络地址 /// Default: diff --git a/Sln.Imm.Daemon.Model/dao/BaseDeviceParamVal.cs b/Sln.Imm.Daemon.Model/dao/BaseDeviceParamVal.cs new file mode 100644 index 0000000..22a09bd --- /dev/null +++ b/Sln.Imm.Daemon.Model/dao/BaseDeviceParamVal.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; + +namespace Sln.Imm.Daemon.Model.dao; + +/// +/// 设备工艺参数当前值表 +/// +[SugarTable("BASE_DEVICE_PARAM_VAL"), TenantAttribute("mes")] +public class BaseDeviceParamVal +{ + /// + /// 备 注:主键 + /// + [SugarColumn(ColumnName = "RECORD_ID", IsPrimaryKey = true)] + public decimal RECORD_ID { get; set; } + + /// + /// 备 注:参数编号 + /// + [SugarColumn(ColumnName = "PARAM_CODE")] + public string? PARAM_CODE { get; set; } + + /// + /// 备 注:设备编号 + /// + [SugarColumn(ColumnName = "DEVICE_CODE")] + public string? DEVICE_CODE { get; set; } + + /// + /// 备 注:设备ID + /// + [SugarColumn(ColumnName = "DEVICE_ID")] + public decimal? DEVICE_ID { get; set; } + + /// + /// 备 注:参数名称 + /// + [SugarColumn(ColumnName = "PARAM_NAME")] + public string? PARAM_NAME { get; set; } + + /// + /// 备 注:参数值 + /// + [SugarColumn(ColumnName = "PARAM_VALUE")] + public string? PARAM_VALUE { get; set; } + + /// + /// 备 注:采集时间 + /// + [SugarColumn(ColumnName = "COLLECT_TIME")] + public DateTime? COLLECT_TIME { get; set; } + + /// + /// 备 注:记录时间(默认当前时间) + /// + [SugarColumn(ColumnName = "RECORD_TIME")] + public DateTime RECORD_TIME { get; set; } + + /// + /// 备 注:参数类型 + /// + [SugarColumn(ColumnName = "PARAM_TYPE")] + public string? PARAM_TYPE { get; set; } + +} \ No newline at end of file diff --git a/Sln.Imm.Daemon.Opc/OpcDeviceFactory.cs b/Sln.Imm.Daemon.Opc/OpcDeviceFactory.cs new file mode 100644 index 0000000..1439201 --- /dev/null +++ b/Sln.Imm.Daemon.Opc/OpcDeviceFactory.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Imm.Daemon.Opc +{ + public abstract class OpcDeviceFactory + { + /// + /// 是否连接 + /// + public abstract bool IsConnected { get; set; } + + /// + /// 建立连接 + /// + /// + /// + /// + public abstract bool Connect(string ip, int port); + + /// + /// 断开连接 + /// + /// + public abstract bool DisConnect(); + } +} diff --git a/Sln.Imm.Daemon.Opc/OpcDeviceFactorySetup.cs b/Sln.Imm.Daemon.Opc/OpcDeviceFactorySetup.cs new file mode 100644 index 0000000..e2f7bad --- /dev/null +++ b/Sln.Imm.Daemon.Opc/OpcDeviceFactorySetup.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.DependencyInjection; +using Sln.Imm.Daemon.Cache; +using Sln.Imm.Daemon.Config; +using Sln.Imm.Daemon.Model.dao; +using Sln.Imm.Daemon.Opc.Impl; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Imm.Daemon.Opc +{ + public static class OpcDeviceFactorySetup + { + public static void AddOpcDeviceFactorySetup(this IServiceCollection services) + { + services.AddSingleton>(x => + { + var _cacheService = x.GetService(); + + List deviceInfos = _cacheService.GetValueAsync("BaseDeviceInfoCache").Result; + + List opcs = new List(); + if (deviceInfos != null) + { + foreach (var item in deviceInfos) + { + if (item.isFlag == 1) + { + IOpcService opcDev = null; + if (item.deviceFacture.Contains("伊之密")) + { + opcDev = x.GetService(); + } + else + { + opcDev = x.GetService(); + } + + if(opcDev != null) + { + var res = opcDev.ConnectAsync(item.networkAddress).Result; + + if (!res) + { + Console.WriteLine($"{item.deviceName}设备连接失败"); + + } + else + { + opcs.Add(opcDev); + } + } + } + } + } + + return opcs; + }); + } + } +} diff --git a/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj b/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj index eeb416a..f86cf3f 100644 --- a/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj +++ b/Sln.Imm.Daemon.Opc/Sln.Imm.Daemon.Opc.csproj @@ -13,7 +13,9 @@ + + diff --git a/Sln.Imm.Daemon.sln b/Sln.Imm.Daemon.sln index 54c8b01..141e2a8 100644 --- a/Sln.Imm.Daemon.sln +++ b/Sln.Imm.Daemon.sln @@ -3,23 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35122.118 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon", "Sln.Imm.Daemon\Sln.Imm.Daemon.csproj", "{682DDE67-2CBF-4B54-831F-987EC6667FD1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon", "Sln.Imm.Daemon\Sln.Imm.Daemon.csproj", "{682DDE67-2CBF-4B54-831F-987EC6667FD1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Config", "Sln.Imm.Daemon.Config\Sln.Imm.Daemon.Config.csproj", "{A08A7879-F8B6-4A34-8ABE-2DEE3309F225}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Config", "Sln.Imm.Daemon.Config\Sln.Imm.Daemon.Config.csproj", "{A08A7879-F8B6-4A34-8ABE-2DEE3309F225}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Serilog", "Sln.Imm.Daemon.Serilog\Sln.Imm.Daemon.Serilog.csproj", "{3D173F48-72EC-46F5-AB2E-2B0A1A1729B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Serilog", "Sln.Imm.Daemon.Serilog\Sln.Imm.Daemon.Serilog.csproj", "{3D173F48-72EC-46F5-AB2E-2B0A1A1729B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Common", "Sln.Imm.Daemon.Common\Sln.Imm.Daemon.Common.csproj", "{728A030C-33EC-494C-AF86-ABC24B84AFD0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Common", "Sln.Imm.Daemon.Common\Sln.Imm.Daemon.Common.csproj", "{728A030C-33EC-494C-AF86-ABC24B84AFD0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Model", "Sln.Imm.Daemon.Model\Sln.Imm.Daemon.Model.csproj", "{D0A7BFAC-A920-48E1-9F54-CA70DE7D01CF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Model", "Sln.Imm.Daemon.Model\Sln.Imm.Daemon.Model.csproj", "{D0A7BFAC-A920-48E1-9F54-CA70DE7D01CF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Imm.Daemon.Repository", "Sln.Imm.Daemon.Repository\Sln.Imm.Daemon.Repository.csproj", "{BE51B37D-92B4-4561-92B6-D236300E33FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Repository", "Sln.Imm.Daemon.Repository\Sln.Imm.Daemon.Repository.csproj", "{BE51B37D-92B4-4561-92B6-D236300E33FA}" 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Imm.Daemon.Opc", "Sln.Imm.Daemon.Opc\Sln.Imm.Daemon.Opc.csproj", "{05C0F145-6166-4939-9576-D208F2A69D1A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Sln.Imm.Daemon/Program.cs b/Sln.Imm.Daemon/Program.cs index 3f2a344..79c4c0d 100644 --- a/Sln.Imm.Daemon/Program.cs +++ b/Sln.Imm.Daemon/Program.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NeoSmart.Caching.Sqlite; +using Newtonsoft.Json; using Sln.Imm.Daemon.Business; using Sln.Imm.Daemon.Cache; using Sln.Imm.Daemon.Config; +using Sln.Imm.Daemon.Model.dto; using Sln.Imm.Daemon.Opc; using Sln.Imm.Daemon.Opc.Impl; using Sln.Imm.Daemon.Repository; @@ -18,7 +20,7 @@ namespace Sln.Imm.Daemon { public static IServiceProvider? ServiceProvider = null; - static void Main(string[] args) + static async Task Main(string[] args) { var services = new ServiceCollection(); ConfigureServices(services); @@ -29,25 +31,37 @@ 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); - // } - //} + //var res = await _opcService.ConnectAsync("opc.tcp://10.10.63.102:4842"); + + var deviceCollectionBusiness = ServiceProvider.GetService(); + + //deviceCollectionBusiness?.Handle(); + + List opcs = ServiceProvider.GetService>(); + + if( opcs != null ) + { + foreach( var opc in opcs) + { + var res = opc.ReadNodeAsync(new List() + { + "ns=4;s=APPL.Core1.sv_CoreOutput.NormalIn.Velocity.Output.rOutputValue", + "ns=4;s=APPL.Core1.sv_CoreSetTimes.MoveIn.dSetMoveTime" + }); + + Console.WriteLine($"数据采集完成:{JsonConvert.SerializeObject(res)}"); + + Thread.Sleep(5000); + + opc.DisconnectAsync(); + + + Console.WriteLine($"断开设备连接"); + } + } + + Task.Delay(-1).Wait(); } @@ -90,9 +104,11 @@ namespace Sln.Imm.Daemon .WithDistributedCache( new SqliteCache(new SqliteCacheOptions { - CachePath = "F:\\桌面\\数据缓存\\FusionCache.db" + CachePath = "D:\\working_area\\project\\澳柯玛注塑采集\\cache\\FusionCache.db" //CachePath = "/Users/wenxiansheng/Public/WorkSpace/Mesnac/项目资料/澳柯玛注塑车间MES项目/数据缓存/FusionCache.db" })); + + services.AddOpcDeviceFactorySetup(); } } } \ No newline at end of file diff --git a/Sln.Imm.Daemon/appsettings.json b/Sln.Imm.Daemon/appsettings.json index f85720c..32bb5ad 100644 --- a/Sln.Imm.Daemon/appsettings.json +++ b/Sln.Imm.Daemon/appsettings.json @@ -7,7 +7,7 @@ "dbType": 3, "isFlag": true, //"connStr": "server=127.0.0.1;Port=4000;Database=tao_iot;Uid=root;" - "connStr": "Data Source=1.13.177.47:1521/helowin;User ID=c##aucma_mes;Password=aucma;" + "connStr": "Data Source=1.13.177.47:1521/helowin;User ID=haiwei;Password=aucma;" } ] }