diff --git a/Common/09MesDataFunction/MesDataFunction/ToPrinter/PP.cs b/Common/09MesDataFunction/MesDataFunction/ToPrinter/PP.cs index 53ab265..e755d2e 100644 --- a/Common/09MesDataFunction/MesDataFunction/ToPrinter/PP.cs +++ b/Common/09MesDataFunction/MesDataFunction/ToPrinter/PP.cs @@ -13,6 +13,7 @@ namespace MesDataFunction public static string PrintURL { get; set; } public static int IsDemoMesServer { get; set; } public static Dictionary SpecialOpList { get; set; } + public static Dictionary>> SpecialOpRuleConfig { get; set; } } } diff --git a/MisDataFunction/Function1062/DataAccess/DataAccess.cs b/MisDataFunction/Function1062/DataAccess/DataAccess.cs new file mode 100644 index 0000000..c7cb3db --- /dev/null +++ b/MisDataFunction/Function1062/DataAccess/DataAccess.cs @@ -0,0 +1,360 @@ +using DataLinkMesWork1; +using MesServerWork; +using MisDataFunDll; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + private const string ManualStaticOutOpName = "OP431"; + private const string ManualStaticPadTopic = "OP431PAD"; + private const decimal DefaultManualStaticMinutes = 30m; + private const string ManualStaticLocation = "777777"; + private const string ManualStaticOperator = "PAD"; + private static readonly object ManualStaticMinutesLocker = new object(); + private static readonly Dictionary ManualStaticMinutesByEngine = new Dictionary(); + + private class ManualStaticOutProduct + { + public string EngineID { get; set; } + public string OrderForm { get; set; } + public string EngineType { get; set; } + public int EngineTypeID { get; set; } + } + + private class ManualStaticOutData + { + public string SourceOpName { get; set; } + public DateTime StartTime { get; set; } + public DateTime FinishTime { get; set; } + public decimal Minutes { get; set; } + public int QualityMask { get; set; } + } + + private bool TryGetManualStaticOutProduct(string engineID, out ManualStaticOutProduct product, out string message) + { + product = null; + message = ""; + + if (string.IsNullOrWhiteSpace(engineID)) + { + message = "产品码为空"; + return false; + } + + MisDataFun.Moby_Select_PDC(engineID, out string orderForm, out string engineType, out int engineTypeID); + if (engineTypeID <= 0 || string.IsNullOrWhiteSpace(engineType)) + { + message = "未查询到产品执行信息:" + engineID; + return false; + } + + product = new ManualStaticOutProduct + { + EngineID = engineID.Trim(), + OrderForm = orderForm, + EngineType = engineType, + EngineTypeID = engineTypeID + }; + return true; + } + + private bool TryGetManualStaticStartTime(string engineID, out string sourceOpName, out DateTime startTime, out string message) + { + sourceOpName = ""; + startTime = DateTime.MinValue; + message = ""; + + try + { + // 取 OP400/OP410/OP420 最后一条有效离开记录作为常温静置开始时间。 + string procedureName = "OP431手动静置出库_前序离开时间_查询"; + SqlParameter[] sqlParameters = new SqlParameter[1]; + sqlParameters[0] = new SqlParameter("@发动机号", engineID); + + DataAccess.ExecuteStoredProcedure(procedureName, ref sqlParameters, out DataTable dt, out string errorMessage); + if (!string.IsNullOrWhiteSpace(errorMessage)) + { + message = "查询前序出站时间失败:" + errorMessage; + return false; + } + + if (dt == null || dt.Rows.Count == 0) + { + message = "未查询到 OP400/OP410/OP420 前序出站记录"; + return false; + } + + sourceOpName = Convert.ToString(dt.Rows[0]["工位号"]); + startTime = Convert.ToDateTime(dt.Rows[0]["开始静置时间"]); + return true; + } + catch (Exception err) + { + message = "查询前序出站时间失败:" + err.Message; + return false; + } + } + + private bool TryBuildManualStaticOutData(string engineID, int qualityMask, out ManualStaticOutData staticOutData, out string message) + { + staticOutData = null; + message = ""; + + if (!TryGetManualStaticStartTime(engineID, out string sourceOpName, out DateTime startTime, out message)) + { + return false; + } + + DateTime finishTime = DateTime.Now; + decimal minutes = Math.Round(Convert.ToDecimal((finishTime - startTime).TotalMinutes), 2); + staticOutData = new ManualStaticOutData + { + SourceOpName = sourceOpName, + StartTime = startTime, + FinishTime = finishTime, + Minutes = minutes, + QualityMask = NormalizeManualStaticQualityMask(qualityMask) + }; + return true; + } + + private int NormalizeManualStaticQualityMask(int qualityMask) + { + return qualityMask == 1 ? 1 : 2; + } + + private bool IsManualStaticTimeEnough(ManualStaticOutData staticOutData, decimal minStaticMinutes, out string message) + { + message = ""; + if (staticOutData.Minutes < minStaticMinutes) + { + message = "静置时间不足 " + minStaticMinutes.ToString("0.##") + " 分钟,当前已静置 " + staticOutData.Minutes + " 分钟"; + return false; + } + return true; + } + + private bool WriteManualStaticOutQualityData(string opName, string engineID, ManualStaticOutData data, out string message) + { + message = ""; + int idTagCf = 0; + if (!TryGetManualStaticMinutesForProduct(engineID, out decimal rememberedMinStaticMinutes)) + { + message = "未找到PAD确认的最小静置标准,请在PAD重新确认上线后再保存:" + engineID; + return false; + } + + string minStaticMinutes = rememberedMinStaticMinutes.ToString("0.##"); + + // OP431 的静置数据由 Function 直接落库,不再通过 OnlineCheckData 做 HTTP 自调用。 + SqlParameter[] indexParams = new SqlParameter[6]; + indexParams[0] = new SqlParameter("@EngineID", engineID); + indexParams[1] = new SqlParameter("@工位号", opName); + indexParams[2] = new SqlParameter("@操作者工号", ManualStaticOperator); + indexParams[3] = new SqlParameter("@扫码时间", data.FinishTime); + indexParams[4] = new SqlParameter("@合格标志", data.QualityMask); + indexParams[5] = new SqlParameter("@Id_tagCF", idTagCf); + indexParams[5].Direction = ParameterDirection.Output; + + DataAccess.ExecuteStoredProcedure("测量数据合格索引_增加_测试设备", ref indexParams, out string errorMessage); + if (!string.IsNullOrWhiteSpace(errorMessage)) + { + message = "写入静置质量索引失败:" + errorMessage; + return false; + } + + idTagCf = Convert.ToInt32(indexParams[5].Value.ToString()); + if (idTagCf <= 0) + { + message = "写入静置质量索引失败:未生成 Id_tagCF"; + return false; + } + + object[,] detailRows = new object[,] + { + { "OP431_M0001_001", "开始静置时间", "min", "1", "60", minStaticMinutes, data.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), data.QualityMask }, + { "OP431_M0001_002", "静置完成时间", "min", "1", "60", minStaticMinutes, data.FinishTime.ToString("yyyy-MM-dd HH:mm:ss"), data.QualityMask }, + { "OP431_M0001_003", "静置时间差", "min", "1", "60", minStaticMinutes, data.Minutes.ToString("0.##"), data.QualityMask }, + { "OP431_M0001_004", "静置库位号", "静置库位", "-1", "-1", "-1", ManualStaticLocation, data.QualityMask } + }; + + for (int i = 0; i < detailRows.GetLength(0); i++) + { + SqlParameter[] detailParams = new SqlParameter[] + { + new SqlParameter("@Id_tagCF", idTagCf), + new SqlParameter("@EngineID", engineID), + new SqlParameter("@TagID", detailRows[i, 0]), + new SqlParameter("@TagValue", detailRows[i, 6]), + new SqlParameter("@IsGoodBad", detailRows[i, 7]), + new SqlParameter("@工位号", opName), + new SqlParameter("@理论值", detailRows[i, 3]), + new SqlParameter("@上限值", detailRows[i, 4]), + new SqlParameter("@下限值", detailRows[i, 5]), + new SqlParameter("@测量项目", detailRows[i, 1]), + new SqlParameter("@测量单位", detailRows[i, 2]) + }; + + DataAccess.ExecuteStoredProcedure("测量数据合格_增加_测试设备", ref detailParams, out errorMessage); + if (!string.IsNullOrWhiteSpace(errorMessage)) + { + message = "写入静置质量明细失败:" + errorMessage; + return false; + } + } + + return true; + } + + private bool CheckManualStaticPadPrecondition(string opName, out string message) + { + message = ""; + + MIS_BASE.Read_WorkStart(opName, out bool workStart); + if (!workStart) + { + message = "当前无托盘到位信号,不允许扫码确认"; + return false; + } + + MIS_BASE.Read_PartLoad(opName, out bool partLoad); + if (!partLoad) + { + message = "PLC尚未申请上线,不允许扫码确认"; + return false; + } + + return true; + } + + private bool ConfirmManualStaticOutProduct(string opName, string engineID, decimal minStaticMinutes, out string message) + { + message = ""; + + // PAD 确认上线时必须重新校验托盘和 PLC 申请状态,不能复用查询时的旧结果。 + if (!CheckManualStaticPadPrecondition(opName, out message)) + { + return false; + } + + if (!TryGetManualStaticOutProduct(engineID, out ManualStaticOutProduct product, out message)) + { + return false; + } + + if (!TryBuildManualStaticOutData(product.EngineID, 1, out ManualStaticOutData staticOutData, out message)) + { + return false; + } + + // 二次校验静置时间,最小分钟数来自 PAD 当前配置,默认 30 分钟。 + if (!IsManualStaticTimeEnough(staticOutData, minStaticMinutes, out message)) + { + return false; + } + + MIS_BASE.Read_PalletCode(opName, out int palletCode); + MisDataFun.OpName_MIS_MOBY_Insert(opName, product.EngineTypeID, product.EngineType, product.EngineID, palletCode, product.OrderForm, 0, 0, ""); + + // 写入 MES->PLC 产品区,PLC 收到 EngineTypeGetOver_MES 后再回传 44 做进站校验。 + MIS_BASE.Write_EngineTypeID_MES(opName, product.EngineTypeID); + MIS_BASE.Write_EngineID_MES(opName, product.EngineID); + MIS_BASE.Write_EngineType_MES(opName, product.EngineType); + MIS_BASE.Write_OrderForm_MES(opName, product.OrderForm); + MIS_BASE.Write_EngineTypeGetOver_MES(opName, true); + RememberManualStaticMinutesForProduct(product.EngineID, minStaticMinutes); + + message = "PAD确认上线成功:" + product.EngineID; + return true; + } + + private bool QueryManualStaticOutProduct(string opName, string engineID, decimal minStaticMinutes, out string resultMessage) + { + resultMessage = ""; + + // 扫码查询只返回产品与静置信息,不写 PLC、不调用进站,也不依赖托盘到位/PLC申请上线实时消息。 + // PAD 页面可能不是一直打开,托盘到位等实时 MQTT 可能丢失,最终状态由确认上线时后端再次校验。 + if (!TryGetManualStaticOutProduct(engineID, out ManualStaticOutProduct product, out resultMessage)) + { + return false; + } + + if (!TryBuildManualStaticOutData(product.EngineID, 1, out ManualStaticOutData staticOutData, out resultMessage)) + { + return false; + } + + bool timeEnough = IsManualStaticTimeEnough(staticOutData, minStaticMinutes, out string timeMessage); + MIS_BASE.Read_PartLoad(opName, out bool partLoad); + + bool canConfirm = timeEnough && partLoad; + string confirmMessage = canConfirm + ? "允许确认上线" + : !timeEnough + ? timeMessage + : "PLC尚未申请上线,不允许确认上线"; + + resultMessage = string.Join("^", new string[] + { + product.EngineID, + product.OrderForm, + product.EngineType, + product.EngineTypeID.ToString(), + staticOutData.SourceOpName, + staticOutData.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + staticOutData.Minutes.ToString("0.##"), + minStaticMinutes.ToString("0.##"), + partLoad ? "1" : "0", + canConfirm ? "1" : "0", + confirmMessage + }); + return true; + } + + private void SendManualStaticOutResult(string opName, string command, bool success, string message) + { + string value = (success ? "1" : "0") + "^" + message; + SendManualStaticPadMqttMessage(command, opName, value); + + // 同步给工位主题,保留现有页面状态监听能力;PAD 以 OP431PAD 主题为准。 + SendMessageFun.SendInfomation(command, value, opName); + if (!success) + { + SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】" + message); + } + } + + private void SendManualStaticPadMqttMessage(string command, string opName, string value) + { + SendMessage.AsyncSendNotOpMessage(ManualStaticPadTopic, "MES|" + command + "|" + opName + "|" + value); + } + + private void RememberManualStaticMinutesForProduct(string engineID, decimal minStaticMinutes) + { + lock (ManualStaticMinutesLocker) + { + ManualStaticMinutesByEngine[engineID] = minStaticMinutes; + } + } + + private bool TryGetManualStaticMinutesForProduct(string engineID, out decimal minStaticMinutes) + { + lock (ManualStaticMinutesLocker) + { + if (ManualStaticMinutesByEngine.ContainsKey(engineID)) + { + minStaticMinutes = ManualStaticMinutesByEngine[engineID]; + return true; + } + } + + minStaticMinutes = DefaultManualStaticMinutes; + return false; + } + } +} diff --git a/MisDataFunction/Function1062/DataAccess/vssver2.scc b/MisDataFunction/Function1062/DataAccess/vssver2.scc new file mode 100644 index 0000000..1083b3a Binary files /dev/null and b/MisDataFunction/Function1062/DataAccess/vssver2.scc differ diff --git a/MisDataFunction/Function1062/Function08.csproj.vspscc b/MisDataFunction/Function1062/Function08.csproj.vspscc new file mode 100644 index 0000000..82c6db2 --- /dev/null +++ b/MisDataFunction/Function1062/Function08.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "relative:MesServer.Function\\Function08" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/MisDataFunction/Function1062/Function1062.csproj b/MisDataFunction/Function1062/Function1062.csproj new file mode 100644 index 0000000..8371f7b --- /dev/null +++ b/MisDataFunction/Function1062/Function1062.csproj @@ -0,0 +1,109 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {B5A0BAA3-C1A7-4AAB-9486-2E650ADACA7E} + Library + Properties + Function1062 + Function1062 + v4.8 + 512 + + + + + + + + + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AnyCPU + false + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\..\dll_common\BasicData.dll + + + False + ..\..\dll_common\MesWork.DeviceDriver.dll + + + False + ..\..\dll_common\Newtonsoft.Json.dll + + + + + + ..\..\DLL\System.Net.Http.dll + + + + + + + + + + + + + + + + + + + + + + + + + {90e76fb4-646d-45e2-95db-0b8ab4e465d6} + 01-MesDataFunction + + + {1dc48c83-1842-4de8-acaf-26e1e7f7c58f} + 00_MyLog4Net + + + + + + + + + diff --git a/MisDataFunction/Function1062/MesServerFunction/Function1062.cs b/MisDataFunction/Function1062/MesServerFunction/Function1062.cs new file mode 100644 index 0000000..462201d --- /dev/null +++ b/MisDataFunction/Function1062/MesServerFunction/Function1062.cs @@ -0,0 +1,128 @@ +using MesServerWork; +using MisDataFunDll; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace MesServerFunctions +{ + public partial class FunctionMES + { + public void FunWork(string recData) + { + List dataArray = MisDataFun.GetCmdStringList(recData); + string source = dataArray[0].ToString(); + string command = dataArray[1].ToString(); + string opName = dataArray[2].ToString(); + string value = dataArray[3].ToString(); + + var e = new DeviceDriver_BasicData.CustomeEvetnArgs(); + switch (source) + { + case "WEB": + e.OpName = opName; + e.TagValue = command + "|" + value; + fun_WebSubmit(e); + break; + case "PLC": + e = Newtonsoft.Json.JsonConvert.DeserializeObject(value); + e.TagValue = NormalizeTagValue(e.TagValue); + RoutePlcSignal(e, opName); + break; + } + } + + private string NormalizeTagValue(object tagValue) + { + string value = Convert.ToString(tagValue); + switch (value.ToLower()) + { + case "false": + return "0"; + case "true": + return "1"; + default: + return value; + } + } + + private void RoutePlcSignal(DeviceDriver_BasicData.CustomeEvetnArgs e, string opName) + { + switch (e.TagTypeCodeID.ToString()) + { + // PLC 工件到位 + case "2": + fun_WorkStart(e); + break; + // PLC 允许保存质量数据 + case "19": + fun_MesRead(e); + break; + // PLC 申请上线 + case "43": + PartLoad(e); + break; + // PLC 传递机型 + case "44": + fun_EngineTypeGetOverPLC(e); + break; + // PLC/MES 系统健康心跳 + case "14": + fun_HeartBeatMIS(e); + break; + // MES 设备报警复位 + case "61": + MIS_BASE.Write_MesErrorType(opName, 0); + break; + } + } + + /// + /// 传递机型 + /// + public void fun_EngineTypeGetOverPLC(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(EngineTypeGetOver_PLC, e); + } + + /// + /// 保存完成 19 + /// + public void fun_MesRead(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(MesRead, e); + } + + /// + /// 工件到位离开,清空 MES 信号 + /// + public void fun_WorkStart(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(WorkStart, e); + } + + /// + /// 处理 WEB/PAD 页面传递过来的数据 + /// + public void fun_WebSubmit(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(WebSubmit, e); + } + + /// + /// MES 系统健康心跳监测 + /// + public void fun_HeartBeatMIS(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(HeartBeatMES, e); + } + + /// + /// PLC 系统健康心跳监测 + /// + public void fun_HeartBeatPLC(DeviceDriver_BasicData.CustomeEvetnArgs e) + { + ThreadPool.QueueUserWorkItem(HeartBeatPLC, e); + } + } +} diff --git a/MisDataFunction/Function1062/MesServerFunction/vssver2.scc b/MisDataFunction/Function1062/MesServerFunction/vssver2.scc new file mode 100644 index 0000000..775bb6c Binary files /dev/null and b/MisDataFunction/Function1062/MesServerFunction/vssver2.scc differ diff --git a/MisDataFunction/Function1062/OpcServer/EngineGetOver.cs b/MisDataFunction/Function1062/OpcServer/EngineGetOver.cs new file mode 100644 index 0000000..273079f --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/EngineGetOver.cs @@ -0,0 +1,97 @@ +using MesServerWork; +using MisDataFunDll; +using MisDataSaveDate; +using System; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + object lockBarcodeEngineTypeGetOver = new object(); + + /// + /// OP431 PLC 回传机型后,校验 PAD 确认信息并调用工厂 MES 进站。 + /// + private void EngineTypeGetOver_PLC(object e) + { + lock (lockBarcodeEngineTypeGetOver) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string opName = Convert.ToString(msgEvent.OpName); + int tagValue = Convert.ToInt32(msgEvent.TagValue); + + if (tagValue != 1) + { + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC传递机型取消!"); + MIS_BASE.Write_FixAllow(opName, false); + return; + } + + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC传递机型!"); + MIS_BASE.Write_TagValueByTagTypeID(34, opName, false); + + MIS_BASE.GetEngineTypeFromPLC(opName, out int engineTypeID, out string engineType, out string engineID, out int palletCode, out string orderForm); + MIS_BASE.GetEngineTypeFromPLC_MES(opName, out int engineTypeIDMes, out string engineTypeMes, out string engineIDMes, out string orderFormMes); + + if (string.IsNullOrWhiteSpace(engineID) || engineTypeID <= 0) + { + WriteManualStaticEngineError(opName, "PLC产品码或机型为空,产品码:" + engineID + ",机型代码:" + engineTypeID); + return; + } + + if (string.IsNullOrWhiteSpace(engineIDMes) || engineTypeIDMes <= 0) + { + WriteManualStaticEngineError(opName, "未查询到PAD确认上线信息,请先在PAD确认上线"); + return; + } + + if (!string.Equals(engineID.Trim(), engineIDMes.Trim(), StringComparison.OrdinalIgnoreCase)) + { + WriteManualStaticEngineError(opName, "PLC产品码与PAD确认产品码不一致,PLC:" + engineID + ",PAD:" + engineIDMes); + return; + } + + if (engineTypeID != engineTypeIDMes) + { + WriteManualStaticEngineError(opName, "PLC机型与PAD确认机型不一致,PLC:" + engineTypeID + ",PAD:" + engineTypeIDMes); + return; + } + + if (!TryGetManualStaticOutProduct(engineID, out ManualStaticOutProduct product, out string productMessage)) + { + WriteManualStaticEngineError(opName, productMessage); + return; + } + + CallWebService.Call_miFindCustomAndSfcData(opName, engineID, out string code, out string message); + if (code != "0") + { + WriteManualStaticEngineError(opName, "进站失败:" + engineID + ",code:" + code + ",message:" + message); + return; + } + + MisDataFun.LogRecording_OP(opName, "INFO", "MOM", "进站成功:" + engineID); + MisDataFun.OpName_MIS_MOBY_Insert(opName, product.EngineTypeID, product.EngineType, product.EngineID, palletCode, product.OrderForm, 0, 0, ""); + + MIS_BASE.Write_FixAllow(opName, true); + SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】进站成功,允许PLC工作:" + engineID); + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.EngineTypeGetOver", err.Message); + MisDataFun.LogRecording_OP(ManualStaticOutOpName, "ERROR", "EngineTypeGetOver", err.Message); + } + } + } + + private void WriteManualStaticEngineError(string opName, string message) + { + MIS_BASE.Write_FixAllow(opName, false); + MIS_BASE.Write_TagValueByTagTypeID(34, opName, true); + SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】" + message); + MisDataFun.LogRecording_OP(opName, "ERROR", "EngineTypeGetOver", message); + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/HeartBeat.cs b/MisDataFunction/Function1062/OpcServer/HeartBeat.cs new file mode 100644 index 0000000..64e4e91 --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/HeartBeat.cs @@ -0,0 +1,56 @@ +using MesServerWork; +using System; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + /// + /// MES与PLC心跳监听 + /// + /// + /// + private void HeartBeatMES(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string OpName = Convert.ToString(msgEvent.OpName); + int tagValue = Convert.ToInt32(msgEvent.TagValue); + + //返写在MES心跳中 + MIS_BASE.Write_TagValueByTagTypeID(15, OpName, tagValue); + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.HeartBeatMES", err.Message); + } + } + + /// + /// MES与PLC心跳监听 + /// + /// + /// + private void HeartBeatPLC(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string OpName = Convert.ToString(msgEvent.OpName); + int tagValue = Convert.ToInt32(msgEvent.TagValue); + + int HeartBeatPLC; + //读取PLC心跳数据 + HeartBeatPLC = Convert.ToInt32(MIS_BASE.Read_TagValueByTagTypeID(14, OpName)); + //通知WEB修改状态 + string message = "MES|HeartBeatPLC|" + OpName + "|" + HeartBeatPLC.ToString(); + SendMessage.AsyncSendMessage(message); + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.HeartBeatPLC", err.Message); + } + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/MesRead.cs b/MisDataFunction/Function1062/OpcServer/MesRead.cs new file mode 100644 index 0000000..28ec96b --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/MesRead.cs @@ -0,0 +1,109 @@ +using MesServerWork; +using MisDataFunDll; +using MisDataSaveDate; +using System; +using System.Data; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + /// + /// OP431 保存:按 PLC 允许保存时间生成静置质量数据,正常过站后带质量数据出站。 + /// + private void MesRead(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string opName = Convert.ToString(msgEvent.OpName); + int tagValue = Convert.ToInt32(msgEvent.TagValue); + + if (tagValue != 1) + { + MIS_BASE.Write_MesReadOver(opName, false); + MIS_BASE.Write_MesWorkFinish(opName, false); + MIS_BASE.Write_OpNameIsOk(opName, -99); + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC允许保存数据取消!"); + return; + } + + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC允许保存OP431静置数据!"); + + MIS_BASE.GetEngineTypeFromPLC(opName, out int engineTypeID, out string engineType, out string engineID, out int palletCode, out string orderForm); + if (string.IsNullOrWhiteSpace(engineID)) + { + WriteManualStaticMesReadError(opName, "PLC产品码为空,无法保存静置数据"); + return; + } + + MisDataFun.Moby_Select_PDC(engineID, out orderForm, out engineType, out engineTypeID); + if (engineTypeID <= 0) + { + WriteManualStaticMesReadError(opName, "未查询到产品执行信息:" + engineID); + return; + } + + MIS_BASE.Read_QualityMask(opName, out int qualityMask); + qualityMask = NormalizeManualStaticQualityMask(qualityMask); + + if (!TryBuildManualStaticOutData(engineID, qualityMask, out ManualStaticOutData staticOutData, out string message)) + { + WriteManualStaticMesReadError(opName, message); + return; + } + + if (!WriteManualStaticOutQualityData(opName, engineID, staticOutData, out message)) + { + WriteManualStaticMesReadError(opName, message); + return; + } + + MisDataFun.WorkStartInsert(opName, qualityMask); + MisDataFun.GetPartQualityGroupParams(opName, engineID, out DataTable dtQuality); + if (dtQuality == null || dtQuality.Rows.Count == 0) + { + WriteManualStaticMesReadError(opName, "未查询到OP431静置质量数据组,禁止静默出站:" + engineID); + return; + } + + CallWebService.Call_dataCollectForSfcEx(opName, engineID, dtQuality, qualityMask, out string code, out string momMessage); + if (code == "0") + { + MIS_BASE.Write_OpNameIsOk(opName, qualityMask); + } + else if (code == "13921") + { + qualityMask = 2; + MIS_BASE.Write_OpNameIsOk(opName, qualityMask); + MisDataFun.LogRecording_OP(opName, "INFO", "MOM", engineID + " 工厂MES质量判定不合格,按不合格完成OP431过站"); + } + else + { + WriteManualStaticMesReadError(opName, "出站失败:" + engineID + ",错误代码:" + code + ",消息:" + momMessage); + return; + } + + MIS_BASE.Write_MesWorkFinish(opName, true); + MIS_BASE.Write_MesReadOver(opName, true); + + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "MES保存OP431静置数据完成!"); + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "MES工作完成!"); + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.MesRead", err.Message); + MisDataFun.LogRecording_OP(ManualStaticOutOpName, "ERROR", "MesRead", err.Message); + } + } + + private void WriteManualStaticMesReadError(string opName, string message) + { + MIS_BASE.Write_MesReadOver(opName, false); + MIS_BASE.Write_MesWorkFinish(opName, false); + MIS_BASE.Write_TagValueByTagTypeID(34, opName, true); + SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】" + message); + MisDataFun.LogRecording_OP(opName, "ERROR", "MesRead", message); + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/PartLoad.cs b/MisDataFunction/Function1062/OpcServer/PartLoad.cs new file mode 100644 index 0000000..195b3e6 --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/PartLoad.cs @@ -0,0 +1,44 @@ +using MesServerWork; +using MisDataFunDll; +using System; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + /// + /// OP431 申请上线:只记录 PLC 申请状态,等待 PAD 扫描已有产品。 + /// + private void PartLoad(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string opName = Convert.ToString(msgEvent.OpName); + int tagValue = Convert.ToInt32(msgEvent.TagValue); + + if (tagValue == 1) + { + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC申请上线,等待PAD扫描已有产品!"); + MIS_BASE.Write_TagValueByTagTypeID(34, opName, false); + SendMessage.AsyncSendMessage_Notice(opName, "【" + opName + "】PLC已申请上线,请PAD扫描产品码"); + // PAD 页面可能未打开,申请上线状态由 PadScanQuery 当前读取后返回。 + SendMessageFun.SendInfomation("PartLoad", "1", opName); + return; + } + + MisDataFun.LogRecording_OP(opName, "INFO", "MIS", "PLC申请上线取消!"); + MIS_BASE.Write_EngineTypeGetOver_MES(opName, false); + MIS_BASE.Write_FixAllow(opName, false); + MIS_BASE.Write_TagValueByTagTypeID(34, opName, false); + // 不向 OP431PAD 发送实时取消消息,避免 PAD 依赖可能丢失的 MQTT 状态。 + SendMessageFun.SendInfomation("PartLoad", "0", opName); + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.PartLoad", err.Message); + MisDataFun.LogRecording_OP(ManualStaticOutOpName, "ERROR", "PartLoad", err.Message); + } + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/WebSubmit.cs b/MisDataFunction/Function1062/OpcServer/WebSubmit.cs new file mode 100644 index 0000000..292786e --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/WebSubmit.cs @@ -0,0 +1,183 @@ +using MesServerWork; +using MisDataFunDll; +using MisDataSaveDate; +using System; +using System.Collections; + +namespace MesServerFunctions +{ + partial class FunctionMES + { + /// + /// 处理 PAD/MQTT 发送到 Function1062 的 WEB 命令。 + /// 消息外层格式由通讯层统一为:WEB|命令|工位号|参数。 + /// 命令名不增加 OP431 前缀,工位归属统一通过 opName 判断,避免同一业务出现多套命令名。 + /// + private void WebSubmit(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string opName = Convert.ToString(msgEvent.OpName); + string content = Convert.ToString(msgEvent.TagValue); + + // Function 入口已将 MQTT 的 Command 和 Value 合并为“命令|参数”。 + // 参数支持“产品码”或“产品码^最小静置分钟数”,订单、机型等业务信息必须由后端重新查询。 + string[] dataArray = content.Split('|'); + if (dataArray.Length != 2) + { + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", "PAD命令格式错误,内容:" + content); + return; + } + + string command = dataArray[0].Trim(); + string value = dataArray[1].Trim(); + + switch (command) + { + // PAD 扫码查询:只查询产品、前序离开时间和静置分钟数,不写 PLC,也不调用工厂 MES 进站。 + case "PadScanQuery": + { + if (!TryGetManualStaticPadRequest(opName, "PadScanQuery", value, out string engineID, out decimal minStaticMinutes)) + { + return; + } + + bool success = QueryManualStaticOutProduct(opName, engineID, minStaticMinutes, out string message); + SendManualStaticOutResult(opName, "PadScanQuery", success, message); + MisDataFun.LogRecording_OP(opName, success ? "INFO" : "WARNING", "PadScanQuery", message); + break; + } + // PAD 确认上线:重新校验托盘到位、PLC申请上线和前端配置的最小静置时间,通过后写 PLC 产品区。 + case "PadConfirmOnline": + { + if (!TryGetManualStaticPadRequest(opName, "PadConfirmOnline", value, out string engineID, out decimal minStaticMinutes)) + { + return; + } + + bool success = ConfirmManualStaticOutProduct(opName, engineID, minStaticMinutes, out string message); + SendManualStaticOutResult(opName, "PadConfirmOnline", success, message); + MisDataFun.LogRecording_OP(opName, success ? "INFO" : "ERROR", "PadConfirmOnline", message); + break; + } + // 页面刷新或 PAD 重新订阅 MQTT 后,用当前 PLC/MES 状态恢复界面显示。 + case "IniSingalToWeb": + InitializeSingalToWeb(opName); + break; + default: + MisDataFun.LogRecording_OP(opName, "WARNING", "WebSubmit", "不支持的PAD命令:" + command); + break; + } + } + catch (Exception err) + { + MyLog4Net.MyLogHelper.Error("Function1062.WebSubmit", err.Message); + MisDataFun.LogRecording_OP(ManualStaticOutOpName, "ERROR", "WebSubmit", err.Message); + } + } + + private bool TryGetManualStaticPadRequest(string opName, string command, string value, out string engineID, out decimal minStaticMinutes) + { + engineID = ""; + minStaticMinutes = DefaultManualStaticMinutes; + string requestValue = Convert.ToString(value).Trim(); + + // PAD 上传产品码和可选最小静置分钟数。订单、机型、静置开始时间等信息全部从 MES 数据库重新取。 + if (string.IsNullOrWhiteSpace(requestValue)) + { + string message = "PAD命令参数为空,格式:" + command + "|产品码 或 " + command + "|产品码^最小静置分钟数"; + SendManualStaticOutResult(opName, command, false, message); + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", message); + return false; + } + + if (requestValue.Contains("&")) + { + string message = "PAD命令参数格式错误,格式:" + command + "|产品码 或 " + command + "|产品码^最小静置分钟数,收到:" + value; + SendManualStaticOutResult(opName, command, false, message); + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", message); + return false; + } + + string[] requestItems = requestValue.Split('^'); + if (requestItems.Length > 2) + { + string message = "PAD命令参数格式错误,格式:" + command + "|产品码 或 " + command + "|产品码^最小静置分钟数,收到:" + value; + SendManualStaticOutResult(opName, command, false, message); + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", message); + return false; + } + + engineID = requestItems[0].Trim(); + if (string.IsNullOrWhiteSpace(engineID)) + { + string message = "PAD命令产品码为空,格式:" + command + "|产品码 或 " + command + "|产品码^最小静置分钟数"; + SendManualStaticOutResult(opName, command, false, message); + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", message); + return false; + } + + if (requestItems.Length == 2) + { + string minStaticMinutesText = requestItems[1].Trim(); + if (!decimal.TryParse(minStaticMinutesText, out minStaticMinutes) || minStaticMinutes < 0) + { + string message = "PAD命令最小静置分钟数无效,收到:" + minStaticMinutesText; + SendManualStaticOutResult(opName, command, false, message); + MisDataFun.LogRecording_OP(opName, "ERROR", "WEB", message); + return false; + } + } + + return true; + } + + /// + /// 页面重新登录或 PAD 重新订阅时恢复关键状态。 + /// 这里发送的是通用命令名,工位由 opName 区分。 + /// + private void InitializeSingalToWeb(string opName) + { + MIS_BASE.GetEngineTypeFromDB(opName, out int engineTypeID, out string engineType, out string engineID, out int partPalletCode); + + MIS_BASE.Read_WorkStart(opName, out bool workStart); + MIS_BASE.Read_PartLoad(opName, out bool partLoad); + MIS_BASE.Read_EngineGetOver_PLC(opName, out bool engineGetOver); + MIS_BASE.Read_FixAllow(opName, out bool fixAllow); + MIS_BASE.Read_QualityData_Read_CF(opName, out bool mesRead); + MIS_BASE.Read_MesReadOver(opName, out bool readOver); + MIS_BASE.Read_MaterialVerifyOver(opName, out bool workFinish); + MIS_BASE.Read_QualityMask(opName, out int qualityMask); + MIS_BASE.Read_FalseGo(opName, out bool falseGo); + + if (!string.IsNullOrWhiteSpace(engineID)) + { + SendMessageFun.SendMessage("5123", "1111", opName); + } + + // 以下状态沿用通用 MES 页面命令,PAD 或页面通过 opName 判断属于哪个工位。 + SendMessageFun.SendInfomation("WorkStart", Convert.ToString(Convert.ToInt32(workStart)), opName); + SendMessageFun.SendInfomation("PartLoad", Convert.ToString(Convert.ToInt32(partLoad)), opName); + SendMessageFun.SendInfomation("EngineTypeGetOverPLC", Convert.ToString(Convert.ToInt32(engineGetOver)), opName); + SendMessageFun.SendInfomation("FixAllow", Convert.ToString(Convert.ToInt32(fixAllow)), opName); + SendMessageFun.SendInfomation("MesRead", Convert.ToString(Convert.ToInt32(mesRead)), opName); + SendMessageFun.SendInfomation("MesReadOver", Convert.ToString(Convert.ToInt32(readOver)), opName); + SendMessageFun.SendInfomation("MaterialVerifyOver", Convert.ToString(Convert.ToInt32(workFinish)), opName); + SendMessageFun.SendInfomation("QualityMask", Convert.ToString(qualityMask), opName); + SendMessageFun.SendInfomation("FalseGO", Convert.ToString(Convert.ToInt32(falseGo)), opName); + SendMessageFun.SendInfomation("CurrentProduct", engineID + "^" + engineTypeID + "^" + engineType + "^" + partPalletCode, opName); + + InitAlarm(opName); + } + + private void InitAlarm(string opName) + { + FuntionMesRead.Alarm_InitAlarm_Send(opName, out Hashtable ht); + foreach (DictionaryEntry kv in ht) + { + FuntionMesRead.Send_AlarmMsg(kv.Key.ToString(), kv.Value.ToString()); + } + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/WorkStart.cs b/MisDataFunction/Function1062/OpcServer/WorkStart.cs new file mode 100644 index 0000000..80f6e99 --- /dev/null +++ b/MisDataFunction/Function1062/OpcServer/WorkStart.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using MesServerWork; +using MisDataFunDll; +//using SystemFramework; + + +namespace MesServerFunctions +{ + partial class FunctionMES + { + /// + /// 工件到位与离开时,清空MES自身信号 + /// + /// + /// + private void WorkStart(object e) + { + try + { + var msgEvent = (DeviceDriver_BasicData.CustomeEvetnArgs)e; + string OpName = Convert.ToString(msgEvent.OpName); + string tagValue = msgEvent.TagValue.ToString(); + + if (tagValue == "1") + { + string msg = "工件到位!"; + MisDataFun.LogRecording_OP(OpName, "INFO", "MIS", msg); + MisDataFun.MOBY_TrayStart_Update(OpName); // 记录托盘到达时间 + MIS_BASE.Write_TagValueByTagTypeID(34, OpName, false); // MES报警点 + } + else + { + string msg = "工件离开!"; + MisDataFun.LogRecording_OP(OpName, "INFO", "MIS", msg); + // 清除工位数据 + MIS_BASE.Write_EngineTypeGetOver_MES(OpName, false); // 传递机型(MES) + MIS_BASE.Write_MesWorkFinish(OpName, false); // 工作完成(MES) + MIS_BASE.Write_FalseGO(OpName, false); // 不合格放行(MES) + MIS_BASE.Write_ConfirmOkGo(OpName, false); // 合格放行(MES) + MIS_BASE.Write_FixAllow(OpName, false); // 允许PLC工作(MES) + MIS_BASE.Write_MesReadOver(OpName, false); // MES保存完数据(MES) + MIS_BASE.Write_EngineTypeID_MES(OpName, 0); // 机型标志(MES) + MIS_BASE.Write_EngineID_MES(OpName, ""); // 工件编号(MES) + MIS_BASE.Write_EngineType_MES(OpName, ""); // 产品型号(MES) + MIS_BASE.Write_OrderForm_MES(OpName, ""); // 订单号(MES) + MIS_BASE.Write_MesErrorType(OpName, 0); // MES报警点 + } + } + catch (Exception err) + { + MisDataFun.LogRecording_OP("Function1062", "ERROR", "WorkStart", err.Message.ToString()); + } + } + } +} diff --git a/MisDataFunction/Function1062/OpcServer/vssver2.scc b/MisDataFunction/Function1062/OpcServer/vssver2.scc new file mode 100644 index 0000000..e8dac03 Binary files /dev/null and b/MisDataFunction/Function1062/OpcServer/vssver2.scc differ diff --git a/MisDataFunction/Function1062/Properties/AssemblyInfo.cs b/MisDataFunction/Function1062/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..07e9e76 --- /dev/null +++ b/MisDataFunction/Function1062/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("Function08")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("大连迈思信息技术有限公司")] +[assembly: AssemblyProduct("Function08")] +[assembly: AssemblyCopyright("Copyright © 大连迈思信息技术有限公司 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("47710f2c-e141-4252-8d13-701bf4c1c027")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 内部版本号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MisDataFunction/Function1062/Properties/vssver2.scc b/MisDataFunction/Function1062/Properties/vssver2.scc new file mode 100644 index 0000000..87eebff Binary files /dev/null and b/MisDataFunction/Function1062/Properties/vssver2.scc differ diff --git a/MisDataFunction/Function1062/app.config b/MisDataFunction/Function1062/app.config new file mode 100644 index 0000000..95269f5 --- /dev/null +++ b/MisDataFunction/Function1062/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/MisDataFunction/Function1062/mssccprj.scc b/MisDataFunction/Function1062/mssccprj.scc new file mode 100644 index 0000000..f184c61 --- /dev/null +++ b/MisDataFunction/Function1062/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[Function08.csproj] +SCC_Aux_Path = "\\192.168.0.100\vss\MES4.0" +SCC_Project_Name = "$/MisData/MisData/MisData/MesServer.Function/Function08", KRHAAAAA diff --git a/MisDataFunction/Function1062/vssver2.scc b/MisDataFunction/Function1062/vssver2.scc new file mode 100644 index 0000000..93af295 Binary files /dev/null and b/MisDataFunction/Function1062/vssver2.scc differ