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.

471 lines
17 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 SlnMesnac.Business.@base;
using SlnMesnac.Config;
using SlnMesnac.Model.domain;
using SlnMesnac.Model.dto;
using SlnMesnac.Model.enums;
using SlnMesnac.Plc;
using SlnMesnac.Repository.service;
using SlnMesnac.Rfid;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TouchSocket.Core;
#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 IBaseRealTaskService _baseRealTaskService;
private List<RealPalletTask> _palletTasks;
private DebugConfig debugConfig = DebugConfig.Instance;
public ProdMgmtBusiness(ILogger<ProdMgmtBusiness> logger, AppConfig appConfig, List<PlcAbsractFactory> plcFactories, List<RfidAbsractFactory> rfidFactories, IMesProductPlanService mesProductPlanService, IBasePalletInfoService basePalletInfoService, IBaseRealTaskService baseRealTaskService, List<RealPalletTask> palletTasks,IServiceProvider serviceProvider) : base(logger, appConfig, plcFactories, rfidFactories, serviceProvider)
{
_mesProductPlanService = mesProductPlanService;
_basePalletInfoService = basePalletInfoService;
_baseRealTaskService = baseRealTaskService;
_palletTasks = palletTasks;
}
#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 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连接信息为空");
}
MesProductPlan prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
if (prodPlan == null || productPlanDto == null)
{
//throw new ArgumentException($"未获取到需要执行的生产计划");
RefreshMessage($"未获取到需要执行的生产计划");
Thread.Sleep(5000);
continue;
}
if (!plc.readBoolByAddress(GetPlcAddressByConfigKey("设备叫料")))
{
RefreshMessage("等待设备叫料信号触发......");
Thread.Sleep(5000);
continue;
}
BaseRealTask task = _baseRealTaskService.GetExeTask();
if (task!=null) {
RefreshMessage("已经叫料请等待AGV送料......");
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送料......");
//进行叫料
if (ApplyDeliveryHandle(productPlanDto.MaterialId.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;
while (true)
{
//mes计划
MesProductPlan prodPlan = null;
//本地叫料任务
BaseRealTask localPlan = null;
try
{
var plc = base.GetPlcByKey("plc");
if (plc == null)
{
_logger.LogInformation("读取物料到位信号,PLC连接信息为空......");
Thread.Sleep(3000);
continue;
}
if (!plc.readBoolByAddress(GetPlcAddressByConfigKey("物料到位")))
{
_logger.LogInformation("等待物料到位信号触发......");
Thread.Sleep(3000);
continue;
}
// TODO 时间校验
bool timeCheck = JudgeProductTime();
if (!timeCheck)
{
RefreshMessage($"收到物料到位信号,但是距离上次投料不满20min,禁止投料");
Thread.Sleep(1000 * 15);
continue;
}
//TODO 调用调度接口判断AGV小车是否已经离开到位否则不允许投料
//TODO根据本地叫料计划查询对应的mes计划完成
prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
if (productPlanDto == null || prodPlan == null)
{
RefreshMessage($"收到物料到位信号,未获取到正在执行的生产计划,请开始执行计划");
Thread.Sleep(5000);
continue;
}
// 获取本地执行中的叫料任务,完成本地任务
localPlan = _baseRealTaskService.GetExeTask();
if (localPlan == null)
{
}
#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
}
catch (Exception e)
{
_logger.LogInformation($"投料校验异常:{e.Message}");
// TODO修改成传入本地计划查询出的mes计划
// result = MatPutInValidTriggerEvent(2, prodPlan, string.Empty, $"投料校验异常:{e.Message},是否继续投料");
}
if (prodPlan != null)
{
// TODO修改成传入本地计划查询出的mes计划停止计划导致的滞留物料归属哪个订单计划对应更新
PutInResutlHandle(prodPlan, result);
// 完成本地计划
if(localPlan!= null)
{
localPlan.Status = 2;
localPlan.UpdateTime = DateTime.Now;
_baseRealTaskService.Update(localPlan);
}
}
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>
/// 申请叫料TODO等待调度接口
/// </summary>
/// <param name="materialCode"></param>
/// <param name="result"></param>
/// <exception cref="InvalidOperationException"></exception>
public bool ApplyDeliveryHandle(string materialCode)
{
bool result = false;
try
{
var plc = base.GetPlcByKey("plc");
if (plc != null)
{
if (string.IsNullOrEmpty(materialCode))
{
throw new ArgumentException("申请叫料处理异常:物料参数为空");
}
// TODO -- 修改为调用wcs接口,参数为申请单id,同时本地生成一个叫料任务,任务状态为执行中
// plc.writeInt16ByAddress(base.GetPlcAddressByConfigKey("WCS叫料"), 1);
// plc.writeStringByAddress(base.GetPlcAddressByConfigKey("物料型号"), materialCode);
result = true;
}
}
catch (Exception e)
{
throw new InvalidOperationException($"申请叫料处理异常:{e.Message}");
}
return result;
}
/// <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("投料时间间隔")); // 单位分钟
if (debugConfig.LastTime != "")
{
DateTime lastTime = DateTime.Parse(debugConfig.LastTime);
TimeSpan timeDifference = DateTime.Now - lastTime;
if (timeDifference.TotalMinutes > IntervalMin)
{
return true;
}
else
{
return false;
}
}
else
{
return true;
}
}
catch (Exception ex)
{
_logger.LogError($"距离上次投料时间合格判断异常JudgeProductTime{ex.Message}");
return false;
}
}
}
}