Files
sy_hx_pbl_backend/DOAN.ServiceCore/DoanBackgroundService.cs
赵正易 07880805a3 feat: 添加PLC配置并优化后台服务
refactor(PLCTool): 重构PLC工具类,优化连接处理和错误日志
fix: 修正数据库连接字符串和开发环境配置
feat(DoanBackgroundService): 添加新的后台服务实现,改进库存监控逻辑
2025-08-19 09:30:44 +08:00

450 lines
20 KiB
C#
Raw Permalink 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.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
{
/// <summary>
/// 永驻线程
/// 功能:检测传感器信号,判断箱子数
/// </summary>
public class DoanBackgroundService : BaseService<Storagelocation>, 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 List<PlcButton> buttonPositionList = new List<PlcButton>();
private Timer refreshTimer;
public DoanBackgroundService(IHubContext<PBLhub> hubContext)
{
notificationHubContext = hubContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine($"PCL定时任务开启");
pLCTool = new PLCTool();
pLCTool.ConnectPLC();
// sql标准
Context.Queryable<Storagelocation>();
// 初始化料架层和点位表数据
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; // 返回取反后的值
}
/// <summary>
/// 功能:检测传感器信号,判断箱子数并更新库存及日志
/// </summary>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
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<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
)
{
using (var scope = DbScoped.SugarScope.CopyNew())
{
// 是否已经报警过,并且
bool hasLastAlarm = await scope
.Queryable<AlarmLog>()
.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}"
);
}
}
/// <summary>
/// 刷新料架层和点位表数据
/// </summary>
/// <param name="state"></param>
private async void RefreshData(object state)
{
try
{
Console.WriteLine($"刷新料架点位和按钮");
using (var scope = DbScoped.SugarScope.CopyNew())
{
storagelocationList = await scope.Queryable<Storagelocation>().ToListAsync();
pointPositionList = await scope.Queryable<PlcAddressTable>().ToListAsync();
buttonPositionList = await scope.Queryable<PlcButton>().ToListAsync();
}
}
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
{
_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);
}
}
}
}