using Aitex.Common.Util; using Aitex.Core.RT.Event; using Aitex.Core.RT.Log; using Aitex.Core.RT.SCCore; using EPInterface.Datas; using System; using System.Collections.Generic; using System.Xml; namespace VirgoRT.Modules { /// /// Recipe head /// public class RecipeHead { public string RecipeVariation { get; set; } public string CreationTime { get; set; } public string LastRevisionTime { get; set; } public string CreatedBy { get; set; } public string LastModifiedBy { get; set; } public string PressureMode { get; set; } public string Description { get; set; } public string Barcode { get; set; } public string BasePressure { get; set; } public string PumpDownLimit { get; set; } //public string ElectrodeTemp { get; set; } //public string HeaterTemp { get; set; } public string PurgeActive { get; set; } public string MatchPositionC1 { get; set; } public string MatchPositionC2 { get; set; } public string SubstrateTemp { get; set; } public string PumpingPinState { get; set; } public string NotToPurgeOrVent { get; set; } //For keep vacuum after idle clean public string VentingPinState { get; set; } public string PinDownPressure { get; set; } } public class RecipeStep { public string StepName; public double StepTime; public bool IsJumpStep; public bool IsLoopStartStep; public bool IsLoopEndStep; public int LoopCount; public string EndBy; public string EndPointConfig; public bool FaultIfNoEPDTrigger; //public double EndByValue; public Dictionary RecipeCommands = new Dictionary(); public Dictionary ToleranceCommands = new Dictionary(); } public class Recipe { /// /// 当前Recipe Run 记录对应的Guid /// 每个Recipe Run 都对应有一个唯一的Guid /// public static Guid CurrentRecipeRunGuid; /// /// 解析工艺程序文件 /// /// 工艺程序名 /// xml格式的工艺数据内容 /// 返回解析的工艺数据变量 /// True:解析成功 | False:解析失败 public static bool Parse(string chamberId, string recipe, out RecipeHead recipeHead, out List recipeData) { recipeHead = new RecipeHead(); recipeData = new List(); try { //获取工艺程序文件中允许的命令字符串列表 //目的:如果工艺程序文件中含有规定之外的命令,则被禁止执行 HashSet recipeAllowedCommands = new HashSet(); XmlDocument rcpFormatDoc = new XmlDocument(); string recipeSchema = PathManager.GetCfgDir() + "RecipeFormat.xml"; rcpFormatDoc.Load(recipeSchema); XmlNodeList rcpItemNodeList = rcpFormatDoc.SelectNodes("/Aitex/TableRecipeFormat/Catalog/Group/Step"); foreach (XmlElement item in rcpItemNodeList) recipeAllowedCommands.Add(item.Attributes["ControlName"].Value); //获取工艺程序文件中所有步的内容 XmlDocument rcpDataDoc = new XmlDocument(); rcpDataDoc.LoadXml(recipe); recipeHead.PressureMode = rcpDataDoc.DocumentElement.HasAttribute("PressureMode") ? rcpDataDoc.DocumentElement.Attributes["PressureMode"].Value : ""; recipeHead.BasePressure = rcpDataDoc.DocumentElement.HasAttribute("BasePressure") ? rcpDataDoc.DocumentElement.Attributes["BasePressure"].Value : ""; recipeHead.PumpDownLimit = rcpDataDoc.DocumentElement.HasAttribute("PumpDownLimit") ? rcpDataDoc.DocumentElement.Attributes["PumpDownLimit"].Value : ""; recipeHead.PurgeActive = rcpDataDoc.DocumentElement.HasAttribute("PurgeActive") ? rcpDataDoc.DocumentElement.Attributes["PurgeActive"].Value : ""; recipeHead.MatchPositionC1 = rcpDataDoc.DocumentElement.HasAttribute("MatchPositionC1") ? rcpDataDoc.DocumentElement.Attributes["MatchPositionC1"].Value : ""; recipeHead.MatchPositionC2 = rcpDataDoc.DocumentElement.HasAttribute("MatchPositionC2") ? rcpDataDoc.DocumentElement.Attributes["MatchPositionC2"].Value : ""; recipeHead.Barcode = rcpDataDoc.DocumentElement.HasAttribute("Barcode") ? rcpDataDoc.DocumentElement.Attributes["Barcode"].Value : ""; recipeHead.SubstrateTemp = rcpDataDoc.DocumentElement.HasAttribute("SubstrateTemp") ? rcpDataDoc.DocumentElement.Attributes["SubstrateTemp"].Value : ""; recipeHead.PumpingPinState = rcpDataDoc.DocumentElement.HasAttribute("PumpingPinState") ? rcpDataDoc.DocumentElement.Attributes["PumpingPinState"].Value : "Down"; //For keep vacuum after idle clean recipeHead.NotToPurgeOrVent = rcpDataDoc.DocumentElement.HasAttribute("NotToPurgeOrVent") ? rcpDataDoc.DocumentElement.Attributes["NotToPurgeOrVent"].Value : ""; recipeHead.VentingPinState = rcpDataDoc.DocumentElement.HasAttribute("VentingPinState") ? rcpDataDoc.DocumentElement.Attributes["VentingPinState"].Value : "Down"; recipeHead.PinDownPressure = rcpDataDoc.DocumentElement.HasAttribute("PinDownPressure") ? rcpDataDoc.DocumentElement.Attributes["PinDownPressure"].Value : "1000"; XmlNodeList stepNodeList = rcpDataDoc.SelectNodes("/TableRecipeData/Step"); for (int i = 0; i < stepNodeList.Count; i++) { var recipeStep = new RecipeStep(); recipeData.Add(recipeStep); XmlElement stepNode = stepNodeList[i] as XmlElement; Dictionary dic = new Dictionary(); //遍历Step节点 foreach (XmlAttribute att in stepNode.Attributes) { if (recipeAllowedCommands.Contains(att.Name)) { dic.Add(att.Name, att.Value); } } //遍历Step子节点中所有的attribute属性节点 foreach (XmlElement subStepNode in stepNode.ChildNodes) { foreach (XmlAttribute att in subStepNode.Attributes) { if (recipeAllowedCommands.Contains(att.Name)) { dic.Add(att.Name, att.Value); } } //遍历Step子节点的子节点中所有的attribute属性节点 foreach (XmlElement subsubStepNode in subStepNode.ChildNodes) { foreach (XmlAttribute att in subsubStepNode.Attributes) { if (recipeAllowedCommands.Contains(att.Name)) { dic.Add(att.Name, att.Value); } } } } recipeStep.IsJumpStep = true;//!Convert.ToBoolean(dic["Ramp"]); recipeStep.StepName = dic["Name"]; recipeStep.StepTime = double.Parse(dic["Time"]); string loopStr = dic["Loop"]; recipeStep.IsLoopStartStep = System.Text.RegularExpressions.Regex.Match(loopStr, @"Loop\x20\d+\s*$").Success; recipeStep.IsLoopEndStep = System.Text.RegularExpressions.Regex.Match(loopStr, @"Loop End$").Success; if (recipeStep.IsLoopStartStep) recipeStep.LoopCount = Convert.ToInt32(loopStr.Replace("Loop", string.Empty)); else recipeStep.LoopCount = 0; //recipeStep.EndByValue = Convert.ToDouble(dic["EndValue"]); recipeStep.EndBy = dic["EndBy"]; if (recipeStep.EndBy == "EndByRfTime") { recipeStep.StepTime = double.Parse(dic["Rf.SetPowerOnTime"]); if (recipeStep.StepTime <= 0) { LOG.Error("recipe 没有定义RF Power on的时间"); return false; } } int rfPower = (int)Convert.ToDouble(dic["Rf.SetPower"]); dic.Add("Rf.SetPowerOnOff", rfPower > 0 ? "true" : "false"); if (SC.GetValue($"{chamberId}.BiasRf.EnableBiasRF")) { int rfPowerBias = (int)Convert.ToDouble(dic["BiasRf.SetPower"]); dic.Add("BiasRf.SetPowerOnOff", rfPowerBias > 0 ? "true" : "false"); // RS232 AdTec match if (SC.GetValue($"{chamberId}.match.CommunicationType") == (int)CommunicationType.RS232 && SC.GetValue($"{chamberId}.match.MFG") == (int)MatchMFG.AdTec) { //BiasRf1.SetMatchProcessMode dic["match.SetMatchProcessMode"] = dic["BiasRf.SetMatchProcessMode"]; dic.Remove("BiasRf.SetMatchProcessMode"); dic["match.SetMatchPositionC1"] = dic["BiasRf.SetMatchPositionC1"]; dic.Remove("BiasRf.SetMatchPositionC1"); dic["match.SetMatchPositionC2"] = dic["BiasRf.SetMatchPositionC2"]; dic.Remove("BiasRf.SetMatchPositionC2"); } } else { dic.Remove("BiasRf.SetPower"); dic.Remove("BiasRf.SetMatchProcessMode"); dic.Remove("BiasRf.SetMatchPositionC1"); dic.Remove("BiasRf.SetMatchPositionC2"); } bool epdInstalled = SC.ContainsItem("System.SetUp.EPDInstalled") && SC.GetValue($"System.SetUp.EPDInstalled"); if (!epdInstalled) { if (dic.ContainsKey("EPD.SetConfig")) dic.Remove("EPD.SetConfig"); } else { recipeStep.EndPointConfig = dic.ContainsKey("EPD.SetConfig") ? dic["EPD.SetConfig"] : null; if (string.IsNullOrEmpty(recipeStep.EndPointConfig)) { if (recipeStep.EndBy == "EndByEndPoint") { EV.PostWarningLog("System", "EndPoint is empty"); return false; } recipeStep.EndPointConfig = SC.GetStringValue("System.EndPoint.EndPointDefaultValue"); } else { if (!ParseEPD(recipeStep.EndPointConfig, out EPDConfig config)) { EV.PostWarningLog("System", "EndPoint config is not valid"); return false; } recipeStep.FaultIfNoEPDTrigger = config.FaultIfNoEPDTrigger; } } //tolerance List items = new List() { "MfcGas1", "MfcGas2", "MfcGas3", "MfcGas4", "MfcGas5", "PressureControl", "Rf", "BiasRf" }; Dictionary tolerance = new Dictionary(); foreach (var item in items) { if (item == "BiasRf" && !SC.GetValue($"{chamberId}.BiasRf.EnableBiasRF")) { dic.Remove(($"{item}.SoftTolerance")); dic.Remove(($"{item}.HardTolerance")); continue; } var time = SC.GetValue($"{chamberId}.RecipeToleranceIgnoreTime"); var warning = dic.ContainsKey($"{item}.SoftTolerance") ? dic[$"{item}.SoftTolerance"] : "0"; var alarm = dic.ContainsKey($"{item}.HardTolerance") ? dic[$"{item}.HardTolerance"] : "0"; tolerance[$"{item}.SetRecipeTolerance"] = new object[] { time, warning, alarm }; dic.Remove(($"{item}.SoftTolerance")); dic.Remove(($"{item}.HardTolerance")); } recipeStep.ToleranceCommands = tolerance; //dic.Remove("Ramp"); dic.Remove("StepNo"); dic.Remove("Name"); dic.Remove("Loop"); dic.Remove("Time"); dic.Remove("EndBy"); //dic.Remove("EndValue"); dic.Remove("Rf.SetPowerOnTime"); foreach (string key in dic.Keys) recipeStep.RecipeCommands.Add(key, dic[key]); } } catch (Exception ex) { LOG.Write(ex); return false; } return true; } private static bool ParseEPD(string config, out EPDConfig epdConfig) { epdConfig = new EPDConfig(); try { epdConfig.nParameterCount = 1; string[] items = config.Split(';'); foreach (var item in items) { if (string.IsNullOrEmpty(item)) continue; string[] pairs = item.Split('='); if (pairs.Length != 2) continue; switch (pairs[0]) { case "ExposureTime": epdConfig.Columns[0].nCCDExposureTime = int.Parse(pairs[1]); break; case "WaveLengthA": epdConfig.Columns[0].nWaveLength[0] = ushort.Parse(pairs[1]); break; case "BinningA": epdConfig.Columns[0].nBinning[0] = ushort.Parse(pairs[1]); break; case "WaveLengthB": epdConfig.Columns[0].nWaveLength[1] = ushort.Parse(pairs[1]); break; case "BinningB": epdConfig.Columns[0].nBinning[1] = ushort.Parse(pairs[1]); break; case "WaveLengthC": epdConfig.Columns[0].nWaveLength[2] = ushort.Parse(pairs[1]); break; case "BinningC": epdConfig.Columns[0].nBinning[2] = ushort.Parse(pairs[1]); break; case "WaveLengthD": epdConfig.Columns[0].nWaveLength[3] = ushort.Parse(pairs[1]); break; case "BinningD": epdConfig.Columns[0].nBinning[3] = ushort.Parse(pairs[1]); break; case "Fd": epdConfig.Columns[0].cFunc = pairs[1]; break; case "PrefilterTime": epdConfig.Columns[0].nPreFilterTime = int.Parse(pairs[1]); break; case "PostfilterTime": epdConfig.Columns[0].nPostFilterTime = int.Parse(pairs[1]); break; case "AlgorithmType": epdConfig.Columns[0].algorithmType = MapType(pairs[1]); break; case "Criteria": epdConfig.Columns[0].nCriteria = float.Parse(pairs[1]); break; case "DelayTime": epdConfig.Columns[0].nDelayTime = int.Parse(pairs[1]); break; case "ValidationTime": epdConfig.Columns[0].nValidationTime = int.Parse(pairs[1]); break; case "ValidationValue": epdConfig.Columns[0].nValidationValue = int.Parse(pairs[1]); break; case "TimeWindow": epdConfig.Columns[0].nTimeWindow = int.Parse(pairs[1]); break; case "MinimalTime": epdConfig.Columns[0].nMinimalTime = int.Parse(pairs[1]); break; case "PostponeTime": epdConfig.Columns[0].nPostponeTime = int.Parse(pairs[1]); break; case "Control": epdConfig.Columns[0].bControl = Convert.ToBoolean(pairs[1]); break; case "Normalization": epdConfig.Columns[0].bNormalization = Convert.ToBoolean(pairs[1]); break; case "EnablePostponePercent": epdConfig.Columns[0].bPostponePercent = Convert.ToBoolean(pairs[1]); break; case "EnableCriterialPercent": epdConfig.Columns[0].bCriteriaPercent = Convert.ToBoolean(pairs[1]); break; case "EnableEventTrigger": epdConfig.Columns[0].bEvtTrigger = Convert.ToBoolean(pairs[1]); break; case "IsFaultIfNoTrigger": epdConfig.FaultIfNoEPDTrigger = Convert.ToBoolean(pairs[1]); break; } } return true; } catch (Exception ex) { LOG.Write(ex); EV.PostMessage("System", EventEnum.DefaultAlarm, "EPD config input not valid, ", ex.Message); return false; } } private static AlgorithmType MapType(string type) { switch (type) { case "Unknown": return AlgorithmType.ALG_NONE; case "Above_ABS_Value": return AlgorithmType.ALG_RISE_VALUE; case "Below_ABS_Value": return AlgorithmType.ALG_FALL_VALUE; case "Drop_Percent": return AlgorithmType.ALG_FALL_PERCENT; case "Up_Percent": return AlgorithmType.ALG_RISE_PERCENT; case "Range_In": return AlgorithmType.ALG_RANGE_IN; case "Gradient": return AlgorithmType.ALG_GRADIENT; case "Peek": return AlgorithmType.ALG_PEAK; case "Valley": return AlgorithmType.ALG_VALLEY; case "Min_Drop_Percent": return AlgorithmType.ALG_MIN_FALL_PERCENT; case "Min_Up_Percent": return AlgorithmType.ALG_MIN_RISE_PERCENT; case "Max_Drop_Percent": return AlgorithmType.ALG_MAX_FALL_PERCENT; case "Max_Up_Percent": return AlgorithmType.ALG_MAX_RISE_PERCENT; case "Rise_Fall": return AlgorithmType.ALG_RISE_FALL; case "Fall_Rise": return AlgorithmType.ALG_FALL_RISE; } return AlgorithmType.ALG_NONE; } } }