pblbackend/DOAN.ServiceCore/DoanBackgroundService.cs

258 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 永驻线程
/// 功能:检测传感器信号,判断箱子数
/// </summary>
public class DoanBackgroundService : IHostedService, IDisposable
{
private readonly IHubContext<PBLhub> notificationHubContext;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private Task _executingTask;
private PLCTool pLCTool;
private List<Storagelocation> storagelocationList = new List<Storagelocation>();
private List<PlcAddressTable> pointPositionList = new List<PlcAddressTable>();
private Timer refreshTimer;
public DoanBackgroundService(IHubContext<PBLhub> 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; // 返回取反后的值
}
/// <summary>
/// 功能:检测传感器信号,判断箱子数并更新库存及日志
/// </summary>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
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<Storagelocation>();
var inventoryLogs = new List<Inventorylog>();
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();
}
// 插入库存变更日志
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);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("任务已取消");
}
catch (Exception ex)
{
Console.WriteLine($"DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
}
}
/// <summary>
/// 刷新料架层和点位表数据
/// </summary>
/// <param name="state"></param>
private void RefreshData(object state)
{
try
{
using (var scope = DbScoped.SugarScope.CopyNew())
{
storagelocationList = scope.Queryable<Storagelocation>().ToListAsync().Result;
pointPositionList = scope.Queryable<PlcAddressTable>().ToListAsync().Result;
}
}
catch (Exception ex)
{
Console.WriteLine($"刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
// 如果刷新失败,保持现有数据不变
}
}
/// <summary>
/// 更新料架位置的箱子数量并记录库存日志
/// </summary>
/// <param name="storageLocation">料架位置对象</param>
/// <param name="newPackageCount">新的箱子数量</param>
/// <param name="operation">操作类型 (1-出库, 2-入库)</param>
/// <param name="operatorName">操作员名称</param>
/// <param name="inventoryLogs">库存日志列表</param>
private void UpdateAndLog(
Storagelocation storageLocation,
int newPackageCount,
int operation,
string operatorName,
List<Inventorylog> 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);
}
}
}
}