|
|
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>
|
|
|
/// 设备生产管理业务类
|
|
|
/// </summary>
|
|
|
public class ProdMgmtBusiness: BaseBusiness
|
|
|
{
|
|
|
public readonly IMesProductPlanService _mesProductPlanService;
|
|
|
|
|
|
public readonly IBasePalletInfoService _basePalletInfoService;
|
|
|
|
|
|
private List<RealPalletTask> _palletTasks;
|
|
|
|
|
|
|
|
|
public ProdMgmtBusiness(ILogger<ProdMgmtBusiness> logger, AppConfig appConfig, List<PlcAbsractFactory> plcFactories, List<RfidAbsractFactory> rfidFactories, IMesProductPlanService mesProductPlanService, IBasePalletInfoService basePalletInfoService, List<RealPalletTask> palletTasks,IServiceProvider serviceProvider) : base(logger, appConfig, plcFactories, rfidFactories, serviceProvider)
|
|
|
{
|
|
|
_mesProductPlanService = mesProductPlanService;
|
|
|
_basePalletInfoService = basePalletInfoService;
|
|
|
_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连接信息为空");
|
|
|
}
|
|
|
|
|
|
MesProductPlan prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
|
|
|
if (prodPlan == null || productPlanDto == null)
|
|
|
{
|
|
|
throw new ArgumentException($"未获取到需要执行的生产计划");
|
|
|
}
|
|
|
|
|
|
if (plc.readInt16ByAddress(GetPlcAddressByConfigKey("设备叫料")) != 1)
|
|
|
{
|
|
|
_logger.LogInformation("等待设备叫料信号触发......");
|
|
|
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("根据计划自动申请叫料成功");
|
|
|
|
|
|
plc.writeInt16ByAddress(GetPlcAddressByConfigKey("设备叫料"), 0);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
RefreshMessage("根据计划自动申请叫料失败");
|
|
|
}
|
|
|
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
_logger.LogInformation($"计划执行逻辑处理异常:{e.Message}");
|
|
|
}
|
|
|
Thread.Sleep(5000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 投料校验:轮询物料到位信号
|
|
|
/// </summary>
|
|
|
/// <exception cref="ArgumentException"></exception>
|
|
|
public void MaterialPutInCheck()
|
|
|
{
|
|
|
bool result = false;
|
|
|
while (true)
|
|
|
{
|
|
|
//获取当前计划
|
|
|
var prodPlan = _mesProductPlanService.GetStartedProdPlan(out MesProductPlanDto productPlanDto);
|
|
|
|
|
|
try
|
|
|
{
|
|
|
var plc = base.GetPlcByKey("plc");
|
|
|
|
|
|
if(plc == null)
|
|
|
{
|
|
|
_logger.LogInformation("读取物料到位信号,PLC连接信息为空......");
|
|
|
Thread.Sleep(3000);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (plc.readInt16ByAddress(GetPlcAddressByConfigKey("物料到位")) != 1)
|
|
|
{
|
|
|
_logger.LogInformation("等待物料到位信号触发......");
|
|
|
Thread.Sleep(3000);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (productPlanDto == null || prodPlan == null)
|
|
|
{
|
|
|
RefreshMessage($"收到物料到位信号,未获取到正在执行的生产计划,请开始执行计划");
|
|
|
plc.writeInt16ByAddress(GetPlcAddressByConfigKey("物料到位"), 0);
|
|
|
Thread.Sleep(3000);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
plc.writeInt16ByAddress(GetPlcAddressByConfigKey("物料到位"), 0);
|
|
|
|
|
|
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 ? "成功" : "失败")}");
|
|
|
}
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
_logger.LogInformation($"投料校验异常:{e.Message}");
|
|
|
result = MatPutInValidTriggerEvent(2, prodPlan, string.Empty, $"投料校验异常:{e.Message},是否继续投料");
|
|
|
}
|
|
|
|
|
|
PutInResutlHandle(prodPlan, result);
|
|
|
|
|
|
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 materialCode)
|
|
|
{
|
|
|
bool result = false;
|
|
|
try
|
|
|
{
|
|
|
var plc = base.GetPlcByKey("plc");
|
|
|
|
|
|
if (plc != null)
|
|
|
{
|
|
|
if (string.IsNullOrEmpty(materialCode))
|
|
|
{
|
|
|
throw new ArgumentException("申请叫料处理异常:物料参数为空");
|
|
|
}
|
|
|
|
|
|
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}");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|