You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

605 lines
27 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Masuit.Tools;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using SlnMesnac.Config;
using SlnMesnac.Extensions;
using SlnMesnac.Model.domain;
using SlnMesnac.Model.dto.taskType;
using SlnMesnac.Model.dto.webapi;
using SlnMesnac.Plc;
using SlnMesnac.Rfid;
using SlnMesnac.Serilog;
using SlnMesnac.WCS.Global;
using SlnMesnac.WCS.Library;
using SlnMesnac.WCS.WCS;
using SqlSugar;
using System.Timers;
namespace SlnMesnac.WCS
{
public class MainCentralControl
{
private IHost host;
//删除日志定时器
private System.Timers.Timer deleteLogTimer;
//plc心跳交互定时器
private System.Timers.Timer plcHeartTimer;
// 新增一个标志位,用于表示方法是否正在执行
private bool isExecuting = false;
private readonly SerilogHelper _logger;
private IServiceProvider serviceProvider;
private ISqlSugarClient SqlSugarClient;
private BearAgv bearAgv;
#region 变量定义
public List<PlcAbsractFactory> plcList;
//容器里面的读写器集合
public List<RfidAbsractFactory> rfidList;
#endregion 变量定义
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host"></param>
public MainCentralControl(IHost _host)
{
host = _host;
SqlSugarClient = host.Services.GetRequiredService<ISqlSugarClient>();
_logger = host.Services.GetRequiredService<SerilogHelper>();
plcList = host.Services.GetRequiredService<List<PlcAbsractFactory>>();
rfidList = host.Services.GetRequiredService<List<RfidAbsractFactory>>();
InitCacheData();
}
/// <summary>
/// 加载缓存数据
/// </summary>
private void InitCacheData()
{
//加载plc地址信息
StaticData.BaseConfigList = SqlSugarClient.Queryable<BaseConfigInfo>().ToList();
StaticData.WmsMachineInfos = SqlSugarClient.Queryable<WmsMachineInfo>().ToList();
StaticData.WcsAgvCodeMaps = SqlSugarClient.Queryable<WcsAgvCodeMap>().ToList();
#region 定时删除日志
//DeleteLogsLogic();
//deleteLogTimer = new System.Timers.Timer(24 * 60 * 60 * 1000);
//deleteLogTimer.Elapsed += OnTimedEvent;
//deleteLogTimer.AutoReset = true;
//deleteLogTimer.Enabled = true;
#endregion 定时删除日志
#region 定时写入心跳
plcHeartTimer = new System.Timers.Timer(1000);
plcHeartTimer.Elapsed += OnTimedPlcHeartEvent;
plcHeartTimer.AutoReset = true;
plcHeartTimer.Enabled = true;
#endregion 定时写入心跳
}
/// <summary>
/// 启动程序
/// </summary>
public void Start()
{
StartCheckStatus();
GetAgvStatus();
DeleteTaskLogic();
//根据条件创建任务
CreateTaskByRecord createTaskByRecord = new CreateTaskByRecord(host.Services);
createTaskByRecord.StartPoint();
_logger.Agv("WCS调度程序启动成功!");
//背负式agv执行任务
bearAgv = new BearAgv(host.Services);
bearAgv.StartPoint();
}
#region
/// <summary>
/// 检查设备状态自动重连
/// </summary>
private void StartCheckStatus()
{
//数据库里面的读写器集合,仅用来更新状态
List<BaseRfidInfo> rfidDbList = SqlSugarClient.Ado.Context.CopyNew().Queryable<BaseRfidInfo>().ToList();
List<WcsBaseEquip> agvEquipList = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsBaseEquip>().Where(it => it.EquipType == 1).ToList();
Task.Run(async () =>
{
while (true)
{
try
{
#region PLC状态
foreach (var plc in plcList)
{
PlcConfig? plcConfig = host.Services.GetService<AppConfig>().plcConfig.FirstOrDefault(x => x.plcKey == plc.ConfigKey);
if (plc != null && plc.IsConnected)
{
plc.IsConnected = plc.readHeartByAddress("M100"); //DB100.DBX0.0
if (!plc.IsConnected)
{
plc.IsConnected = await plc.ConnectAsync(plcConfig.plcIp, plcConfig.plcPort);
}
}
else
{
if (plcConfig != null)
{
bool result = await plc.ConnectAsync(plcConfig.plcIp, plcConfig.plcPort);
plc.IsConnected = result;
}
}
}
#endregion PLC状态
#region RFID状态
foreach (var rfid in rfidList)
{
if (rfid.ConfigKey == "2#Transplant")
{
}
bool status = rfid.GetOnlineStatus();
if (!status)
{
status = await rfid.ConnectAsync(rfid.ip, rfid.port);
}
//状态同步数据库
BaseRfidInfo? rfidDb = rfidDbList.FirstOrDefault(x => x.EquipIp == rfid.ip);
if (rfidDb != null)
{
rfidDb.IsOnline = status ? 1 : 0;
}
}
lock (string.Empty)
{
try
{
SqlSugarClient.Ado.Context.CopyNew().Updateable(rfidDbList).UpdateColumns(it => new { it.IsOnline }).ExecuteCommand();
}
catch (Exception ex)
{
_logger.Error($"更新RFID状态异常:{ex.Message}",ex);
}
}
#endregion RFID状态
//色粉库位有未取走色粉声光报警
ListeningTonerLocation();
//大托盘库位有未取走色粉声光报警
ListeningBigPlateLocation();
}
catch (Exception ex)
{
_logger.Error($"监听设备状态异常:{ex.Message}");
}
await Task.Delay(1000 * 10);
}
});
}
/// <summary>
/// 获取agv状态
/// </summary>
private void GetAgvStatus()
{
List<WcsBaseEquip> agvEquipList = SqlSugarClient.Queryable<WcsBaseEquip>().Where(it => it.EquipType == 1).ToList();
Task.Run(async () =>
{
while (true)
{
try
{
//实时获取agv状态
MessageSynchronousLogic(agvEquipList);
}
catch (Exception ex)
{
_logger.Error($"获取agv状态异常:{ex.Message}");
}
await Task.Delay(1000 * 30);
}
});
}
#endregion
/// <summary>
/// 监听色粉库位是否色粉未从料想取出有的话通知plc声光报警
/// </summary>
private void ListeningTonerLocation()
{
try
{
PlcAbsractFactory? workShop3Plc = plcList.FirstOrDefault(x => x.ConfigKey == "workShop3Plc");
// 构建查询并执行 Any 方法
bool hasRecords = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsBaseEquip>()
.InnerJoin<WmsPalletInfo>((wbe, wpi) => wbe.ContainerCode == wpi.PalletInfoCode
&& !string.IsNullOrEmpty(wbe.ContainerCode)
&& wpi.TonerFlag == 1 && wbe.EquipType == 7).Any();
if (workShop3Plc != null && workShop3Plc.IsConnected)
{
if (hasRecords)
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#异常色粉未处理提示"), true);
}
else
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#异常色粉未处理提示"), false);
}
}
}
catch (Exception ex)
{
_logger.Error($"监听色粉库位异常:{ex.Message}");
}
}
private void ListeningBigPlateLocation()
{
try
{
PlcAbsractFactory? workShop3Plc = plcList.FirstOrDefault(x => x.ConfigKey == "workShop3Plc");
bool hasRecords = SqlSugarClient.Ado.Context.CopyNew().Queryable<WmsBaseLocation>()
.InnerJoin<WmsPalletInfo>((wbe, wpi) => wbe.ContainerCode == wpi.PalletInfoCode
&& !string.IsNullOrEmpty(wbe.ContainerCode)
&& wpi.Amount > 0 && wbe.MachineId > 0 && wbe.MachineId < 12).Any();
bool hasRecords12 = SqlSugarClient.Ado.Context.CopyNew().Queryable<WmsBaseLocation>()
.InnerJoin<WmsPalletInfo>((wbe, wpi) => wbe.ContainerCode == wpi.PalletInfoCode
&& !string.IsNullOrEmpty(wbe.ContainerCode)
&& wpi.Amount > 0 && wbe.MachineId == 12).Any();
if (workShop3Plc != null && workShop3Plc.IsConnected)
{
if (hasRecords)
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#机台校验失败提示"), true);
}
else
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#机台校验失败提示"), false);
}
if (hasRecords12)
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#12号机台未校验提示"), true);
}
else
{
workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#12号机台未校验提示"), false);
}
}
}
catch(Exception ex)
{
_logger.Error($"监听大托盘库位异常:{ex.Message}");
}
}
/// <summary>
/// agv状态实时刷新
/// </summary>
private void MessageSynchronousLogic(List<WcsBaseEquip> agvEquipList)
{
try
{
//List<WcsAgvStatus> agvList = SqlSugarClient.Queryable<WcsAgvStatus>().ToList();
List<WcsAgvStatus> agvList = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsAgvStatus>().ToList();
var data = new
{
reqCode = StaticData.SnowId.NextId(),
mapCode = "AA",
};
string result = HttpHelper.SendPostMessage(agvEquipList.First().ServerIp, agvEquipList.First().ServerPort, "rcms-dps/rest/queryAgvStatus", data.ToJsonString());
var reponse = JsonConvert.DeserializeObject<AgvStatusDto>(result);
if (reponse != null && reponse.code == "0")
{
foreach (var agv in agvList)
{
CardStatus? agvCard = reponse.data.FirstOrDefault(x => x.robotCode == agv.RobotCode);
if (agvCard != null)
{
agv.Battery = agvCard.battery;
agv.ExclType = agvCard.exclType;
agv.Online = agvCard.online;
agv.PosX = agvCard.posX;
agv.PosY = agvCard.posY;
agv.RobotDir = agvCard.robotDir;
agv.Speed = agvCard.speed;
agv.Status = agvCard.status;
WcsAgvCodeMap? wcsAgvCodeMap = StaticData.WcsAgvCodeMaps.FirstOrDefault(x => x.AgvCode == long.Parse(agv.Status));
if (wcsAgvCodeMap != null)
{
agv.StatusDetail = wcsAgvCodeMap.agv_display_content;
}
agv.Stop = agvCard.stop;
agv.Timestamp = DateTime.Now;
}
}
//TaskHandleByAgv(agvList);
//SqlSugarClient.Updateable(agvList).ExecuteCommand();
SqlSugarClient.Ado.Context.CopyNew().Updateable(agvList).ExecuteCommand();
}
}
catch (Exception ex)
{
_logger.Info($"同步agv状态信息异常:{ex.Message},{ex.StackTrace.ToJsonString()}");
}
}
public void TaskHandleByAgv(List<WcsAgvStatus> agvInfos)
{
if (agvInfos == null || agvInfos.Count == 0)
{
return;
}
var targetPoint = new { X = 142430, Y = 108540 };
var agvInfo = agvInfos.OrderBy(agv => CalculateDistance(double.Parse(agv.PosX), double.Parse(agv.PosY), targetPoint.X, targetPoint.Y)).First();
_logger.Agv($"=====>>>>离接驳位最近的设备为:{agvInfo.RobotCode}");
//判断当前小车有没有送空箱任务,下发PLC触发取料信号
if (agvInfo.StatusDetail.Contains("待机模式") || agvInfo.StatusDetail.Contains("空闲"))
{
_logger.Agv($"当前空闲车辆{agvInfo.RobotCode};离取料点最近优先进行取料锁定线体下发PLC触发取料信号");
}
else
{
_logger.Agv($"当前没有空闲小车离取料点最近,等线体空闲后进行取料");
bearAgv.workShop3Plc.writeBoolByAddress(StaticData.GetPlcAddress("3#空箱进缓存请求"),true);
bool flag = bearAgv.workShop3Plc.readBoolByAddress(StaticData.GetPlcAddress("3#空箱进缓存允许"));
if (flag == true)
{
//线体空闲了,可以锁定进行取料
_logger.Agv($"线体空闲了,可以锁定使用远处小车进行取料");
}
}
}
private static double CalculateDistance(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
}
#region 删除任务 -- 已经下发的任务需要通知agv取消
/// <summary>
/// 定时监测是否有待删除任务,完成删除
/// </summary>
private void DeleteTaskLogic()
{
Task.Run(() =>
{
var sqlClient = SqlSugarClient.Ado.Context.CopyNew();
List<WcsBaseEquip> agvEquipList = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsBaseEquip>().Where(it => it.EquipType == 1).ToList();
while (true)
{
try
{
List<WcsTask> wcsTasks = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsTask>().Where(t => t.IsDelete == 1 || t.IsDelete == 2).ToList();
foreach (var item in wcsTasks)
{
WcsBaseEquip agvEquip = agvEquipList.First();
if (!string.IsNullOrEmpty(item.TaskCode))
{
var cancelTask = new
{
reqCode = StaticData.SnowId.NextId().ToString(),
taskCode = item.TaskCode
};
string reponse = HttpHelper.SendPostMessage(agvEquip.ServerIp, agvEquip.ServerPort, "rcms/services/rest/hikRpcService/cancelTask", cancelTask.ToJsonString());
var result = JsonConvert.DeserializeObject<ReponseMessage>(reponse);
if (result != null && result.code == "0")
{
#region 解锁起点库位与终点库位
//根据任务类型,如果是大料箱解锁wmsbaseLocation
List<int> bigType = new List<int>() { 1, 3, 8, 10 };
if (bigType.Contains(item.TaskType))
{
List<WmsBaseLocation>? wmsBaseLocations = SqlSugarClient.Ado.Context.CopyNew().Queryable<WmsBaseLocation>().Where(x => x.LocationCode == item.CurrPointNo || x.LocationCode == item.EndPointNo).ToList();
if (wmsBaseLocations != null && wmsBaseLocations.Count > 0)
{
wmsBaseLocations.ForEach(x => x.LocationStatus = 0);
SqlSugarClient.Ado.Context.CopyNew().Updateable(wmsBaseLocations).ExecuteCommand();
}
}
else //如果是小料箱解锁wcsbaseEquip
{
List<WcsBaseEquip> equips = SqlSugarClient.Ado.Context.CopyNew().Queryable<WcsBaseEquip>().Where(x => x.AgvPositionCode == item.CurrPointNo || x.AgvPositionCode == item.EndPointNo).ToList();
if (equips != null && equips.Count > 0)
{
equips.ForEach(x => x.EquipStatus = 0);
SqlSugarClient.Ado.Context.CopyNew().Updateable(equips).ExecuteCommand();
}
}
#endregion
SqlSugarClient.Ado.Context.CopyNew().Deleteable<WcsTask>(item).ExecuteCommand();
_logger.Agv($"任务id:{item.Id},任务名:{item.TaskName}已删除");
//
if (!string.IsNullOrEmpty(item.NextPointNo) && item.TaskStatus >= 2 && item.IsDelete == 2) //有执行任务的agv编号,并且需要去异常库位
{
WmsBaseLocation? endLocation = SqlSugarClient.Ado.Context.CopyNew().Queryable<WmsBaseLocation>().First(x => x.LocationCode == "3066" && x.LocationStatus == 0 && string.IsNullOrEmpty(x.ContainerCode));
if (endLocation != null)
{
WcsTask task = new WcsTask();
task.TaskType = StaticTaskType.ExceptionTask;
task.CurrPointNo = "ExceptionStart";
task.NextPointNo = item.NextPointNo; //指定agv
task.EndPointNo = endLocation.AgvPositionCode;
task.TaskStatus = 0;
task.CreatedTime = DateTime.Now;
task.CreatedBy = "wcs";
task.TaskName = "前往异常库位移库任务";
task.PalletInfoCode = item.PalletInfoCode;
sqlClient.AsTenant().BeginTran();
try
{
int id = SqlSugarClient.Ado.Context.CopyNew().Insertable(task).ExecuteReturnIdentity();
WcsTaskLog wcsTaskLog = CoreMapper.Map<WcsTaskLog>(task);
wcsTaskLog.Id = id;
//锁定库位
endLocation.LocationStatus = 1;
SqlSugarClient.Ado.Context.CopyNew().Updateable(endLocation).ExecuteCommand();
SqlSugarClient.Ado.Context.CopyNew().Insertable(wcsTaskLog).ExecuteCommand();
sqlClient.AsTenant().CommitTran();
_logger.Agv($"生成{task.TaskName},起点:{task.CurrPointNo},终点:{task.EndPointNo}");
}
catch (Exception ex)
{
_logger.Error($"生成移库任务提交事务异常:{ex.Message}");
sqlClient.AsTenant().RollbackTran();
}
}
else
{
_logger.Error("想要创建异常库位任务,但是异常库位不可用或已经有托盘");
}
}
}
else
{
_logger.Error($"任务id:{item.Id},任务名:{item.TaskName}删除失败agv接口回复:{result.message}");
}
}
else
{
sqlClient.Deleteable<WcsTask>(item).ExecuteCommand();
_logger.Agv($"任务id:{item.Id},任务名:{item.TaskName}已删除");
}
}
}
catch (Exception ex)
{
_logger.Error($"任务删除异常:{ex.Message}");
}
Thread.Sleep(2000);
}
});
}
#endregion 删除任务 -- 已经下发的任务需要通知agv
#region 定时删除日志处理
/// <summary>
/// 定时器触发的事件处理方法
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
DeleteLogsLogic();
}
/// <summary>
/// 清除日志文件
/// </summary>
private void DeleteLogsLogic()
{
try
{
// 获取当前程序运行目录下的 Log 文件夹
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
// 检查 Log 文件夹是否存在
if (!Directory.Exists(logPath))
{
// 如果 Log 文件夹不存在,可以选择记录日志或者直接返回
Console.WriteLine($"Log 文件夹 {logPath} 不存在。");
return;
}
// 获取 Log 文件夹下的所有子文件夹
string[] logDirectories = Directory.GetDirectories(logPath);
foreach (string directoryPath in logDirectories)
{
string directoryName = Path.GetFileName(directoryPath);
if (!string.IsNullOrEmpty(directoryName) && DateTime.TryParse(directoryName, out DateTime directoryDate))
{
// 检查文件夹日期是否早于当前日期三天之前
if (directoryDate < DateTime.Now.AddDays(-3))
{
try
{
// 删除符合条件的文件夹
Directory.Delete(directoryPath, true);
Console.WriteLine($"已删除文件夹: {directoryPath}");
}
catch (Exception ex)
{
// 记录删除文件夹时的异常信息
Console.WriteLine($"删除文件夹 {directoryPath} 时出错: {ex.Message}");
}
}
}
}
}
catch (Exception ex)
{
// 记录整体操作的异常信息
Console.WriteLine($"执行删除日志逻辑时出错: {ex.Message}");
}
}
#endregion
#region 定时plc发送心跳脉冲
private bool heartFlag = true;
/// <summary>
/// 定时器触发的事件处理方法
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedPlcHeartEvent(Object source, ElapsedEventArgs e)
{
// 检查方法是否正在执行
if (isExecuting)
{
return;
}
try
{
// 设置标志位为 true表示方法正在执行
isExecuting = true;
PlcAbsractFactory? plc3 = plcList.FirstOrDefault(x => x.ConfigKey == "workShop3Plc");
PlcAbsractFactory? plc2 = plcList.FirstOrDefault(x => x.ConfigKey == "workShop2Plc");
if (plc3 != null && plc3.IsConnected)
{
plc3.writeBoolByAddress("DB100.DBX84.0", heartFlag);
heartFlag = !heartFlag;
}
if (plc2 != null && plc2.IsConnected)
{
plc2.writeBoolByAddress("DB100.DBX24.0", heartFlag);
heartFlag = !heartFlag;
}
}
finally
{
isExecuting = false;
}
}
#endregion
}
}