using System; 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.ServiceCore.Signalr; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; 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; } public async Task StopAsync(CancellationToken cancellationToken) { // 请求取消后台任务 _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) { if (position < 0 || position > 7) { throw new ArgumentOutOfRangeException(nameof(position), "Position must be between 0 and 7."); } byte mask = (byte)(1 << position); bool isSet = (b & mask) != 0; return !isSet; // 返回取反后的值 } /// /// 功能:检测传感器信号,判断箱子数并更新库存及日志 /// /// 取消令牌 /// private async Task ExecuteAsync(CancellationToken stoppingToken) { try { while (!stoppingToken.IsCancellationRequested) { // 读取PLC I/O状态(12个字节) byte[] plcSensorValues; try { plcSensorValues = pLCTool.ReadAllValue("VB100", 12); } catch (Exception ex) { Console.WriteLine($"读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); continue; } var updateStoragelocationList = new List(); var inventoryLogs = new List(); foreach (var layerItem in storagelocationList) { // 获取这个料架层的点位表 var layerPoints = pointPositionList.Where(it => it.FkStorageId == layerItem.Id).ToList(); if (layerPoints.Any()) { int currentPackageCount = 0; // 默认最小值为0 foreach (var point in layerPoints) { int row = point.ByteNum - 100; int col = point.BitNum - 1; if (plcSensorValues != null && row >= 0 && row < plcSensorValues.Length) { currentPackageCount += GetInvertedBit(plcSensorValues[row], col) ? 1 : 0; } } // 检查箱子数量变化并记录日志 int previousPackageCount = layerItem.PackageNum.GetValueOrDefault(); if (currentPackageCount > previousPackageCount) { UpdateAndLog(layerItem, currentPackageCount, 2, "出库", inventoryLogs); updateStoragelocationList.Add(layerItem); } else if (currentPackageCount < previousPackageCount) { 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.Any()) { await DbScoped.SugarScope.CopyNew().Updateable(updateStoragelocationList).ExecuteCommandAsync(); // 发送库存变更Socket通知 string changeMessage = "库存变动"; await notificationHubContext.Clients.All.SendAsync("PBL_storagelocation_change", changeMessage); } // 插入库存变更日志 if (inventoryLogs.Any()) { await DbScoped.SugarScope.CopyNew().Insertable(inventoryLogs).ExecuteCommandAsync(); } // 添加延迟以避免频繁查询 await Task.Delay(200, stoppingToken); } } catch (OperationCanceledException) { Console.WriteLine("任务已取消"); } catch (Exception ex) { Console.WriteLine($"DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); } } /// /// 刷新料架层和点位表数据 /// /// 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}"); // 如果刷新失败,保持现有数据不变 } } /// /// 更新料架位置的箱子数量并记录库存日志 /// /// 料架位置对象 /// 新的箱子数量 /// 操作类型 (1-出库, 2-入库) /// 操作员名称 /// 库存日志列表 private void UpdateAndLog( Storagelocation storageLocation, int newPackageCount, int operation, string operatorName, List inventoryLogs) { storageLocation.PackageNum = newPackageCount; storageLocation.UpdatedBy = operatorName; storageLocation.UpdatedTime = DateTime.Now; var inventoryLog = new Inventorylog { Id = SnowFlakeSingle.Instance.NextId().ToString(), RackCode = storageLocation.RackCode, Partnumber = storageLocation.Partnumber, Operation = operation, PackageNum = Math.Abs(newPackageCount - storageLocation.PackageNum.GetValueOrDefault()), CreatedBy = operatorName, CreatedTime = DateTime.Now.ToLocalTime() }; inventoryLogs.Add(inventoryLog); } public void Dispose() { try { pLCTool.ConnectClose(); _cancellationTokenSource.Cancel(); _executingTask?.Wait(); _cancellationTokenSource.Dispose(); refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); refreshTimer?.Dispose(); } catch (Exception ex) { Console.WriteLine("DoanBackGround线程Dispose异常:" + ex.Message); } } } }