SMTFujiParser.cs

*SMTFujiParser.cs*

using System;
using System.Threading;
using Bartech.SGCore.Base;
using Bartech.SGCore.Local;
using Bartech.SGCore.Logic.NVCZ.Modules.SMT.Model.FUJI;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Metadata;
using System.Collections.Generic;
using Bartech.SGCore.Local.Services;
using Bartech.SGCore.Logic.NVCZ.Modules.LaserParser.Model;
using System.Linq;
using xdata = xTrace03_SG_Data;
using System.Collections.Concurrent;
using Bartech.xTrace.NVCZ;
using Bartech.SGCore.Local.DataObjects;
using Bartech.SGCore.Base.Web;
using Bartech.SGCore.Model.ContextData;
using Bartech.SGCore.Helpers;
using Bartech.SGCore.Model.Messages;
using Bartech.SGCore.Model;

namespace Bartech.SGCore.Logic.NVCZ.Modules
{

    [XModule("SMTFujiParser", "Modul pro parsovaní SMT FUJI", ModuleGroupType.Other, applicationType: xTraceApplicationType.NvCzSMT)]
    public class SMTFujiParser : SGModuleLocal
    {
        class InternalData
        {
            private DateTime m_LastScanDT;

            public InternalData()
            {
                MCID = string.Empty;
                LINEID = string.Empty;
                MaxModuleNo = 0;
                Clear();
            }
            public string MCID { get; set; }
            public int MaxModuleNo { get; set; }
            public BoardTrace LastBoard { get; set; }
            public DateTime LastScanDT { get { return m_LastScanDT; } set { m_LastScanDT = value; } }
            public bool CompareWithBom { get; set; }
            public bool CompareResult { get; set; }
            public int ScanInterval { get; set; }
            public bool m_SendPickOffErrorAlarm { get; set; }
            public bool ParseOnlyNoReads { get; set; }
            public string LINEID { get; set; }

            public void Clear()
            {
                LastBoard = new BoardTrace();
                CompareResult = false;
                LastScanDT = DateTime.Now;
            }
        }

        public SMTFujiParser(SGSession session, MSSQL cData)
            : base(session)
        {
            m_cData = cData;
            l_MachineStatus = new List<MachineStatus>();
            m_privateData = new InternalData();

            Session.Initialized += new EventHandler((s, e) =>
            {
                InitXPO(m_cData);
            });

            m_dxSession = Session.Resolve<SGDxSessionLocker>();
            m_smtFujiSvc = Session.Resolve<SmtFujiService>();
            Session.Messenger.Register<ObjectChangedMessage<BusUserGroup>>(this, (msg) => { OnUserChanged(msg); });
            Session.ProcessContext.InitFujiTraceData();

            //AllowedInDevs = new SGDeviceType[] { SGDeviceType.Scanner };
        }

        #region privatni promenne
        protected volatile bool m_shouldStop = false;
        protected object m_lock = new object();
        private IDataLayer m_cDataDL;
        private SGDxSessionLocker m_dxSession;
        private MSSQL m_cData;
        private Thread m_parseThread;
        private bool m_enabled = false;
        private InternalData m_privateData;
        private List<MachineStatus> l_MachineStatus;
        private bool m_threadStarted = false;
        private bool m_useBlocker;
        private int m_ioBlockDefinition = 1;
        private ConcurrentQueue<BoardTrace> m_BoardQueue;

        #endregion

        protected SmtFujiService m_smtFujiSvc;

        #region public promenne
        //public FujiTrace FujiTraceData { get { return m_FujiTrace; } private set { m_FujiTrace = value; } }
        #endregion

        private void InitXPO(MSSQL cdata)
        {
            #region Inicializace XPO pro cData
            var cs = "XpoProvider=MSSqlServer;" + cdata.ConnectionString;

            var dict = new ReflectionDictionary();
            dict.GetDataStoreSchema(typeof(LaserBoard));
            dict.GetDataStoreSchema(typeof(LaserBoardCode));
            dict.GetDataStoreSchema(typeof(ParsedFile));

            var provider = XpoDefault.GetConnectionProvider(cs, AutoCreateOption.SchemaAlreadyExists);
            m_cDataDL = new ThreadSafeDataLayer(dict, provider);
            #endregion
        }
        protected override bool OnSetParameters()
        {
            SetOK();

            m_privateData.MCID = Session.SysParams.TryGetString(this, "MCID", "", "MCID linky FUJI pro kterou budeme osazovací data nacítat");
            m_privateData.CompareWithBom = Session.SysParams.GetParamValue<bool>(this, "CompareWithBom", true, "Jestli se mají nactená osazovací data porovnat s kusovníkem");
            m_privateData.ScanInterval = Session.SysParams.GetParamValue<int>(this, "ScanInterval", 3, "Interval v sekundách skenování DB Fuji") * 1000;
            m_privateData.m_SendPickOffErrorAlarm = Session.SysParams.GetBoolean(this, "SendPickOffErrorAlarm", false, "Jestli se má odeslat alarm 5xtejných PICKOFFERRORS behem hodiny");
            m_privateData.ParseOnlyNoReads = Session.SysParams.GetBoolean(this, "ParseOnlyNoReads", false, "Jestli ma vlakno parsovat jen NOREADy, barcode jinak dojde pres HandlePortData");
            m_privateData.LINEID = Session.SysParams.TryGetString(this, "LINEID", "", "ID linky FUJI pro kterou budeme osazovací data nacítat (nahrazuje MCID)");

            return m_enabled = CallResult;
        }
        void OnUserChanged(ObjectChangedMessage<BusUserGroup> msg)
        {
            if (msg.Data == null || msg.Data?.OID == 0)
                m_smtFujiSvc.DeactivateActualJob(m_cDataDL, SetError, m_privateData.LINEID);
        }
        public override void StartLevel(SGTreeModuleData mdata)
        {
            // podivam se jestli je nacteny job, operace a kusovnik a mozu nacitat jen noready
            if (Session.Job.DoJob.IsSelected && Session.CycleContext.OperationOverride != null && !string.IsNullOrEmpty(m_privateData.LINEID))
            {
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nastavuji linku {m_privateData.LINEID}: VO={Session.Job.DoJob.CustomerBarcode}, Operace={Session.CycleContext.OperationOverride.RoutingPosIndex}");

                m_smtFujiSvc.SetActualJob(m_cDataDL, SetError, m_privateData.LINEID, Session.WorkPlace.WorkPlaceID, Session.Job.DoJob.JobID, Session.CycleContext.OperationOverride.RoutingItemID, Session.CycleContext.OperationOverride.RoutingPosIndex, Session.CycleContext.OperationOverride.OID, Session.UserWorkGroup.MainUser.UserID, Session.UserWorkGroup.UserWorkGroupID, GetProgramName(string.Empty));

                // nastav joba
                if (!m_threadStarted)
                {
                    CreateKPIs();
                    Session.ProcessContext.CurrentFujiData.Clear();
                    // spusteni parsovaciho vlakna
                    m_threadStarted = StartThread();
                }
            }

            if (Session.Part.DoPart.IsPartStarted && !string.IsNullOrEmpty(Session.Part.CurBarcode))
            {
                LoadFujiDataForBarcode_v2(Session.Part.CurBarcode, Session.Part.DoPart.DetailID);
            }

        }
        public override SGLMState ModuleChecker(SGTreeModuleData mdata)
        {
            if (Session.Part.DoPart.IsPartStarted && Session.Part.DoPart.GS == 35)
            {
                Session.Part.DoPart.MS = 35;
            }
            return SGLMState.Handled | SGLMState.OK | SGLMState.UpLevel;
        }
        /// <summary>
        /// Pokusi se nacit osazovaci data pro barcode, ktery prisel normalne pres HandlePortData do modulu PartStart, ktery musi byt pred timto modulem
        /// </summary>
        /// <param name="barcode"></param>
        public void LoadFujiDataForBarcode(string barcode, long? detailID)
        {

            // pokusim se najit data
            PcbTrace pcb = m_smtFujiSvc.GetDonePcb(m_cDataDL, m_privateData.LINEID, barcode);

            if (pcb != null && !string.IsNullOrEmpty(pcb.PCBID))
            {
                Log(LogMsg.CUSTOM_DEBUG_MSG, $"FUJI osadila panel PCBID={pcb.PCBID}, PCBRECNO={pcb.PCBRECNO}, SIDE={pcb.SIDE}, STARTTIME='{pcb.STARTTIME}', ENDTIME='{pcb.ENDTIME}'.");

                LoadPcbData(pcb.PCBID, pcb.PCBRECNO, detailID);
            }
            else
                Message = Log(LogMsg.SMT_NO_PLACE_DATA, barcode);
        }

        public void LoadFujiDataForBarcode_v2(string barcode, long detailID)
        {

            PcbTrace pcb = m_smtFujiSvc.GetDonePcb(m_cDataDL, m_privateData.LINEID, barcode);

            if (pcb != null && !string.IsNullOrEmpty(pcb.PCBID))
            {
                Log(LogMsg.CUSTOM_DEBUG_MSG, $"FUJI osadila panel PCBID={pcb.PCBID}, PCBRECNO={pcb.PCBRECNO}, SIDE={pcb.SIDE}, STARTTIME='{pcb.STARTTIME}', ENDTIME='{pcb.ENDTIME}'.");

                LoadPcbData_v2(pcb.PCBID, pcb.PCBRECNO, detailID);
            }
            else
                Message = Log(LogMsg.SMT_NO_PLACE_DATA, barcode);
        }

        private bool LoadPcbData(string pcbid, string pcbrecno, long? detailID)
        {
            bool r = false;
            Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nacítám data z FUJI pro panel:pcbid='{pcbid}', pcbrecno={pcbrecno}");

            Session.ProcessContext.CurrentFujiData.Board = m_smtFujiSvc.LoadBoardTraceData(m_cDataDL, m_privateData.MCID, Session.Job.DoJob.JobID, pcbid, pcbrecno);
            // nactu si pcbtrace z xtrace db                                    
            if (!Session.ProcessContext.CurrentFujiData.HasBoard)
            {
                // nenacetl osazovaci datapanelu z fuji db
                Log(LogMsg.CUSTOM_WARNING_MSG, $"Data pro panel '{pcbid}' nebyla ve FUJIDB nalezena!");
                return false;
            }
            if (!Session.ProcessContext.CurrentFujiData.Board.ISNEW)
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Opakované nactení panelu ID={Session.ProcessContext.CurrentFujiData.Board.OID},SN={Session.ProcessContext.CurrentFujiData.Board.BOARDID}");
            if (m_privateData.LastBoard.BOARDID != Session.ProcessContext.CurrentFujiData.Board.BOARDID)
            {
                BoardTrace board = Session.ProcessContext.CurrentFujiData.Board;
                // nacetl docti data stroje chyby
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Sériové císlo '{pcbid}': Strana={board.SIDE}, Pocet desek={board.NUMBLOCKS}, Pocet komponent={board.NUMCOMP}, Pocet chyb={board.NUMERRORS}.");

                // nactu osazovaci data
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nacítám osazovací data...Operace={Session.CycleContext.OperationOverride.RoutingPosIndex}, RoutingItemID={Session.CycleContext.OperationOverride.RoutingItemID}");
                int inspectedColumns = 0;
                int inspectedRows = 0;
                board.Placements = m_smtFujiSvc.LoadPlacementData(m_cDataDL, SetError, m_privateData.MCID, Session.Job.DoJob.JobID, pcbid, Session.WorkPlace.WorkPlaceID, Session.CycleContext.OperationOverride.RoutingPosIndex, Session.CycleContext.OperationOverride.OID, Session.CycleContext.OperationOverride.Name, Session.UserWorkGroup.MainUser.UserID, detailID, Session.CycleContext.OperationOverride.RoutingItemID, Session.UserWorkGroup.UserWorkGroupID, out inspectedColumns, out inspectedRows, true);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Placements count={board.Placements.Count}, inspectedRows={inspectedRows}, inspectedColumns={inspectedColumns}");

                // nacist device data (pocty, chyby)
                Message = Log(LogMsg.CUSTOM_INFO_MSG, "Nacítám device trace data...");
                List<DeviceTrace> deviceTrace = m_smtFujiSvc.LoadDeviceTrace(m_cDataDL, m_privateData.MCID, board.JOBID, board.BOARDID);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Device trace count={deviceTrace.Count}");
                Session.ProcessContext.CurrentFujiData.DeviceTrace.NewTraceData(deviceTrace);
                Session.ProcessContext.CurrentFujiData.SummarizedTraceData.SummarizeDeviceTrace(Session.ProcessContext.CurrentFujiData.DeviceTrace.SourceData);
                /*
                foreach (var d in m_FujiTrace.SummarizedTraceData.DeviceTraceSummary)
                    Log(LogMsg.CUSTOM_INFO_MSG, $"SUM****DEVICEKEY={d.DEVICEKEY}, FEEDCOUNT={d.KPIFEEDCOUNT.GetLastValue()}+{d.KPIFEEDCOUNT.LastIncrement}=>{d.KPIFEEDCOUNT.GetValue()}, REJECTPARTS={d.KPIREJECTPARTS.GetLastValue()}+{d.KPIREJECTPARTS.LastIncrement}=>{d.KPIREJECTPARTS.GetValue()}, NOPICKUPCOUNT={d.KPINOPICKUPCOUNT.GetLastValue()}+{d.KPINOPICKUPCOUNT.LastIncrement}=>{d.KPINOPICKUPCOUNT.GetValue()}, PICKUPERRORS={d.KPIPICKUPERRORS.GetLastValue()}+{d.KPIPICKUPERRORS.LastIncrement}=>{d.KPIPICKUPERRORS.GetValue()}, VISIONERRORS={d.KPIVISIONERRORS.GetLastValue()}+{d.KPIVISIONERRORS.LastIncrement}=>{d.KPIVISIONERRORS.GetValue()} ****");
                */
                //foreach (var d in m_FujiTrace.SummarizedTraceData.DeviceTraceTotalSummary)
                var t = Session.ProcessContext.CurrentFujiData.SummarizedTraceData.DeviceTraceTotalSummary;
                //Log(LogMsg.CUSTOM_INFO_MSG, $"SUMTOTAL****DEVICEKEY={t.DEVICEKEY}, FEEDCOUNT={t.KPIFEEDCOUNT.GetLastValue()}+{t.KPIFEEDCOUNT.LastIncrement}=>{t.KPIFEEDCOUNT.GetValue()}, REJECTPARTS={t.KPIREJECTPARTS.GetLastValue()}+{t.KPIREJECTPARTS.LastIncrement}=>{t.KPIREJECTPARTS.GetValue()}, NOPICKUPCOUNT={t.KPINOPICKUPCOUNT.GetLastValue()}+{t.KPINOPICKUPCOUNT.LastIncrement}=>{t.KPINOPICKUPCOUNT.GetValue()}, PICKUPERRORS={t.KPIPICKUPERRORS.GetLastValue()}+{t.KPIPICKUPERRORS.LastIncrement}=>{t.KPIPICKUPERRORS.GetValue()}, VISIONERRORS={t.KPIVISIONERRORS.GetLastValue()}+{t.KPIVISIONERRORS.LastIncrement}=>{t.KPIVISIONERRORS.GetValue()} ****");

                SaveDeviceTraceKPI(t);

                var tt = Session.ProcessContext.CurrentFujiData.SummarizedTraceData.DeviceTraceSummaryByFeeder;
                /*
                foreach (var d in tt)
                    Log(LogMsg.CUSTOM_INFO_MSG, $"SUM****FEEDER={d.FIDL}, FEEDCOUNT={d.KPIFEEDCOUNT.GetLastValue()}+{d.KPIFEEDCOUNT.LastIncrement}=>{d.KPIFEEDCOUNT.GetValue()}, REJECTPARTS={d.KPIREJECTPARTS.GetLastValue()}+{d.KPIREJECTPARTS.LastIncrement}=>{d.KPIREJECTPARTS.GetValue()}, NOPICKUPCOUNT={d.KPINOPICKUPCOUNT.GetLastValue()}+{d.KPINOPICKUPCOUNT.LastIncrement}=>{d.KPINOPICKUPCOUNT.GetValue()}, PICKUPERRORS={d.KPIPICKUPERRORS.GetLastValue()}+{d.KPIPICKUPERRORS.LastIncrement}=>{d.KPIPICKUPERRORS.GetValue()}, VISIONERRORS={d.KPIVISIONERRORS.GetLastValue()}+{d.KPIVISIONERRORS.LastIncrement}=>{d.KPIVISIONERRORS.GetValue()} ****");
                */
                SaveFeederTraceKPI(tt);
                // nacist nozzletrac data
                Message = Log(LogMsg.CUSTOM_INFO_MSG, "Nacítám nozzle trace data...");
                List<NozzleTrace> nozzleTrace = m_smtFujiSvc.LoadNozzleTrace(m_cDataDL, m_privateData.MCID, board.JOBID, board.BOARDID);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nozzle trace count={nozzleTrace.Count}");

                // porovnat nactene komponenty s kusovnikem, jestli to sedi (porovnat jen SX soucastky nebo aj reference?)
                if (m_privateData.CompareWithBom)
                {
                    // funcke by mela vratit seznam co nesedi. pokud je prazdny pak je to OK, jinak by mela vypsat rozdili a pripadne zavolat alarm
                    ComparePlacementDataWithBom(board);
                }
                // zapis ProductionData pro KPI..

                // zapamatovat posledni panel
                m_privateData.LastBoard = board;

                // pokud se jedna o davkou - NOREAD potom zapisu osazovaci data - soucastky k JobOperation
                if (board.BOARDID.ToUpper().Contains("NOREAD"))
                {
                    Message = Log(LogMsg.SMD_CHECKER_Noread);

                    Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "NOREADS")?.Inc();

                    SaveJobOperationInputBach(board);
                }

                r = true;
            }

            return r;
        }

        private bool LoadPcbData_v2(string pcbid, string pcbrecno, long? detailID)
        {
            bool r = false;
            Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nacítám data z FUJI pro panel:pcbid='{pcbid}', pcbrecno={pcbrecno}");
            Session.ProcessContext.CurrentFujiData.Board = m_smtFujiSvc.LoadBoardTraceData(m_cDataDL, m_privateData.MCID, Session.Job.DoJob.JobID, pcbid, pcbrecno);

            //m_smtFujiSvc.InsertBarcodeToQueue(m_cDataDL, m_privateData.MCID, Session.Job.DoJob.JobID, Session.CycleContext.OperationOverride.RoutingPosIndex, detailID, pcbid, Session.CycleContext.OperationOverride.Side);

            // nactu si pcbtrace z xtrace db                                    
            if (!Session.ProcessContext.CurrentFujiData.HasBoard)
            {
                // nenacetl osazovaci datapanelu z fuji db
                Log(LogMsg.CUSTOM_WARNING_MSG, $"Data pro panel '{pcbid}' nebyla ve FUJIDB nalezena!");
                return false;
            }
            if (!Session.ProcessContext.CurrentFujiData.Board.ISNEW)
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Opakované nactení panelu ID={Session.ProcessContext.CurrentFujiData.Board.OID},SN={Session.ProcessContext.CurrentFujiData.Board.BOARDID}");

            if (m_privateData.LastBoard.BOARDID != Session.ProcessContext.CurrentFujiData.Board.BOARDID)
            {
                BoardTrace board = Session.ProcessContext.CurrentFujiData.Board;
                // nacetl docti data stroje chyby
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Sériové císlo '{pcbid}': Strana={board.SIDE}, Pocet desek={board.NUMBLOCKS}, Pocet komponent={board.NUMCOMP}, Pocet chyb={board.NUMERRORS}.");

                // nactu osazovaci data
                /*
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nacítám osazovací data...Operace={Session.CycleContext.OperationOverride.RoutingPosIndex}, RoutingItemID={Session.CycleContext.OperationOverride.RoutingItemID}");
                int inspectedColumns = 0;
                int inspectedRows = 0;
                board.Placements = m_smtFujiSvc.LoadPlacementData(m_cDataDL, SetError, m_privateData.MCID, Session.Job.DoJob.JobID, pcbid, Session.WorkPlace.WorkPlaceID, Session.CycleContext.OperationOverride.RoutingPosIndex, Session.CycleContext.OperationOverride.OID, Session.CycleContext.OperationOverride.Name, Session.UserWorkGroup.MainUser.UserID, detailID, Session.CycleContext.OperationOverride.RoutingItemID, Session.UserWorkGroup.UserWorkGroupID, out inspectedColumns, out inspectedRows, true);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Placements count={board.Placements.Count}, inspectedRows={inspectedRows}, inspectedColumns={inspectedColumns}");
                */

                // nacist device data (pocty, chyby)
                Message = Log(LogMsg.CUSTOM_INFO_MSG, "Nacítám device trace data...");
                List<DeviceTrace> deviceTrace = m_smtFujiSvc.LoadDeviceTrace(m_cDataDL, m_privateData.MCID, board.JOBID, board.BOARDID);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Device trace count={deviceTrace.Count}");
                Session.ProcessContext.CurrentFujiData.DeviceTrace.NewTraceData(deviceTrace);
                Session.ProcessContext.CurrentFujiData.SummarizedTraceData.SummarizeDeviceTrace(Session.ProcessContext.CurrentFujiData.DeviceTrace.SourceData);

                var t = Session.ProcessContext.CurrentFujiData.SummarizedTraceData.DeviceTraceTotalSummary;
                SaveDeviceTraceKPI(t);

                var tt = Session.ProcessContext.CurrentFujiData.SummarizedTraceData.DeviceTraceSummaryByFeeder;
                SaveFeederTraceKPI(tt);

                // nacist nozzletrac data
                Message = Log(LogMsg.CUSTOM_INFO_MSG, "Nacítám nozzle trace data...");
                List<NozzleTrace> nozzleTrace = m_smtFujiSvc.LoadNozzleTrace(m_cDataDL, m_privateData.MCID, board.JOBID, board.BOARDID);
                Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Nozzle trace count={nozzleTrace.Count}");

                // porovnat nactene komponenty s kusovnikem, jestli to sedi (porovnat jen SX soucastky nebo aj reference?)
                /*
                if (m_privateData.CompareWithBom)
                {
                    // funcke by mela vratit seznam co nesedi. pokud je prazdny pak je to OK, jinak by mela vypsat rozdili a pripadne zavolat alarm
                    ComparePlacementDataWithBom(board);
                }
                */
                // zapis ProductionData pro KPI..

                // zapamatovat posledni panel
                m_privateData.LastBoard = board;

                // pokud se jedna o davkou - NOREAD potom zapisu osazovaci data - soucastky k JobOperation
                /*
                if (board.BOARDID.ToUpper().Contains("NOREAD"))
                {
                    Message = Log(LogMsg.SMD_CHECKER_Noread);

                    Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "NOREADS")?.Inc();

                    SaveJobOperationInputBach(board);
                }
                */
                r = true;
            }

            return r;
        }

        private void SaveJobOperationInputBach(BoardTrace board)
        {
            Session.JobOperation.Clear();
            Session.JobOperation.OperationCD = Session.CycleContext.OperationOverride.OperationCD;
            Session.JobOperation.DoJobOperation.UseRouting = false;
            Session.JobOperation.Create(true);

            var prodSvc = Session.Resolve<ProductService>();
            List<Placement> ibNoMatch = new List<Placement>();
            //foreach (var p in board.Placements.GroupBy(g => g.DID).Select(x => x.First()).ToList())
            foreach (var p in board.Placements)
            {
                DOInputBatch ib = new DOInputBatch(this);
                string batchCD = string.Empty;

                var product = prodSvc.GetProduct(m_dxSession, p.ComponentCD);
                if (product != null)
                {
                    bool inputBatchExists = ib.Load(p.BatchCD, product.OID);
                    if (inputBatchExists)
                    {
                        // zavedu sarzi
                        Session.JobOperation.AddInputBatch(ib, true);
                    }
                    else
                    {
                        ibNoMatch.Add(p);
                    }
                }
                else
                {
                    Message = Log(LogMsg.CUSTOM_WARNING_MSG, $"Soucástka {p.ComponentCD} nenelazene v císelníku MES!");
                }
            }
            // priradim operaci carovy kod a ukoncim ji
            Session.JobOperation.DoJobOperation.AssignBarcode(board.BOARDID);
            Session.JobOperation.DoJobOperation.QtyPass = 0;//(int)board.NUMBLOCKS; // pocet desek na panelu
            Session.JobOperation.DoJobOperation.QtyFail = 0;
            Session.JobOperation.DoJobOperation.QtyScrap = 0;
            Session.JobOperation.Update();
            Session.JobOperation.DoJobOperation.EndOperationType = DOJobOperation.JobEndOperationType.CallRelease;
            Session.JobOperation.End(false, 1, false, false, false);

            if (ibNoMatch.Count > 0)
            {
                Message = Log(LogMsg.CUSTOM_WARNING_MSG, $"Nekteré šarže neexistují v MES a nebyly uložené: {string.Join(",", ibNoMatch.Select(x => x.DID).ToArray())}");
            }
        }
        private void LoadMachineStatus()
        {
            l_MachineStatus.Clear();

            using (var uw = new UnitOfWork(m_cDataDL))
            {
                SelectedData selectedData = uw.ExecuteQuery($"SELECT MCID, MODULENO, LINENAME FROM dbo.FUJIDB_MACHINESTATUS2");
                foreach (SelectStatementResultRow row in selectedData.ResultSet[0].Rows)
                {
                    var machineStatus = new MachineStatus();
                    machineStatus.MCID = row.Values[0].ToString();
                    machineStatus.MODULENO = (decimal)row.Values[1];
                    machineStatus.LINENAME = row.Values[2].ToString();
                    l_MachineStatus.Add(machineStatus);
                }
            }
        }
        private bool StartThread()
        {
            if (!m_enabled || m_privateData.ScanInterval == 0) return false;
            m_parseThread = new Thread(new ThreadStart(ParseThread2));
            m_parseThread.Name = "SMTFUJIParseThread";
            m_parseThread.IsBackground = true;
            m_parseThread.Start();
            return true;
        }

        private void ParseThread()//zde beží samostatné background vlákno
        {
            int warningRepeat = 3;

            Message = Log(LogMsg.CUSTOM_DEBUG_MSG, $"Spuštím parsovací vlákno z FUJI DB pro MCID={m_privateData.MCID}");

            m_shouldStop = false;


            while (!m_shouldStop)
            {
                try
                {
                    //TO-DO
                    if (!Session.Job.DoJob.IsSelected)
                    {
                        StopThread();
                    }
                    // nactu status stroje - jednotlive moduly
                    LoadMachineStatus();

                    Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "TICKS")?.Inc();

                    int maxModuleNo = 0;

                    if (l_MachineStatus.Any(o => o.MCID == m_privateData.MCID))
                    {
                        maxModuleNo = (int)l_MachineStatus.Where(o => o.MCID == m_privateData.MCID).Max(o => o.MODULENO);
                    }

                    if (maxModuleNo == 0 && warningRepeat-- > 0)
                    {
                        Log(LogMsg.CUSTOM_WARNING_MSG, $"Nepodarilo se nacíst Max(MODULENO) na MCID {m_privateData.MCID}. Osazené panely se nebudou automaticky zpracovávat.");
                        Thread.Sleep(m_privateData.ScanInterval);
                        continue;
                    }

                    if (m_privateData.MaxModuleNo != maxModuleNo)
                    {
                        Log(LogMsg.CUSTOM_DEBUG_MSG, $"Nastavuji parametr MaxModuleNo={maxModuleNo} pro {l_MachineStatus.Where(o => o.MCID == m_privateData.MCID).FirstOrDefault().LINENAME}");
                        m_privateData.MaxModuleNo = maxModuleNo;
                    }
                    DateTime lastScanDT = m_privateData.LastScanDT;

                    List<PcbTrace> newPcbs = m_smtFujiSvc.GetDonePcbs(m_cDataDL, m_privateData.MCID, m_privateData.LastScanDT, maxModuleNo.ToString());

                    if (newPcbs.Count > 0)
                    {
                        Log(LogMsg.CUSTOM_DEBUG_MSG, $"{m_privateData.LastScanDT}: Celkem nalezeno panelu {newPcbs.Count}");

                        m_privateData.LastScanDT = newPcbs.Max(o => o.DTE);

                        // projdi SN desek a posli je do HandlePortData
                        foreach (var pcb in newPcbs)
                        {
                            // pokud mam nacitat jen noread a neni to noread tak nic neparsuju
                            if (m_privateData.ParseOnlyNoReads && !pcb.PCBID.ToUpper().StartsWith("NOREAD"))
                                continue;

                            Log(LogMsg.CUSTOM_DEBUG_MSG, $"FUJI osadila panel PCBID={pcb.PCBID}, PCBRECNO={pcb.PCBRECNO}, SIDE={pcb.SIDE}, STARTTIME='{pcb.STARTTIME}', ENDTIME='{pcb.ENDTIME}'.");

                            bool isPcbData = LoadPcbData(pcb.PCBID, pcb.PCBRECNO, null);

                            // nepreposilam pokud mam parsovat jen noready
                            if (!m_privateData.ParseOnlyNoReads)
                                Session.SetNextData(Session.Data.CreateReceivedData(SGDeviceType.Scanner, pcb.PCBID));
                        }
                    }
                    else
                        m_privateData.LastScanDT = DateTime.Now;
                }
                catch (Exception ex)
                {
                    Log(LogMsg.CUSTOM_WARNING_MSG, ex.Message);
                }

                Thread.Sleep(m_privateData.ScanInterval);
            }
        }

        private void ParseThread2()//zde beží samostatné background vlákno
        {

            Message = Log(LogMsg.CUSTOM_DEBUG_MSG, $"Spuštím parsovací vlákno z FUJI DB pro LINENAME={m_privateData.LINEID}");

            m_shouldStop = false;

            while (!m_shouldStop)
            {
                try
                {
                    //TO-DO
                    if (!Session.Job.DoJob.IsSelected)
                    {
                        // odnastav joba
                        if (Session.SysCommands.LastSysCommandType == SGSysCommandType.JobEnd)
                        {
                            Session.SysCommands.ClearLastSysCommandType();
                            m_smtFujiSvc.DeactivateActualJob(m_cDataDL, SetError, m_privateData.LINEID, true);                            
                        }

                        // stopni vlakno
                        StopThread();

                        break;
                    }

                    Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "TICKS")?.Inc();


                    Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Overuji nastavení linky {m_privateData.LINEID}: VO={Session.Job.DoJob.CustomerBarcode}, Operace={Session.CycleContext.OperationOverride.RoutingPosIndex}");

                    // tady prectu informaci z nastaveni jobu
                    JobTrace jobTrace = m_smtFujiSvc.GetActualJob(m_cDataDL, SetError, m_privateData.LINEID, Session.Job.DoJob.JobID, Session.CycleContext.OperationOverride.RoutingPosIndex);

                    if (jobTrace != null)
                    {
                        Session.ProcessContext.SetSmtData(new SMTData()
                        {
                            LineName = jobTrace.LineName,
                            CustomJobBD = jobTrace.CustomJobBD,
                            DTS = jobTrace.DTS,
                            RoutingPosIndex = jobTrace.RoutingPosIndex,
                            ProgramName = jobTrace.ProgramName,
                            BarcodeCount = jobTrace.BarcodeCount,
                            NoReadCount = jobTrace.NoReadCount,
                            LastBarcode = jobTrace.LastBarcode,
                            LastBarcodeDT = jobTrace.LastBarcodeDT,
                            IsActive = jobTrace.IsActive
                        });

                        FullyObservableCollection<SMTPanel> panels = m_smtFujiSvc.GetCurrentPanels(m_cDataDL, SetError, m_privateData.LINEID, Session.Job.DoJob.JobID, Session.CycleContext.OperationOverride.RoutingPosIndex);

                        Session.ProcessContext.SmtData.SetCurrentPanels(panels);

                        //Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Stav VO {jobTrace.CustomJobBD} na FUJI: Datum od={jobTrace.DTS.ToString()}, Operace={jobTrace.RoutingPosIndex}, Aktivní={jobTrace.IsActive}, Celkem barkódu={jobTrace.BarcodeCount}, Celkem NOREAD={jobTrace.NoReadCount}, Poslední barkód={jobTrace.LastBarcode} ({jobTrace.LastBarcodeDT})");
                    }
                    else
                    {
                        Message = Log(LogMsg.CUSTOM_WARNING_MSG, $"FUJI parser nemá nastaven v DB aktivní VO a operaci!");
                    }
                }
                catch (Exception ex)
                {
                    Log(LogMsg.CUSTOM_WARNING_MSG, ex.Message);
                }

                Thread.Sleep(m_privateData.ScanInterval);
            }
        }
        private void StopThread()
        {
            if (m_parseThread != null && m_parseThread.IsAlive)
            {
                Thread.Sleep(1);
                m_shouldStop = true;
                lock (m_lock) { Monitor.Pulse(m_lock); }
                m_parseThread.Join(3000);
                m_parseThread = null;
                m_threadStarted = false;
            }
        }
        /// <summary>
        /// Funkce vezme senam soucastek a referenci podle FUIJI placement dat a porovna je s tim co je v kusovniku BOM
        /// </summary>
        private List<Component> ComparePlacementDataWithBom(BoardTrace board)
        {
            // nactu si kusovník
            //List<Component> compareList = new List<Component>();
            List<Component> comparedList = new List<Component>();
            using (m_dxSession.GetLock())
            {
                // pozor na FUJI se nactitaji komponenty primo ze stroje a v kusovniku z QAD mani priznak nenactitat IsRequested=false.
                var bomComponents = (from j in new XPQuery<xdata.Job>(m_dxSession.Session)
                                     join bi in new XPQuery<xdata.JobBomItem>(m_dxSession.Session) on j.JobBom.OID equals bi.JobBom.OID
                                     where j.OID == Session.Job.DoJob.JobID && (bi.RoutingPosIndex == Session.CycleContext.OperationOverride.RoutingPosIndex) && (bi.Quantity > 0) && (bi.Deleted == false)
                                     select bi).ToList();


                // list obsahuje objekt.DID (soucastka_mesezera_sarze) a objekt.REFERENCE
                var fujiComponents = board.Placements.Select(m => new { m.ComponentCD, m.REFERENCE }).Distinct().ToList();

                // projdi koleckci porovnavej jestli je soucastka na dane referenci v kusovniku
                DOKatalogParams katalog = new DOKatalogParams(this, "Material");
                foreach (var fc in fujiComponents)
                {

                    var bc = bomComponents.FirstOrDefault(o => o.Material.ProductCD == fc.ComponentCD && o.Position.PositionCD == fc.REFERENCE);

                    // zjistit jestli se jedna o pastu
                    if (bc != null)
                    {
                        if (bc.Material.OID == 0) continue;
                        katalog.Load(bc.Material.OID);
                        if (!katalog.GetValue<bool>("IsPaste", false, true))
                        {
                            Component c = new Component() { Code = fc.ComponentCD, Reference = fc.REFERENCE, Matched = (bc != null) };
                            comparedList.Add(c);
                        }
                    }
                    else
                    {
                        Component c = new Component() { Code = fc.ComponentCD, Reference = fc.REFERENCE, Matched = false };
                        comparedList.Add(c);
                    }
                }

                int matched = comparedList.Where(o => o.Matched).Count();
                int notMatched = comparedList.Where(o => !o.Matched).Count();
                if (notMatched > 0)
                {
                    Message = Log(LogMsg.SMT_ComparePlacementError, bomComponents.Count, fujiComponents.Count, notMatched);
                    foreach (Component item in comparedList.Where(c => c.Matched == false))
                    {
                        Message = Log(LogMsg.CUSTOM_INFO_MSG, $"Soucástka: {item.Code}, reference: {item.Reference}");
                    }
                }

            }


            return comparedList;
        }
        public void CreateKPIs()
        {
            SetOK();
            try
            {
                Session.CycleContext.ClearKPICounters();
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("TICKS"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("FEEDCOUNT"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("REJECTPARTS"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("NOPICKUPCOUNT"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("PICKUPERRORS"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("VISIONERRORS"));
                Session.CycleContext.KPICounters.Add(new Model.KPIData.KPICounter("NOREADS"));
            }
            catch (Exception ex)
            {
                SetError(ex);
            }
            LogResult();
        }
        private void SaveDeviceTraceKPI(DeviceTrace deviceTrace)
        {
            SetOK();
            try
            {
                Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "FEEDCOUNT")?.SetValue(deviceTrace.KPIFEEDCOUNT.GetValue());
                Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "REJECTPARTS")?.SetValue(deviceTrace.KPIREJECTPARTS.GetValue());
                Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "NOPICKUPCOUNT")?.SetValue(deviceTrace.KPINOPICKUPCOUNT.GetValue());
                Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "PICKUPERRORS")?.SetValue(deviceTrace.KPIPICKUPERRORS.GetValue());
                Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == "VISIONERRORS")?.SetValue(deviceTrace.KPIVISIONERRORS.GetValue());
            }
            catch (Exception ex)
            {
                SetError(ex);
            }
            LogResult();
        }
        private void SaveFeederTraceKPI(List<DeviceTrace> feederTraceList)
        {
            SetOK();
            try
            {
                foreach (var f in feederTraceList)
                {
                    var kpi = Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == f.FIDL);
                    if (kpi == null)
                    {
                        Model.KPIData.KPIHourCounter hourKPI = new Model.KPIData.KPIHourCounter(f.FIDL);
                        hourKPI.OnCounterOverflow += HourKPI_OnCounterOverflow;
                        hourKPI.StartMonitor(5);
                        Session.CycleContext.KPICounters.Add(hourKPI);
                    }
                    else
                        Session.CycleContext.KPICounters.FirstOrDefault(k => k.Name == f.FIDL)?.SetValue(f.KPIPICKUPERRORS.GetValue());
                }
            }
            catch (Exception ex)
            {
                SetError(ex);
            }
            LogResult();
        }

        private void HourKPI_OnCounterOverflow(object sender, Model.KPIData.HourCounterEventArgs e)
        {
            var k = sender as Model.KPIData.KPIHourCounter;
            TimeSpan ts = e.Timestamp - e.ChangeCounter.State.FirstChangeTime;
            string elapsetTime = string.Format("{0:2d}:{1:2d}", ts.Hours, ts.Minutes);
            Log(LogMsg.CUSTOM_WARNING_MSG, $"Prekrocen pocet ({e.HourCounter.MaxChangesCount}) chybných osazení po sobe behem {e.HourCounter.HourInterval} hodin. Feeder:{e.HourCounter.Name}, Uplynulý cas:{elapsetTime}, {e.HourCounter.FirstValue}/{e.HourCounter.GetValue()}/{e.HourCounter.GetValue() - e.HourCounter.FirstValue}");
            if (m_privateData.m_SendPickOffErrorAlarm)
                Session.Alarm.SendAlarm(Session.UserWorkGroup.MainUser.UserID, SGAlarmEventType.None, "A20040", Session.Job.DoJob.CustomerBarcode, Session.Job.DoProduct.Barcode, string.Join(",", Session.UserWorkGroup.LoggedUsers.Select(u => u.Name)), e.HourCounter.Name, string.Format("Interval:1h, Parametr:PICKUPERRORS, Pocet:{0}", e.HourCounter.GetValue()));
        }
        /// <summary>
        /// Vrat nastaveni hlavniho programu
        /// </summary>
        /// <param name="programName"></param>
        /// <returns></returns>
        private string GetProgramName(string programName = null)
        {
            return (string.IsNullOrEmpty(programName)) ? Session.Job.DoProduct.GmsKatalogParams["Hlavní program"] : programName;
        }
    }
}