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#

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 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
{
/// <summary>
/// 设备生产管理业务类 -3F
/// </summary>
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<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();
// 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连接信息为空");
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);
}
}
/// <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)
{
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);
}
}
/// <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>
/// 申请叫料,等待调度接口
/// </summary>
/// <param name="materialCode"></param>
/// <param name="result"></param>
/// <exception cref="InvalidOperationException"></exception>
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<ReponseMessage>(httpResult);
if (reponseMessage != null && reponseMessage.code == "0")
{
RefreshMessage("Agv叫料成功");
result = true;
}
}
catch (Exception ex)
{
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;
}
}
}