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.

611 lines
23 KiB
C#

using HslCommunication.Secs.Types;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
4 months ago
using Newtonsoft.Json;
using Npgsql.Replication.TestDecoding;
using SlnMesnac.Business.@base;
4 months ago
using SlnMesnac.Common;
using SlnMesnac.Common.Model;
using SlnMesnac.Config;
using SlnMesnac.Model.domain;
using SlnMesnac.Model.dto;
using SlnMesnac.Model.enums;
using SlnMesnac.Plc;
using SlnMesnac.Repository.service;
4 months ago
using SlnMesnac.Repository.service.Impl;
using SlnMesnac.Rfid;
using SlnMesnac.Rfid.Dto;
using SlnMesnac.TouchSocket;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using TouchSocket.Core;
using TouchSocket.Sockets;
#region << 版 本 注 释 >>
/*--------------------------------------------------------------------
* (c) 2024 WenJY
* CLR4.0.30319.42000
* LAPTOP-E0N2L34V
* SlnMesnac.Business
* 655997bf-2bdb-4426-bc49-6fd8def3b395
*
* WenJY
* wenjy@mesnac.com
* 2024-04-07 16:58:59
* V1.0.0
*
*
*--------------------------------------------------------------------
*
*
*
*
* V1.0.0
*--------------------------------------------------------------------*/
#endregion << 版 本 注 释 >>
namespace SlnMesnac.Business
{
/// <summary>
/// 设备生产管理业务类 -3F
/// </summary>
public class ProdMgmtBusiness : BaseBusiness
{
public readonly IMesProductPlanService _mesProductPlanService;
public readonly IBasePalletInfoService _basePalletInfoService;
public readonly IMesPrdBarCodeService _mesPrdBarCodeService;
public readonly IBaseRealTaskService _baseRealTaskService;
4 months ago
public readonly IWmsOutStockService _wmsOutStockService;
private readonly ConfigInfoBusiness _configInfoBusiness;
private ISqlSugarClient sqlSugarClient;
private DebugConfig debugConfig = DebugConfig.Instance;
public ProdMgmtBusiness(ISqlSugarClient _sqlSugarClient, IWmsOutStockService wmsOutStockService, ILogger<ProdMgmtBusiness> logger, AppConfig appConfig, List<PlcAbsractFactory> plcFactories, List<RfidAbsractFactory> rfidFactories, IMesProductPlanService mesProductPlanService, IBasePalletInfoService basePalletInfoService, IBaseRealTaskService baseRealTaskService, IServiceProvider serviceProvider, ConfigInfoBusiness configInfoBusiness) : base(logger, appConfig, plcFactories, rfidFactories, serviceProvider)
{
sqlSugarClient = _sqlSugarClient;
_wmsOutStockService = wmsOutStockService;
_mesProductPlanService = mesProductPlanService;
_basePalletInfoService = basePalletInfoService;
_baseRealTaskService = baseRealTaskService;
_mesPrdBarCodeService = serviceProvider.GetRequiredService<IMesPrdBarCodeService>();
_configInfoBusiness = configInfoBusiness;
//testRFID();
}
public void TestTran()
{
try
{
sqlSugarClient.AsTenant().BeginTran();
BaseRealTask localPlan = _baseRealTaskService.GetExeTask();
localPlan.Status = 2;
sqlSugarClient.AsTenant().GetConnection("local").Updateable(localPlan).ExecuteCommand();
GenerateBarcode(40);
// throw new Exception("事务测试");
sqlSugarClient.AsTenant().CommitTran();
Console.WriteLine("提交事务");
}
catch (Exception ex)
{
sqlSugarClient.AsTenant().RollbackTran();
Console.WriteLine(ex.Message);
}
}
private async void testRFID()
{
//TcpClient tcpClient = new TcpClient();
//var waitClient = tcpClient.CreateWaitingClient(new WaitingOptions()
//{
// FilterFunc = response =>
// {
// return true;
// }
//});
//tcpClient.Setup(new TouchSocketConfig().SetRemoteIPHost($"192.168.2.51:3000"));
//tcpClient.Connect();
3 months ago
// byte[] reciveBuffer = await waitClient.SendThenReturnAsync(pMessagePack.m_pData, timeout);
string aaa = await ReadEpcStrByRfidKeyAsync("secondFloorPallet");
// ReadEpcStrByRfidKey("test", out string epcStr);
Console.WriteLine(aaa);
//RefreshMessage($"投料校验RFID标签读取成功标签信息{epcStr}");
}
#region 委托事件
/// <summary>
/// 刷新计划列表
/// </summary>
public delegate void RefreshProdPlanList(List<MesProductPlanDto> list);
public event RefreshProdPlanList? RefreshProdPlanListEvent;
/// <summary>
/// 刷新执行计划
/// </summary>
public delegate void RefreshProdPlanExec(MesProductPlan productPlan);
public event RefreshProdPlanExec? RefreshProdPlanExecEvent;
/// <summary>
/// 投料确认,validType确认类型1-读取失败重新读取2-校验失败是否投料
/// </summary>
public delegate bool MatPutInValid(int validType, MesProductPlan productPlan, string materialName, string msg);
public event MatPutInValid? MatPutInValidEvent;
#endregion
public void InitProdPlan()
{
RefreshMesProdList();
Task.Run(() => { ProdPlanExecHandle(); });
Task.Run(() => { MaterialPutInCheck(); });
}
/// <summary>
/// 刷新计划执行列表
/// </summary>
private void RefreshMesProdList()
{
try
{
// var aaa = _mesProductPlanService.GetMesProductPlans();
var info = _mesProductPlanService.GetPlanDtos();
RefreshProdPlanListEvent?.Invoke(info);
}
catch (Exception ex)
{
_logger.LogError($"MES生产计划获取异常{ex.Message}");
}
}
/// <summary>
/// 计划执行,叫料逻辑处理
/// </summary>
private void ProdPlanExecHandle()
{
//每5秒执行一次获取是否有已开始的计划获取已开始的计划下发给WCS进行叫料获取货架到位信息进行投料校验持续获取设备要料信号
while (true)
{
try
{
var plc = base.GetPlcByKey("plc");
if (plc == null)
{
//throw new ArgumentException($"PLC连接信息为空");
RefreshMessage($"PLC连接信息为空");
3 months ago
Thread.Sleep(3000);
return;
}
MesProductPlan prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
if (prodPlan == null || productPlanDto == null)
{
//throw new ArgumentException($"未获取到需要执行的生产计划");
RefreshMessage($"未获取到需要执行的生产计划");
Thread.Sleep(5000);
continue;
}
4 months ago
BaseRealTask task = _baseRealTaskService.GetExeTask();
if (task != null)
{
4 months ago
RefreshMessage("已经叫料请等待AGV送料......");
Thread.Sleep(5000);
continue;
}
4 months ago
if (!plc.readBoolByAddress(GetPlcAddressByConfigKey("设备叫料")))
{
RefreshMessage("等待设备叫料信号触发......");
Thread.Sleep(5000);
continue;
}
RefreshMessage("设备要料信号触发成功");
RefreshProdPlanExecEvent?.Invoke(prodPlan);
// string palletCode = GetPalletInfoByTask();
RefreshMessage($"执行计划:{prodPlan.PlanCode};计划数量:{Math.Round(prodPlan.PlanAmount, 2)};完成数量:{Math.Round(prodPlan.CompleteAmount, 2)};发起叫料申请");
4 months ago
//查询下发agv的主键
WmsRawOutstock wmsRaw = _wmsOutStockService.GetProdPlanByPlanCode(prodPlan.PlanCode);
4 months ago
//进行叫料
4 months ago
if (ApplyDeliveryHandle(wmsRaw.rawOutstockId.ToString()))
{
RefreshMessage("根据计划自动申请叫料成功");
#region 本地创建一个叫料任务
BaseRealTask realTask = new BaseRealTask();
realTask.PlanCode = prodPlan.PlanCode;
realTask.PlanAmount = prodPlan.PlanAmount;
realTask.PlanComplete = prodPlan.CompleteAmount;
realTask.MaterialId = prodPlan.MaterialId;
realTask.Status = 1;
realTask.CreateTime = DateTime.Now;
realTask.UpdateTime = DateTime.Now;
_baseRealTaskService.InsertTask(realTask);
#endregion
plc.writeBoolByAddress(GetPlcAddressByConfigKey("设备叫料"), false);
}
else
{
RefreshMessage("根据计划自动申请叫料失败");
}
}
catch (Exception e)
{
_logger.LogError($"计划执行逻辑处理异常:{e.Message}");
}
Thread.Sleep(5000);
}
}
/// <summary>
/// 投料校验:轮询物料到位信号
/// </summary>
/// <exception cref="ArgumentException"></exception>
public void MaterialPutInCheck()
{
bool result = false;
var plc = base.GetPlcByKey("plc");
while (true)
{
//mes计划
MesProductPlan prodPlan = null;
//本地叫料任务
BaseRealTask localPlan = null;
try
{
localPlan = _baseRealTaskService.GetExeTask();
if (localPlan == null)
{
RefreshMessage("本地未获取到叫料任务......");
Thread.Sleep(3000);
continue;
}
if (plc == null)
{
4 months ago
RefreshMessage("读取物料到位信号,PLC连接信息为空......");
Thread.Sleep(3000);
continue;
}
if (!plc.readBoolByAddress(GetPlcAddressByConfigKey("物料到位")))
{
4 months ago
RefreshMessage("等待物料到位信号触发......");
Thread.Sleep(3000);
continue;
}
// TODO 投料时间校验
bool timeCheck = JudgeProductTime();
if (!timeCheck)
{
Thread.Sleep(1000 * 15);
continue;
}
RefreshMessage($"投料时间校验通过");
// 调用调度接口判断AGV小车是否已经离开到位否则不允许投料
bool positionCheck = JudgeAgvPosition();
if (!positionCheck)
{
RefreshMessage($"收到物料到位信号,但是AGV还未离开,禁止投料");
Thread.Sleep(1000 * 5);
continue;
}
//TODO根据本地叫料计划查询对应的mes计划完成
prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
if (productPlanDto == null || prodPlan == null)
{
RefreshMessage($"收到物料到位信号,未获取到正在执行的生产计划,请开始执行计划");
Thread.Sleep(5000);
continue;
}
#region 物料校验 -- TODO 如果计划暂停,开启新计划,校验当前滞留物料是与前一条计划判断,还是当前开始的计划判断
//RefreshMessage("物料到位信号触发成功,读取托盘RFID信息进行投料校验");
//ReadEpcStrByRfidKey("test", out string epcStr);
//RefreshMessage($"投料校验RFID标签读取成功标签信息{epcStr}");
////获取物料托盘上的物料信息
//BasePalletInfo palletInfo = _basePalletInfoService.GetPalletInfoByPalletCode(epcStr);
//if (palletInfo == null)
//{
// result = MatPutInValidTriggerEvent(2, prodPlan, string.Empty, "投料校验失败,未获取到托盘信息,是否继续投料");
//}
//else
//{
// if (palletInfo.MaterialId != prodPlan.MaterialId)
// {
// result = MatPutInValidTriggerEvent(2, prodPlan, string.Empty, "投料校验失败,物料信息不匹配,是否继续投料");
// }
// else
// {
// result = true;
// }
//}
//if (result)
//{
// bool isRes = _basePalletInfoService.UnBindPalletInfo(palletInfo);
// RefreshMessage($"物料校验通过,托盘信息解绑{(isRes ? "成功" : "失败")}");
//}
#endregion
if (prodPlan != null)
{
// 下发翻转信号
plc.writeBoolByAddress(GetPlcAddressByConfigKey("设备投料"), true);
Thread.Sleep(300);
plc.writeBoolByAddress(GetPlcAddressByConfigKey("设备投料"), false);
sqlSugarClient.AsTenant().BeginTran();
//1.完成本地计划
if (localPlan != null)
{
localPlan.Status = 2;
localPlan.UpdateTime = DateTime.Now;
sqlSugarClient.AsTenant().GetConnection("local").Updateable(localPlan).ExecuteCommand();
}
//2.生成小包条码
GenerateBarcode(40);
//BaseConfigInfo configInfo = _configInfoBusiness.GetConfigInfos().Where(x => x.ConfigKey == "系统运行时长").FirstOrDefault();
//configInfo.ConfigValue = "0";
//sqlSugarClient.AsTenant().GetConnection("local").Updateable(configInfo).ExecuteCommand();
//_configInfoBusiness.RefreshConfigInfo();
sqlSugarClient.AsTenant().CommitTran();
//3.清空投料系统运行时间
BaseConfigInfo configInfo = _configInfoBusiness.GetConfigInfos().Where(x => x.ConfigKey == "系统运行时长").FirstOrDefault();
configInfo.ConfigValue = "0";
_configInfoBusiness.UpdateConfigInfo(configInfo);
}
}
catch (Exception e)
{
sqlSugarClient.AsTenant().RollbackTran();
_logger.LogInformation($"投料流程异常:{e.Message}");
Thread.Sleep(5000);
}
Thread.Sleep(3000);
}
}
/// <summary>
/// 投料结果处理
/// </summary>
/// <param name="prodPlan"></param>
/// <param name="isRes"></param>
private void PutInResutlHandle(MesProductPlan prodPlan, bool isRes)
{
try
{
if (isRes)
{
RefreshMessage("投料校验完成,允许投料,更新生产计划");
prodPlan.CompleteAmount += 1;
if (prodPlan.CompleteAmount >= prodPlan.PlanAmount)
{
prodPlan.PlanStatus = PlanStatusEnum.;
}
_mesProductPlanService.Update(prodPlan);
RefreshMesProdList();
}
else
{
RefreshMessage("投料校验失败,人工确认不许投料");
}
}
catch (Exception e)
{
_logger.LogInformation($"投料结果处理异常:{e.Message}");
}
}
/// <summary>
/// 投料校验失败,人工确认是否继续投料
/// </summary>
/// <param name="validType"></param>
/// <param name="productPlan"></param>
/// <param name="materialName"></param>
/// <param name="msg"></param>
/// <returns></returns>
private bool MatPutInValidTriggerEvent(int validType, MesProductPlan productPlan, string materialName, string msg)
{
return MatPutInValidEvent.Invoke(validType, productPlan, materialName, msg);
}
/// <summary>
4 months ago
/// 申请叫料,等待调度接口
/// </summary>
/// <param name="materialCode"></param>
/// <param name="result"></param>
/// <exception cref="InvalidOperationException"></exception>
4 months ago
public bool ApplyDeliveryHandle(String taskId)
{
bool result = false;
try
{
var content = new
{
rawOutstockId = taskId,
};
4 months ago
string message = JsonConvert.SerializeObject(content);
//询问小车是否离开接口 0已经离开
string httpResult = HttpHelper.SendPostMessage("172.16.12.100", 5001, "wcs/RecieveRcs/callMaterial", message, "application/Json");
4 months ago
var reponseMessage = JsonConvert.DeserializeObject<ReponseMessage>(httpResult);
if (reponseMessage != null && reponseMessage.code == "0")
{
RefreshMessage("Agv叫料成功");
result = true;
}
}
4 months ago
catch (Exception ex)
{
4 months ago
RefreshMessage($"申请叫料处理异常:{ex.Message}");
result = false;
}
return result;
}
/// <summary>
/// 投料后产生小包条码存储到数据库等待绑定
/// </summary>
private void GenerateBarcode(int amount)
{
List<MesPrdBarcodeInfo> list = new List<MesPrdBarcodeInfo>();
string baseDate = DateTime.Now.ToString("yyyyMMddHHmm"); // 当前日期和时间到分钟
for (int i = 1; i <= amount; i++)
{
MesPrdBarcodeInfo record = new MesPrdBarcodeInfo();
record = new MesPrdBarcodeInfo();
record.PrdBarcodeInfo = baseDate + i.ToString("D3"); // 将序号格式化为三位数
record.PrintFlag = "0";
record.CreatTime = DateTime.Now;
record.BindStatus = 0;
list.Add(record);
}
sqlSugarClient.AsTenant().GetConnection("mes").Insertable(list).ExecuteCommand();
}
/// <summary>
/// 更新生产计划
/// </summary>
/// <param name="planInfo"></param>
public void UpdateProdPlan(MesProductPlanDto planInfo)
{
try
{
if (planInfo == null)
{
throw new ArgumentException("更新生产计划异常:参数为空");
}
var info = _mesProductPlanService.GetProdPlanByPlanCode(planInfo.PlanCode);
if (info == null)
{
throw new ArgumentException("更新生产计划异常:根据计划编号获取计划信息为空");
}
info.PlanStatus = planInfo.PlanStatus;
if (!_mesProductPlanService.UpdateProdPlan(info))
{
throw new InvalidOperationException("生产计划更新失败");
}
RefreshMesProdList();
}
catch (Exception e)
{
throw new InvalidOperationException($"生产计划更新异常:{e.Message}");
}
}
///
/// <summary
/// 距离上次投料时间合格判断
/// </summary
private bool JudgeProductTime()
{
try
{
int IntervalMin = int.Parse(GetPlcAddressByConfigKey("投料时间间隔")); // 单位分钟
int systemRunTime = int.Parse(GetPlcAddressByConfigKey("系统运行时长")); // 单位分钟
if (systemRunTime > IntervalMin)
{
return true;
}
else
{
RefreshMessage($"收到物料到位请求投料信号,但是距离上次投料不满20min,禁止投料");
return false;
}
}
catch (Exception ex)
{
_logger.LogError($"距离上次投料时间合格判断异常JudgeProductTime{ex.Message}");
return false;
}
}
private bool JudgeAgvPosition()
{
bool result = false;
//询问小车是否离开接口 0已经离开
string httpResult = HttpHelper.SendPostMessage("172.16.12.100", 5001, "wcs/RecieveRcs/AgvTaskComplete", "", "application/Json");
var reponseMessage = JsonConvert.DeserializeObject<ReponseMessage>(httpResult);
if (reponseMessage != null && reponseMessage.code == "0")
{
RefreshMessage("Agv已经离开...");
result = true;
}
return result;
}
}
}