using Advantech.Motion;
using AssistClient.Device;
using AssistClient.Utils;
using Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenCvSharp;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Yolo5;
using static AssistClient.Device.SizeLib;
using Point = System.Drawing.Point;
using Size = System.Drawing.Size;

namespace AssistClient
{
    public partial class FrmMain : Form
    {
        #region pic缩放变量 
        private double ratio = 1;        // 图片的起始显示比例
        private double ratioStep = 0.1;
        private Size pic_size;
        private int xPos;
        private int yPos;
        #endregion

        private bool bExitApp = false;
        private Service.ProductService svcProduct = new Service.ProductService();
        private Service.OrderService svcOrder = new Service.OrderService();

        private DevContainer devContainer = new DevContainer(); 
        private Queue<scannerCBmpLoc> scannerCBmpQueue = new Queue<scannerCBmpLoc>();
        private int sizeBmpNum = 0, sizeBmpNumResult = 0;
        private string SN = "";
        private int OrderId = 0;
        private JArray currProductMarkList = null;
        private int currDefectIndex = 0;
        private List<DefectStruct> defectList = new List<DefectStruct>();
        private Dictionary<string, Bitmap> defectBmpsDir = new Dictionary<string, Bitmap>();
        private Dictionary<string, List<List<string>>> defectInfoDir = new Dictionary<string, List<List<string>>>();

        private object myLock = new object();
        private WarningEnum warningLevel;//警告等级
        private CurrentPTEnum currentPT;//当前点位
        private CurrentStateEnum currentState;//当前状态 
        /// <summary>
        /// 当前流程ID，暂停/继续时使用
        /// </summary>
        private int currProcessIndex = -1; 
        private Models.Product currProductModel = null;//当前产品   
        private System.Timers.Timer timer = new System.Timers.Timer();
        Yolo_Class yolo = new Yolo_Class();
        private class scannerCBmpLoc
        {
            public scannerCBmpLoc(string path, double posX, double posY)
            {
                Path = path;
                PosX = posX;
                PosY = posY;
            }
            public scannerCBmpLoc(Bitmap bmp, double posX, double posY)
            {
                BMP = bmp;
                PosX = posX;
                PosY = posY;
            }
            public Bitmap BMP { get; private set; }
            public string Path { get; private set; }
            public double PosX { get; private set; }
            public double PosY { get; private set; }
        }
        public FrmMain()
        {
            InitializeComponent();
            this.tsbtnCloseDev.Visible = false;

            this.dgvProcess.AutoGenerateColumns = false;
            //显示行号与列宽度自动调整
            dgvProcess.RowHeadersVisible = true;
            dgvProcess.RowHeadersWidth = 30;
            dgvProcess.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
            //dgvProcess.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;//数据量过百绑定太变
            dgvProcess.RowPostPaint += (sender, e) =>//序号列头
            {
                Utils.Util.showRowNum_onDataGrid_RowPostPaint(this.dgvProcess, sender, e);
            }; 

            for (int i = 0; i < dgvProcess.Columns.Count; i++)//禁止点击列头排序 
                dgvProcess.Columns[i].SortMode = DataGridViewColumnSortMode.NotSortable;

            //模糊查询,Append自动填充 
            this.cbxSN.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            this.cbxSN.AutoCompleteSource = AutoCompleteSource.ListItems;
        }
        private void FrmMain_Load(object sender, EventArgs e)
        {
            this.tsbtnPause.Left = this.tsbtnGoDownPT.Left = this.gpbZAxis.Right + 10;     

        }
        private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止设备后才能关闭程序！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                e.Cancel = true;
                return;
            }

            if (!bExitApp && MessageBox.Show($"确认退出？", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
            {
                e.Cancel = true;
                return;
            }
        }
        private void FrmMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            Application.Exit();
            System.GC.Collect();
            System.Environment.Exit(0);
        }

        #region 菜单
        private void 设备调试ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止设备，再进行设备调试！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmDebug3 frm = new FrmDebug3();
            frm.ShowDialog();
        }
        private void tsMenuPTSetting_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止设备，再进行设置！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmPTSetting from = new FrmPTSetting();
            from.ShowDialog();
        }
        private void tsMenuCmdSetting_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止设备，再进行设置！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmCMDProcess from = new FrmCMDProcess();
            from.ShowDialog();
        }
        private void tsMenuSysSetting_Click(object sender, EventArgs e)
        {
            FrmSysSetting frmSetting = new FrmSysSetting();
            frmSetting.ShowDialog();
        }
        #endregion
        #region 图片缩放控制
        private void reloadPic(Bitmap bmp, List<List<string>> defectInfor2RepairTable, DefectStruct defectInfo)
        {
            //绘框
            if (defectInfor2RepairTable != null && defectInfo != null)
            {
                AddTextEvent("更新图片", $"打标:MainX={defectInfo.MainX},MainY={defectInfo.MainY},CurrIndex={defectInfo.CurrIndex},RepairTable={JsonConvert.SerializeObject(defectInfor2RepairTable)}", warningLevel);
                Mat mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmp);
                mat = yolo.RepairTableDrawRec(defectInfor2RepairTable, defectInfo.MainX, defectInfo.MainY, defectInfo.CurrIndex, mat);
                bmp = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat);
            }
            else
                AddTextEvent("更新图片", "无需打标！", warningLevel);
            var newSize = Util.getNewSize(splitContainer2.Panel1.ClientSize, new Size(bmp.Size.Width*100, bmp.Size.Height*100));
            this.pnlPic.Size = newSize;
            if (this.pnlPic.Width < splitContainer2.Panel1.ClientSize.Width)
                this.pnlPic.Left = (splitContainer2.Panel1.ClientSize.Width - this.pnlPic.Width) / 2;
            if (this.pnlPic.Height < splitContainer2.Panel1.ClientSize.Height)
                this.pnlPic.Top = (splitContainer2.Panel1.ClientSize.Height - this.pnlPic.Height) / 2;

            ratio = 1.0;// 图片的起始显示比例 
            Size size = bmp.Size;

            //this.pnlPic.ClientSize = new Size(this.pnlPic.ClientSize.Width,
            //    (int)(this.pnlPic.ClientSize.Width * (size.Height * 1.0f / size.Width)));
            this.pictureBox1.Size = this.pnlPic.ClientSize;
            this.pictureBox1.Location = new Point(0, 0);
            this.pictureBox1.Image = bmp;//del

            pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
            //2023-12-22 之前工程师写的，存在bug，屏蔽
            pictureBox1.MouseWheel += new MouseEventHandler(pictureBox1_MouseWheel);
            pictureBox1.MouseMove += pictureBox1_MouseMove;
            pictureBox1.MouseDown += pictureBox1_MouseDown;
            this.ActiveControl = this.pictureBox1; // 设置焦点   

            pic_size = this.pnlPic.ClientSize;
        }
        private void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
        {
            //Point pictureBoxPoint = this.PointToClient(Cursor.Position);
            ////this.Text = $"{e.X}:{e.Y}; {pictureBoxPoint.X}:{pictureBoxPoint.Y}; {pictureBox1.Left}:{pictureBox1.Top}; " +
            ////    $"{this.pictureBox1.Width}:{this.pictureBox1.Height}; {this.Width}:{this.Height}; " +
            ////    $"Cursor:{Cursor.Position.X}:{Cursor.Position.Y}";
            //if (e.Delta > 0)
            //{
            //    ratio += ratioStep;
            //    if (ratio > 100) // 放大上限
            //        ratio = 100;
            //    else
            //    {
            //        int x = (int)(pictureBox1.Left - e.X * (e.X * 1.0d / pictureBox1.Width * ratioStep) + 0.5);
            //        int y = (int)(pictureBox1.Top - e.Y * (e.Y * 1.0d / pictureBox1.Height * ratioStep) + 0.5);

            //        //this.Text = $"{pictureBox1.Width}-{pic_size.Width};{ratio}，{pictureBox1.Left}-{e.X}* 比例:{e.X*1.0d/pictureBox1.Width}={e.X}/{pictureBox1.Width} * {(ratioStep)}={x} | " +
            //        //    $"{pictureBox1.Top}-{e.Y}* {e.Y * 1.0f / pictureBox1.Height * (ratio - 1.0d)}={y}"; 
            //        this.changePictureBoxSize(new Point(x, y), ratio);
            //    }

            //}
            //else if (ratio - ratioStep >= 1)
            //{
            //    ratio -= ratioStep;
            //    //if (ratio < 0.1)  // 放大下限
            //    //    ratio = 0.1;
            //    //else 
            //    int x = (int)(pictureBox1.Left + e.X * (e.X * 1.0d / pictureBox1.Width * ratioStep) + 0.5);
            //    int y = (int)(pictureBox1.Top + e.Y * (e.Y * 1.0d / pictureBox1.Height * ratioStep) + 0.5);
            //    this.changePictureBoxSize(new Point(x, y), ratio);
            //}
        }
        private void changePictureBoxSize(Point location, double ratio)
        {
            //var picSize = pictureBox1.Size;
            //picSize.Width = Convert.ToInt32(picSize.Width * ratio);
            //picSize.Height = Convert.ToInt32(picSize.Height * ratio);
            //pictureBox1.Size = picSize;

            //if (location.X > 0) location.X = 0;
            //if (location.Y > 0) location.Y = 0;
            //if (picSize.Width + location.X < this.pnlPic.ClientSize.Width) location.X = -(picSize.Width - this.pnlPic.ClientSize.Width);
            //if (picSize.Height + location.Y < this.pnlPic.ClientSize.Height) location.Y = -(picSize.Height - this.pnlPic.ClientSize.Height);

            ////Point location = new Point();
            ////location.X = (this.ClientSize.Width - this.pictureBox1.Width) / 2;
            ////location.Y = (this.ClientSize.Height - this.pictureBox1.Height) / 2;
            //this.pictureBox1.Location = location;
        } 
        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            //try
            //{
            //    // 鼠标按下拖拽图片
            //    if (e.Button == MouseButtons.Left)
            //    {
            //        this.Text = $"{e.X}:{e.Y};{xPos}:{yPos}";
            //        // 限制拖拽出框
            //        if (pictureBox1.Width > this.pnlPic.ClientSize.Width
            //            || pictureBox1.Height > this.pnlPic.ClientSize.Height)
            //        {
            //            int moveX = e.X - xPos;
            //            int moveY = e.Y - yPos;
            //            if ((pictureBox1.Left + moveX) <= 0
            //                && (pictureBox1.Top + moveY) <= 0
            //                && (pictureBox1.Right + moveX) >= this.pnlPic.ClientSize.Width
            //                && (pictureBox1.Bottom + moveY) >= this.pnlPic.ClientSize.Height)
            //            {
            //                pictureBox1.Left += moveX;//设置x坐标.
            //                pictureBox1.Top += moveY;//设置y坐标. 
            //            }
            //        }
            //    }
            //}
            //catch (Exception dd)
            //{
            //    MessageBox.Show(dd.Message);
            //}
        } 
        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            //xPos = e.X;//当前x坐标.
            //yPos = e.Y;//当前y坐标.
        }
        #endregion
        
        #region 中断命令Fun
        private Thread threadProcess;
        /// <summary>
        /// 启动
        /// </summary>
        private void startCommand()
        { 
            if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.等待启动)
                return;
            if (currentPT == CurrentPTEnum.MakeTag)
            {
                AddTextEvent("启动", "打标未结束，非可启动状态！", warningLevel);
                return;
            }

            if (!devContainer.devAxis.isReady())
            {
                AddTextEvent("启动", "轴状态异常，不可启动！", warningLevel);
                return;
            }
            if (!devContainer.devAxis.IsReset)
            {
                AddTextEvent("启动", "轴需先进行复位操作！", WarningEnum.High);
                return;
            }

            devContainer.io_output(CMDName.启动按钮);
            //devContainer.io_output(CMDName.绿灯输出);
            //devContainer.io_output(CMDName.暂停按钮, false, true, 0);
            //devContainer.io_output(CMDName.红灯输出, false, true, 0);
            //devContainer.io_output(CMDName.黄灯输出, false, true, 0);

            //暂停-》继续
            if (currProcessIndex >= 0 && currentPT == CurrentPTEnum.Moving && currentState == CurrentStateEnum.等待启动)
            {
                AddTextEvent("启动", $"暂停 -> 继续 当前工序索引：{currProcessIndex + 1}");
                threadProcess = new System.Threading.Thread(() =>
                {
                    int nextStepId = currProcessIndex;
                    do
                    {
                        currentState = CurrentStateEnum.运行中;
                        nextStepId = nextProcess(currProductModel, nextStepId);
                    } while (nextStepId >= 0 && !isBreakProcessRun());
                    //nextProcess(currProductModel, currProcessIndex);
                });
                threadProcess.IsBackground = true;
                threadProcess.Start();
                this.setButtonEnabled(this.tsbtnPause, true);
                this.setButtonEnabled(this.tsbtnStopNow, true);
            }
            else//开始/重新开始
            {
                //校正从复位-》运行，不会新启动 
                resetUIValue();
                AddTextEvent("启动", "移动至上料位...");
                Task.Run(() => { loadOrderSNList(); });//加载SN
                gotoUpPT();

                this.Invoke(new System.Action(() =>
                {
                    //新开始
                    runStep();
                }));
                this.setButtonEnabled(this.tsbtnPause, false);
                this.setButtonEnabled(this.tsbtnStopNow, false);
            }

            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnReset, false);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
        }
        /// <summary>
        /// 暂停
        /// </summary>
        /// <param name="buzzer">是否响蜂鸣</param>
        private void pauseCommand(bool buzzer = false)
        {
            //devContainer.io_output(CMDName.暂停按钮);
            //if (buzzer)
            //{
            //    devContainer.io_output(CMDName.红灯输出);
            //    devContainer.io_output(CMDName.蜂鸣器输出);
            //}
            //else
            //    devContainer.io_output(CMDName.黄灯输出);
            devContainer.io_output(CMDName.启动按钮, false, true, 0);
            //devContainer.io_output(CMDName.绿灯输出, false, true, 0);

            this.setButtonEnabled(this.tsbtnWarning, true);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnPause, false);
            this.setButtonEnabled(this.tsbtnStopNow, false);
            this.setButtonEnabled(this.tsbtnReset, false);
            AddTextEvent("暂停", $"当前工序索引：{currProcessIndex + 1}", WarningEnum.Low);
        }
        /// <summary>
        /// 急停
        /// </summary> 
        private void stopNowCommand()
        {
            if (!devContainer.state)
            {
                this.setButtonEnabled(this.tsbtnStopNow, false);
                return;
            }

            //devContainer.io_output(CMDName.Y轴复位输出, false, true, 0);
            //devContainer.io_output(CMDName.红灯输出);
            //if (!this.disableBuzzer) devContainer.io_output(CMDName.蜂鸣器输出);
            //devContainer.io_output(CMDName.暂停按钮, false, true, 0);
            //devContainer.io_output(CMDName.黄灯输出, false, true, 0);
            devContainer.io_output(CMDName.启动按钮, false, true, 0);
            //devContainer.io_output(CMDName.绿灯输出, false, true, 0);

            this.setButtonEnabled(this.tsbtnWarning, true);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnPause, false);
            this.setButtonEnabled(this.tsbtnStopNow, false);
            this.setButtonEnabled(this.tsbtnReset, false);
            if(currentState== CurrentStateEnum.运行中)
                AddTextEvent("急停", $"当前工序索引：{currProcessIndex + 1}", WarningEnum.High);
            else
                AddTextEvent("急停", $"已急停，请人工处理。", WarningEnum.High);
        }
        private bool reseting = false;
        private void resetCommand()
        {
            currentState = CurrentStateEnum.等待复位;
            warningLevel = WarningEnum.Normal;
            currentPT = CurrentPTEnum.InitPT;
            this.Invoke(new System.Action(() =>
            {
                this.tsbtnWarning.Enabled = this.tsbtnGoDownPT.Enabled = this.tsbtnStart.Enabled 
                    = this.tsbtnStopNow.Enabled = this.tsbtnPause.Enabled = this.tsbtnReset.Enabled 
                    = this.gpbXYAxis.Enabled = gpbZAxis.Enabled = false;
            }));

            try
            {
                if (devContainer.state && !reseting)
                {
                    reseting = true; 
                    resetUIValue();
                    AddTextEvent("复位", $"设备复位中..."); 

                    this.setButtonEnabled(this.tsbtnReset, false);
                    if ((AxisState)devContainer.devAxis.AxState[0] == AxisState.STA_AX_EXT_JOG) devContainer.devAxis.closeJogMode(0);//关闭jog时自动停止
                    if ((AxisState)devContainer.devAxis.AxState[1] == AxisState.STA_AX_EXT_JOG) devContainer.devAxis.closeJogMode(1);//关闭jog时自动停止
                    // I/O reset后，输出默认状态 
                    AddTextEvent("复位", $"I/O复位中...");
                    if (!devContainer.devIOCard.reset())
                    {
                        AddTextEvent("复位", "I/O板卡复位失败！", WarningEnum.High);
                        return;
                    }
                    if (!devContainer.io_output(CMDName.IO默认输出))
                    {
                        //AddTextEvent("复位", "I/O板卡复位默认值失败！", WarningEnum.High);
                        //return;
                    }

                    //板卡复位输出灯 
                    //devContainer.io_output(CMDName.复位按钮);
                    devContainer.io_output(CMDName.复位按钮, true);//闪

                    //goto ORG
                    AddTextEvent("复位", $"轴正在回原点...");
                    devContainer.devAxis.closeJogMode();
                    devContainer.devAxis.resetAxisState(-1);//reset state 
                    //AddTextEvent("复位", $"重置轴状态...");
                    for (int i = 0; i < Config.Axis_HomeMode.Length; i++)
                    {
                        //到起始位速度使用回HOME速度
                        devContainer.devAxis.setAxisVelParam((double)Config.Axis_HomeVelLow[i], (double)Config.Axis_HomeVelHigh[i],
                                 (double)Config.Axis_HomeAcc[i], (double)Config.Axis_HomeDec[i], i);
                        
                        //devContainer.devAxis.setAxisVelParam(40000,200000,5000000, 5000000,i,true);
                        devContainer.devAxis.home(i, (uint)Config.Axis_HomeMode[i], (uint)Config.Axis_HomeDir[i]); 
                    }
                    AddTextEvent("复位", $"等待轴状态完成...");
                    while (!devContainer.devAxis.isReady())
                    {
                        if (!devContainer.state || currentState != CurrentStateEnum.等待复位)
                        {
                            AddTextEvent("复位", $"当前状态：" + ((CurrentStateEnum)currentState).ToString());
                            return;
                        }
                        //AddTextEvent("复位", $"轴0状态:"+ ((AxisState)devContainer.devAxis.AxState[0]).ToString());
                        //AddTextEvent("复位", $"轴1状态:" + ((AxisState)devContainer.devAxis.AxState[1]).ToString());
                        //AddTextEvent("复位", $"轴2状态:" + ((AxisState)devContainer.devAxis.AxState[2]).ToString());
                        //AddTextEvent("复位", $"轴3状态:" + ((AxisState)devContainer.devAxis.AxState[3]).ToString());

                        Thread.Sleep(1000);
                        //Application.DoEvents();
                    }

                    //
                    if (devContainer.devAxis.isError())
                        throw new Exception("轴回原点失败！");

                    // 
                    //goto InitPT  
                    AddTextEvent("复位", $"轴回原点完成，回到初始位...");
                    JArray arrPT = Config.joPTSetting.Value<JArray>("initPT");
                    for (int i = 0; i < arrPT.Count; i++)
                    {
                        if (!devContainer.state || currentState != CurrentStateEnum.等待复位)
                            return;

                        AddTextEvent($"复位", $"轴{i}准备运动至初始位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                        devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.MODE1_Abs);
                    }
                    while (!devContainer.devAxis.isReady())
                    {
                        if (!devContainer.state || warningLevel != WarningEnum.Normal)
                            return;
                        Thread.Sleep(100);
                        //Application.DoEvents();
                    }
                    if (devContainer.devAxis.isError())
                        throw new Exception("轴移动至初始位失败！");

                    //
                    this.setButtonEnabled(this.tsbtnReset, true);
                    this.setButtonEnabled(this.tsbtnStart, true);
                    this.setButtonEnabled(this.tsbtnStopNow, true);
                    this.setButtonEnabled(this.tsbtnPause, false);
                    this.setButtonEnabled(this.tsbtnGoDownPT, false);
                    this.setButtonEnabled(this.tsbtnWarning, false);
                    this.setButtonEnabled(this.cbxSN, false);

                    this.Invoke(new System.Action(() =>
                    {//根据UI上是否勾选打开激光
                        chkLaser_CheckedChanged(null, null);
                    })); 
                    //devContainer.io_output(CMDName.复位按钮, false, true, 0);//熄灭
                    devContainer.io_output(CMDName.复位按钮);//长亮
                    currentPT = CurrentPTEnum.InitPT;
                    currentState = CurrentStateEnum.等待启动;
                    AddTextEvent("复位", $"复位完成。"); 
                }
                else
                {
                    AddTextEvent("复位", "非可复位状态，请先停止后再复位！", WarningEnum.High);
                }
            }
            catch (Exception ex)
            {
                AddTextEvent("复位", "复位失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High, true);
            }
            finally
            {
                reseting = false;
            }
        }
        /// <summary>
        /// 上料
        /// </summary>
        private void gotoUpPT()
        {
            try
            {
                JArray arrPT = Config.joPTSetting.Value<JArray>("upPT");
                for (int i = 0; i < arrPT.Count; i++)
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal)
                        return;

                    AddTextEvent($"上料", $"轴{i}准备运动至上料位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                    devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.MODE1_Abs);
                }
                while (!devContainer.devAxis.isReady())
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal)
                        return;
                    Thread.Sleep(100);
                }
                if (devContainer.devAxis.isError())
                    throw new Exception("轴移动至上料位失败！");

                //
                currentPT = CurrentPTEnum.UpPT;
                currentState = CurrentStateEnum.等待扫码;
                this.setButtonEnabled(cbxSN, true);
                AddTextEvent($"上料", "到达上料位！");
            }
            catch (Exception ex)
            {
                AddTextEvent("上料", "上料初始失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High);
            }
        }
        /// <summary>
        /// 下料（暂当完成）
        /// </summary>
        private void gotoDownPT()
        {
            try
            {
                AddTextEvent("下料", "检测状态...");
                if (!devContainer.state || warningLevel != WarningEnum.Normal)
                {
                    AddTextEvent("下料失败", "轴状态异常！", WarningEnum.Low);
                    return;
                }
                AddTextEvent("下料", "开始下料！");
                if (!devContainer.devAxis.isReady())
                {
                    AddTextEvent("下料失败", "轴状态异常！", WarningEnum.Low);
                    return;
                }

                if (currentState != CurrentStateEnum.打标中 && currentState != CurrentStateEnum.等待启动 && currentState != CurrentStateEnum.自动流程结束)
                {
                    AddTextEvent($"下料", $"非可下料状态:{currentState.ToString()}");
                    return;
                }

                currentState = CurrentStateEnum.下料中; 

                JArray arrPT = Config.joPTSetting.Value<JArray>("downPT");
                for (int i = 0; i < arrPT.Count; i++)
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.下料中)
                        return;

                    AddTextEvent($"下料", $"轴{i}准备运动至下料位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                    devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.MODE1_Abs);
                }

                while (!devContainer.devAxis.isReady())
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.下料中)
                        return;
                    Thread.Sleep(100);
                }
                if (devContainer.devAxis.isError())
                    throw new Exception("轴移动至下料位失败！");

                //
                currentPT = CurrentPTEnum.DownPT;
                currentState = CurrentStateEnum.等待启动; 
                devContainer.io_output(CMDName.启动按钮, false, true, 0);
                devContainer.devLight.setDigitalValue(1, 0);//关闭上光源
                devContainer.devLight.setDigitalValue(2, 0);//关闭下光源
                this.Invoke(new System.Action(() =>
                {
                    this.chkLaser.Checked = false;
                    this.gpbXYAxis.Enabled = gpbZAxis.Enabled = false;
                }));
                setButtonEnabled(tsbtnStart, true);
                setButtonEnabled(tsbtnGoDownPT, false);
                AddTextEvent($"下料", "下料完成！");

                //更新指定列
                //2023-10-31 加入异常情况
                FrmRepairInfo frmInfo = new FrmRepairInfo();
                DialogResult dr = frmInfo.ShowDialog();

                if (svcOrder.Update(it => new Order() { 
                    State = dr == DialogResult.OK? 10 :5 , 
                    Abnormalities = frmInfo.RepairInfo,
                    RepairCode = Config.loginUser.Code
                }, it => it.Id == OrderId))
                    AddTextEvent($"检测结束", $"保存检测结果成功。");
                else
                    AddTextEvent($"检测结束", $"保存检测结果失败！",WarningEnum.High);
            }
            catch (Exception ex)
            {
                AddTextEvent("下料", "下料失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High);
            }
        }
        private void openLowerLight()//下光源
        {
            var value = devContainer.devLight.getDigitalValue(2);
            devContainer.devLight.setDigitalValue(2, value == 0 ? 255 : 0);
        }
   
        private void setButtonEnabled(ToolStripButton button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }
        private void setButtonEnabled(ToolStripMenuItem button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }
        private void setButtonEnabled(Control button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }
        #endregion
        #region 自动流程 nextProcess
        /// <summary>
        /// 中断工序运行
        /// </summary>
        /// <returns></returns>
        private bool isBreakProcessRun()
        {
            return warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.运行中;
        }
        private void runStep()
        {
            sizeBmpNum = sizeBmpNumResult = 0;
            AddTextEvent("启动", "等待网版编码扫码..."); 
            if (devContainer.devCodeScanner != null)
            {
                devContainer.devCodeScanner.stop();
                devContainer.devCodeScanner = null;
            }
            devContainer.devCodeScanner = new CodeScannerDev();
            if (!devContainer.devCodeScanner.start())
            {
                AddTextEvent("扫码枪", "扫码枪初始化失败！");
                return;
            }
            devContainer.devCodeScanner.ScanerEvent = (code) =>
            {
                if (devContainer.devCodeScanner.isLock ||!devContainer.state 
                    || currentState != CurrentStateEnum.等待扫码 || string.IsNullOrWhiteSpace(code) )
                    return;

                devContainer.devCodeScanner.isLock = true;
                Thread threadtest = new System.Threading.Thread(() =>
                {
                    try
                    {
                        //注：需要重复输入SN时不能在此释放
                        //devContainer.devCodeScanner.stop();
                        //devContainer.devCodeScanner = null;

                        //
                        AddTextEvent("扫码枪", $"开始读取网版编码:{code} 数据...");
                        var modOrder = svcOrder.GetModelNav(code);//from sn
                        //AddTextEvent("扫码枪", $"读取网版编码:{code}DB数据完成");
                        string modeState = "";
                        if (modOrder == null)
                            AddTextEvent("扫码枪", $"网版编码{code}不存在!");
                        if (modOrder.State == 5)
                        {
                            modeState = $"网版编码{code}已被检测!";
                            AddTextEvent("扫码枪", $"网版编码{code}已被检测!"); 
                            loadOrderSNList(); 
                         }
                        else if (modOrder.State == 10)
                        {
                            modeState = $"网版编码{code}已被检测, 并且异常!";
                            AddTextEvent("扫码枪", $"网版编码{code}已被检测, 并且异常!");
                            loadOrderSNList();
                        }
                        if(!string.IsNullOrEmpty(modeState))
                        {
                            if(DialogResult.No ==  MessageBox.Show(modeState + "是否重新检测！", "网版已被检测", MessageBoxButtons.YesNo, MessageBoxIcon.Information))
                            {
                                return;
                            }
                        }
                        //2023-10-20 
                        //else if (modOrder.ProductInfo == null)
                        if (modOrder.ProductInfo == null)
                            AddTextEvent("扫码枪", $"网版编码:{code}对应产品信息已不存在!");
                        else if (modOrder.DefectCount < 1)
                            AddTextEvent("扫码枪", $"网版编码:{code}没有缺陷!");
                        //else if (string.IsNullOrWhiteSpace(modOrder.MarkData))
                        //    AddTextEvent("扫码枪", $"产品(网版编码:{code})在主机台未保存原始Mark点!");
                        else if (modOrder.ProductInfo.AssistStepInfo == null
                        || modOrder.ProductInfo.AssistStepInfo.ProcessList.Count < 1)
                            AddTextEvent("扫码枪", $"产品({modOrder.ProductInfo.Name})未配置检测流程!");
                        else
                        {
                            if (string.IsNullOrWhiteSpace(modOrder.MarkData))
                            {
                                AddTextEvent("扫码枪", $"产品(网版编码:{code})在主机台未保存原始Mark点!");
                                modOrder.MarkData = "[0,0,0,0,0,0,0,0]";
                            }
                            else
                                AddTextEvent("主机台", $"主机台Mark数据:{modOrder.MarkData}");
                            currProductMarkList = JArray.Parse(modOrder.MarkData);
                            if (currProductMarkList.Count < 8)
                            {
                                AddTextEvent("主机台", $"产品(网版编码:{code})在主机台原始Mark点数量错误!");
                                return;
                            }
                            //异步读取 
                            JObject parm = new JObject()
                            {
                                {"date",modOrder.CreateTime.ToString("yyyyMMdd") },
                                {"sn",code }
                            };
                            Task.Run(() => loadDefectInfo(parm));

                            //
                            devContainer.devCodeScanner.stop();
                            devContainer.devCodeScanner = null;

                            currentState = CurrentStateEnum.运行中;
                            currentPT = CurrentPTEnum.Moving;
                            currProductModel = modOrder.ProductInfo;
                            AddTextEvent("扫码枪", $"网版编码:{code}({currProductModel.Name} {currProductModel.Spec} [{currProductModel.Code}])");
                            this.Invoke(new System.Action(() =>
                            {
                                this.cbxSN.Enabled = false;
                                this.txtBatchId.Text = modOrder.BatchId.ToString();
                                this.txtProductName.Text = currProductModel.Name;
                                this.txtProductCode.Text = currProductModel.Code;
                                this.txtCreateTime.Text = modOrder.CreateTime.ToString("yyyy-MM-dd HH:mm");
                                //this.dgvProcess.DataSource = new BindingSource(currProductModel.StepInfo.ProcessList, null);
                                devContainer.libFor.clear();
                                devContainer.libIF.clear();
                            }));

                            //    
                            this.SN = code;
                            this.OrderId = modOrder.Id;
                            this.setButtonEnabled(this.tsbtnPause, true);
                            this.setButtonEnabled(this.tsbtnStopNow, true);
                            devContainer.devLight.setDigitalValue(2, 255);//下光源
                            int nextStepId = 0;
                            do
                            {
                                nextStepId = nextProcess(currProductModel, nextStepId);
                            } while (nextStepId >= 0 && !isBreakProcessRun());
                        }
                    }
                    catch (Exception ex)
                    {
                        AddTextEvent("扫码枪", $"读取网版编码信息失败：{ex.Message}");
                    }
                    finally
                    {
                        if (devContainer.devCodeScanner != null)
                            devContainer.devCodeScanner.isLock = false;
                    }
                });
                threadtest.IsBackground = true;
                threadtest.Start();

            };
        }
        private void loadDefectInfo(object parm)
        {
            try
            { 
                JObject req = (JObject)parm;
                currDefectIndex = 0;
                defectList.Clear();
                defectInfoDir.Clear();
                defectBmpsDir.Clear();
                //从主机台取缺陷文件名列表和JSON数组
                var obj = WebApi.getDefectFromSN(req);
                if (obj.Value<int>("code") != 200)
                    throw new Exception(obj.Value<string>("data")); 
                // 
                var defectInfo = obj.Value<JObject>("data"); //文件名列表(主机台已对文件名排序，这里不需再排序(主机台按各自index进行的排序，比对在缺陷文件名后面)
                if (defectInfo.Count < 1)
                    throw new Exception("主机台缺陷文件已不存在！");

                //string date = req.Value<string>("date");
                //string sn = req.Value<string>("sn");
                getDefectBmpList(req, defectInfo);
            }
            catch (Exception ex)
            {
                AddTextEvent("网络访问",$"{ex.Message}",WarningEnum.High);
                warning(WarningEnum.High); 
            }
            
        }
        /// <summary>
        /// 通过文件名列表取各文件名对应BMP
        /// </summary>
        /// <param name="req"></param>
        /// <param name="defectInfo">文件名列表</param>
        private void getDefectBmpList(JObject req, JObject defectInfo)
        { 
            int liStep = 0;
            try
            {
                #region 这里转换绑定没实际用处，只是为了提前显示列表
                foreach (var kv in defectInfo)
                {
                    defectInfoDir.Add(kv.Key, JsonConvert.DeserializeObject<List<List<string>>>(kv.Value.ToString()));

                    //
                    //["4","3.9","-0.8","dk","0.39"],["index","X","Y","缺陷类型","缺陷的置信度"]
                    JArray arr = kv.Value as JArray;//二维数组
                    AddTextEvent("主机台", $"缺陷大项:{kv.Key}，含小项:{arr.Count}.");
                    foreach (JArray arr2 in arr) {
                        var obj = new DefectStruct();
                        obj.Key = kv.Key;
                        obj.Index = Convert.ToInt32(arr2[0].ToString());
                        obj.X = Convert.ToDouble(arr2[1].ToString()); 
                        obj.Y = Convert.ToDouble(arr2[2].ToString());
                        obj.DefectName = Config.getDefectName(arr2[3].ToString());
                        obj.DefectCC = Convert.ToDouble(arr2[4].ToString()); 
                        defectList.Add(obj);
                    }
                }
                this.Invoke(new System.Action(() =>
                {
                    //注：这样绑定List中的对象必需得用{get;set;}方式定义属性,否则单独格为空内容
                    AddTextEvent("主机台", $"准备显示缺陷列表，共{defectList.Count}项.");
                    this.dgvProcess.DataSource =  new BindingSource(defectList, null);
                    AddTextEvent("主机台", $"显示缺陷列表完成.");
                    this.gpbDefectList.Text = $"缺陷明细({defectList.Count}条)";
                    //2023-11-1 图纸对比缺陷标红
                    for (int i = 0; i < defectList.Count; i++)
                    {
                        string defName = (string)this.dgvProcess.Rows[i].Cells["colDefectName"].Value;
                        if ((defName == Config.getDefectName("ds"))||((defName == Config.getDefectName("yx"))))
                            this.dgvProcess.Rows[i].DefaultCellStyle.BackColor = Color.Red;
                    }
                }));
                #endregion

                req.Add("file_name", "");
                liStep = 1;
                bool first = true;
                int index = 1;
                foreach (var kv in defectInfo)
                {
                    req["file_name"] = kv.Key;
                    var obj = WebApi.getDefectBmpBase64(req);
                    if (!devContainer.state || warningLevel == WarningEnum.High ||
                        (currentState != CurrentStateEnum.运行中 && currentState != CurrentStateEnum.暂停 && currentState != CurrentStateEnum.等待启动 && currentState != CurrentStateEnum.打标中)) 
                        return;
                    if (obj.Value<int>("code") != 200)
                        throw new Exception(obj.Value<string>("data")); 
                    Bitmap bmp = new Bitmap(new MemoryStream(Convert.FromBase64String(obj.Value<string>("data"))));
                    defectBmpsDir.Add(kv.Key, bmp);
                    AddTextEvent("网络访问", $"已读取第{index++}/{defectInfo.Count}张缺陷图片;");
                    if (first)
                    {
                        first = false;
                        this.Invoke(new System.Action(() =>
                        {
                            this.reloadPic(bmp,null,null);
                            pictureBox1.Tag = kv.Key;
                        }));
                    }
                } 
            }
            catch (Exception ex)
            {
                if (liStep == 0)
                {
                    AddTextEvent("网络访问", $"解析主机缺陷报文失败：{ex.Message}",WarningEnum.High);
                    warning(WarningEnum.High);
                }
                else
                    AddTextEvent("网络访问", $"读取主机台缺陷Bmp图片失败：{ex.Message}");
            } 
        }
        private void convert2LocXY()
        {
            //AddTextEvent($"缺陷项", $"转换缺陷项坐标值...");
            //  public string Key;
            //public string Key0;//index
            //public string Key1;//X
            //public string Key2;//Y
            //public string Key3;//缺陷类型
            //public string Key4;//缺陷的置信度 
            currDefectIndex = 0;
            defectList.Clear(); 
            double X, Y;
            int MainIndex;
            foreach (var item in defectInfoDir)
            {
                //Key : Defect_SN{order.SN}_I{res.index}_X{res.Xmm}_Y{res.Ymm}_C{res.defectCount}
                string[] str = item.Key.Split('_');
                MainIndex = Convert.ToInt32(str[2].Substring(1));
                X = Convert.ToDouble(str[3].Substring(1));
                Y = Convert.ToDouble(str[4].Substring(1));

                AddTextEvent("主机台", $"转换前，缺陷大项:{item.Key}，含小项:{item.Value.Count}.");
                var arr = yolo.ImageXY2RepairTable(item.Value, X, Y, Config.SizeRepairTablePath);
                AddTextEvent("主机台", $"转换后，缺陷大项:{item.Key}，含小项:{arr.Count}.");
                int i = 0;
                foreach (var arr2 in arr)
                {
                    var obj = new DefectStruct();
                    obj.Key = item.Key;
                    obj.CurrIndex = i++;
                    obj.MainIndex = MainIndex;
                    obj.MainX = X;
                    obj.MainY = Y;
                    obj.Index = Convert.ToInt32(arr2[0].ToString());
                    obj.X = Convert.ToDouble(arr2[1].ToString());
                    obj.Y = Convert.ToDouble(arr2[2].ToString());
                    obj.DefectName = Config.getDefectName(arr2[3].ToString());
                    obj.DefectCC = Convert.ToDouble(arr2[4].ToString());
                    defectList.Add(obj);
                }
            }

            //2024-03-05 将尺寸未通过排到最前
            for (int i = (defectList.Count - 1); i >=0 ; i--)
            {
                if (defectList[i].DefectName == Config.getDefectName("wtg"))
                {
                    defectList.Insert(0, defectList[i]);
                    defectList.RemoveAt(i + 1);
                }
            }
            this.Invoke(new System.Action(() =>
            {
                this.dgvProcess.DataSource = null;
                //注：这样绑定List中的对象必需得用{get;set;}方式定义属性,否则单独格为空内容
                this.dgvProcess.DataSource = new BindingSource(defectList, null);
                this.gpbDefectList.Text = $"缺陷明细({defectList.Count}条)";
            }));
            gotoDefctListIndex(currDefectIndex);
        }
        private void gotoDefctListIndex(int index,int preIndex=-1)
        {
            try
            {
                AddTextEvent($"缺陷项", $"移至缺陷项:{index + 1}...");
                this.Invoke(new System.Action(() =>
                {
                    string key = defectList[index].Key;
                    if (defectBmpsDir.ContainsKey(key))
                    {
                        if (pictureBox1.Tag == null || pictureBox1.Tag.ToString() != key)
                        {
                            //reloadPic(defectBmpsDir[key], defectInfoDir[key], defectList[index]);
                            pictureBox1.Tag = key;
                        }
                        reloadPic(defectBmpsDir[key], defectInfoDir[key], defectList[index]);
                    }
                    else
                        AddTextEvent($"缺陷项", $"缺陷图:{defectList[index].Key}还未传输完成！");

                    if (preIndex > -1)
                        this.dgvProcess.Rows[preIndex].DefaultCellStyle.BackColor = Color.White;

                    this.dgvProcess.Rows[index].DefaultCellStyle.BackColor = Color.LightGreen;
                    this.dgvProcess.Rows[index].Selected = true;
                    this.dgvProcess.CurrentCell = this.dgvProcess.Rows[index].Cells["colResult"];
                    this.dgvProcess.Rows[index].Cells["colResult"].Value = "已检";  
                }));
                if ((defectList[index].X <= 0) || (defectList[index].Y <= 0))
                {
                    AddTextEvent($"缺陷项", $"移动报错，当前缺陷项({index + 1})坐标:X{defectList[index].X},Y{defectList[index].Y}", WarningEnum.Low);
                    return;
                }

                while (!devContainer.devAxis.isReady(0)) Thread.Sleep(100);
                devContainer.devAxis.move_ptp(0, defectList[index].X, AxMoveMode.MODE1_Abs);
                while (!devContainer.devAxis.isReady(1)) Thread.Sleep(100);
                devContainer.devAxis.move_ptp(1, defectList[index].Y, AxMoveMode.MODE1_Abs);
                AddTextEvent($"缺陷项", $"移动完成，当前缺陷项({index + 1})坐标:X{defectList[index].X},Y{defectList[index].Y}");
            }
            catch (Exception ex)
            {
                AddTextEvent("缺陷项", "轴移动失败:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High, true);
            }
        }
        private void gotoNextPic()
        {
            if (currDefectIndex>= defectList.Count-1) return;
            this.Invoke(new System.Action(() =>
            { 
                for(int i= currDefectIndex+1;i< defectList.Count; i++)
                {
                    if (this.pictureBox1.Tag == null || this.pictureBox1.Tag.ToString() != defectList[i].Key)
                    { 
                        AddTextEvent($"缺陷项", $"移至下张图:{i+1}...");
                        int preIndex = currDefectIndex;
                        currDefectIndex = i;
                        gotoDefctListIndex(currDefectIndex, preIndex);
                        return;
                    }
                }
                 
            })); 
        }
        private void gotoPrePic()
        {
            if (currDefectIndex < 1) return;
            this.Invoke(new System.Action(() =>
            {
                if (this.pictureBox1.Tag == null) return;
                for (int i = currDefectIndex-1; i >=0; i--)
                {
                    if (this.pictureBox1.Tag.ToString() != defectList[i].Key)
                    {
                        AddTextEvent($"缺陷项", $"移至上张图:{i + 1}...");
                        int preIndex = currDefectIndex;
                        currDefectIndex = i;
                        gotoDefctListIndex(currDefectIndex, preIndex);
                        return;
                    }
                }

            }));
        }
        private class DefectStruct
        {
            //Defect_{order.SN}_{res.index}_X{res.Xmm}_Y{res.Ymm} 
            public string Key  { get; set; }
            public int CurrIndex { get; set; }//当前缺陷在本大图中的索引0-n
            public int MainIndex { get; set; }//大图
            public double MainX { get; set; }//大图X 大相机拍照时原始X,Y轴坐标
            public double MainY { get; set; }//大图X

            //["4","3.9","-0.8","dk","0.39"],["index","X","Y","缺陷类型","缺陷的置信度"]
            public int Index { get; set; } 
            public double X { get; set; }
            public double Y { get; set; }
            public string DefectName { get; set; }//缺陷类型
            public double DefectCC { get; set; }//缺陷的置信度 

            //2023-10-30 修复情况
            public string Restoratory { get; set; }
        }
        private double[] convert(JArray data)
        {
            double[] result = new double[data.Count];   
            for (int i = 0; i < data.Count; i++)
                result[i]=Convert.ToDouble(data[i]);
            return result;
        }
        /// <summary>
        /// 工序循环
        /// </summary>
        /// <param name="model"></param>
        /// <param name="stepIndex">0-n</param>
        /// <returns>大于等于0正常工序； -1:结束 -2：异常</returns>
        private int nextProcess(Models.Product model, int stepIndex)
        {
            try
            {
                //记录当前index
                this.currProcessIndex = stepIndex;
                //this.Invoke(new System.Action(() =>
                //{
                //    try
                //    {
                //        this.dgvProcess.Rows[stepIndex].Selected = true;
                //        dgvProcess.CurrentCell = dgvProcess.Rows[stepIndex].Cells[1];
                //    }
                //    catch { }
                //}));
                lock (myLock)
                {
                    if (isBreakProcessRun())
                        return stepIndex;
                } 

                var processList = model.AssistStepInfo.ProcessList;
                var processInfo = processList[stepIndex];
                string processName = processInfo.ProcessName;
                //AddTextEvent($"{stepIndex + 1}-{processName}", $"工序开始...");
                string jsonParams = null;//配方

                if (model.ProductProcessList != null && model.ProductProcessList.Count > 0)//使用产品配方
                {
                    ProductProcess productProcessParams = model.ProductProcessList.First(m => m.ProcessCode == processInfo.ProcessCode);
                    if (productProcessParams != null)
                    {
                        jsonParams = productProcessParams.ProcessParams;
                        AddTextEvent($"{stepIndex + 1}-{processName}", $"使用产品专用配方:{jsonParams}");
                    }
                }
                if (jsonParams == null)//使用流程默认配方
                {
                    jsonParams = processInfo.ProcessParams;
                    AddTextEvent($"{stepIndex + 1}-{processName}", $"使用流程默认配方:{jsonParams}");
                }

                //
                JObject processParam = JObject.Parse(jsonParams);
                if (!processParam.ContainsKey("Disable") || !processParam.Value<bool>("Disable"))
                {
                    AutoResetEvent endEvent;
                    uint sleepPre = processParam.Value<uint>("SleepPre");
                    uint sleepLater = processParam.Value<uint>("SleepLater");
                    if (sleepPre > 0)
                        Thread.Sleep((int)sleepPre);
                    double limitThresholdVal, lowerThresholdVal;

                    //======Switch 工序类型
                    int liStatocStepIndex = stepIndex;
                    switch (processInfo.ProcessCode)
                    {
                        case "IOCard":
                            #region
                            var direction = (IODirectionEnum)processParam.Value<int>("Direction");
                            if (direction == IODirectionEnum.输入 || direction == IODirectionEnum.输入输出)
                            {
                                uint IN_Waiting_Timeout = processParam.Value<uint>("IN_Waiting_Timeout");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"等待I/O输入信号{(IN_Waiting_Timeout > 0 ? $"(超时时长: {IN_Waiting_Timeout})" : "...")}");
                                string[] inValue = processParam.Value<JArray>("IN_OP_SHOW").ToObject<List<string>>().ToArray();
                                uint inWaitingTime = 0;
                                while (true)
                                {
                                    if (isBreakProcessRun())
                                        return stepIndex;

                                    if (Util.compareIOInput(inValue, devContainer.devIOCard.DIData))
                                        break;

                                    Thread.Sleep(10);
                                    inWaitingTime += 10;
                                    if (IN_Waiting_Timeout > 0 && inWaitingTime >= IN_Waiting_Timeout)
                                    {
                                        AddTextEvent($"{stepIndex + 1}-{processName}", $"输入等待超时告警!", WarningEnum.High);
                                        warning(WarningEnum.Low);//暂停  
                                        return stepIndex;
                                    }
                                }
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"I/O输入信号对比完成！");
                            }

                            if (direction == IODirectionEnum.输出 || direction == IODirectionEnum.输入输出)
                                devContainer.io_output($"{stepIndex + 1}-{processName}", processParam);
                            #endregion
                            break; 
                        case "Axis":
                            #region
                            bool asynRun = processParam.Value<bool>("AsynRun");//异步
                            int AxisIndex = processParam.Value<int>("AxisIndex");
                            int DeviceType = processParam.Value<int>("DeviceType");

                            double VelLow = processParam.Value<double>("VelLow");
                            double VelHigh = processParam.Value<double>("VelHigh");
                            double Acc = processParam.Value<double>("Acc");
                            double Dec = processParam.Value<double>("Dec");

                            AxMoveMode MoveMode = (AxMoveMode)processParam.Value<int>("MoveMode");//绝对位置
                            double PPUValue = processParam.Value<double>("Value");

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}准备({(MoveMode == AxMoveMode.MODE1_Abs ? "绝对" : "相对")})运动至{PPUValue}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[AxisIndex]).ToString()})...");

                            ////移动Axis前等待厚度传感器收回
                            //if (!Config.SkipHeight)
                            //{
                            //    while (devContainer.devHeight.getHeight() < (double)Math.Abs(Config.HeightDev_SafeValue))
                            //    {
                            //        if (isBreakProcessRun())
                            //            return stepIndex;
                            //        Thread.Sleep(10);
                            //    }
                            //}
                            while (!devContainer.devAxis.isReady(AxisIndex))
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                    return stepIndex;
                            }
                            devContainer.devAxis.setAxisVelParam(VelLow, VelHigh, Acc, Dec, AxisIndex);
                            if (!devContainer.devAxis.move_ptp(AxisIndex, PPUValue, MoveMode))
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}运动失败！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"等待轴{AxisIndex}运行完成...");
                            while (!asynRun && !devContainer.devAxis.isReady(AxisIndex))
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    currProcessIndex = stepIndex + 1;
                                    return stepIndex;
                                }
                            }
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}运行完成,当前命令位置:{devContainer.devAxis.getCmdPos_mm(AxisIndex)},反馈位置:{devContainer.devAxis.getActualPos_mm(AxisIndex)}");
                            #endregion
                            break;
                        case "Light":
                            #region 
                            if (Config.SkipLight)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                //setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            int ChannelIndex = processParam.Value<int>("ChannelIndex"); //通道
                            int DigitalValue = processParam.Value<int>("DigitalValue"); //亮度
                            int nowDiaitalValue = devContainer.devLight.getDigitalValue(ChannelIndex);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"通道{ChannelIndex}当前值:{nowDiaitalValue},准备更新值:{DigitalValue}...");
                            devContainer.devLight.setDigitalValue(ChannelIndex, DigitalValue);
                            nowDiaitalValue = devContainer.devLight.getDigitalValue(ChannelIndex);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"通道{ChannelIndex}更新后当前值:{nowDiaitalValue}。");
                            #endregion
                            break;
                        case "Scanner_CC":
                            #region
                            if (Config.SkipScannerCC)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                //setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            while (!devContainer.devAxis.isReady())//因启用轴异步功能,使用前需等待
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    //currProcessIndex = stepIndex;//本工序没执行，step不变
                                    return stepIndex;
                                }
                            }

                            float ExposureTimeCC = processParam.Value<float>("ExposureTime"); //曝光
                            float GainCC = processParam.Value<float>("Gain"); //增益
                            float ResultingFrameRateCC = processParam.Value<float>("ResultingFrameRate"); //帧率

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"相机开始采集照片...");
                            devContainer.devScannerCC.setParam(ExposureTimeCC, GainCC, ResultingFrameRateCC);

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"相机参数设置完成。");
                            AutoResetEvent endEventCC = new AutoResetEvent(false);
                            //devContainer.devScannerCC.ScanEvent = (num, bmp2) =>
                            //{
                            //    AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片完成.");
                            //    scannerCBmpQueue.Enqueue(bmp2,
                            //      devContainer.devAxis.getActualPos_mm(0),
                            //      devContainer.devAxis.getActualPos_mm(1))
                            //    endEventCC.Set();//线程返回
                            //};
                            devContainer.devScannerCC.ScanEventPath += new System.Action<int, string>((num, path2) =>
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片完成."); 
                                scannerCBmpQueue.Enqueue(new scannerCBmpLoc(path2,
                                    devContainer.devAxis.getActualPos_mm(0),
                                    devContainer.devAxis.getActualPos_mm(1)));//Dequeue
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"图像队列数量: {scannerCBmpQueue.Count}");
                                endEventCC.Set();//线程返回
                            });
                            if (!devContainer.devScannerCC.scan(1))//软触发拍照
                            {
                                devContainer.devScannerCC.ScanEventPath = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片失败！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }

                            if (!endEventCC.WaitOne(5000))
                            {
                                devContainer.devScannerCC.ScanEventPath = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片超时！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }
                            devContainer.devScannerCC.ScanEventPath = null;
                            #endregion
                            break; 
                        case "Size":
                            #region
                            limitThresholdVal = processParam.Value<double>("LimitThresholdVal");
                            lowerThresholdVal = processParam.Value<double>("LowerThresholdVal");
                            int sizeIndex = processParam.Value<int>("Index");
                            double xPos = 0;
                            double yPos = 0;
                            if (scannerCBmpQueue.Count < 1)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测异常，无源图像!!", WarningEnum.Low);
                                warning(WarningEnum.Low);//暂停
                                return stepIndex;
                            }
                            var bmpCBmpQueue = scannerCBmpQueue.Dequeue();
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"开始尺寸检测，index:{sizeIndex},图像队列数量: {scannerCBmpQueue.Count}...");
                            //需要偏移校正，index=0时不能异步
                            endEvent = new AutoResetEvent(false);
                            sizeBmpNum++;
                            devContainer.libSize.add(new SizeTask()
                            {
                                index = sizeIndex,
                                stepIndex = stepIndex,//仅供回调时用
                                engineName = processParam.Value<string>("EngineName"),
                                bmp = bmpCBmpQueue.BMP,//bmp/file_path二选一，优先bmp
                                file_path = bmpCBmpQueue.Path,
                                posX = bmpCBmpQueue.PosX,
                                posY = bmpCBmpQueue.PosY,
                                MarkPointList= convert(currProductMarkList),
                                finishEvent = (res) =>
                                {
                                    if(res.isSucceed)
                                    {
                                        AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测完成，index:{res.index}。");
                                    }
                                    else
                                    {
                                        //setDgvContentCol(liStatocStepIndex, $"失败:{res.resultInfo}");
                                        AddTextEvent($"{res.stepIndex + 1}-{processName}", $"尺寸检测失败index:{res.index}:{res.resultInfo}");
                                        //warning(WarningEnum.Low);//暂停  这里不能暂停，stepIndex和scannerBmpQueue队列也不对了
                                    }

                                    sizeBmpNumResult++;
                                    string sizeSavePath = Config.SizeBmp_Path + "\\" + DateTime.Now.ToString("yyyyMMdd") + "\\";
                                    if (!Directory.Exists(sizeSavePath))
                                        Directory.CreateDirectory(sizeSavePath);
                                    sizeSavePath += $"SN{this.SN}_I{res.index}_X{res.posX}_Y{res.posY}.bmp";
                                    if (res.bmp != null)
                                    {
                                        res.bmp.Save(sizeSavePath,ImageFormat.Bmp);
                                        res.bmp.Dispose();
                                        res.bmp = null;
                                    }
                                    else
                                    {
                                        //File.Copy(res.file_path, defectFileName + ".bmp", true);
                                        API.CopyFile(res.file_path, sizeSavePath, false);//更快
                                        File.Delete(res.file_path);
                                    }
                                }
                            }); 
                            #endregion
                            break;
                        case "For":
                            #region
                            long UniqueId = processParam.Value<long>("UniqueId");
                            int GotoStepIndex = processParam.Value<int>("GotoStepIndex");//1-n
                            int LimitNum = processParam.Value<int>("LimitNum");//1-n
                            bool Reset = processParam.Value<bool>("Reset");
                            if (GotoStepIndex - 1 == stepIndex)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"For死循环！！！");
                                warning(WarningEnum.High);
                                return stepIndex;
                            }
                            if (!devContainer.libFor.dicData.ContainsKey(UniqueId))
                                devContainer.libFor.dicData.Add(UniqueId, 0);
                            //
                            int Num = devContainer.libFor.dicData[UniqueId];
                            Num++;
                            if (Num <= LimitNum)
                            {
                                if (Num == LimitNum)
                                {
                                    //setDgvContentCol(liStatocStepIndex, $"第[{Num}/{LimitNum}]次，循环完成");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"第[{Num}/{LimitNum}]次，循环完成。");
                                }
                                else
                                {
                                    //setDgvContentCol(liStatocStepIndex, $"第[{Num}/{LimitNum}]次");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"第[{Num}/{LimitNum}]次跳转到步骤[{GotoStepIndex}]...");
                                    stepIndex = GotoStepIndex - 2;
                                }
                                devContainer.libFor.dicData[UniqueId] = Num;
                            }
                            else
                            {
                                //setDgvContentCol(liStatocStepIndex, $"已失效不执行");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"本循环已失效不执行！");
                            }

                            //达到limit重置0
                            if (devContainer.libFor.dicData[UniqueId] >= LimitNum && Reset)
                            {
                                devContainer.libFor.dicData[UniqueId] = 0;
                                //setDgvContentCol(liStatocStepIndex, $"第[0/{LimitNum}]次");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器已重置。");
                            }
                            #endregion
                            break;
                        case "If":
                            #region
                            long UniqueId_if = processParam.Value<long>("UniqueId");
                            int GotoStepIndex_if = processParam.Value<int>("GotoStepIndex");//1-n
                            int LimitNum_if = processParam.Value<int>("LimitNum");//1-n
                            bool Reset_if = processParam.Value<bool>("Reset");
                            if (GotoStepIndex_if - 1 == stepIndex)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"If死循环，不可自我跳转！！！");
                                warning(WarningEnum.High);
                                return stepIndex;
                            }
                            //
                            if (!devContainer.libIF.dicData.ContainsKey(UniqueId_if))
                                devContainer.libIF.dicData.Add(UniqueId_if, 0);
                            //
                            int Num_if = devContainer.libIF.dicData[UniqueId_if];
                            Num_if++;
                            if (Num_if <= LimitNum_if)
                            {
                                if (Num_if == LimitNum_if)
                                {
                                    //setDgvContentCol(liStatocStepIndex, $"第[{Num_if}/{LimitNum_if}]次，跳转至[{GotoStepIndex_if}]");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器[{Num_if}/{LimitNum_if}]，跳转至步骤[{GotoStepIndex_if}]...");
                                    stepIndex = GotoStepIndex_if - 2;
                                }
                                else
                                {
                                    //setDgvContentCol(liStatocStepIndex, $"第[{Num_if}/{LimitNum_if}]次，不跳转");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器[{Num_if}/{LimitNum_if}]，不跳转。");
                                }
                                //
                                devContainer.libIF.dicData[UniqueId_if] = Num_if;
                            }
                            else
                            {
                                //setDgvContentCol(liStatocStepIndex, $"已失效不执行");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"本IF已失效不执行。");
                            }

                            //达到limit重置0
                            if (devContainer.libIF.dicData[UniqueId_if] >= LimitNum_if && Reset_if)
                            {
                                devContainer.libIF.dicData[UniqueId_if] = 0;
                                //setDgvContentCol(liStatocStepIndex, $"第[0/{LimitNum_if}]次");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器已重置。");
                            }
                            #endregion
                            break;
                        default:
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"未知工序：{processInfo.ProcessCode}");
                            warning(WarningEnum.High);
                            return stepIndex;
                    }
                    if (sleepLater > 0) Thread.Sleep((int)sleepLater);
                }

                //============结束，判断是否自动下料 
                if (stepIndex == processList.Count - 1)
                {
                    setButtonEnabled(tsbtnPause, false);//后面手动模式暂停无意义
                    //等待Mark图处理完成
                    while (sizeBmpNum != sizeBmpNumResult)
                        Thread.Sleep(100);
                    //
                    this.Invoke(new System.Action(() =>
                    {
                        this.chkLaser.Checked = true;
                        this.gpbXYAxis.Enabled = gpbZAxis.Enabled = true;
                    }));
                    AddTextEvent("完成", $"Mark点计算工序结束，开始转换主机台缺陷坐标...");
                    convert2LocXY();
                    AddTextEvent("完成", $"开始进行人工修复...");
                    currentPT = CurrentPTEnum.MakeTag;
                    currentState = CurrentStateEnum.打标中;
                    setButtonEnabled(tsbtnGoDownPT, true);
                    setButtonEnabled(tsbtnReset, true);
                     
                    //==
                    currProcessIndex = -1;
                    return -1;
                }
                else //继续
                {
                    return ++stepIndex;
                    //return nextProcess(model, ++stepIndex);
                }
            }
            catch (Exception ex)
            {
                if (devContainer.devAxis.isError())
                {
                    AddTextEvent("工序", $"[{stepIndex}] Err:" + ex.Message);
                    warning(WarningEnum.High);
                }
                else
                {
                    AddTextEvent("工序", $"[{stepIndex}] Err:" + ex.Message);
                    warning(WarningEnum.Low);
                }
                return -2;
            }
        }   
        #endregion
        #region Fun

        /// <summary>
        /// 全局中断
        /// </summary>
        private void globalBreakEvent(int portIndex, byte data)
        {
            AddTextEvent("中断命令", $"{portIndex}-HEX:{Convert.ToString(data, 16)}");
            if (compareIOInput(CMDName.启动按钮) && this.tsbtnStart.Enabled)
            {
                AddTextEvent("中断命令", $"启动-按钮");
                startCommand();
                AddTextEvent("中断命令", $"启动-完成");
            }
            else if (compareIOInput(CMDName.暂停按钮) && this.tsbtnPause.Enabled)
            {
                AddTextEvent("中断命令", $"暂停-按钮");
                warning(WarningEnum.Low, false);
                AddTextEvent("中断命令", $"暂停-完成");
            }
            else if (compareIOInput(CMDName.完成下料) && this.tsbtnGoDownPT.Enabled)
            {
                AddTextEvent("中断命令", $"完成-按钮");
                gotoDownPT();
                AddTextEvent("中断命令", $"完成-完成");
            }
            else if (compareIOInput(CMDName.下光源按钮))
            {
                AddTextEvent("中断命令", $"下光源-按钮");
                openLowerLight();
                AddTextEvent("中断命令", $"下光源-完成");
            }
            else if (compareIOInput(CMDName.上一张图) && currentState == CurrentStateEnum.打标中 && currDefectIndex > 0)
            {
                AddTextEvent("中断命令", $"上一张图-按钮");
                gotoPrePic();
                AddTextEvent("中断命令", $"上一张图-完成");
            }
            else if (compareIOInput(CMDName.下一张图) && currentState == CurrentStateEnum.打标中 && currDefectIndex < defectList.Count - 1)
            {
                AddTextEvent("中断命令", $"下一张图-按钮");
                gotoNextPic();
                AddTextEvent("中断命令", $"下一张图-完成");
            }
            else if (compareIOInput(CMDName.上一缺陷) && currentState == CurrentStateEnum.打标中 && currDefectIndex > 0)
            {
                AddTextEvent("中断命令", $"上一缺陷-按钮");
                gotoDefctListIndex(--currDefectIndex, currDefectIndex + 1);
                AddTextEvent("中断命令", $"上一缺陷-完成");
            }
            else if (compareIOInput(CMDName.下一缺陷) && currentState == CurrentStateEnum.打标中 && currDefectIndex < defectList.Count - 1)
            {
                AddTextEvent("中断命令", $"下一缺陷-按钮");
                gotoDefctListIndex(++currDefectIndex, currDefectIndex - 1);
                AddTextEvent("中断命令", $"下一缺陷-完成");
            }
            else if (compareIOInput(CMDName.复位按钮) && this.tsbtnReset.Enabled)
            {
                AddTextEvent("中断命令", $"复位-按钮");
                resetCommand();
                AddTextEvent("中断命令", $"复位-完成");
            }
         
            //
            if (devContainer.state && warningLevel == WarningEnum.Normal
                && currentState == CurrentStateEnum.打标中 && currentPT == CurrentPTEnum.MakeTag)
            {
                int axisIndex = 0;
                bool isJog_axis = (AxisState)devContainer.devAxis.AxState[axisIndex] == AxisState.STA_AX_EXT_JOG;
                if(!handleJog(axisIndex, CMDName.手柄左移,1) && !handleJog(axisIndex, CMDName.手柄右移,0))
                {
                    if (isJog_axis)
                    {
                        AddTextEvent("中断命令", $"轴{axisIndex}关闭JOG模式！");
                        devContainer.devAxis.closeJogMode(axisIndex);//关闭jog时自动停止
                    }
                }
                axisIndex = 1;
                isJog_axis = (AxisState)devContainer.devAxis.AxState[axisIndex] == AxisState.STA_AX_EXT_JOG;
                if (!handleJog(axisIndex, CMDName.手柄前移,0) && !handleJog(axisIndex, CMDName.手柄后移,1))
                {
                    if (isJog_axis)
                    {
                        AddTextEvent("中断命令", $"轴{axisIndex}关闭JOG模式！");
                        devContainer.devAxis.closeJogMode(axisIndex);//关闭jog时自动停止
                    }
                }
  
            }
        }
        private bool compareIOInput(CMDName key)
        {
            JObject joJson = Config.CMDProcess[key];
            IODirectionEnum direction = (IODirectionEnum)joJson.Value<int>("Direction");
            if (direction == IODirectionEnum.输入 || direction == IODirectionEnum.输入输出)
            {
                return Util.compareIOInput(
                    joJson.Value<JArray>("IN_OP_SHOW").ToObject<List<string>>().ToArray(),
                    devContainer.devIOCard.DIData);
            }
            return false;
        }
        private bool handleJog(int axisIndex,CMDName cmd,ushort dir)
        {
            if (compareIOInput(cmd) && currentState == CurrentStateEnum.打标中)
            {
                AddTextEvent("中断命令", $"{cmd.ToString()}:开"); 
                devContainer.devAxis.openJogMode(axisIndex);
                devContainer.devAxis.setAxisVelParam((double)Config.Axis_JogVelLow[axisIndex], (double)Config.Axis_JogVelLow[axisIndex],
                            (double)Config.Axis_JogVelLow[axisIndex], (double)Config.Axis_JogVelLow[axisIndex], axisIndex, true);
                devContainer.devAxis.jog(axisIndex, dir);//jog move
                return true;
            }
            return false;
        }
        /// <summary>
        /// 报警,只响应low,high
        /// </summary>
        private void warning(WarningEnum level, bool buzzer = true)
        {
            if (level == WarningEnum.Normal)
                return;

            lock (myLock)
                warningLevel = level;
            if (level == WarningEnum.Low)//暂停
            {
                currentState = CurrentStateEnum.暂停;
                pauseCommand(buzzer);
            }
            else if (level == WarningEnum.High)//急停
            {
                currentState = CurrentStateEnum.急停;
                devContainer.devAxis.stopNow();

                stopNowCommand();
            }

            //启用报警消除按钮
            setButtonEnabled(tsbtnWarning, true); 
        }
        private void resetUIValue()
        {
            currProcessIndex = -1; 
            currProductModel = null;
            currProductMarkList = null;
            scannerCBmpQueue.Clear();
            defectBmpsDir.Clear();
            this.Invoke(new System.Action(() =>
            { 
                cbxSN.Enabled = false;
                cbxSN.Text = txtProductCode.Text = txtProductName.Text = txtBatchId.Text = 
                    txtBatchId.Text = txtCreateTime.Text = "";


                this.gpbDefectList.Text = $"缺陷明细";
                this.dgvProcess.DataSource = null; 
                this.lstLog.Items.Clear();

                this.pictureBox1.Image = null;
                pictureBox1.Refresh();
            }));
        }
        private void delDirFiles(string dirPath)
        {
            try
            {
                var files = Directory.EnumerateFiles(dirPath, "*.bmp", SearchOption.TopDirectoryOnly);
                foreach (string file in files)
                    API.DeleteFile(file);
            }
            catch (Exception ex)
            {

            }
        }

        public delegate void AddTextDelegate(string tag, string msg, WarningEnum level);
        /// <summary>
        /// 异步输出日志
        /// </summary>
        /// <param name="tag">模板标识</param>
        /// <param name="msg">内容</param>
        /// <param name="type"></param>
        private void AddTextEvent(string tag, string msg, WarningEnum level = WarningEnum.Normal)
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new AddTextDelegate(AddTextEvent), new object[]
                    {
                        tag,
                        msg,
                        level
                    });
                }
                else
                {
                    if (tag != null && tag != "")
                        tag = $" - [{tag}]";

                    var now = DateTime.Now;
                    msg = now.ToString("HH:mm:ss fff") + tag + " - " + msg;
                    //cont = MyHelper.subString(cont, 300);

                    //写日志，warn和error日志直接写
                    writeLog(now, level, msg);

                    //
                    //if (type > 0)
                    //    cont = $"<color=\"{(type == 1 ? "Yellow" : "Red")}\">{cont}</color>";

                    msg = (level == WarningEnum.Normal ? "B" : level == WarningEnum.Low ? "Y" : "R") + msg;
                    this.Invoke(new System.Action(() =>
                    {
                        if (this.lstLog.Items.Count > 1000)
                            this.lstLog.Items.Clear();
                        lstLog.Items.Insert(0, msg);
                    }));
                    //日志滚动 
                    //lstLog.SelectedIndex = lstLog.Items.Count - 1; 
                }
            }
            catch (Exception ex)
            {
                //MessageBox.Show("AddTextEvent ex=(" + ex.Message + ")", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
            }
        }
        private void lstLog_DrawItem(object sender, DrawItemEventArgs e)
        {
            e.DrawBackground();
            if (e.Index < 0) return;

            string str = lstLog.Items[e.Index].ToString();
            e.Graphics.DrawString(str.Substring(1), e.Font,
                new SolidBrush(str[0] == 'R' ? Color.Red : (str[0] == 'Y' ? Color.Orange : Color.Black)),
                e.Bounds);
        }
        private void writeLog(DateTime now, WarningEnum level, string text)
        {
            string directory = Config.LogPath + "\\" + DateTime.Now.ToString("yyyyMM") + "\\";
            //if (type == 0) directory = Application.StartupPath + "\\Log\\Info\\";
            //else if (type == 1) directory = Application.StartupPath + "\\Log\\Warn\\";
            //else directory = Application.StartupPath + "\\Log\\Error\\";

            if (!System.IO.Directory.Exists(directory))
                System.IO.Directory.CreateDirectory(directory);

            File.AppendAllText(directory + now.ToString("yyyyMMdd") + ".log", text + "\r\n");
        }
        #endregion

        private void tsbtnOpenDev_Click(object sender, EventArgs e)
        {
            Config.LoadAllConfig();
            if(string.IsNullOrWhiteSpace(Config.DBConStr)|| string.IsNullOrWhiteSpace(Config.ServerIP))
            {
                AddTextEvent("设备启动", "启动失败，请先设置数据库和主机台通讯地址！",WarningEnum.High);
                return;
            }
            //设置程序最小/大线程池
            // Get the current settings. 
            int minWorker, minIOC;
            ThreadPool.GetMinThreads(out minWorker, out minIOC);
            ThreadPool.SetMinThreads(25, minIOC);
            //ThreadPool.SetMaxThreads(256, 256);

            this.tsbtnOpenDev.Enabled = false;
            scannerCBmpQueue.Clear();
            //scannerCBmpIndex = 0;

            this.resetUIValue();  
            currentState = CurrentStateEnum.等待复位;
            warningLevel = WarningEnum.Normal;
            currentPT = CurrentPTEnum.InitPT;
            this.tsbtnWarning.Enabled = this.tsbtnGoDownPT.Enabled = this.tsbtnStart.Enabled 
                    = this.tsbtnStopNow.Enabled = this.tsbtnPause.Enabled = this.tsbtnReset.Enabled
                    = this.gpbXYAxis.Enabled = gpbZAxis.Enabled = false;
            //后台线程回调事件
            devContainer.StateChange = (state, msg) =>
            {
                if (state)
                {
                    //全局中断
                    devContainer.devIOCard.INEvent = globalBreakEvent;
                    devContainer.OutDebugEvent = (tag, debugInfo) =>
                    {
                        AddTextEvent(tag, debugInfo);
                    };
                    //
                    AddTextEvent("设备启动", "请先进行复位操作！");
                    this.Invoke(new System.Action(() =>
                    {
                        this.tsbtnReset.Enabled = true;
                        tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                            = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = false;

                        this.chkLaser.Enabled = true;
                        this.tsbtnOpenDev.Enabled = true;
                        this.tsbtnOpenDev.Visible = false;
                        this.tsbtnCloseDev.Visible = true;
                    }));

                    timer.Elapsed += Timer_Elapsed;
                    timer.Interval = 500;
                    timer.Start();
                }
                else
                {
                    AddTextEvent("设备启动", $"启动失败，{msg}", WarningEnum.High);
                    this.Invoke(new System.Action(() =>
                    {
                        this.chkLaser.Enabled = false;
                        this.tsbtnOpenDev.Enabled = true;
                        this.tsbtnOpenDev.Visible = true;
                        this.tsbtnCloseDev.Visible = false;
                    }));
                }

            };
            devContainer.WarningEvent = (level, msg) =>
            {
                AddTextEvent("设备事件", msg, level);
                if (level == WarningEnum.High)
                    warning(level, true);
            };
            AddTextEvent("设备启动", "启动");
            devContainer.start(IntPtr.Zero, IntPtr.Zero);
        }
        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.Invoke(new System.Action(() =>
            {
                this.tsAxisState.Text = $"[命令位:{devContainer.devAxis.CmdPos[0]} 反馈位:{devContainer.devAxis.ActualPos[0]} ({((AxisState)devContainer.devAxis.AxState[0]).ToString()})] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[1]} 反馈位:{devContainer.devAxis.ActualPos[1]} ({((AxisState)devContainer.devAxis.AxState[1]).ToString()})] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[2]} 反馈位:{devContainer.devAxis.ActualPos[2]} ({((AxisState)devContainer.devAxis.AxState[2]).ToString()})] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[3]} 反馈位:{devContainer.devAxis.ActualPos[3]} ({((AxisState)devContainer.devAxis.AxState[3]).ToString()})] | ";
            }));
        }
        private void tsbtnCloseDev_Click(object sender, EventArgs e)
        {
            AddTextEvent("设备停止", $"设备停止..."); 
            this.tsbtnOpenDev.Visible = true;
            this.tsbtnCloseDev.Visible = false;

            this.chkLaser.Enabled = false;
            this.tsbtnReset.Enabled = tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled
                = this.gpbXYAxis.Enabled = this.gpbZAxis.Enabled = false;

            if (devContainer.state)
            {
                devContainer.devIOCard.reset();
                devContainer.io_output(CMDName.IO默认输出);
            }
            timer.Stop();
            devContainer.stop();
        }
        private void dgvProcess_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0 || !devContainer.state || currentState != CurrentStateEnum.打标中)
                return;

            int preIndex = currDefectIndex;
            currDefectIndex = e.RowIndex;
            gotoDefctListIndex(currDefectIndex, preIndex);
        }

        private void 流程管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止设备，再进行流程修改！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmStepList frm = new FrmStepList();
            frm.ShowDialog();
        }

        private void 产品管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //if (devContainer.state)
            //{
            //    MessageBox.Show("请先停止设备，再进行产品修改！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            //    return;
            //}

            FrmProductList frm = new FrmProductList();
            frm.ShowDialog();
        }
        private void loadOrderSNList()
        {
            try
            {
                //ConditionalType: 0-等于 3-大于等于  5-小于等于 6-IN "CSharpTypeName":"int"
                JArray domainList =new JArray();
                domainList.Add(new JObject()
                {
                    {"FieldName","State" },
                    {"FieldValue",0 },//0-初始; 5-已检测
                    {"ConditionalType",0 },
                });
                domainList.Add(new JObject()
                {
                    {"FieldName","DefectCount" },
                    {"FieldValue",1 },
                    {"ConditionalType",3 },
                });
                domainList.Add(new JObject()
                {
                    {"FieldName","CreateTime" },
                    {"FieldValue",DateTime.Now.AddDays(-1) },
                    {"ConditionalType",3 },
                });
                var list = svcOrder.GetList("Order", "SN", domainList.ToString(), "SN DESC");
                List<string> snList = new List<string>();
                foreach(var item in list)
                    snList.Add(item.ToList().Select(m => m.Value.ToString()).ToArray()[0]);
               
                this.Invoke(new System.Action(() =>
                {
                    this.cbxSN.Text = "";
                    this.cbxSN.Items.Clear();
                    this.cbxSN.Items.AddRange(snList.ToArray());
                })); 
            }
            catch (Exception ex)
            {
                AddTextEvent("启动", "加载已检测网版编码失败:" + ex.Message, WarningEnum.Low);
            }
        }

        private void splitContainer1_SizeChanged(object sender, EventArgs e)
        {
            this.gpbProductInfo.Width = this.gpbDefectList.Width = splitContainer1.Panel2.Width-10; 
            this.gpbProductInfo.Top = this.splitContainer1.ClientSize.Height- this.gpbProductInfo.Height;
            this.gpbDefectList.Height = this.gpbProductInfo.Top-10;

            this.cbxSN.Width= this.txtProductCode.Width = this.txtProductName.Width
                = this.txtBatchId.Width = this.txtCreateTime.Width = this.gpbProductInfo.Width- this.cbxSN.Left-5;
            //this.dgvProcess.Width = gpbDefectList.Width - 10;
            //this.dgvProcess.Height = this.splitContainer1.ClientSize.Height - this.gpbProductInfo.Height; 

            //

        }
        private void splitContainer2_SizeChanged(object sender, EventArgs e)
        {
            this.tsbtnPause.Left = this.tsbtnGoDownPT.Left = this.splitContainer1.SplitterDistance- this.tsbtnPause.Width - 10;
            this.gpbZAxis.Left = this.tsbtnPause.Left - this.gpbZAxis.Width-10;
            this.gpbXYAxis.Left = this.gpbZAxis.Left - this.gpbXYAxis.Width - 10;
            this.gpbLog.Width = this.gpbXYAxis.Left  - 10;
            this.splitContainer2.SplitterDistance = this.ClientRectangle.Height - gpbLog.Height - 10; 
        }

        private void tsbtnReset_Click(object sender, EventArgs e)
        {
            Task.Run(() => this.resetCommand());
        }
        private void tsbtnStart_Click(object sender, EventArgs e)
        {
            Task.Run(() => this.startCommand()); 
        }
        private void tsbtnPause_Click(object sender, EventArgs e)
        {
            warning(WarningEnum.Low, false);
        }

        private void tsbtnGoDownPT_Click(object sender, EventArgs e)
        {
            AddTextEvent("下料", "准备下料。。。");
            // Task.Run(() => this.gotoDownPT()); 
            gotoDownPT();
        }
        private void tsbtnWarning_Click(object sender, EventArgs e)
        {
            //if (warningLevel == WarningEnum.Normal)
            //{
            //    tsbtnWarning.Enabled = false;
            //    return;
            //}

            //重置AXIS状态
            devContainer.devAxis.resetAxisState();
            if (!devContainer.devAxis.isReady())
            {
                AddTextEvent("解除报警", "轴状态未恢复，解除报警失败！", warningLevel);
                return;
            }
            if (devContainer.devAxis.isError())
            {
                AddTextEvent("解除报警", "轴IO状态异常，解除报警失败！", warningLevel);
                return;
            }
            //if (!this.disableDoorSensor && compareIOInput(CMDName.门磁告警输入))
            //{
            //    AddTextEvent("解除报警", "门磁告警未清除 ，解除报警失败！", warningLevel);
            //    return;
            //}
            //if (!this.disableDoorSensor && compareIOInput(CMDName.喷墨告警输入))
            //{
            //    AddTextEvent("解除报警", "喷墨告警输入未清除 ，解除报警失败！", warningLevel);
            //    return;
            //}
            ////关闭蜂鸣器
            //devContainer.io_output(CMDName.蜂鸣器输出, false, true, 0);
            //devContainer.io_output(CMDName.红灯输出, false, true, 0);
            //devContainer.io_output(CMDName.复位按钮, false, true, 0);
            if (warningLevel == WarningEnum.High)
            {
                AddTextEvent("解除报警", "急停告警已解除，但必需进行复位操作！", warningLevel);
                currentState = CurrentStateEnum.等待复位;
                this.tsbtnReset.Enabled = true;
                tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                    = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = false;
            }
            else
            {
                //devContainer.io_output(CMDName.黄灯输出);
                AddTextEvent("解除报警", "告警已解除，请继续选择下一步操作！", warningLevel);
                if (currentPT == CurrentPTEnum.MakeTag)
                    currentState = CurrentStateEnum.打标中;
                else
                    currentState = CurrentStateEnum.等待启动;

                tsbtnWarning.Enabled = false;
                this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled = true;
                this.tsbtnReset.Enabled = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = true;
            }
            warningLevel = WarningEnum.Normal;
        }

        private void 测试ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //devContainer.start(IntPtr.Zero, IntPtr.Zero);
            //devContainer.state = true;
            //currentState = CurrentStateEnum.等待扫码;
            //cbxSN.Enabled = true;
            //runStep();

            //Bitmap bmp = (Bitmap)Bitmap.FromFile(Application.StartupPath + "\\test.bmp");
            //AddTextEvent("", $"{this.splitContainer1.Width},{this.pnlPic.Width},bmp:{bmp.Width}");
            //pnlPic.BackColor = Color.Red;
            //reloadPic(bmp); 


            //for (int i = 0; i < 100; i++)
            //    this.dgvProcess.Rows.Add(i, i, i, i, i, i, i, i, i, i, i);
            //this.dgvProcess.Rows[1].DefaultCellStyle.BackColor = Color.LightGreen;

            //loadOrderSNList();
        }

        private void btnFront_MouseDown(object sender, MouseEventArgs e)
        {
            if (devContainer.state && currentState == CurrentStateEnum.打标中)
            {
                ushort dir;
                int axisIndex;
                PictureBox btn = sender as PictureBox;
                switch (btn.Name)
                {
                    case "btnFront":
                        dir = 1;
                        axisIndex = 1;
                        break;
                    case "btnBack":
                        dir = 0;
                        axisIndex = 1;
                        break;
                    case "btnLeft":
                        dir = 1;
                        axisIndex = 0;
                        break;
                    case "btnRight":
                        dir = 0;
                        axisIndex = 0;
                        break;
                    case "btnZUp":
                        dir = 0;
                        axisIndex = 2;
                        break;
                    case "btnZDown":
                        dir = 1;
                        axisIndex = 2;
                        break;
                    default:
                        return;
                }

                //
                devContainer.devAxis.openJogMode(axisIndex);
                devContainer.devAxis.setAxisVelParam((double)Config.Axis_JogVelLow[axisIndex], (double)Config.Axis_JogVelLow[axisIndex],
                            (double)Config.Axis_JogVelLow[axisIndex], (double)Config.Axis_JogVelLow[axisIndex], axisIndex, true);
                devContainer.devAxis.jog(axisIndex, dir);//jog move
            }
        } 
        private void btnFront_MouseUp(object sender, MouseEventArgs e)
        {
            devContainer.devAxis.closeJogMode();
        }

        private void chkLaser_CheckedChanged(object sender, EventArgs e)
        {
            if (!devContainer.state) return;

            if(chkLaser.Checked)
                devContainer.io_output(CMDName.激光灯输出);
            else
                devContainer.io_output(CMDName.激光灯输出, false, true, 0);
        }

        private void tsbtnLog_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(Config.LogPath)) return;
            string lsPath = (Config.LogPath + "\\" + DateTime.Now.ToString("yyyyMM") + "\\").Replace("\\\\", "\\");
            if (!Directory.Exists(lsPath))
                Directory.CreateDirectory(lsPath);
            System.Diagnostics.Process.Start("explorer.exe", lsPath);
        }
          

        private void FrmMain_KeyUp(object sender, KeyEventArgs e)
        {
            //AddTextEvent("KEY",e.KeyCode.ToString());
            switch (e.KeyCode)
            {
                //case Keys.Left://上一张图
                //    if (devContainer.state && currentState == CurrentStateEnum.打标中 && currDefectIndex > 0)
                //        gotoPrePic();
                //    break;
                case Keys.F:// Keys.Right://下一张图
                    if (devContainer.state && currentState == CurrentStateEnum.打标中 && currDefectIndex < defectList.Count - 1)
                        gotoNextPic();
                    break;
                case Keys.K://Keys.Up://上一缺陷
                    if (devContainer.state && currentState == CurrentStateEnum.打标中 && currDefectIndex > 0)
                        gotoDefctListIndex(--currDefectIndex, currDefectIndex+1);
                    break;
                case Keys.P://Keys.Down://下一缺陷
                    if (devContainer.state && currentState == CurrentStateEnum.打标中 && currDefectIndex < defectList.Count - 1)
                        gotoDefctListIndex(++currDefectIndex, currDefectIndex-1);
                    break;
            }
        }

        private void 关于ToolStripMenuItem_Click(object sender, EventArgs e)
        { 
        }

        private void cbxSN_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (currentState != CurrentStateEnum.等待扫码) return;
                string sn = this.cbxSN.Text.Trim();
                if (string.IsNullOrWhiteSpace(sn))
                    return;

                //FrmInput frm = new FrmInput(productCodeList, "请选择产品编码:");
                //if (frm.ShowDialog() != DialogResult.OK && string.IsNullOrWhiteSpace(frm.inputData))
                //    return;
                Task.Factory.StartNew(() =>
                {
                    devContainer.devCodeScanner.ScanerEvent?.Invoke(sn);
                });
            }
        }

        private void tsbtnGoDownPT_Click_1(object sender, EventArgs e)
        {
            AddTextEvent("下料", "准备下料。。。");
            // Task.Run(() => this.gotoDownPT()); 
            gotoDownPT();
        }

        private void cbxSN_KeyPress(object sender, KeyPressEventArgs e)
        {
            //if (e.KeyChar == (char)Keys.Enter)
            //{ 
            //    if (currentState != CurrentStateEnum.等待扫码) return;
            //    string sn = this.cbxSN.Text.Trim();
            //    if (string.IsNullOrWhiteSpace(sn))
            //        return;

            //    //FrmInput frm = new FrmInput(productCodeList, "请选择产品编码:");
            //    //if (frm.ShowDialog() != DialogResult.OK && string.IsNullOrWhiteSpace(frm.inputData))
            //    //    return;
            //    Task.Factory.StartNew(() =>
            //    {
            //        devContainer.devCodeScanner.ScanerEvent?.Invoke(sn);
            //    }); 
            //}
        }
         
    }
}
