using HslCommunication.Secs.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Npgsql.Replication.TestDecoding; using SlnMesnac.Business.@base; 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; 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 保留所有权利。 * CLR版本:4.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 { /// /// 设备生产管理业务类 -3F /// public class ProdMgmtBusiness : BaseBusiness { public readonly IMesProductPlanService _mesProductPlanService; public readonly IBasePalletInfoService _basePalletInfoService; public readonly IMesPrdBarCodeService _mesPrdBarCodeService; public readonly IBaseRealTaskService _baseRealTaskService; public readonly IWmsOutStockService _wmsOutStockService; private readonly ConfigInfoBusiness _configInfoBusiness; private ISqlSugarClient sqlSugarClient; private DebugConfig debugConfig = DebugConfig.Instance; public ProdMgmtBusiness(ISqlSugarClient _sqlSugarClient, IWmsOutStockService wmsOutStockService, ILogger logger, AppConfig appConfig, List plcFactories, List 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(); _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(); // 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 委托事件 /// /// 刷新计划列表 /// public delegate void RefreshProdPlanList(List list); public event RefreshProdPlanList? RefreshProdPlanListEvent; /// /// 刷新执行计划 /// public delegate void RefreshProdPlanExec(MesProductPlan productPlan); public event RefreshProdPlanExec? RefreshProdPlanExecEvent; /// /// 投料确认,validType确认类型:1-读取失败重新读取;2-校验失败是否投料 /// 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(); }); } /// /// 刷新计划执行列表 /// private void RefreshMesProdList() { try { // var aaa = _mesProductPlanService.GetMesProductPlans(); var info = _mesProductPlanService.GetPlanDtos(); RefreshProdPlanListEvent?.Invoke(info); } catch (Exception ex) { _logger.LogError($"MES生产计划获取异常:{ex.Message}"); } } /// /// 计划执行,叫料逻辑处理 /// private void ProdPlanExecHandle() { //每5秒执行一次,获取是否有已开始的计划,获取已开始的计划下发给WCS进行叫料,获取货架到位信息进行投料校验,持续获取设备要料信号 while (true) { try { var plc = base.GetPlcByKey("plc"); if (plc == null) { //throw new ArgumentException($"PLC连接信息为空"); RefreshMessage($"PLC连接信息为空"); Thread.Sleep(3000); return; } MesProductPlan prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto); if (prodPlan == null || productPlanDto == null) { //throw new ArgumentException($"未获取到需要执行的生产计划"); RefreshMessage($"未获取到需要执行的生产计划"); Thread.Sleep(5000); continue; } BaseRealTask task = _baseRealTaskService.GetExeTask(); if (task != null) { RefreshMessage("已经叫料,请等待AGV送料......"); Thread.Sleep(5000); continue; } 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)};发起叫料申请"); //查询下发agv的主键 WmsRawOutstock wmsRaw = _wmsOutStockService.GetProdPlanByPlanCode(prodPlan.PlanCode); //进行叫料 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); } } /// /// 投料校验:轮询物料到位信号 /// /// 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) { RefreshMessage("读取物料到位信号,PLC连接信息为空......"); Thread.Sleep(3000); continue; } if (!plc.readBoolByAddress(GetPlcAddressByConfigKey("物料到位"))) { 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); } } /// /// 投料结果处理 /// /// /// 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}"); } } /// /// 投料校验失败,人工确认是否继续投料 /// /// /// /// /// /// private bool MatPutInValidTriggerEvent(int validType, MesProductPlan productPlan, string materialName, string msg) { return MatPutInValidEvent.Invoke(validType, productPlan, materialName, msg); } /// /// 申请叫料,等待调度接口 /// /// /// /// public bool ApplyDeliveryHandle(String taskId) { bool result = false; try { var content = new { rawOutstockId = taskId, }; string message = JsonConvert.SerializeObject(content); //询问小车是否离开接口 0已经离开 string httpResult = HttpHelper.SendPostMessage("172.16.12.100", 5001, "wcs/RecieveRcs/callMaterial", message, "application/Json"); var reponseMessage = JsonConvert.DeserializeObject(httpResult); if (reponseMessage != null && reponseMessage.code == "0") { RefreshMessage("Agv叫料成功"); result = true; } } catch (Exception ex) { RefreshMessage($"申请叫料处理异常:{ex.Message}"); result = false; } return result; } /// /// 投料后产生小包条码存储到数据库等待绑定 /// private void GenerateBarcode(int amount) { List list = new List(); 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(); } /// /// 更新生产计划 /// /// 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}"); } } /// /// 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(httpResult); if (reponseMessage != null && reponseMessage.code == "0") { RefreshMessage("Agv已经离开..."); result = true; } return result; } } }