Files
WC-CZGKJ/SCADA/Funtion/MIS_Funtion.cs
2026-05-12 11:42:06 +08:00

384 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ExternalDataSync;
using System;
using DC_A95;
/// <summary>
///
/// </summary>
namespace MesWork
{
/// <summary>
/// 处理MIS逻辑
/// </summary>
public partial class MesWorkForm
{
/// <summary>
/// PLC信号变化处理入口
/// 当Bool型信号值变化时由框架自动触发
/// </summary>
/// <param name="e"></param>
private void MIS_Funtion(object e)
{
try
{
var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e;
if (msgEvent.TagValue == null) return;
var tagID = msgEvent.TagID.ToString();
var opName = msgEvent.OpName.ToString();
var tagTypeCodeID = (int)msgEvent.TagTypeCodeID;
var tagTypeID = (EnumTagTypeID)msgEvent.TagTypeID;
var isFirstValue = msgEvent.IsFirstValue.ToString().ToLower();
var alarmLevel = msgEvent.ShaftID.ToString();
var alarmMsg = msgEvent.ItemName.ToString();
string tagValue;
switch (tagTypeID)
{
case EnumTagTypeID.BOOL:
tagValue = (Convert.ToInt32(msgEvent.TagValue)).ToString();
break;
default:
tagValue = msgEvent.TagValue.ToString();
break;
}
// ── 报警处理编码900~2000──
if (tagTypeCodeID >= 900 & tagTypeCodeID < 2000)
{
if (ValueT(isFirstValue))
{
//不处理第一次变化的值
return;
}
var opNameNew = opName.Replace("_Alarm", "");
if (tagValue == "1")
{
B_DB_Opera.Event_Alarm_Start(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
else
{
B_DB_Opera.Event_Alarm_End(opNameNew, tagTypeCodeID, alarmMsg, alarmLevel, tagID);
}
}
// ── 信号分发 ──
switch (tagTypeCodeID)
{
case 2: // 工件到位/离开
WritePLC_IF(112, opName, 0); // 报警代码清空
break;
case 14: // PLC心跳 → 回写PC心跳
WritePLC_IF(15, opName, tagValue);
break;
case 44: // 请求工作
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
WeighingPage?.AppendLog($"[{opName}] PLC请求工作开始处理...");
Weighing_HandleRequestWork(opName, tagID);
}
else
{
WritePLC_IF(66, opName, false); //允许工作
}
break;
case 19: // 请求保存
if (ValueT(isFirstValue)) return;
if (tagValue == "1")
{
CurveCollector_StopSampling(opName, true); // 先停止采样保存完成后再关联称重记录ID
WeighingPage?.AppendLog($"[{opName}] PLC请求保存数据...");
Weighing_HandleRequestSave(opName, tagID);
}
else
{
WritePLC_IF(20, opName, false); //保存完成
WritePLC_IF(21, opName, false); //合格标志
}
break;
case 203: // 设备状态
B_DB_Opera.Event_DeviceStatus_Change(opName, tagValue);
break;
default:
break;
}
}
catch (Exception err)
{
string errMsg = $"PLC信号处理异常{err.Message}";
B_DB_Opera.SaveLog_Request("", Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, errMsg, out long AID);
B_DB_Opera.SaveLog_Response(errMsg, AID);
WeighingPage?.AppendLog(errMsg);
}
}
// ====================================================================
// 称重交互核心方法
// ====================================================================
/// <summary>
/// 获取称重类型:地面/放油前/放油后
/// Ground模式统一返回"地面"
/// Hanging模式OP10=放油前OP20=放油后
/// </summary>
private string GetWeighingType(string opName)
{
if (WeighingType == "Ground")
return "地面";
// Hanging模式
return opName == "OP10" ? "放油前" : "放油后";
}
/// <summary>
/// 码块数据转换:将产品编号统一转大写,并拆分出订货号与产品编号
/// 支持格式:
/// 0/DHN2.5Q0219-36/D426E016767
/// DHH02K0014-400/B726E00584555
/// </summary>
private bool TryConvertCodeBlockData(string codeBlock, out string orderNo, out string productNo)
{
orderNo = "";
productNo = "";
if (string.IsNullOrWhiteSpace(codeBlock))
{
return false;
}
string normalizedCode = codeBlock.Trim().ToUpperInvariant();
string[] parts = normalizedCode.Split('/');
if (parts.Length >= 2)
{
orderNo = parts[parts.Length - 2].Trim();
productNo = parts[parts.Length - 1].Trim();
}
else
{
productNo = normalizedCode;
}
return !string.IsNullOrWhiteSpace(productNo);
}
/// <summary>
/// 处理请求工作信号 (tagTypeCodeID=44)
/// 流程:读取工件信息 → 查询参数 → 写过站记录 → 按类型处理称重记录 → 回写允许工作
/// </summary>
private void Weighing_HandleRequestWork(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【44-请求工作】", out long AID);
try
{
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 原始代码串 0/dhn2.5q0219-36/d426e016767
string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName)); // 机型号
string orderNo = PLC_R.GetString_CleanGarbled(ReadPLC(39, opName)); // 工单号
string palletNo = PLC_R.GetString_CleanGarbled(ReadPLC(80, opName)); // 托盘号
// 解析代码串格式: 0/DHN2.5Q0219-36/D426E016767 或 DHH02K0014-400/B726E00584555
if (TryConvertCodeBlockData(engineNo, out string parsedOrderNo, out string parsedProductNo))
{
if (!string.IsNullOrWhiteSpace(parsedOrderNo))
{
orderNo = parsedOrderNo;
}
engineNo = parsedProductNo;
int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1;
if (!B_DB_Opera.QueryWorkpieceByOrderNo(orderNo, out string matchedModelNo, out decimal addOilQty, out decimal extractOilQty, out decimal density,
out decimal residualOilUpperLimit, out decimal residualOilLowerLimit))
{
WritePLC_IF(112, opName, 3); // 报警代码=3机型错误
B_DB_Opera.SaveLog_Response($"【{opName}】工件管理中未找到订货号[{orderNo}]的匹配规则,发动机={engineNo}", AID);
WeighingPage?.AppendLog($"[{opName}] 订货号[{orderNo}]未匹配到工件参数");
return;
}
modelNo = matchedModelNo;
B_DB_Opera.SaveLog_Response($"【{opName}】读取工件:发动机={engineNo},机型={modelNo},订货号={orderNo},托盘={palletNo}", AID);
WeighingPage?.UpdateStationInfo(stIdx, engineNo, modelNo, orderNo);
WeighingPage?.AppendLog($"[{opName}] 读取工件:{engineNo},机型={modelNo}");
WeighingPage?.UpdateOilInfo(stIdx, addOilQty, extractOilQty, density, 0, 0);
string weighType = GetWeighingType(opName); // 地面/放油前/放油后
string stationName = weighType; // 工位名称直接用称重类型名
B_DB_Opera.InsertStationRecord(engineNo, modelNo, orderNo, palletNo,
opName, stationName, addOilQty, extractOilQty, density, Curr_UserName); // 请求工作成功先写到站记录
if (weighType == "放油后")
{
// 空中放油后:查找最新放油前记录(无论是否完成,支持多次测量)
if (!B_DB_Opera.QueryWeighingBeforeOil(engineNo, out _, out decimal preWeight, out _, out _, out _))
{
WritePLC_IF(112, opName, 3);
B_DB_Opera.SaveLog_Response($"【{opName}】未找到发动机[{engineNo}]的空中称重记录", AID);
WeighingPage?.AppendLog($"[{opName}] 未找到[{engineNo}]放油前记录,进站失败");
return;
}
else
{
B_DB_Opera.SaveLog_Response($"【{opName}】空中放油后准备完成,发动机={engineNo},机型={modelNo},放油前重量={preWeight}kg", AID);
}
}
else
{
// 地面 或 空中放油前:每次都新建称重记录(支持同一产品多次测量取最新)
string recordType = weighType == "地面" ? "地面" : "空中";
B_DB_Opera.InsertWeighingRecord(opName, recordType, engineNo, modelNo, orderNo, palletNo,
0, 0, addOilQty, extractOilQty, density, residualOilUpperLimit, residualOilLowerLimit, 0, 0, 0, 0,
opName, Curr_UserName, isComplete: 0); // 放油前/地面先占位,保存时回填重量结果
B_DB_Opera.SaveLog_Response($"【{opName}】{weighType}准备完成,发动机={engineNo},机型={modelNo},残油量范围={residualOilLowerLimit:F4}-{residualOilUpperLimit:F4}L", AID);
}
WritePLC_IF(66, opName, true);
CurveCollector_Start(opName, engineNo, modelNo); // 允许工作后开始记录本次曲线
}
else
{
WritePLC_IF(112, opName, 1); // 报警代码=1获取数据失败
B_DB_Opera.SaveLog_Response($"【{opName}】码块数据转换失败:原始码值=[{engineNo}]", AID);
WeighingPage?.AppendLog($"[{opName}] 码块数据转换失败:{engineNo}");
}
}
catch (Exception err)
{
WritePLC_IF(112, opName, 1); // 报警代码=1获取数据失败
B_DB_Opera.SaveLog_Response($"【{opName}】请求工作处理失败:{err.Message}", AID);
}
}
/// <summary>
/// 处理请求保存信号 (tagTypeCodeID=19)
/// 流程:读取称重数据 → 更新过站记录 → 按类型处理称重记录 → 回写保存完成
/// </summary>
private void Weighing_HandleRequestSave(string opName, string tagID)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"触发信号【19-请求保存】", out long AID);
try
{
decimal plcWeight = Convert.ToDecimal(ReadPLC(100140, opName)); // PLC锁定重量曲线无效时兜底使用
decimal weight = plcWeight; // 默认使用PLC锁定重量曲线算法成功后覆盖
decimal waterContent = Convert.ToDecimal(ReadPLC(100180, opName)); // 水含量
string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 产品编号
if (TryConvertCodeBlockData(engineNo, out _, out string parsedProductNo))
{
engineNo = parsedProductNo;
}
if (CurveCollector_TryGetStableWeight(opName, out decimal curveWeight, out int curvePointCount, out string curveMsg))
{
weight = curveWeight; // 优先使用曲线稳定段计算重量
B_DB_Opera.SaveLog_Response($"【{opName}】曲线重量={curveWeight:F3}kgPLC重量={plcWeight:F3}kg差值={(curveWeight - plcWeight):F3}kg点数={curvePointCount}", AID);
}
else
{
B_DB_Opera.SaveLog_Response($"【{opName}】曲线重量不可用使用PLC锁定重量={plcWeight:F3}kg原因={curveMsg}", AID);
}
long curveWeighingRecordId = 0;
B_DB_Opera.UpdateStationComplete(engineNo, opName, weight); // 过站记录保存最终采用重量
string weighType = GetWeighingType(opName);
decimal oilReleaseQty = 0;
decimal residualOilQty = 0;
int finalQualityFlag = 1;
string qualityMsg = "";
if (weighType == "地面")
{
// 地面:查最新记录 → 放油量=重量/密度 → 标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out decimal addOil, out decimal extractOil, out decimal dens);
curveWeighingRecordId = recordId; // 曲线记录最终关联这条称重记录
oilReleaseQty = dens > 0 ? weight / dens : 0; // 地面:油桶重量/密度=放油量
residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
B_DB_Opera.UpdateWeighingComplete(recordId, "地面", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】地面保存完成:发动机={engineNo},重量={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 保存完成:{engineNo},重量={weight:F2}kg放油量={oilReleaseQty:F4}L");
}
else if (weighType == "放油前")
{
// 空中放油前:查最新记录 → 仅写入进站重量,不标记完成
B_DB_Opera.QueryLatestRecord(opName, engineNo,
out long recordId, out _, out _, out _, out _);
curveWeighingRecordId = recordId; // 放油前曲线也关联同一条称重记录
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油前", opName,
weight, 0, 0, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油前保存完成:发动机={engineNo},进站重量={weight}kg合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.AppendLog($"[{opName}] 放油前保存:{engineNo},重量={weight:F2}kg");
}
else // 放油后
{
// 空中放油后:查最新放油前记录(无论是否完成),计算放油量更新到该记录
if (B_DB_Opera.QueryWeighingBeforeOil(engineNo, out long recordId, out decimal preWeight,
out decimal addOil, out decimal extractOil, out decimal dens))
{
curveWeighingRecordId = recordId; // 放油后回填放油前创建的称重记录
oilReleaseQty = dens > 0 ? (preWeight - weight) / dens : 0; // 空中:前后重量差/密度=放油量
residualOilQty = addOil - extractOil - oilReleaseQty; // 残油=加油-抽油-放油
B_DB_Opera.UpdateWeighingComplete(recordId, "空中放油后", opName,
weight, oilReleaseQty, residualOilQty, waterContent, out finalQualityFlag, out qualityMsg);
B_DB_Opera.SaveLog_Response(
$"【{opName}】空中放油后保存完成:发动机={engineNo},放油前={preWeight}kg放油后={weight}kg放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L合格标志={finalQualityFlag}{qualityMsg}", AID);
WeighingPage?.UpdateOilResult(WeighingPage?.GetStationIndex(opName) ?? 1, oilReleaseQty, residualOilQty);
WeighingPage?.AppendLog($"[{opName}] 放油后保存:{engineNo},放油量={oilReleaseQty:F4}L残油量={residualOilQty:F4}L");
}
else
{
// 找不到放油前记录可能直接进OP20创建一条放油后记录并标记完成
curveWeighingRecordId = B_DB_Opera.InsertWeighingRecord(opName, "空中放油后", engineNo, "", "", "",
0, weight, 0, 0, 0, 0, 0, 0, 0, 2, waterContent,
opName, Curr_UserName, isComplete: 1); // 异常兜底记录,质量直接判不合格
finalQualityFlag = 2;
qualityMsg = "未找到放油前记录";
B_DB_Opera.SaveLog_Response(
$"【{opName}】放油后保存(无放油前记录):发动机={engineNo},放油后重量={weight}kg", AID);
WeighingPage?.AppendLog($"[{opName}] ⚠️ 放油后保存:{engineNo}(无放油前记录,已新建)");
}
}
CurveCollector_EndSegment(opName, curveWeighingRecordId); // 保存点位、最终重量和取点区间
// TODO: MES接口数据上传接口对接后实现
if (finalQualityFlag == 2 && !string.IsNullOrWhiteSpace(qualityMsg))
{
WeighingPage?.AppendLog($"[{opName}] 判定不合格:{qualityMsg}");
}
// 4. 先回写上位机判定的合格标志,再回写保存完成
WritePLC_IF(21, opName, finalQualityFlag); // 合格标志
WritePLC_IF(20, opName, true); // 保存完成
}
catch (Exception err)
{
CurveCollector_EndSegment(opName, 0);
WritePLC_IF(112, opName, 2); // 报警代码=2保存失败
B_DB_Opera.SaveLog_Response($"【{opName}】保存处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 保存异常:{err.Message}");
}
}
// ====================================================================
// 扫码枪业务处理
// ====================================================================
/// <summary>
/// 扫码枪扫码完成后的业务处理
/// 将码值写入PLC地址102500触发完成信号100016=11秒后复位
/// </summary>
/// <param name="opName">工位号OP10/OP20由COM口绑定决定</param>
/// <param name="barcode">扫到的条码值</param>
public void Barcode_HandleScan(string opName, string barcode)
{
B_DB_Opera.SaveLog_Request(opName, Log_Type.ZK_MesHandler, "MIS_Function",
Log_FromAndTo.ZK, Log_FromAndTo.ZK, $"扫码枪触发【{opName}】码值={barcode}", out long AID);
try
{
// 1. 写入码值到 PLC 地址 102500PLC_PC_扫码枪值
WritePLC_IF(102500, opName, barcode);
// 2. 写入扫码完成信号 100016 = 1PC_PLC_扫码枪扫码完成
WritePLC_IF(100016, opName, true);
// 3. 日志
B_DB_Opera.SaveLog_Response($"【{opName}】扫码完成:码值={barcode}", AID);
WeighingPage?.AppendLog($"[{opName}] 触发扫码 码值:{barcode}");
// 4. 1秒后复位 100016 = 0
System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ =>
{
try { WritePLC_IF(100016, opName, false); }
catch { }
});
}
catch (Exception err)
{
B_DB_Opera.SaveLog_Response($"【{opName}】扫码处理失败:{err.Message}", AID);
WeighingPage?.AppendLog($"[{opName}] ❌ 扫码处理异常:{err.Message}");
}
}
}
}