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;
}
}
}