diff --git a/DOAN.Model/PBL/Dto/StoragelocationDto.cs b/DOAN.Model/PBL/Dto/StoragelocationDto.cs index 84e909f..b06f41c 100644 --- a/DOAN.Model/PBL/Dto/StoragelocationDto.cs +++ b/DOAN.Model/PBL/Dto/StoragelocationDto.cs @@ -31,6 +31,16 @@ public class StoragelocationDto public int? PackageNum { get; set; } + /// + /// 触发报警的箱子数 + /// + public int? AlarmNum { get; set; } + + /// + /// 是否缺料报警 + /// + public int? IsLackAlarm { get; set; } + /// /// 箱子数 /// diff --git a/DOAN.Model/PBL/Storagelocation.cs b/DOAN.Model/PBL/Storagelocation.cs index 19fc64e..153ea45 100644 --- a/DOAN.Model/PBL/Storagelocation.cs +++ b/DOAN.Model/PBL/Storagelocation.cs @@ -40,9 +40,21 @@ namespace DOAN.Model.PBL /// [SugarColumn(ColumnName = "package_num")] public int? PackageNum { get; set; } - + /// - /// 是否亮灯 + /// 触发报警的箱子数 + /// + [SugarColumn(ColumnName = "alarm_num")] + public int? AlarmNum { get; set; } + + /// + /// 是否缺料报警 + /// + [SugarColumn(ColumnName = "is_lack_alarm")] + public int? IsLackAlarm { get; set; } + + /// + /// 是否处于亮灯状态 /// [SugarColumn(ColumnName = "is_light")] public int? IsLight { get; set; } diff --git a/DOAN.Service/PBL/MESInteractionServcie.cs b/DOAN.Service/PBL/MESInteractionServcie.cs index bc5d9dd..9cfd788 100644 --- a/DOAN.Service/PBL/MESInteractionServcie.cs +++ b/DOAN.Service/PBL/MESInteractionServcie.cs @@ -8,7 +8,6 @@ using Mapster; using Microsoft.AspNetCore.SignalR; using Newtonsoft.Json.Linq; - namespace DOAN.Service.PBL { /// @@ -17,16 +16,17 @@ namespace DOAN.Service.PBL [AppService(ServiceType = typeof(IMESInteractionServcie), ServiceLifetime = LifeTime.Transient)] public class MESInteractionServcie : BaseService, IMESInteractionServcie { - private readonly IHubContext notificationHubContext; - public MESInteractionServcie(IHubContext _notificationHubContext) { + private readonly IHubContext notificationHubContext; - notificationHubContext= _notificationHubContext; + public MESInteractionServcie(IHubContext _notificationHubContext) + { + notificationHubContext = _notificationHubContext; } + public bool TestPLc(string address, PLCTool pLCTool) { bool isSucesss = pLCTool.ReadBit(address); return isSucesss; - } /// @@ -46,17 +46,24 @@ namespace DOAN.Service.PBL // 2.根据总成零件号 ,版本 查询对应零件号,使得对应料架亮灯 - // 同一个会有多个料架 + // 同一个会有多个料架 //镜壳 料架层 - List MirrorshellShelfList = Context.Queryable().Where(it => it.Partnumber == - SqlFunc.Subqueryable().Where(It => It.Productcode == light.AssemblyPartNumber).Select(it => it.MirrorshellCode)).ToList(); + List MirrorshellShelfList = Context + .Queryable() + .Where(it => + it.Partnumber + == SqlFunc + .Subqueryable() + .Where(It => It.Productcode == light.AssemblyPartNumber) + .Select(it => it.MirrorshellCode) + ) + .ToList(); //镜体 料架 // Storagelocation MirrorshellBody = Context.Queryable().Where(it => it.Partnumber == // SqlFunc.Subqueryable().Where(It => It.Productcode == light.AssemblyPartNumber && It.Version == light.Version).Select(it => it.MirrorbodyCode)).First(); if (MirrorshellShelfList != null && MirrorshellShelfList.Count() > 0) { - foreach (var item1 in MirrorshellShelfList) { // 3.对应料架亮灯 @@ -70,10 +77,8 @@ namespace DOAN.Service.PBL item1.IsLight = 1; Context.Updateable(item1).ExecuteCommand(); - } - //亮灯日志 Light_Log light_Log = new Light_Log(); light_Log.Id = XUEHUA; @@ -84,7 +89,6 @@ namespace DOAN.Service.PBL light_Log.CreatedTime = DateTime.Now; light_Log.IsSuccess = isSucesss; - //Light_Log light_Log2 = new Light_Log(); //light_Log2.Id = XUEHUA; //light_Log2.LightOperation = 1; @@ -98,25 +102,22 @@ namespace DOAN.Service.PBL //light_Log2.IsSuccess = isSucesss; //Context.Insertable(light_Log2).ExecuteCommand(); result += Context.Insertable(light_Log).ExecuteCommand(); - } - } else { // 发送socket 通知 - string message=$"MES产品编号{light.AssemblyPartNumber}或者版本{light.Version},在PBL中找不到。请维护PBL料架信息"; + string message = + $"MES产品编号{light.AssemblyPartNumber}或者版本{light.Version},在PBL中找不到。请维护PBL料架信息"; notificationHubContext.Clients.All.SendAsync("PBL_bom_except", message); return false; } return result > 0; - } - /// /// MES灭灯 /// @@ -133,10 +134,13 @@ namespace DOAN.Service.PBL Context.Insertable(item).ExecuteCommand(); //2 找到对应的库存最大料架 灭灯 - Storagelocation storagelocation = Context.Queryable().Where(it => it.Partnumber.Contains(scan_code)).OrderByDescending(it => it.PackageNum).First(); + Storagelocation storagelocation = Context + .Queryable() + .Where(it => it.Partnumber.Contains(scan_code)) + .OrderByDescending(it => it.PackageNum) + .First(); if (storagelocation != null) { - // TODO PLC 交互 bool isSuccess = pLCTool.WriteBit(storagelocation.PlcAddress, false); @@ -153,14 +157,16 @@ namespace DOAN.Service.PBL int[] ids = { 1, 2, 3, 4, 5, 6, 7, 8 }; if (ids.Contains(storagelocation.Id)) { - Context.Updateable().SetColumns(it=>it.IsLight == 0).Where(it=>it.Partnumber == storagelocation.Partnumber).ExecuteCommand(); + Context + .Updateable() + .SetColumns(it => it.IsLight == 0) + .Where(it => it.Partnumber == storagelocation.Partnumber) + .ExecuteCommand(); } else { Context.Updateable(storagelocation).ExecuteCommand(); } - - } //灭灯日志 Light_Log light_Log = new Light_Log(); @@ -179,9 +185,6 @@ namespace DOAN.Service.PBL return false; } return result > 0; - } } - - -} \ No newline at end of file +} diff --git a/DOAN.ServiceCore/DoanBackgroundService.cs b/DOAN.ServiceCore/DoanBackgroundService.cs index 619eaf4..0e90ccf 100644 --- a/DOAN.ServiceCore/DoanBackgroundService.cs +++ b/DOAN.ServiceCore/DoanBackgroundService.cs @@ -1,35 +1,50 @@ using System; -using System.Security.Cryptography.X509Certificates; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using DOAN.Infrastructure.PLC; using DOAN.Model.PBL; -using DOAN.Model.System; +using DOAN.ServiceCore.Signalr; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; -using SqlSugar; using SqlSugar.IOC; + namespace DOAN.ServiceCore { - /// /// 永驻线程 /// 功能:检测传感器信号,判断箱子数 /// public class DoanBackgroundService : IHostedService, IDisposable { + private readonly IHubContext notificationHubContext; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private Task _executingTask; private PLCTool pLCTool; + private List storagelocationList = new List(); + private List pointPositionList = new List(); + private Timer refreshTimer; + + public DoanBackgroundService(IHubContext hubContext) + { + notificationHubContext = hubContext; + } public Task StartAsync(CancellationToken cancellationToken) { pLCTool = new PLCTool(); - pLCTool.ConnectPLC(); - // 当服务开始时,启动后台任务 + + // 初始化料架层和点位表数据 + RefreshData(null); + + // 启动后台任务 _executingTask = ExecuteAsync(_cancellationTokenSource.Token); - + // 设置定时器每分钟刷新一次料架层和点位表 + refreshTimer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; } @@ -39,25 +54,24 @@ namespace DOAN.ServiceCore // 请求取消后台任务 _cancellationTokenSource.Cancel(); + // 停止定时器 + refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); + refreshTimer?.Dispose(); + // 等待后台任务完成 await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); } - private static bool GetInvertedBit(byte b, int position) + private static bool GetInvertedBit(byte b, int position) { if (position < 0 || position > 7) { throw new ArgumentOutOfRangeException(nameof(position), "Position must be between 0 and 7."); } - // 创建一个掩码,其中只有要检查的那一位是1 byte mask = (byte)(1 << position); - - // 使用按位与运算符(&)来检查该位是否为1 bool isSet = (b & mask) != 0; - - // 返回取反后的值 - return !isSet; + return !isSet; // 返回取反后的值 } /// @@ -71,64 +85,87 @@ namespace DOAN.ServiceCore { while (!stoppingToken.IsCancellationRequested) { - // 读取PLC I/O状态 12个数组 - byte[] plcSensorValues = pLCTool.ReadAllValue("VB100", 12); + // 读取PLC I/O状态(12个字节) + byte[] plcSensorValues; + try + { + plcSensorValues = pLCTool.ReadAllValue("VB100", 12); + } + catch (Exception ex) + { + Console.WriteLine($"读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); + continue; + } - // 获取所有料架层 - List storagelocationList = await DbScoped.SugarScope.CopyNew().Queryable().ToListAsync(); + var updateStoragelocationList = new List(); + var inventoryLogs = new List(); - // 获取点位表 - List pointPositionList = await DbScoped.SugarScope.CopyNew().Queryable().ToListAsync(); - - List updateStoragelocationList = []; - List inventoryLogs = []; - - foreach (Storagelocation layerItem in storagelocationList) + foreach (var layerItem in storagelocationList) { // 获取这个料架层的点位表 - List layerPoints = pointPositionList.Where(it => it.FkStorageId == layerItem.Id).ToList(); + var layerPoints = pointPositionList.Where(it => it.FkStorageId == layerItem.Id).ToList(); - if (layerPoints != null && layerPoints.Count > 0) + if (layerPoints.Any()) { int currentPackageCount = 0; // 默认最小值为0 - foreach (PlcAddressTable point in layerPoints) + foreach (var point in layerPoints) { int row = point.ByteNum - 100; int col = point.BitNum - 1; - if (plcSensorValues != null) + if (plcSensorValues != null && row >= 0 && row < plcSensorValues.Length) { currentPackageCount += GetInvertedBit(plcSensorValues[row], col) ? 1 : 0; } } // 检查箱子数量变化并记录日志 - if (currentPackageCount > layerItem.PackageNum) + int previousPackageCount = layerItem.PackageNum.GetValueOrDefault(); + if (currentPackageCount > previousPackageCount) { - UpdateAndLog(layerItem, currentPackageCount, 2, "补料", inventoryLogs); + UpdateAndLog(layerItem, currentPackageCount, 2, "出库", inventoryLogs); updateStoragelocationList.Add(layerItem); } - else if (currentPackageCount < layerItem.PackageNum) + else if (currentPackageCount < previousPackageCount) { - UpdateAndLog(layerItem, currentPackageCount, 1, "出料", inventoryLogs); + UpdateAndLog(layerItem, currentPackageCount, 1, "入库", inventoryLogs); updateStoragelocationList.Add(layerItem); } + + // 补料报警触发 + if (layerItem.IsLackAlarm == 1 && currentPackageCount <= layerItem.AlarmNum.GetValueOrDefault()) + { + var alarmData = new + { + RackCode = layerItem.RackCode, + LayerNum = layerItem.LayerNum, + CurrentPackageCount = currentPackageCount, + AlarmThreshold = layerItem.AlarmNum + }; + + string alarmMessage = System.Text.Json.JsonSerializer.Serialize(alarmData); + await notificationHubContext.Clients.All.SendAsync("PBL_lack_alarm", alarmMessage); + } } } // 更新库存 - if (updateStoragelocationList.Count > 0) + if (updateStoragelocationList.Any()) { await DbScoped.SugarScope.CopyNew().Updateable(updateStoragelocationList).ExecuteCommandAsync(); } // 插入库存变更日志 - if (inventoryLogs.Count > 0) + if (inventoryLogs.Any()) { await DbScoped.SugarScope.CopyNew().Insertable(inventoryLogs).ExecuteCommandAsync(); } + // 发送Socket通知 + string changeMessage = "库存变动"; + await notificationHubContext.Clients.All.SendAsync("PBL_storagelocation_change", changeMessage); + // 添加延迟以避免频繁查询 await Task.Delay(200, stoppingToken); } @@ -143,6 +180,27 @@ namespace DOAN.ServiceCore } } + /// + /// 刷新料架层和点位表数据 + /// + /// + private void RefreshData(object state) + { + try + { + using (var scope = DbScoped.SugarScope.CopyNew()) + { + storagelocationList = scope.Queryable().ToListAsync().Result; + pointPositionList = scope.Queryable().ToListAsync().Result; + } + } + catch (Exception ex) + { + Console.WriteLine($"刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); + // 如果刷新失败,保持现有数据不变 + } + } + /// /// 更新料架位置的箱子数量并记录库存日志 /// @@ -151,19 +209,24 @@ namespace DOAN.ServiceCore /// 操作类型 (1-出库, 2-入库) /// 操作员名称 /// 库存日志列表 - private void UpdateAndLog(Storagelocation storageLocation, int newPackageCount, int operation, string operatorName, List inventoryLogs) + private void UpdateAndLog( + Storagelocation storageLocation, + int newPackageCount, + int operation, + string operatorName, + List inventoryLogs) { storageLocation.PackageNum = newPackageCount; storageLocation.UpdatedBy = operatorName; storageLocation.UpdatedTime = DateTime.Now; - Inventorylog inventoryLog = new Inventorylog + var inventoryLog = new Inventorylog { Id = SnowFlakeSingle.Instance.NextId().ToString(), RackCode = storageLocation.RackCode, Partnumber = storageLocation.Partnumber, Operation = operation, - PackageNum = Math.Abs((int)(newPackageCount - storageLocation.PackageNum)), + PackageNum = Math.Abs(newPackageCount - storageLocation.PackageNum.GetValueOrDefault()), CreatedBy = operatorName, CreatedTime = DateTime.Now.ToLocalTime() }; @@ -177,17 +240,18 @@ namespace DOAN.ServiceCore { pLCTool.ConnectClose(); _cancellationTokenSource.Cancel(); - _executingTask.Wait(); + _executingTask?.Wait(); _cancellationTokenSource.Dispose(); + refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); + refreshTimer?.Dispose(); } catch (Exception ex) { Console.WriteLine("DoanBackGround线程Dispose异常:" + ex.Message); } - } - - - } } + + +