浏览代码

兼容去胶机使用的Endpoint.

sangwq 1 年之前
父节点
当前提交
68c484e4c3

+ 8 - 7
Venus/Venus_RT/Devices/EPD/EPDClient.cs

@@ -11,11 +11,12 @@ using Aitex.Core.RT.Device;
 using Aitex.Core.RT.Log;
 using Aitex.Core.Util;
 using EPD.Data;
+using Venus_RT.Devices;
 
 namespace Venus_RT.Devices.EPD
 {
     
-    class EPDClient : IDevice
+    class EPDClient : JetEPDBase
     {
         public enum EDPStatus
         {
@@ -40,8 +41,8 @@ namespace Venus_RT.Devices.EPD
 
         public List<string> CFGFileList { get; private set; }
 
-        public string Module { get; set; }
-        public string Name { get; set; }
+        public new string Module { get; set; }
+        public new string Name { get; set; }
         public string EPDVersion { get; private set; }
         public string EPDState { get; private set; }
         public string SensorStatus { get; private set; }
@@ -71,7 +72,7 @@ namespace Venus_RT.Devices.EPD
             _socketClient.OnError += OnError;
         }
 
-        public bool Initialize()
+        public override bool Initialize()
         { 
             _socketClient.Connect(_ip, _port);
             _socketClient.ConnectEPD();
@@ -81,15 +82,15 @@ namespace Venus_RT.Devices.EPD
             return true;
         }
 
-        public void Monitor()
+        public override void Monitor()
         {
             HeartBeat();
         }
 
-        public void Reset()
+        public override void Reset()
         { }
 
-        public void Terminate()
+        public override void Terminate()
         {
             Status = EDPStatus.Idle;
             _socketClient.DisconnectEPD();

+ 458 - 0
Venus/Venus_RT/Devices/EPDs/EPDDevice.cs

@@ -0,0 +1,458 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security;
+using System.ServiceModel;
+using System.Text;
+using Aitex.Core.RT.DataCenter;
+using Aitex.Core.RT.Device;
+using Aitex.Core.RT.Event;
+using Aitex.Core.RT.Log;
+using Aitex.Core.RT.OperationCenter;
+using Aitex.Core.RT.SCCore;
+using Aitex.Core.Util;
+using Aitex.Core.WCF;
+using EPInterface;
+using EPInterface.Data;
+using EPInterface.Datas;
+using Venus_RT.Devices;
+
+namespace Aitex.RT.Device.Custom
+{
+    public class EPDDevice : JetEPDBase
+    {
+        private PeriodicJob _monitorThead;
+
+        private R_TRIG _triggerConnected = new R_TRIG();
+        private R_TRIG _triggerNotConnected = new R_TRIG();
+
+        private int _channel;
+
+        private string _channelStatus;
+
+
+        private object _lockerTrigger = new object();
+
+        private bool _isEnd = false;
+
+        public bool IsEnd
+        {
+            get
+            {
+                return _isEnd;
+            }
+        }
+
+        public bool IsConnected
+        {
+            get
+            {
+                return _triggerConnected.M;
+            }
+        }
+
+        private int _connectionCounter = 0;
+
+        public EPDDevice(string module, string name)// :
+     //       base(module, name, name, name)
+        {
+        }
+
+        public override bool Initialize()
+        {
+            _channel = SC.GetValue<int>($"{Module}.{Name}.ChannelNumber");
+
+            DATA.Subscribe($"{Module}.{Name}.IsConnected", ()=> IsConnected);
+            DATA.Subscribe($"{Module}.{Name}.CurrentChannel", () => _channel);
+            DATA.Subscribe($"{Module}.{Name}.ChannelStatus", () => _channelStatus);
+
+            OP.Subscribe($"{Module}.{Name}.SetConfig", (out string reason, int time, object[] args) => {
+ 
+                if (!IsConnected)
+                {
+                    LOG.Write(eEvent.WARN_ENDPOINT, Module, $"{Module} {Name} not connected, can not set config");
+                    reason = $"{Module} {Name} not connected, can not set config";
+                    return false;
+                }
+
+                _isEnd = false;
+
+                StepStart(args[1].ToString(), Convert.ToInt32(args[0]));
+
+                reason = "";
+                return true;
+            });
+
+            _monitorThead = new PeriodicJob(100, OnTimer, "EPDMonitor", true);
+
+            EPDCallbackClient.Instance.Notify += Instance_Notify;
+            EPDCallbackClient.Instance.Trigger += Instance_Trigger;
+
+            return true;
+        }
+
+        public void RecipeStart(string recipeName)
+        {
+            if (!IsConnected)
+            {
+                LOG.Write( eEvent.ERR_ENDPOINT, Module, "EPD not connected, call recipe start ignored");
+                return;
+            }
+            EV.PostInfoLog(Module, $"{Module} {Name}, Notify EPD recipe {recipeName} start");
+
+            EPDClient.Instance.Service.RecipeStart(_channel, recipeName);
+        }
+
+        public void RecipeStop()
+        {
+            if (!IsConnected)
+            {
+                LOG.Write(eEvent.ERR_ENDPOINT, Module, "EPD not connected, call recipe start ignored");
+                return;
+            }
+
+            EV.PostInfoLog(Module, $"{Module} {Name}, Notify EPD recipe stopped");
+
+            EPDClient.Instance.Service.RecipeStop(_channel);
+        }
+
+        /*
+         * ExposureTime=222;WaveLengthA=2;BinningA=3;WaveLengthB=4;BinningB=6;WaveLengthC=5;BinningC=8;
+         * WaveLengthD=7;BinningD=9;Fd=1;PrefilterTime=2;PostfilterTime=3;AlgorithmType=Valley;
+         * Criteria=4;DelayTime=5;ValidationTime=6;ValidationValue=7;
+         * TimeWindow=8;MinimalTime=9;PostponeTime=10;Control=11;Normalization=12;
+         * EnablePostponePercent=True;EnableCriterialPercent=True;
+         * TriggerMode=System.Windows.Controls.ComboBoxItem: Event;IsFaultIfNoTrigger=True;
+         */
+        public void StepStart(string config, int index)
+        {
+            if (!IsConnected)
+            {
+                LOG.Write(eEvent.ERR_ENDPOINT, Module, "EPD not connected, call step start ignored");
+                return;
+            }
+
+            EV.PostInfoLog(Module, $"{Module} {Name}, Notify EPD recipe step {index+1} start");
+            EV.PostInfoLog(Module, $"{Module} {Name}, EPD config {config}");
+
+            try
+            {
+                EPDConfig epd = new EPDConfig();
+
+                epd.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":
+                            epd.Columns[0].nCCDExposureTime = int.Parse(pairs[1]);
+                            break;
+                        case "WaveLengthA":
+                            epd.Columns[0].nWaveLength[0] = ushort.Parse(pairs[1]);
+                            break;
+                        case "BinningA":
+                            epd.Columns[0].nBinning[0] = ushort.Parse(pairs[1]);
+                            break;
+                        case "WaveLengthB":
+                            epd.Columns[0].nWaveLength[1] = ushort.Parse(pairs[1]);
+                            break;
+                        case "BinningB":
+                            epd.Columns[0].nBinning[1] = ushort.Parse(pairs[1]);
+                            break;
+                        case "WaveLengthC":
+                            epd.Columns[0].nWaveLength[2] = ushort.Parse(pairs[1]);
+                            break;
+                        case "BinningC":
+                            epd.Columns[0].nBinning[2] = ushort.Parse(pairs[1]);
+                            break;
+                        case "WaveLengthD":
+                            epd.Columns[0].nWaveLength[3] = ushort.Parse(pairs[1]);
+                            break;
+                        case "BinningD":
+                            epd.Columns[0].nBinning[3] = ushort.Parse(pairs[1]);
+                            break;
+
+                        case "Fd":
+                            epd.Columns[0].cFunc = pairs[1];
+                            break;
+                        case "PrefilterTime":
+                            epd.Columns[0].nPreFilterTime = int.Parse(pairs[1]);
+                            break;
+                        case "PostfilterTime":
+                            epd.Columns[0].nPostFilterTime = int.Parse(pairs[1]);
+                            break;
+                        case "AlgorithmType":
+                            epd.Columns[0].algorithmType = MapType(pairs[1]);
+                            break;
+                        case "Criteria":
+                            epd.Columns[0].nCriteria = float.Parse(pairs[1]);
+                            break;
+
+                        case "DelayTime":
+                            epd.Columns[0].nDelayTime = int.Parse(pairs[1]);
+                            break;
+                        case "ValidationTime":
+                            epd.Columns[0].nValidationTime = int.Parse(pairs[1]);
+                            break;
+                        case "ValidationValue":
+                            epd.Columns[0].nValidationValue = int.Parse(pairs[1]);
+                            break;
+                        case "TimeWindow":
+                            epd.Columns[0].nTimeWindow = int.Parse(pairs[1]);
+                            break;
+                        case "MinimalTime":
+                            epd.Columns[0].nMinimalTime = int.Parse(pairs[1]);
+                            break;
+                        case "PostponeTime":
+                            epd.Columns[0].nPostponeTime = int.Parse(pairs[1]);
+                            break;
+
+                        case "Control":
+                            epd.Columns[0].bControl = Convert.ToBoolean(pairs[1]);
+                            break;
+                        case "Normalization":
+                            epd.Columns[0].bNormalization = Convert.ToBoolean(pairs[1]);
+                            break;
+                        case "EnablePostponePercent":
+                            epd.Columns[0].bPostponePercent = Convert.ToBoolean(pairs[1]);
+                            break;
+                        case "EnableCriterialPercent":
+                            epd.Columns[0].bCriteriaPercent = Convert.ToBoolean(pairs[1]);
+                            break;
+
+
+                        case "EnableEventTrigger":
+                            epd.Columns[0].bEvtTrigger = Convert.ToBoolean(pairs[1]);
+                            break;
+                    }
+                }
+
+                EPDClient.Instance.Service.StartByConfig(_channel, index, $"step{index}", epd);
+            }
+            catch (Exception ex)
+            {
+                LOG.WriteExeption(ex);
+            }
+        }
+
+        private 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;
+        }
+
+        public void StepStop()
+        {
+            if (!IsConnected)
+            {
+                LOG.Write(eEvent.ERR_ENDPOINT, Module, "EPD not connected, call step stop ignored");
+                return;
+            }
+
+            EV.PostInfoLog(Module, $"{Module} {Name}, Notify EPD recipe step stopped");
+
+            EPDClient.Instance.Service.Stop(_channel);
+        }
+ 
+
+        private bool OnTimer()
+        {
+            try
+            {
+                bool retryConnect = false;
+                lock (_lockerTrigger)
+                {
+                    retryConnect = _triggerConnected.M;
+                    if (!_triggerConnected.M)
+                    {
+                        //if (_enableRetry)
+                        {
+                            retryConnect = true;
+                        }
+                    }
+
+                    if (retryConnect)
+                    {
+                        _connectionCounter++;
+                        if (_connectionCounter > 10000)
+                            _connectionCounter = 1;
+
+                        _triggerConnected.CLK = _connectionCounter== EPDClient.Instance.Service.Heartbeat(_connectionCounter);
+
+                        _triggerNotConnected.CLK = !_triggerConnected.M;
+
+                        if (_triggerConnected.Q)
+                        {
+                            EPDCallbackClient.Instance.Init();
+                            EV.PostInfoLog(Module, $"{Module} {Name}, EPD Connected");
+                        }
+
+                        if (_triggerConnected.M)
+                        {
+                            _channelStatus = EPDClient.Instance.Service.QueryState(_channel).ToString();
+                        }
+
+                        if (_triggerNotConnected.Q)
+                        {
+                            EPDCallbackClient.Instance.Stop();
+                            EV.PostWarningLog(Module, $"{Module} {Name}, EPD disconnected");
+
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                LOG.WriteExeption(ex);
+            }
+
+            return true;
+        }
+
+        private void Instance_Notify(int channel, string e)
+        {
+            if (_channel != channel)
+                return;
+
+            EV.PostInfoLog(Module, $"{Module} {Name}, EPD Feedback:{e}");
+        }
+
+        private void Instance_Trigger(int channel, TriggerEventArgs e)
+        {
+            if (_channel != channel)
+                return;
+
+            _isEnd = true;
+
+            EV.PostInfoLog(Module, $"{Module} {Name}, EPD: {e.Channel}.{e.Name} Triggered");
+        }
+
+        public override void Monitor()
+        {
+             
+        }
+
+        public override void Terminate()
+        {
+        }
+
+        public override void Reset()
+        {
+            lock (_lockerTrigger)
+            {
+                if (!_triggerConnected.M)
+                {
+
+                    _triggerConnected.RST = true;
+                    _triggerNotConnected.RST = true;
+                }
+
+
+            }
+        }
+    }
+
+    public class EPDCallbackClient : Singleton<EPDCallbackClient>, IEPDCallback
+    {
+        public event Action<int, string> Notify;
+
+        public event Action<int, TriggerEventArgs> Trigger;
+
+        private EPDCallbackServiceClient _service;
+
+        public EPDCallbackClient()
+        {
+            _service = new EPDCallbackServiceClient(this);
+
+
+        }
+
+        public void Init()
+        {
+            _service.Register();
+        }
+
+        public void Stop()
+        {
+            _service.UnRegister();
+        }
+
+        //public void OnNotify(string channel, string message)
+        //{
+
+        //}
+
+        //public void OnTrigger(string channel, string name, long ticket)
+        //{
+
+        //}
+
+        public void OnNotify(int channel, EPDEventType EventType, string message)
+        {
+            Notify?.Invoke(channel, $"{EventType}:{message}");
+             
+        }
+
+        public void OnTrigger(int channel, string name, long ticket)
+        {
+            Trigger?.Invoke(channel, new TriggerEventArgs() { Channel = channel, Name = name, Ticket = ticket });
+        }
+    }
+
+    public class TriggerEventArgs
+    {
+        public int Channel { get; set; }
+        public string Name { get; set; }
+        public long Ticket { get; set; }
+    }
+
+    public class EPDCallbackServiceClient : DuplexChannelServiceClientWrapper<IEPDCallbackService>
+    {
+        private Guid _clientId = Guid.Empty;
+
+        public EPDCallbackServiceClient(IEPDCallback callback) : base(new InstanceContext(callback), "Client_IEPDCallbackService", "IEPDCallbackService")
+        {
+        }
+
+        public void Register()
+        {
+            if (_clientId != Guid.Empty)
+                UnRegister();
+
+            _clientId = Guid.NewGuid();
+            Invoke(x => x.Register(_clientId));
+        }
+
+        public void UnRegister()
+        {
+            Invoke(x => x.UnRegister(_clientId));
+        }
+    }
+}

+ 165 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Datas/DataItemDefine.cs

@@ -0,0 +1,165 @@
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+
+namespace EPInterface.Data
+{
+    [DataContract]
+    public enum EPDState
+    {
+        [EnumMember]
+        UnSampling,    //硬件未进入采样
+
+        [EnumMember]
+        Running,       //计算中
+
+        [EnumMember]
+        Pause,         //暂停
+
+        [EnumMember]
+        Idle,
+    };
+
+    [DataContract]
+    public enum EPDEventType
+    {
+        [EnumMember]
+        Default = 0,    //基础信息
+
+        [EnumMember]
+        Info = 1,    //基础信息
+
+
+        [EnumMember]
+        Error,    //基础信息
+
+        [EnumMember]
+        Start,       //步骤 开始 
+
+        [EnumMember]
+        Delay,       //步骤 延迟 
+
+        [EnumMember]
+        Normalize,   //步骤 
+
+        
+        [EnumMember]
+        Check,   //步骤 检查条件
+
+        [EnumMember]
+        EndPoint,   //步骤 触发
+
+        [EnumMember]
+        Stop,   //步骤 停止
+    };
+
+    [DataContract]
+    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
+    public struct HardwareInfo
+    {
+        [MarshalAs(UnmanagedType.U8)]
+        public ulong Ver;
+        [MarshalAs(UnmanagedType.U2)]
+        public ushort Pixes;
+        [MarshalAs(UnmanagedType.R8)]
+        public double FirstCoefficient;
+        [MarshalAs(UnmanagedType.R8)]
+        public double SecondCoefficient;
+        [MarshalAs(UnmanagedType.R8)]
+        public double ThirdCoefficient;
+        [MarshalAs(UnmanagedType.R8)]
+        public double Intercept;
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+        public char[] Serial;
+
+        [MarshalAs(UnmanagedType.U8)]
+        public ulong IntegrationTime;
+
+        [MarshalAs(UnmanagedType.U8)]
+        public ulong SamplingTime;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
+        public double[] Wave;
+    }
+
+    [DataContract]
+    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
+    public struct RawDataItem
+    {
+        public static readonly int Size = Marshal.SizeOf(typeof(RawDataItem));
+        [MarshalAs(UnmanagedType.I8)]
+        public long Ticket;
+
+        [MarshalAs(UnmanagedType.I4)]    //累积次数
+        public int Counter;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]  //全光谱数据
+        public ushort[] RawData;
+
+        [MarshalAs(UnmanagedType.I4)]    //波段数据
+        public int RegionNum;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+        public double[] RegionData;
+
+        [MarshalAs(UnmanagedType.I4)]    //趋势数据, 表达式
+        public int TrendNum;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
+        public double[] TrendData;
+    }
+
+
+    [DataContract]
+
+    public class CRawDataItem
+    {
+        [DataMember]
+        public long Ticket
+        {
+            get;set;
+        }
+        [DataMember]
+        public ushort[] RawData
+        { get;
+          set;
+        }
+
+        [DataMember]
+        public int RegionNum
+        {
+            get; set;
+        }
+
+        [DataMember]
+        public double[] RegionData
+        {
+            get;set;
+        }
+
+        [DataMember]
+        public int TrendNum
+        {
+            get;set;
+        }
+        [DataMember]
+        public double[] TrendData
+        {
+            get;set;
+        }
+
+        public CRawDataItem()
+        {
+            Ticket = 0;
+            RawData = new ushort[1024];
+
+            RegionNum = 32;
+            RegionData = new double[32];
+
+            TrendNum = 32;
+            TrendData = new double[32];
+        }
+    }
+
+
+
+}

+ 128 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Datas/EPDConfig.cs

@@ -0,0 +1,128 @@
+using System.Runtime.Serialization;
+
+namespace EPInterface.Datas
+{
+    [DataContract]
+    public enum AlgorithmType
+    {
+        [EnumMember]
+        ALG_NONE,            //非endpoint
+        //level trigger, invaild time
+        [EnumMember]
+        ALG_FALL_VALUE,              //"FallValue"    ValidationTime,Threshold       < 
+        [EnumMember]
+        ALG_RISE_VALUE,              //"RiseValue"    ValidationTime,Threshold       >
+        [EnumMember]
+        ALG_FALL_PERCENT,            //"FallPecent"    ValidationTime,Threshold, TimeWindow       < 
+        [EnumMember]
+        ALG_RISE_PERCENT,            //"RisePecent"    ValidationTime,Threshold, TimeWindow      >
+
+        [EnumMember]
+        ALG_MIN_FALL_PERCENT,   //"MinFallPecent"    ValidationTime,Threshold, TimeWindow, BandTime  
+        [EnumMember]
+        ALG_MAX_FALL_PERCENT,   //"MaxFallPecent"    ValidationTime,Threshold, TimeWindow, BandTime  
+        [EnumMember]
+        ALG_MIN_RISE_PERCENT,   //"MinRisePecent"    ValidationTime,Threshold, TimeWindow, BandTime  
+        [EnumMember]
+        ALG_MAX_RISE_PERCENT,   //"MaxRisePecent"    ValidationTime,Threshold, TimeWindow, BandTime  
+
+        //Edge Trigger
+        [EnumMember]
+        ALG_GRADIENT,       //"Gradient"    Threshold, TimeWindow 
+        [EnumMember]
+        ALG_RANGE_IN,    //"RangeIn"    Threshold, TimeWindow 
+        [EnumMember]
+        ALG_PEAK,        //Peak     Threshold, TimeWindow, ValidationTime
+        [EnumMember]
+        ALG_VALLEY,      //Vallery  Threshold, TimeWindow, ValidationTime
+        [EnumMember]
+        ALG_RISE_FALL,    //"RiseFall"     Threshold, ValidationTime,ValidationValue,
+        [EnumMember]
+        ALG_FALL_RISE,    //"FallRise"     Threshold, ValidationTime,ValidationValue,
+
+    }
+
+    [DataContract]
+    public class EPDColumn
+    {
+        [DataMember]
+        public bool bNormalization { get; set; }
+        [DataMember]
+        public bool bControl { get; set; }
+        [DataMember]
+        public bool bPostponePercent { get; set; }
+        [DataMember]
+        public bool bEvtTrigger { get; set; }
+        [DataMember]
+        public bool bCriteriaPercent { get; set; }
+
+        [DataMember]
+        public int nCCDExposureTime { get; set; }
+
+        [DataMember]
+        public int nPreFilterTime { get; set; }
+        [DataMember]
+        public int nPostFilterTime { get; set; }
+
+        [DataMember]
+        public AlgorithmType algorithmType { get; set; }
+        [DataMember]
+        public double nCriteria { get; set; }
+        [DataMember]
+        public int nDelayTime { get; set; }
+        [DataMember]
+        public int nValidationTime { get; set; }
+        [DataMember]
+        public int nTimeWindow { get; set; }
+        [DataMember]
+        public int nMinimalTime { get; set; } // Version 2
+        [DataMember]
+        public int nPostponeTime { get; set; } // Version 2
+        [DataMember]
+        public int nValidationValue { get; set; } // Used for rise / fall algorithm
+        [DataMember]
+        public int nRefractionIndex { get; set; }// Only for IEP, always zero
+        [DataMember]
+        public float fEtchAmount { get; set; }  // Only for IEP, always zero
+
+
+        [DataMember]
+        public ushort[] nWaveLength { get; set; }
+        [DataMember]
+        public ushort[] nBinning { get; set; }
+
+        [DataMember]
+        public string cPSMode { get; set; } // Version 7
+        [DataMember]
+        public string cFunc { get; set; }
+
+        public EPDColumn()
+        {
+            nWaveLength = new ushort[8];
+            nBinning = new ushort[8];
+        }
+    }
+
+    [DataContract]
+    public class EPDConfig
+    {
+        [DataMember]
+        public int nParameterCount { get; set; }
+
+        [DataMember]
+        public EPDColumn[] Columns;
+
+        public bool FaultIfNoEPDTrigger { get; set; }
+
+
+        [DataMember]
+        public string cLogic { get; set; } // logic for multiple endpoints, resevered for future to support logic 'OR' Version 3
+
+        public EPDConfig()
+        {
+            Columns = new EPDColumn[3];
+            for (int i = 0; i < Columns.Length; i++)
+                Columns[i] = new EPDColumn();
+        }
+    }
+}

+ 196 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Interface/EPDClient.cs

@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using EPInterface.Data;
+using System.ServiceModel;
+using Aitex.Core.Util;
+using Aitex.Core.WCF;
+using EPInterface.Datas;
+using MECF.Framework.Common.Utilities;
+
+namespace EPInterface
+{
+    public class EPDClient : Singleton<EPDClient>
+    {
+ 
+        private IEPDService _service;
+        public IEPDService Service
+        {
+            get
+            {
+                if (_service == null)
+                {
+ 
+                        _service = new EPDServiceClient();
+                }
+
+                return _service;
+            }
+        }
+    }
+
+    public class EPDServiceClient : ServiceClientWrapper<IEPDService>, IEPDService
+    {
+        public EPDServiceClient()
+            : base("Client_IEPDService", "EPDService")
+        {
+        }
+
+        public void SetModel(int channel,string name, string model)
+        {
+            Invoke(svc => {  svc.SetModel(channel, name, model); });
+        }
+
+        public int Heartbeat(int counter)
+        {
+            int result = 0;
+
+            Invoke(svc => { result = svc.Heartbeat(counter); });
+
+            return result;
+        }
+
+        public void Online()
+        {
+            Invoke(svc => { svc.Online(); });
+        }
+        public void Offline()
+        {
+            Invoke(svc => { svc.Offline(); });
+        }
+
+        public bool IsOnline()
+        {
+            bool result = false;
+            Invoke(svc => { result = svc.IsOnline(); });
+            return result;
+        }
+
+        public void SetOption(string name, string value)
+        {
+            Invoke(svc => { svc.SetOption(name, value); });
+        }
+
+        public string GetOption(string name )
+        {
+            string result = string.Empty;
+            Invoke(svc => { result = svc.GetOption(name); });
+            return result;
+        }
+
+        public void RecipeStart(int channel, string name)
+        {
+            Invoke(svc => { svc.RecipeStart(channel, name); });
+        }
+        public void RecipeStop(int channel)
+        {
+            Invoke(svc => { svc.RecipeStop(channel); });
+        }
+        public void Start(int channel,int idx, string name, string model)
+        {
+            Invoke(svc => {  svc.Start(channel, idx, name, model); });
+        }
+
+        public void StartByConfig(int channel, int idx, string name, EPDConfig config)
+        {
+            Invoke(svc => { svc.StartByConfig(channel, idx, name, config); });
+        }
+
+        public void Stop(int channel)
+        {
+            Invoke(svc => {  svc.Stop(channel); });
+        }
+
+        public void Pause(int channel)
+        {
+            Invoke(svc => {  svc.Pause(channel); });
+        }
+
+        public void Resume(int channel)
+        {             
+            Invoke(svc => {  svc.Resume(channel); });
+        }
+        public int QueryChannels()
+        {
+            int result = 0;
+            Invoke(svc => { result = svc.QueryChannels(); });
+            return result;
+        }
+
+        public List<string> QueryChannelNames()
+        {
+            List<string> result = new List<string>();
+            Invoke(svc => { result = svc.QueryChannelNames(); });
+            return result;
+        }
+
+        public string QueryChannelName(int channel)
+        {
+            string result = "";
+            Invoke(svc => { result = svc.QueryChannelName(channel); });
+            return result;
+        }
+
+        public Tuple<string,string> QueryModel(int channel)
+        {
+            Tuple<string, string> result = null;
+            Invoke(svc => { result = svc.QueryModel(channel); });
+            return result;
+        }
+
+        public double[] QueryWave(int channel)
+        {
+            double[] result = null;
+            Invoke(svc => { result = svc.QueryWave(channel); });
+            return result;
+        }
+
+        public CRawDataItem QueryData(int channel)
+        {
+            CRawDataItem result = default(CRawDataItem) ;
+            Invoke(svc => { result = svc.QueryData(channel); });
+            return result;
+        }
+
+        public EPDState QueryState(int channel)
+        {
+            EPDState result = EPDState.UnSampling;
+            Invoke(svc => { result = svc.QueryState(channel); });
+            return result;
+        }
+
+        public bool QueryRecordState(int channel)
+        {
+            bool result = false;
+            Invoke(svc => { result = svc.QueryRecordState(channel); });
+            return result;
+        }
+
+        public long QueryRecipeTime(int channel)
+        {
+            long result = 0;
+            Invoke(svc => { result = svc.QueryRecipeTime(channel); });
+            return result;
+        }
+        public List<long> QueryStepTime(int channel)
+        {
+            List<long> result = new List<long>();
+            Invoke(svc => { result = svc.QueryStepTime(channel); });
+            return result;
+        }
+
+        public List<string> QueryConfigList()
+        {
+            List<string> result = new List<string>();
+            Invoke(svc => { result = svc.QueryConfigList(); });
+            return result;
+        }
+
+        public string QueryConfig(string name)
+        {
+            string result = string.Empty;
+            Invoke(svc => { result = svc.QueryConfig(name); });
+            return result;
+        }
+    }
+}

+ 16 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Interface/IEPDCallback.cs

@@ -0,0 +1,16 @@
+using EPInterface.Data;
+using System.ServiceModel;
+
+namespace EPInterface
+{
+    [ServiceContract]
+
+    public interface IEPDCallback
+    {
+        [OperationContract(IsOneWay = true)]
+        void OnNotify(int channel, EPDEventType type, string message);   //信息反馈
+
+		[OperationContract(IsOneWay = true)]
+		void OnTrigger(int channel, string name, long ticket);     //Endpoint Trigger
+    }
+}

+ 17 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Interface/IEPDCallbackService.cs

@@ -0,0 +1,17 @@
+using System;
+using System.ServiceModel;
+using EPInterface.Data;
+
+namespace EPInterface
+{
+    [ServiceContract(CallbackContract = typeof(IEPDCallback))]
+    [ServiceKnownType(typeof(EPDEventType))]
+    public interface IEPDCallbackService
+    {
+        [OperationContract]
+        bool Register(Guid id);
+
+        [OperationContract(IsOneWay = true)]
+        void UnRegister(Guid id);
+    }
+}

+ 160 - 0
Venus/Venus_RT/Devices/EPDs/EPInterface/Interface/IEPDService.cs

@@ -0,0 +1,160 @@
+using EPInterface.Data;
+using System;
+using System.Collections.Generic;
+using System.ServiceModel;
+using EPInterface.Datas;
+
+namespace EPInterface
+{
+
+    [ServiceContract]
+    [ServiceKnownType(typeof(EPDState))]
+    [ServiceKnownType(typeof(CRawDataItem))]
+    [ServiceKnownType(typeof(List<long>))]
+    public interface IEPDService
+	{
+  
+        /// <summary>
+        /// 设置当前model
+        /// </summary>
+        /// <param name="model"></param>
+        [OperationContract]
+        void SetModel(int channel, string name, string model);
+
+        [OperationContract]
+        int Heartbeat(int counter);
+
+        [OperationContract]
+        void Online();
+
+        [OperationContract]
+        void Offline();
+
+        [OperationContract]
+        bool IsOnline();
+
+        [OperationContract]
+        void SetOption(string name, string value);
+
+        [OperationContract]
+        string GetOption(string name);
+
+
+        /// <summary>
+        ///RecipeStart
+        /// </summary>
+        /// <param name="recipe"></param>
+        [OperationContract]
+        void RecipeStart(int channel, string name);
+
+        /// <summary>
+        ///RecipeStart
+        /// </summary>
+        /// <param name="model"></param>
+        [OperationContract]
+        void RecipeStop(int channel);
+
+        /// <summary>
+        /// Online 模式,开始计算
+        /// </summary>
+        /// <param name="model"></param>
+        [OperationContract]
+        void Start(int channel, int idx, string name, string model);
+
+        [OperationContract]
+        void StartByConfig(int channel, int idx, string name, EPDConfig config);
+
+        /// <summary>
+        /// online 模式,停止计算
+        /// </summary>
+        [OperationContract]
+        void Stop(int channel);        
+
+        /// <summary>
+        /// online模式,暂停
+        /// </summary>
+        [OperationContract]
+        void Pause(int channel);
+
+        [OperationContract]
+
+        /// <summary>
+        /// online模式,恢复计算
+        /// </summary>
+        void Resume(int channel);
+
+        /// <summary>
+        /// 获取硬件通道数
+        /// </summary>
+
+        [OperationContract]
+        int QueryChannels();
+
+        [OperationContract]
+        List<string> QueryChannelNames();
+
+        [OperationContract]
+        string QueryChannelName(int channel);
+
+        [OperationContract]
+        ///
+        /// 获取当前model <name,xml>
+        ///
+        Tuple<string,string> QueryModel(int channel);
+ 
+        /// <summary>
+        /// 获取 波长信息,频谱图 X 坐标
+        /// </summary>
+        /// <param name="wave"></param>
+        [OperationContract]
+        double[] QueryWave(int channel);
+
+        /// <summary>
+        /// 获取当前帧数据        
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns>False,为无数据。
+        /// </returns>
+        [OperationContract]
+        CRawDataItem QueryData(int channel);
+
+
+        [OperationContract]
+        ///
+        /// 获取当前State
+        ///
+        EPDState QueryState(int channel);
+        
+        [OperationContract]
+        bool QueryRecordState(int channel);
+
+        [OperationContract]
+        long QueryRecipeTime(int channel);
+
+        [OperationContract]
+        List<long> QueryStepTime(int channel);
+
+        [OperationContract]
+        List<string> QueryConfigList();
+        [OperationContract]
+        string QueryConfig(string name);
+    }
+
+    public interface IRecipeService
+    {
+        [OperationContract]
+        List<string> QueryRecipes();
+
+        [OperationContract]
+        string Load(string name);
+
+
+        [OperationContract]
+        void Save(string name, string recipe);
+
+
+        [OperationContract]
+        void Delete(string name);
+
+    }
+}

+ 18 - 0
Venus/Venus_RT/Devices/JetEPDBase.cs

@@ -0,0 +1,18 @@
+using Aitex.Core.RT.DataCenter;
+using Aitex.Core.RT.Device;
+using Aitex.Core.RT.OperationCenter;
+using MECF.Framework.Common.Equipment;
+using System.Collections.Generic;
+using Venus_Core;
+
+namespace Venus_RT.Devices
+{
+    abstract public class JetEPDBase : BaseDevice, IDevice
+    {
+        public abstract bool Initialize();
+        public abstract void Monitor();
+        public abstract void Terminate();
+
+        public abstract void Reset();
+    }
+}

+ 447 - 29
Venus/Venus_RT/Modules/AutoCycle.cs

@@ -2,12 +2,14 @@
 using System.Collections.Generic;
 using System.Linq;
 
+using Aitex.Core.Common;
 using Aitex.Core.RT.Routine;
 using Aitex.Core.RT.SCCore;
 using Aitex.Sorter.Common;
 using Aitex.Core.RT.Log;
 using Aitex.Core.Util;
 using Aitex.Core.RT.DataCenter;
+using Aitex.Core.RT.RecipeCenter;
 using Aitex.Core.RT.Fsm;
 
 using MECF.Framework.Common.Jobs;
@@ -15,6 +17,7 @@ using MECF.Framework.Common.Routine;
 using MECF.Framework.Common.Equipment;
 using MECF.Framework.Common.SubstrateTrackings;
 using MECF.Framework.Common.Schedulers;
+using MECF.Framework.Common.DBCore;
 
 using Venus_Core;
 
@@ -82,6 +85,9 @@ namespace Venus_RT.Modules
         Dictionary<SlotItem, Guid> _vacWaferTargets = new Dictionary<SlotItem, Guid>();
         Dictionary<SlotItem, Guid> _atmWaferTargets = new Dictionary<SlotItem, Guid>();
 
+        private SchedulerFACallback _faCallback;
+        private SchedulerDBCallback _dbCallback;
+
         private bool _isCycleMode;
         private int _cycleSetPoint = 0;
         private int _cycledCount = 0;
@@ -90,6 +96,9 @@ namespace Venus_RT.Modules
         #region public interface
         public AutoCycle() 
         {
+            _faCallback = new SchedulerFACallback();
+            _dbCallback = new SchedulerDBCallback();
+
             InitModules();
 
             DATA.Subscribe("Scheduler.CycledCount", () => _cycledCount);
@@ -123,6 +132,162 @@ namespace Venus_RT.Modules
         {
         }
 
+        public void CreateJob(Dictionary<string, object> param)
+        {
+            _isCycleMode = SC.GetValue<bool>("System.IsCycleMode");
+            _cycleSetPoint = _isCycleMode ? SC.GetValue<int>("System.CycleCount") : 0;
+
+
+            string[] slotSequence = (string[])param["SlotSequence"];
+            string jobId = (string)param["JobId"];
+            string module = (string)param["Module"];
+            //bool autoStart = (bool)param["AutoStart"];
+            string lotId = jobId;
+            if (param.ContainsKey("LotId"))
+                lotId = (string)param["LotId"];
+
+            if (slotSequence.Length != SC.GetValue<int>("EFEM.LoadPort.SlotNumber"))
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"slot sequence parameter not valid, length is {slotSequence.Length}, should be {SC.GetValue<int>("EFEM.LoadPort.SlotNumber")}");
+                _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                return;
+            }
+
+            if (!ModuleHelper.IsLoadPort(ModuleHelper.Converter(module)))
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{module} should be LoadPort");
+                _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                return;
+            }
+
+            if (string.IsNullOrEmpty(jobId))
+            {
+                jobId = "CJ_Local_" + module;
+            }
+
+            if (_lstControlJobs.Exists(x => x.Name == jobId))
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{jobId} already created");
+                _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                return;
+            }
+
+            SchedulerLoadPort lp = _atmSchedulers[ModuleHelper.Converter(module)] as SchedulerLoadPort;
+            if (!lp.CheckReadyRunJob())
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{module} not ready");
+                _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                return;
+            }
+
+            ControlJobInfo cj = new ControlJobInfo();
+            cj.Name = jobId;
+            cj.Module = module;
+            cj.LotName = lotId;
+            cj.LotInnerId = Guid.NewGuid();
+            cj.LotWafers = new List<WaferInfo>();
+            cj.SetState(EnumControlJobState.WaitingForStart);
+
+            Dictionary<string, bool[]> seqSlot = new Dictionary<string, bool[]>();
+            Dictionary<string, List<Tuple<ModuleName, int>>> seqSlotWafers = new Dictionary<string, List<Tuple<ModuleName, int>>>();
+            Dictionary<string, string> indexSequence = new Dictionary<string, string>();
+
+            bool enableGroupBySequence = SC.GetValue<bool>("Scheduler.GroupWaferBySequence");
+            for (int i = 0; i < SC.GetValue<int>("EFEM.LoadPort.SlotNumber"); i++)
+            {
+                if (string.IsNullOrEmpty(slotSequence[i]) || string.IsNullOrEmpty(slotSequence[i].Trim()))
+                    continue;
+
+                string groupName = enableGroupBySequence ? slotSequence[i].Trim() : i.ToString();
+                indexSequence[groupName] = slotSequence[i];
+                if (!seqSlot.ContainsKey(groupName))
+                {
+                    seqSlot[groupName] = new bool[SC.GetValue<int>("EFEM.LoadPort.SlotNumber")];
+                }
+                if (!seqSlotWafers.ContainsKey(groupName))
+                {
+                    seqSlotWafers[groupName] = new List<Tuple<ModuleName, int>>();
+                }
+                seqSlot[groupName][i] = true;
+
+                if (!WaferManager.Instance.CheckHasWafer(module, i))
+                {
+                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} not in the carrier");
+                    return;
+                }
+                if (!WaferManager.Instance.CheckWafer(ModuleHelper.Converter(module), i, WaferStatus.Normal))
+                {
+                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Status}");
+                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                    return;
+                }
+
+                if (WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState != EnumWaferProcessStatus.Idle)
+                {
+                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} process status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState}");
+                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                    return;
+                }
+
+                cj.LotWafers.Add(WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i));
+                WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).SequenceName = slotSequence[i];
+
+                seqSlotWafers[groupName].Add(Tuple.Create(ModuleHelper.Converter(module), i));
+
+                cj.JobWaferSize = WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Size;
+
+                LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"Assigned wafer job, wafer {module}.{i + 1}, sequence: {slotSequence[i]}");
+            }
+
+            if (seqSlotWafers.Count == 0)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job has not assign wafer");
+                _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                return;
+            }
+
+            List<ProcessJobInfo> pjs = new List<ProcessJobInfo>();
+            string[] seqs = seqSlot.Keys.ToArray();
+            for (int i = 0; i < seqs.Length; i++)
+            {
+                ProcessJobInfo pj = new ProcessJobInfo();
+                pj.Name = jobId + "_" + (i + 1);
+                pj.Sequence = SequenceInfoHelper.GetInfo(indexSequence[seqs[i]]);
+                pj.ControlJobName = cj.Name;
+                pj.LotName = lotId;
+                pj.SlotWafers = seqSlotWafers[seqs[i]];
+                pj.SetState(EnumProcessJobState.Queued);
+
+                if (!CheckSequencePmReady(pj.Sequence, null, out _, out string reason))
+                {
+                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"no valid chamber for the {reason}");
+                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                    return;
+                }
+
+                if (!SC.GetValue<bool>("System.IsATMMode") && !CheckSequenceRecipeFileValid(pj.Sequence, out reason))
+                {
+                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"recipe file not valid in the sequence, {reason}");
+                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
+                    return;
+                }
+
+                pjs.Add(pj);
+            }
+
+            _dbCallback.LotUpdate(cj);
+            foreach (var pj in pjs)
+            {
+                cj.ProcessJobNameList.Add(pj.Name);
+                _lstProcessJobs.Add(pj);
+            }
+
+            _lstControlJobs.Add(cj);
+            //AssociatedPMWithLP(cj);
+
+            _faCallback.JobCreated(cj, GetFirstProcessJob(cj));
+        }
+
         public bool CheckAllJobDone()
         {
             foreach (var cj in _lstControlJobs)
@@ -151,6 +316,141 @@ namespace Venus_RT.Modules
             return false;
         }
 
+        public ProcessJobInfo GetFirstProcessJob(ControlJobInfo cj)
+        {
+            foreach (var pj in _lstProcessJobs)
+            {
+                if (pj.ControlJobName == cj.Name)
+                    return pj;
+            }
+
+            return null;
+        }
+        public void AbortJob(string jobName)
+        {
+            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
+            if (cj == null)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"abort job rejected, not found job with id {jobName}");
+                return;
+            }
+
+            List<ProcessJobInfo> pjAbortList = new List<ProcessJobInfo>();
+            foreach (var pj in _lstProcessJobs)
+            {
+                if (pj.ControlJobName == cj.Name)
+                {
+                    pj.SetState(EnumProcessJobState.Aborting);
+                    pjAbortList.Add(pj);
+
+                    int unprocessed = 0;
+                    int aborted = 0;
+                    WaferInfo[] wafers = WaferManager.Instance.GetWaferByProcessJob(pj.Name);
+                    foreach (var waferInfo in wafers)
+                    {
+                        waferInfo.ProcessJob = null;
+                        waferInfo.NextSequenceStep = 0;
+                        if (waferInfo.ProcessState != EnumWaferProcessStatus.Completed)
+                            unprocessed++;
+                    }
+                    JobDataRecorder.EndPJ(pj.InnerId.ToString(), aborted, unprocessed);
+                }
+            }
+
+            _faCallback.JobAborted(cj, GetFirstProcessJob(cj));
+            _dbCallback.LotFinished(cj);
+            foreach (var pj in pjAbortList)
+            {
+                _lstProcessJobs.Remove(pj);
+            }
+
+            _lstControlJobs.Remove(cj);
+        }
+
+        public void StopJob(string jobName)
+        {
+            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
+            if (cj == null)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"stop job rejected, not found job with id {jobName}");
+                return;
+            }
+
+            foreach (var pj in _lstProcessJobs)
+            {
+                if (pj.ControlJobName == cj.Name)
+                {
+                    pj.SetState(EnumProcessJobState.Stopping);
+                }
+            }
+
+            _faCallback.JobStopped(cj, GetFirstProcessJob(cj));
+            _dbCallback.LotFinished(cj);
+        }
+        public void ResumeJob(string jobName)
+        {
+            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
+            if (cj == null)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"resume job rejected, not found job with id {jobName}");
+                return;
+            }
+
+            if (cj.State == EnumControlJobState.Paused)
+            {
+                cj.SetState(EnumControlJobState.Executing);
+            }
+
+            _faCallback.JobResumed(cj, GetFirstProcessJob(cj));
+        }
+
+        public void PauseJob(string jobName)
+        {
+            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
+            if (cj == null)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"pause job rejected, not found job with id {jobName}");
+                return;
+            }
+
+            if (cj.State == EnumControlJobState.Executing)
+            {
+                cj.SetState(EnumControlJobState.Paused);
+            }
+            _faCallback.JobPaused(cj, GetFirstProcessJob(cj));
+        }
+
+
+        public void StartJob(string jobName)
+        {
+            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
+            if (cj == null)
+            {
+                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"start job rejected, not found job with id {jobName}");
+                return;
+            }
+
+            if (cj.State == EnumControlJobState.WaitingForStart)
+            {
+                cj.SetState(EnumControlJobState.Executing);
+                //PreJobClean(cj);
+
+                (_atmSchedulers[ModuleHelper.Converter(cj.Module)] as SchedulerLoadPort).NoteJobStart();
+
+                cj.StartTime = DateTime.Now;
+
+                _dbCallback.LotCreated(cj);
+                _faCallback.JobStarted(cj, GetFirstProcessJob(cj));
+
+                //if (!_blockingWatcher.IsRunning)
+                //    _blockingWatcher.Restart();
+
+                //ResetTraceFlag();
+            }
+        }
+
+
+
         #endregion
 
         private void InitModules()
@@ -693,37 +993,36 @@ namespace Venus_RT.Modules
         {
             var inSlots = new List<int>();
 
-            return inSlots;
-
 
             //if (GetSystemInnerWaferCount() >= SystemInternalWaferCount || GetBufferWaferCount() > _maxBufferWaferCount)
-            //    return new SlotItem(ModuleName.System, -1);
-
-            //foreach (var cj in _lstControlJobs)
-            //{
-            //    if (lpModule != ModuleName.System && (cj.Module != lpModule.ToString()))
-            //        continue;
-
-            //    if (cj.State == EnumControlJobState.Executing)
-            //    {
-            //        foreach (var pj in _lstProcessJobs)
-            //        {
-            //            if (pj.ControlJobName == cj.Name && pj.State == EnumProcessJobState.Processing)
-            //            {
-            //                foreach (var pjSlotWafer in pj.SlotWafers)
-            //                {
-            //                    if (CheckWaferNeedProcessAndPMAvailable(pjSlotWafer.Item1, pjSlotWafer.Item2))
-            //                    {
-            //                        return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
-            //                    }
-
-            //                }
-            //            }
-            //        }
-            //    }
-            //}
-
-            //return new SlotItem(ModuleName.System, -1);
+                //return new SlotItem(ModuleName.System, -1);
+
+            foreach (var cj in _lstControlJobs)
+            {
+                if (lp != ModuleName.System && (cj.Module != lp.ToString()))
+                    continue;
+
+                if (cj.State == EnumControlJobState.Executing)
+                {
+                    foreach (var pj in _lstProcessJobs)
+                    {
+                        if (pj.ControlJobName == cj.Name && pj.State == EnumProcessJobState.Processing)
+                        {
+                            foreach (var pjSlotWafer in pj.SlotWafers)
+                            {
+                                if (CheckWaferNeedProcessAndPMAvailable(pjSlotWafer.Item1, pjSlotWafer.Item2))
+                                {
+                                    //return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
+                                    inSlots.Add(pjSlotWafer.Item2);
+                                }
+
+                            }
+                        }
+                    }
+                }
+            }
+
+            return inSlots;
         }
 
         private bool ProcessAlignerEFEMRobotTask(ModuleName aligner)
@@ -808,5 +1107,124 @@ namespace Venus_RT.Modules
         }
 
         #endregion Atm System
+
+        #region Sequence validation
+        private bool CheckSequencePmReady(SequenceInfo seq, List<ModuleName> pmOccupied, out List<ModuleName> pmUsed, out string reason)
+        {
+            pmUsed = new List<ModuleName>();
+            reason = "";
+            bool pmIsReady = true;
+
+            for (int i = 0; i < seq.Steps.Count; i++)
+            {
+                SequenceStepInfo stepInfo = seq.Steps[i];
+                bool hasPm = false;
+                foreach (var module in stepInfo.StepModules)
+                {
+                    if (!ModuleHelper.IsPm(module))
+                    {
+                        hasPm = true;
+                        break;
+                    }
+
+                    if (ModuleHelper.IsInstalled(module) && (pmOccupied == null || !pmOccupied.Contains(module)))
+                    {
+                        hasPm = true;
+                    }
+
+                    if (!pmUsed.Contains(module))
+                        pmUsed.Add(module);
+                }
+
+                if (pmIsReady && !hasPm)
+                {
+                    reason = $"Step {i + 1} no valid PM, " + string.Join("|", stepInfo.StepModules);
+                    pmIsReady = false;
+                }
+            }
+
+            return pmIsReady;
+        }
+
+        private bool CheckSequenceRecipeFileValid(SequenceInfo seq, out string reason)
+        {
+            for (int i = 0; i < seq.Steps.Count; i++)
+            {
+                SequenceStepInfo stepInfo = seq.Steps[i];
+                foreach (var module in stepInfo.StepModules)
+                {
+                    if (ModuleHelper.IsPm(module))
+                    {
+                        string attr = $"{module}Recipe";
+                        if (stepInfo.StepParameter.ContainsKey(attr)
+                            && !string.IsNullOrEmpty((string)stepInfo.StepParameter[attr]))
+                        {
+                            var recipeName = (string)stepInfo.StepParameter[attr];
+                            if (!string.IsNullOrEmpty(recipeName))
+                            {
+                                var recipeContent =
+                                    RecipeFileManager.Instance.LoadRecipe($"", recipeName, false);
+                                if (string.IsNullOrEmpty(recipeContent))
+                                {
+                                    reason = $"Can not find recipe file{recipeName}";
+                                    return false;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            reason = "";
+            return true;
+        }
+
+        private bool CheckWaferNeedProcessAndPMAvailable(ModuleName waferModule, int waferSlot, ModuleName processIn = ModuleName.System)
+        {
+            WaferInfo wafer = WaferManager.Instance.GetWafer(waferModule, waferSlot);
+
+            if (wafer.IsEmpty)
+                return false;
+
+            if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
+                return false;
+
+            if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
+                return false;
+
+            for (int i = wafer.NextSequenceStep; i < wafer.ProcessJob.Sequence.Steps.Count; i++)
+            {
+                var hasPmAvailable = false;
+                foreach (var stepModule in wafer.ProcessJob.Sequence.Steps[i].StepModules)
+                {
+                    if (!ModuleHelper.IsPm(stepModule))
+                        break;
+
+                    var pm = _vacSchedulers[stepModule] as SchedulerPM;
+                    if (pm != null)
+                    {
+                        if (pm.IsAvailable || (pm.IsOnline && pm.Task != SchedulerModule.TaskType.PreJobProcess))
+                        {
+                            hasPmAvailable = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (!hasPmAvailable)
+                    continue;
+
+                if (processIn == ModuleName.System)
+                    return true;
+
+                if (wafer.ProcessJob.Sequence.Steps[i].StepModules
+                    .Contains(processIn))
+                    return true;
+            }
+
+            return false;
+        }
+
+        #endregion
     }
 }

+ 5 - 5
Venus/Venus_RT/Modules/TM/MFPlaceRoutine.cs

@@ -85,11 +85,11 @@ namespace Venus_RT.Modules.TM
         public RState Monitor()
         {
             Runner.Wait((int)PlaceStep.WaitModuleReady, () => _llModule.IsIdle, _delay_60s)
-                .Run((int)PlaceStep.ModulePrepare, ModulePrepare, IsModulePrepareReady)
-                .Run((int)PlaceStep.OpenSlitDoor, OpenSlitDoor, IsSlitDoorOpen)
-                .Run((int)PlaceStep.Placing, Placing, WaitPlaceDone)
-                .Run((int)PlaceStep.CloseSlitDoor, CloseSlitDoor, IsSlitDoorClosed)
-                .End((int)PlaceStep.NotifyDone, NotifyLLDone, _delay_50ms);          
+                .Run((int)PlaceStep.ModulePrepare,      ModulePrepare,          IsModulePrepareReady)
+                .Run((int)PlaceStep.OpenSlitDoor,       OpenSlitDoor,           IsSlitDoorOpen)
+                .Run((int)PlaceStep.Placing,            Placing,                WaitPlaceDone)
+                .Run((int)PlaceStep.CloseSlitDoor,      CloseSlitDoor,          IsSlitDoorClosed)
+                .End((int)PlaceStep.NotifyDone,         NotifyLLDone,           _delay_50ms);          
             return Runner.Status;
         }
 

+ 8 - 0
Venus/Venus_RT/Venus_RT.csproj

@@ -115,6 +115,13 @@
     <Compile Include="Devices\EFEM\EfemMessage.cs" />
     <Compile Include="Devices\EFEM\JetEfem.cs" />
     <Compile Include="Devices\EFEM\Loadport.cs" />
+    <Compile Include="Devices\EPDs\EPDDevice.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Datas\DataItemDefine.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Datas\EPDConfig.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Interface\EPDClient.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Interface\IEPDCallback.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Interface\IEPDCallbackService.cs" />
+    <Compile Include="Devices\EPDs\EPInterface\Interface\IEPDService.cs" />
     <Compile Include="Devices\EPD\Data\ByteReader.cs" />
     <Compile Include="Devices\EPD\Data\ByteStructConverter.cs" />
     <Compile Include="Devices\EPD\Data\CytAsciiDefine.cs" />
@@ -140,6 +147,7 @@
     <Compile Include="Devices\IODevices\IoSignalTower.cs" />
     <Compile Include="Devices\IODevices\IoSwitch.cs" />
     <Compile Include="Devices\IODevices\IoTMPressureCtl.cs" />
+    <Compile Include="Devices\JetEPDBase.cs" />
     <Compile Include="Devices\JetPMBase.cs" />
     <Compile Include="Devices\JetVenusPM.cs" />
     <Compile Include="Devices\JetKepler2300PM.cs" />