using ExternalDataSync; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Linq; using System.Threading; using DC_A95; /// /// /// namespace MesWork { /// /// 处理MIS逻辑 /// public partial class MesWorkForm { /// /// PLC信号变化处理入口 /// 当Bool型信号值变化时由框架自动触发 /// /// 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: // 工件到位 → 启动/停止实时重量曲线采集 if (tagValue == "1") { WeighingPage?.AppendLog($"[{opName}] 工件到位信号触发"); CurveCollector_Start(opName); } else { CurveCollector_Stop(opName); } 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) { } } // ==================================================================== // 称重交互核心方法 // ==================================================================== /// /// 获取称重类型:地面/放油前/放油后 /// Ground模式:统一返回"地面" /// Hanging模式:OP10=放油前,OP20=放油后 /// private string GetWeighingType(string opName) { if (WeighingType == "Ground") return "地面"; // Hanging模式 return opName == "OP10" ? "放油前" : "放油后"; } /// /// 处理请求工作信号 (tagTypeCodeID=44) /// 流程:读取工件信息 → 查询参数 → 写过站记录 → 按类型处理称重记录 → 回写允许工作 /// 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 { // 1. 读取PLC工件信息 string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 总成编号 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)); // 托盘号 B_DB_Opera.SaveLog_Response($"【{opName}】读取工件:发动机={engineNo},机型={modelNo},工单={orderNo},托盘={palletNo}", AID); // 推送工件信息到称重UI int stIdx = WeighingPage?.GetStationIndex(opName) ?? 1; WeighingPage?.UpdateStationInfo(stIdx, engineNo, modelNo, orderNo); WeighingPage?.AppendLog($"[{opName}] 读取工件:{engineNo},机型={modelNo}"); // 2. 查询工件管理参数(加油量/抽油量/密度/残油量上下限) if (!B_DB_Opera.QueryWorkpieceByModel(modelNo, 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}】工件管理中未找到机型[{modelNo}]的参数,发动机={engineNo}", AID); WeighingPage?.AppendLog($"[{opName}] 机型[{modelNo}]未找到配置参数"); return; } // 3. 推送工件参数到UI WeighingPage?.UpdateOilInfo(stIdx, addOilQty, extractOilQty, density, 0, 0); // 4. 确定称重类型和工位名称 string weighType = GetWeighingType(opName); // 地面/放油前/放油后 string stationName = weighType; // 工位名称直接用称重类型名 // 4. 写过站记录(所有类型统一:INSERT一条,到达时间=NOW) B_DB_Opera.InsertStationRecord(engineNo, modelNo, orderNo, palletNo, opName, stationName, addOilQty, extractOilQty, density, Curr_UserName); // 5. 按类型处理称重记录 if (weighType == "放油后") { // 空中放油后:查找最新放油前记录(无论是否完成,支持多次测量) if (!B_DB_Opera.QueryWeighingBeforeOil(engineNo, out _, out decimal preWeight, out _, out _, out _)) { // 写报警代码=3(发动机号错误),阻断流程 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); } // 6. 回写允许工作 WritePLC_IF(66, opName, true); } catch (Exception err) { WritePLC_IF(112, opName, 1); // 报警代码=1,获取数据失败 B_DB_Opera.SaveLog_Response($"【{opName}】请求工作处理失败:{err.Message}", AID); } } /// /// 处理请求保存信号 (tagTypeCodeID=19) /// 流程:读取称重数据 → 更新过站记录 → 按类型处理称重记录 → 回写保存完成 /// 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 { // 1. 读取PLC数据 decimal weight = Convert.ToDecimal(ReadPLC(100140, opName)); // 重量 decimal waterContent = Convert.ToDecimal(ReadPLC(100180, opName)); // 水含量 string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); // 产品编号 long curveWeighingRecordId = 0; // 2. 更新过站记录(离开时间+称重重量) B_DB_Opera.UpdateStationComplete(engineNo, opName, weight); // 3. 确定类型并处理 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}"); } } // ==================================================================== // 扫码枪业务处理 // ==================================================================== /// /// 扫码枪扫码完成后的业务处理 /// 将码值写入PLC地址102500,触发完成信号100016=1,1秒后复位 /// /// 工位号(OP10/OP20),由COM口绑定决定 /// 扫到的条码值 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 地址 102500(PLC_PC_扫码枪值) WritePLC_IF(102500, opName, barcode); // 2. 写入扫码完成信号 100016 = 1(PC_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}"); } } private class CurveCollectorState { public string OpName; public long SegmentId; public Thread WorkerThread; public volatile bool IsRunning; public bool IsSegmentEnded; public bool IsWaitingSave; public int SeqNo; public readonly object BufferLock = new object(); public readonly List Buffer = new List(); } private static readonly ConcurrentDictionary _curveCollectors = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// 工件到位后启动实时重量曲线采集。 /// private void CurveCollector_Start(string opName) { try { if (_curveCollectors.TryGetValue(opName, out CurveCollectorState oldState) && !oldState.IsSegmentEnded) { if (oldState.IsRunning) return; CurveCollector_EndSegment(opName, 0); } string engineNo = PLC_R.GetString_CleanGarbled(ReadPLC(10, opName)); string modelNo = PLC_R.GetString_CleanGarbled(ReadPLC(11, opName)); long segmentId = B_DB_Opera.Curve_StartSegment(opName, engineNo, modelNo); if (segmentId <= 0) { WeighingPage?.AppendLog($"[{opName}] 曲线段创建失败,未启动采集"); return; } var state = new CurveCollectorState { OpName = opName, SegmentId = segmentId, IsRunning = true }; state.WorkerThread = new Thread(() => CurveCollector_Work(state)) { IsBackground = true, Name = $"CurveCollector_{opName}" }; _curveCollectors[opName] = state; state.WorkerThread.Start(); WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集启动,段ID={segmentId}"); } catch (Exception err) { WeighingPage?.AppendLog($"[{opName}] 曲线采集启动异常:{err.Message}"); } } /// /// 采集线程:200ms读取一次实时重量,满10条批量写库。 /// private void CurveCollector_Work(CurveCollectorState state) { while (state.IsRunning) { try { decimal weight = Convert.ToDecimal(ReadPLC(100220, state.OpName)); List writePoints = null; lock (state.BufferLock) { state.Buffer.Add(new CurveSamplePoint { SeqNo = ++state.SeqNo, SampleTime = DateTime.Now, Weight = weight }); if (state.Buffer.Count >= 10) { writePoints = new List(state.Buffer); state.Buffer.Clear(); } } if (writePoints != null) { B_DB_Opera.Curve_WritePoints(state.SegmentId, writePoints); } } catch (Exception err) { WeighingPage?.AppendLog($"[{state.OpName}] 曲线采样异常:{err.Message}"); } Thread.Sleep(200); } CurveCollector_Flush(state); } /// /// 停止采样线程并刷出剩余点,但暂不关闭曲线段。 /// private void CurveCollector_StopSampling(string opName, bool waitingSave = false) { if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state)) return; if (waitingSave) { state.IsWaitingSave = true; } state.IsRunning = false; if (state.WorkerThread != null && state.WorkerThread.IsAlive) { state.WorkerThread.Join(2000); } CurveCollector_Flush(state); } /// /// 工件离开时停止采集并关闭曲线段。 /// private void CurveCollector_Stop(string opName) { if (_curveCollectors.TryGetValue(opName, out CurveCollectorState state) && state.IsWaitingSave) { return; } CurveCollector_EndSegment(opName, 0); } /// /// 保存完成后关闭曲线段,并关联称重记录ID。 /// private void CurveCollector_EndSegment(string opName, long weighingRecordId) { if (!_curveCollectors.TryGetValue(opName, out CurveCollectorState state)) return; CurveCollector_StopSampling(opName); if (!state.IsSegmentEnded) { B_DB_Opera.Curve_EndSegment(state.SegmentId, weighingRecordId); state.IsSegmentEnded = true; WeighingPage?.AppendLog($"[{opName}] 实时重量曲线采集结束,段ID={state.SegmentId}"); } _curveCollectors.TryRemove(opName, out _); } /// /// 将采集缓冲区剩余点写入数据库。 /// private void CurveCollector_Flush(CurveCollectorState state) { List writePoints = null; lock (state.BufferLock) { if (state.Buffer.Count > 0) { writePoints = new List(state.Buffer); state.Buffer.Clear(); } } if (writePoints != null) { B_DB_Opera.Curve_WritePoints(state.SegmentId, writePoints); } } } }