using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using DOAN.Infrastructure.PLC; using DOAN.Model.PBL; using DOAN.ServiceCore.Signalr; using JinianNet.JNTemplate; using Microsoft.AspNetCore.SignalR; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Hosting; using SqlSugar.IOC; using static System.Formats.Asn1.AsnWriter; namespace DOAN.ServiceCore { /// /// 永驻线程 /// 功能:检测传感器信号,判断箱子数 /// public class DoanBackgroundService : BaseService, 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 List buttonPositionList = new List(); private Timer refreshTimer; public DoanBackgroundService(IHubContext hubContext) { notificationHubContext = hubContext; } public Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine($"PCL定时任务开启!"); pLCTool = new PLCTool(); pLCTool.ConnectPLC(); // sql标准 Context.Queryable(); // 初始化料架层和点位表数据 RefreshData(null); // 启动后台任务 _executingTask = ExecuteAsync(_cancellationTokenSource.Token); // 设置定时器每10分钟刷新一次料架层和点位表 .FromMinutes(1) refreshTimer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(10)); 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" + position ); } byte mask = (byte)(1 << position); bool isSet = (b & mask) != 0; return !isSet; // 返回取反后的值 } /// /// 功能:检测传感器信号,判断箱子数并更新库存及日志 /// /// 取消令牌 /// private async Task ExecuteAsync(CancellationToken stoppingToken) { Console.WriteLine($"PCL定时任务ExecuteAsync!"); try { while (!stoppingToken.IsCancellationRequested) { // 读取PLC I/O状态(12个字节) byte[] plcSensorValues; try { // 读取1开头100,101。。。等全部地址数据 plcSensorValues = pLCTool.ReadAllValue("VB100", 13); } catch (Exception ex) { Console.WriteLine($"读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); continue; } // XX 按钮处理 // 读取按钮PLC I/O状态 foreach (PlcButton button in buttonPositionList) { int row = button.Address - 100; int col = button.Index; if ( plcSensorValues != null && row >= 0 && row < plcSensorValues.Length && !GetInvertedBit(plcSensorValues[row], col) ) { // 按钮按下灭灯 if (button.Code == "灭灯按钮") { Storagelocation offLightstoragelocation = storagelocationList .Where(it => it.Id == button.StoragelocationId) .First(); bool isSuccess = pLCTool.WriteBit( offLightstoragelocation.PlcAddress, false ); if (isSuccess) { offLightstoragelocation.IsLight = 0; using (var scope = DbScoped.SugarScope.CopyNew()) { await scope .Updateable(offLightstoragelocation) .ExecuteCommandAsync(); //灭灯日志 Light_Log light_Log = new Light_Log { Id = SnowFlakeSingle.Instance.NextId().ToString(), LightOperation = 2, LayerNum = offLightstoragelocation.LayerNum, ShelfCode = offLightstoragelocation.RackCode, IsSuccess = isSuccess, Operationer = "按钮手动灭灯", CreatedTime = DateTime.Now }; await scope.Insertable(light_Log).ExecuteCommandAsync(); } } await notificationHubContext.Clients.All.SendAsync( "PBL_storagelocation_change", "手动灭灯" ); } // 镜体补料按钮 if (button.Code == "镜体补料按钮") { bool lightStatus = pLCTool.ReadBit("V208.4"); // 原本灭灯,亮灯补料 if (!lightStatus) { using (var scope = DbScoped.SugarScope.CopyNew()) { AlarmLog alarmLog = new AlarmLog { Id = SnowFlakeSingle.Instance.NextId().ToString(), Name = "镜体需要补料", Code = "镜体补料", StoragelocationId = 0, Type = 2, Status = 1, ActionTime = DateTime.Now, EndTime = DateTime.Now }; scope.Insertable(alarmLog).ExecuteCommand(); var alarmData = new { RackCode = "镜体需要补料", LayerNum = 1, CurrentPackageCount = 0, AlarmThreshold = 1, ActionTime = DateTime .Now.ToString("yyyy-MM-dd HH:mm:ss") .ToString(), }; string alarmMessage = System.Text.Json.JsonSerializer.Serialize(alarmData); await notificationHubContext.Clients.All.SendAsync( "PBL_lack_alarm", alarmMessage ); } } else { // 原本亮灯,按一下灭灯 pLCTool.WriteBit("V208.4", false); } } } } 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 ) { using (var scope = DbScoped.SugarScope.CopyNew()) { // 是否已经报警过,并且 bool hasLastAlarm = await scope .Queryable() .Where(it => it.StoragelocationId == layerItem.Id) .Where(it => it.Type == 1) .Where(it => it.Status == 1) .Where(it => DateTime.Now <= it.EndTime) .OrderBy(it => it.ActionTime, OrderByType.Desc) .AnyAsync(); if (!hasLastAlarm) { string layerName = layerItem.LayerNum == 1 ? "上层" : "中层"; AlarmLog alarmLog = new AlarmLog { Id = SnowFlakeSingle.Instance.NextId().ToString(), Name = $"镜壳需补料:{layerItem.RackCode} {layerName} 阈值{layerItem.AlarmNum}", Code = "镜壳补料", StoragelocationId = layerItem.Id, Type = 1, Status = 1, ActionTime = DateTime.Now, // 过X分钟超时jishi EndTime = DateTime.Now.AddMinutes(2) }; await scope.Insertable(alarmLog).ExecuteCommandAsync(); var alarmData = new { RackCode = layerItem.RackCode, LayerNum = layerItem.LayerNum, CurrentPackageCount = currentPackageCount, AlarmThreshold = layerItem.AlarmNum, ActionTime = DateTime .Now.ToString("yyyy-MM-dd HH:mm:ss") .ToString(), }; string alarmMessage = System.Text.Json.JsonSerializer.Serialize(alarmData); await notificationHubContext.Clients.All.SendAsync( "PBL_lack_alarm", alarmMessage ); } } } } } // 更新库存 if (updateStoragelocationList.Any()) { using (var scope = DbScoped.SugarScope.CopyNew()) { await scope .Updateable(updateStoragelocationList) .IgnoreColumns(it => new { it.IsLight, it.IsLackAlarm, it.AlarmNum, it.MaxCapacity }) .ExecuteCommandAsync(); } // 发送库存变更Socket通知 string changeMessage = "库存变动"; await notificationHubContext.Clients.All.SendAsync( "PBL_storagelocation_change", changeMessage ); } // 插入库存变更日志 if (inventoryLogs.Any()) { await DbScoped .SugarScope.CopyNew() .Insertable(inventoryLogs) .ExecuteCommandAsync(); } // 添加延迟以避免频繁查询(暂定3秒防误触) await Task.Delay(2000, stoppingToken); } } catch (OperationCanceledException) { Console.WriteLine("任务已取消"); } catch (Exception ex) { Console.WriteLine( $"DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}" ); } } /// /// 刷新料架层和点位表数据 /// /// private async void RefreshData(object state) { try { Console.WriteLine($"刷新料架点位和按钮"); using (var scope = DbScoped.SugarScope.CopyNew()) { storagelocationList = await scope.Queryable().ToListAsync(); pointPositionList = await scope.Queryable().ToListAsync(); buttonPositionList = await scope.Queryable().ToListAsync(); } } 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 { _cancellationTokenSource.Cancel(); _executingTask?.GetAwaiter().GetResult(); // 确保任务完成 _cancellationTokenSource.Dispose(); refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); refreshTimer?.Dispose(); pLCTool.ConnectClose(); } catch (Exception ex) { Console.WriteLine("DoanBackGround线程Dispose异常:" + ex.Message); } } } }