From 4e8bf084f8a04617a9f542099183b8d829fa7c4b Mon Sep 17 00:00:00 2001
From: patrick <patrick.xu@broconcentric.com>
Date: 星期五, 18 十月 2019 19:07:51 +0800
Subject: [PATCH] 1. 修改机器人通信协议及相关流程触发和操作

---
 src/A032.Process/Calibration/CalibrationConfig.cs           |   87 +
 src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs  |   47 
 src/Bro.Device.SeerAGV/SeerAGVConfig.cs                     |    1 
 src/A032.Process/ProcessConfig.cs                           |  114 -
 src/A032.Process/A032.Process.csproj                        |   22 
 src/A032.Process/ProcessControl_Calibration.cs              |  101 +
 src/Bro.Common.Model/Interface/IStationProcess.cs           |    2 
 src/Bro.Device.SeerAGV/SeerAGVDriver.cs                     |   22 
 src/A032.Process/ProcessControl.cs                          |  113 +
 src/Bro.Device.AuboRobot/AuboRobotConfig.cs                 |  107 +
 src/A032.Process/Calibration/FrmCalib9PDynamic.resx         |  120 ++
 src/A032.Process/AGVPath.cs                                 |  110 ++
 src/A032.Process/ProcessControl_Method.cs                   | 1064 +++++++++++++++++++
 src/A032.Process/Calibration/CtrlCalib9PDynamic.resx        |  123 ++
 src/Bro.Device.OmronFins/OmronFinsConfig.cs                 |    8 
 src/Bro.Common.Model/Interface/IMonitor.cs                  |    7 
 src/Bro.Device.AuboRobot/AuboRobotDriver.cs                 |  166 ++
 src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj        |    1 
 src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs |  378 +++++++
 src/A032.Process/AGVBindUnit.cs                             |  295 +++++
 src/Bro.Device.Common/DeviceBase/PLCBase.cs                 |    4 
 src/A032.Process/Calibration/FrmCalib9PDynamic.cs           |   50 
 src/Bro.Common.Model/Helper/ExceptionHelper.cs              |    2 
 src/A032.Process/Calibration/CtrlCalib9PDynamic.cs          |  277 +++++
 24 files changed, 3,037 insertions(+), 184 deletions(-)

diff --git a/src/A032.Process/A032.Process.csproj b/src/A032.Process/A032.Process.csproj
index f644f1f..2a1c97b 100644
--- a/src/A032.Process/A032.Process.csproj
+++ b/src/A032.Process/A032.Process.csproj
@@ -55,6 +55,21 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="AGVBindUnit.cs" />
+    <Compile Include="AGVPath.cs" />
+    <Compile Include="Calibration\CalibrationConfig.cs" />
+    <Compile Include="Calibration\CtrlCalib9PDynamic.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Calibration\CtrlCalib9PDynamic.Designer.cs">
+      <DependentUpon>CtrlCalib9PDynamic.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Calibration\FrmCalib9PDynamic.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Calibration\FrmCalib9PDynamic.Designer.cs">
+      <DependentUpon>FrmCalib9PDynamic.cs</DependentUpon>
+    </Compile>
     <Compile Include="Forms\MonitorSetBindFrm.cs">
       <SubType>Form</SubType>
     </Compile>
@@ -75,6 +90,7 @@
     </Compile>
     <Compile Include="Helper\PropertyConvertHelper.cs" />
     <Compile Include="ProcessConfig.cs" />
+    <Compile Include="ProcessControl_Calibration.cs" />
     <Compile Include="ProcessControl_Method.cs" />
     <Compile Include="ProcessControl.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
@@ -109,6 +125,12 @@
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
+    <EmbeddedResource Include="Calibration\CtrlCalib9PDynamic.resx">
+      <DependentUpon>CtrlCalib9PDynamic.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Calibration\FrmCalib9PDynamic.resx">
+      <DependentUpon>FrmCalib9PDynamic.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Forms\MonitorSetBindFrm.resx">
       <DependentUpon>MonitorSetBindFrm.cs</DependentUpon>
     </EmbeddedResource>
diff --git a/src/A032.Process/AGVBindUnit.cs b/src/A032.Process/AGVBindUnit.cs
new file mode 100644
index 0000000..460c4f4
--- /dev/null
+++ b/src/A032.Process/AGVBindUnit.cs
@@ -0,0 +1,295 @@
+锘縰sing Autofac;
+using Bro.Common.Base;
+using Bro.Common.Helper;
+using Bro.Common.Interface;
+using Bro.Common.Model;
+using Bro.Device.AuboRobot;
+using Bro.Device.SeerAGV;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+
+namespace A032.Process
+{
+    public enum TaskStatus
+    {
+        Available = 1,
+        Running = 2,
+        Warning = 3,
+    }
+
+    public enum TaskAvailableLevel
+    {
+        Robot = 1,
+        AGV = 2,
+        Both = 3,
+    }
+
+    public class AGVTaskModel
+    {
+        public TaskAvailableLevel Level { get; set; } = TaskAvailableLevel.Robot;
+
+        public Func<IOperationConfig, IDevice, ProcessResponse> MethodFunc { get; set; }
+
+        public IOperationConfig OpConfig { get; set; }
+
+        public IDevice Device { get; set; }
+
+        //public int Priority { get; set; } = 10;
+
+        public AGVTaskModel() { }
+
+        public AGVTaskModel(TaskAvailableLevel level, Func<IOperationConfig, IDevice, ProcessResponse> methodFunc, IOperationConfig opConfig = null, IDevice device = null)
+        {
+            Level = level;
+            MethodFunc = methodFunc;
+            OpConfig = opConfig ?? new OperationConfigBase();
+            Device = device;
+        }
+    }
+
+    public class AGVBindUnit : IComplexDisplay
+    {
+        #region 鍙紪杈戦」鐩�
+        [Category("璁惧缁戝畾")]
+        [Description("缁戝畾Id")]
+        [ReadOnly(true)]
+        public string Id { get; set; } = Guid.NewGuid().ToString();
+
+        [Category("璁惧缁戝畾")]
+        [Description("缁戝畾AGV")]
+        [TypeConverter(typeof(AGVDeviceConverter))]
+        public string AGVId { get; set; }
+
+        [Category("璁惧缁戝畾")]
+        [Description("缁戝畾Robot")]
+        [TypeConverter(typeof(RobotDeviceConverter))]
+        public string RobotId { get; set; }
+
+        [Category("璁惧缁戝畾")]
+        [Description("缁戝畾Camera")]
+        [TypeConverter(typeof(CameraDeviceConverter))]
+        public string CameraId { get; set; }
+
+        public string GetDisplayText()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                ProcessConfig config = scope.Resolve<ProcessConfig>();
+
+                string agv = config.AGVConfigCollection.FirstOrDefault(u => u.ID == AGVId)?.Name ?? "鏈粦瀹欰GV";
+                string robot = config.RobotConfigCollection.FirstOrDefault(u => u.ID == RobotId)?.Name ?? "鏈粦瀹氭満鍣ㄤ汉";
+                string camera = config.CameraConfigCollection.FirstOrDefault(u => u.ID == CameraId)?.Name ?? "鏈粦瀹氱浉鏈�";
+
+                return agv + "-" + robot + "-" + camera;
+            }
+        }
+        #endregion
+
+        [Browsable(false)]
+        [JsonIgnore]
+        public Action<AGVBindUnit> OnMethodInvoke { get; set; }
+
+        public string AGVDest { get; set; } = "";
+
+        private TaskStatus agvStatus = TaskStatus.Available;
+        [Browsable(false)]
+        [JsonIgnore]
+        public TaskStatus AGVStatus
+        {
+            get => agvStatus;
+            set
+            {
+                agvStatus = value;
+                InvokeTaskCheck();
+            }
+        }
+
+        private TaskStatus robotStatus = TaskStatus.Available;
+        [Browsable(false)]
+        [JsonIgnore]
+        public TaskStatus RobotStatus
+        {
+            get => robotStatus;
+            set
+            {
+                robotStatus = value;
+                InvokeTaskCheck();
+            }
+        }
+
+        [Browsable(false)]
+        [JsonIgnore]
+        public TaskStatus UnitStatus
+        {
+            get
+            {
+                if (AGVStatus == TaskStatus.Warning || RobotStatus == TaskStatus.Warning)
+                {
+                    return TaskStatus.Warning;
+                }
+                else
+                {
+                    if (AGVStatus == TaskStatus.Available && RobotStatus == TaskStatus.Available)
+                    {
+                        return TaskStatus.Available;
+                    }
+                }
+
+                return TaskStatus.Running;
+            }
+        }
+
+        [Browsable(false)]
+        [JsonIgnore]
+        public SeerAGVDriver AGV { get; set; } = null;
+        [Browsable(false)]
+        [JsonIgnore]
+        public AuboRobotDriver Robot { get; set; } = null;
+        [Browsable(false)]
+        [JsonIgnore]
+        public CameraBase Camera { get; set; } = null;
+        [Browsable(false)]
+        [JsonIgnore]
+        public ObservableCollection<AGVTaskModel> TaskList { get; set; } = new ObservableCollection<AGVTaskModel>();
+
+        [Browsable(false)]
+        [JsonIgnore]
+        public int CurrentEmptyTray { get; set; }
+        [Browsable(false)]
+        [JsonIgnore]
+        public int CurrentFullTray { get; set; }
+        [Browsable(false)]
+        [JsonIgnore]
+        public bool IsFullTrayFull { get; set; }
+        [Browsable(false)]
+        [JsonIgnore]
+        public bool IsFullTrayEmpty { get; set; }
+        [Browsable(false)]
+        [JsonIgnore]
+        public bool IsEmptyTrayEmpty { get; set; }
+
+        [Browsable(false)]
+        [JsonIgnore]
+        public ManualResetEvent RobotIOHandle { get; set; } = new ManualResetEvent(false);
+
+        public AGVBindUnit()
+        {
+            TaskList.CollectionChanged += TaskList_CollectionChanged;
+        }
+
+        private void TaskList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
+            {
+                InvokeTaskCheck();
+            }
+        }
+
+        private void InvokeTaskCheck()
+        {
+            for (int i = 0; i < TaskList.Count; i++)
+            {
+                var task = TaskList[i];
+
+                bool isAgvOK = false;
+                bool isRobotOK = false;
+
+                if ((task.Level & TaskAvailableLevel.AGV) == TaskAvailableLevel.AGV)
+                {
+                    isAgvOK = AGVStatus == TaskStatus.Available;
+                }
+                else
+                {
+                    isAgvOK = true;
+                }
+
+                if ((task.Level & TaskAvailableLevel.Robot) == TaskAvailableLevel.Robot)
+                {
+                    isRobotOK = RobotStatus == TaskStatus.Available;
+                }
+                else
+                {
+                    isRobotOK = true;
+                }
+
+                if (isRobotOK && isAgvOK)
+                {
+                    OnMethodInvoke?.Invoke(this);
+                    TaskList.RemoveAt(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    public class AGVDeviceConverter : ComboBoxItemTypeConvert
+    {
+        public override void GetConvertHash()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                ProcessConfig config = scope.Resolve<ProcessConfig>();
+
+                config.AGVConfigCollection.ForEach(plc =>
+                {
+                    _hash[plc.ID] = plc.Name;
+                });
+            }
+        }
+    }
+
+    public class RobotDeviceConverter : ComboBoxItemTypeConvert
+    {
+        public override void GetConvertHash()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                ProcessConfig config = scope.Resolve<ProcessConfig>();
+
+                config.RobotConfigCollection.ForEach(plc =>
+                {
+                    _hash[plc.ID] = plc.Name;
+                });
+            }
+        }
+    }
+
+    public class CameraDeviceConverter : ComboBoxItemTypeConvert
+    {
+        public override void GetConvertHash()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                ProcessConfig config = scope.Resolve<ProcessConfig>();
+
+                config.CameraConfigCollection.ForEach(plc =>
+                {
+                    _hash[plc.ID] = plc.Name;
+                });
+            }
+        }
+    }
+
+    public class PLCDeviceConverter : ComboBoxItemTypeConvert
+    {
+        public override void GetConvertHash()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                _hash[""] = "鏈寚瀹�";
+
+                ProcessConfig config = scope.Resolve<ProcessConfig>();
+
+                config.PLCConfigCollection.ForEach(plc =>
+                {
+                    _hash[plc.ID] = plc.Name;
+                });               
+            }
+        }
+    }
+}
diff --git a/src/A032.Process/AGVPath.cs b/src/A032.Process/AGVPath.cs
new file mode 100644
index 0000000..1bfb3e0
--- /dev/null
+++ b/src/A032.Process/AGVPath.cs
@@ -0,0 +1,110 @@
+锘縰sing Autofac;
+using Bro.Common.Helper;
+using Bro.Common.Model;
+using Bro.Common.Model.Interface;
+using Bro.Device.HikCamera;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing.Design;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace A032.Process
+{
+    public enum PathPositionDefinition
+    {
+        [Description("鏃犲湴鐐�")]
+        None = 0,
+        [Description("涓婄┖Tray鍦扮偣")]
+        LoadEmptyTray = 1,
+        [Description("鍗歌浇绌篢ray鍦扮偣")]
+        UnloadEmptyTray = 2,
+        [Description("涓婃弧Tray鍦扮偣")]
+        LoadFullTray = 3,
+        [Description("鍗歌浇婊ray鍦扮偣")]
+        UnloadFullTray = 4,
+    }
+
+    public class PathPosition : IComplexDisplay
+    {
+        [Category("瀵艰埅璺緞")]
+        [Description("AGV璺緞鑺傜偣浠g爜")]
+        public string PositionCode { get; set; }
+
+        [Category("瀵艰埅璺緞")]
+        [Description("Robot璺緞鑺傜偣鏁板瓧")]
+        public int PositionNo { get; set; }
+
+        [Category("瀵艰埅璺緞")]
+        [Description("璺緞鑺傜偣鎻忚堪")]
+        public PathPositionDefinition Description { get; set; } = PathPositionDefinition.None;
+
+        [Category("褰掑睘璁惧")]
+        [Description("褰掑睘璁惧缂栧彿")]
+        [TypeConverter(typeof(PLCDeviceConverter))]
+        public string DeviceOwner { get; set; }
+
+        public string GetDisplayText()
+        {
+            return $"{PositionCode}-{Description}";
+        }
+    }
+
+    public class PositionVisionConfig : IComplexDisplay, IHalconToolPath
+    {
+        [Category("鍏宠仈閰嶇疆")]
+        [Description("浣嶇疆浠g爜")]
+        [TypeConverter(typeof(PositionCodeConverter))]
+        public string PositionCode { get; set; }
+
+        [Category("鍏宠仈閰嶇疆")]
+        [Description("閫傜敤鐩告満缂栧彿")]
+        [TypeConverter(typeof(CameraDeviceConverter))]
+        public string CameraId { get; set; }
+
+        [Category("瑙嗚閰嶇疆")]
+        [Description("璇ヤ綅缃爣瀹氱煩闃碉紝鐢ㄤ簬浠庣浉鏈哄儚绱犲潗鏍囪浆鎹㈠埌鏈哄櫒浜虹殑杩愬姩浜岀淮鍧愭爣绯伙紝鍙绠楀亸绉�")]
+        [TypeConverter(typeof(SimpleCollectionConvert<double>))]
+        public List<double> Matrix { get; set; } = new List<double>();
+
+        [Category("瑙嗚閰嶇疆")]
+        [Description("璇ヤ綅缃爣鍑嗙偣浣嶄俊鎭�")]
+        [TypeConverter(typeof(ComplexObjectConvert))]
+        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
+        public CustomizedPoint StandardPoint { get; set; } = new CustomizedPoint();
+
+        [Category("瑙嗚閰嶇疆")]
+        [Description("璇ヤ綅缃媿鎽勯厤缃�")]
+        [TypeConverter(typeof(ComplexObjectConvert))]
+        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
+        public HikCameraOperationConfig CameraOpConfig { get; set; } = new HikCameraOperationConfig();
+
+        public string GetDisplayText()
+        {
+            return $"{PositionCode}:鏇濆厜锛歿CameraOpConfig.Exposure};鏍囧畾鐭╅樀锛歿string.Join(",", Matrix)}";
+        }
+
+        public List<string> GetHalconToolPathList()
+        {
+            return new List<string>() { CameraOpConfig.AlgorithemPath };
+        }
+    }
+
+    public class PositionCodeConverter : ComboBoxItemTypeConvert
+    {
+        public override void GetConvertHash()
+        {
+            using (var scope = GlobalVar.Container.BeginLifetimeScope())
+            {
+                var config = scope.Resolve<ProcessConfig>();
+
+                config.PositionCollection.ForEach(p =>
+                {
+                    _hash[p.PositionCode] = $"{p.PositionCode}-{p.Description.GetEnumDescription()}";
+                });
+            }
+        }
+    }
+}
diff --git a/src/A032.Process/Calibration/CalibrationConfig.cs b/src/A032.Process/Calibration/CalibrationConfig.cs
new file mode 100644
index 0000000..8c0d7fe
--- /dev/null
+++ b/src/A032.Process/Calibration/CalibrationConfig.cs
@@ -0,0 +1,87 @@
+锘縰sing Bro.Common.Base;
+using Bro.Common.Helper;
+using Bro.Common.Model;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Design;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace A032.Process.Calibration
+{
+    [Device("CalibrationCollection", "澶氭鏍囧畾閰嶇疆", EnumHelper.DeviceAttributeType.OperationConfig)]
+    public class CalibrationConfigCollection : OperationConfigBase
+    {
+        [Category("鍏宠仈閰嶇疆")]
+        [Description("浣嶇疆浠g爜")]
+        [TypeConverter(typeof(PositionCodeConverter))]
+        public string PositionCode { get; set; }
+
+        [Category("鍏宠仈閰嶇疆")]
+        [Description("閫傜敤鐩告満缂栧彿")]
+        [TypeConverter(typeof(CameraDeviceConverter))]
+        public string CameraId { get; set; }
+
+        [Category("鏍囧畾閰嶇疆")]
+        [Description("鏍囧畾閰嶇疆闆嗗悎")]
+        [TypeConverter(typeof(CollectionCountConvert))]
+        [Editor(typeof(ComplexCollectionEditor<CalibrationConfig>), typeof(UITypeEditor))]
+        public List<CalibrationConfig> Configs { get; set; } = new List<CalibrationConfig>();
+    }
+
+    //[Device("Calibration", "鍗曟鏍囧畾閰嶇疆", EnumHelper.DeviceAttributeType.OperationConfig)]
+    public class CalibrationConfig : OperationConfigBase, IComplexDisplay, INotifyPropertyChanged
+    {
+        private Bitmap image = null;
+        [JsonIgnore]
+        [Browsable(false)]
+        public Bitmap Image
+        {
+            get => image;
+            set
+            {
+                image = value;
+
+                if (value != null)
+                {
+                    //PropertyChanged?.BeginInvoke(this, new PropertyChangedEventArgs("Image"), null, null);
+                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Image"));
+                }
+            }
+        }
+
+        [Category("瑙嗚淇℃伅")]
+        [Description("绂荤嚎鍥剧墖璺緞")]
+        [Editor(typeof(FileDialogEditor), typeof(UITypeEditor))]
+        public string OfflineImagePath { get; set; }
+
+        [Category("瑙嗚淇℃伅")]
+        [Description("鍥惧儚鏍囧噯鐐瑰潗鏍�")]
+        [TypeConverter(typeof(ComplexObjectConvert))]
+        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
+        public CustomizedPoint ImageMarkPoint { get; set; } = new CustomizedPoint();
+
+        [Category("鐩告満閰嶇疆")]
+        [Description("鐩告満鎿嶄綔閰嶇疆")]
+        [TypeConverter(typeof(ComplexObjectConvert))]
+        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
+        public HalconRelatedCameraOprerationConfigBase CameraOpConfig { get; set; } = new HalconRelatedCameraOprerationConfigBase();
+
+        [Category("杩愬姩骞冲彴璁剧疆")]
+        [Description("骞冲彴褰撳墠鍧愭爣")]
+        [TypeConverter(typeof(ComplexObjectConvert))]
+        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
+        public CustomizedPoint CurrentPlatPoint { get; set; } = new CustomizedPoint();
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public string GetDisplayText()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+}
diff --git a/src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs b/src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs
new file mode 100644
index 0000000..b876f22
--- /dev/null
+++ b/src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs
@@ -0,0 +1,378 @@
+锘縩amespace A032.Process.Calibration
+{
+    partial class CtrlCalib9PDynamic
+    {
+        /// <summary> 
+        /// 蹇呴渶鐨勮璁″櫒鍙橀噺銆�
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// 娓呯悊鎵�鏈夋鍦ㄤ娇鐢ㄧ殑璧勬簮銆�
+        /// </summary>
+        /// <param name="disposing">濡傛灉搴旈噴鏀炬墭绠¤祫婧愶紝涓� true锛涘惁鍒欎负 false銆�</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region 缁勪欢璁捐鍣ㄧ敓鎴愮殑浠g爜
+
+        /// <summary> 
+        /// 璁捐鍣ㄦ敮鎸佹墍闇�鐨勬柟娉� - 涓嶈淇敼
+        /// 浣跨敤浠g爜缂栬緫鍣ㄤ慨鏀规鏂规硶鐨勫唴瀹广��
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.splitMain = new System.Windows.Forms.SplitContainer();
+            this.plOpBtns = new System.Windows.Forms.Panel();
+            this.chkManualConfirm = new System.Windows.Forms.CheckBox();
+            this.label3 = new System.Windows.Forms.Label();
+            this.label2 = new System.Windows.Forms.Label();
+            this.label1 = new System.Windows.Forms.Label();
+            this.chkContinueMode = new System.Windows.Forms.CheckBox();
+            this.chkOfflineRun = new System.Windows.Forms.CheckBox();
+            this.chkOfflineCalib = new System.Windows.Forms.CheckBox();
+            this.btnCalcuMatrix = new System.Windows.Forms.Button();
+            this.btnContinueCalib = new System.Windows.Forms.Button();
+            this.btnSnap = new System.Windows.Forms.Button();
+            this.btnLoadOfflineImages = new System.Windows.Forms.Button();
+            this.btnStepRun = new System.Windows.Forms.Button();
+            this.btnStartCalib = new System.Windows.Forms.Button();
+            this.splitConfig = new System.Windows.Forms.SplitContainer();
+            this.lvConfigs = new System.Windows.Forms.ListView();
+            this.clIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.clConfigData = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.propGridConfig = new System.Windows.Forms.PropertyGrid();
+            this.plImage = new System.Windows.Forms.Panel();
+            this.stspStatus = new System.Windows.Forms.StatusStrip();
+            this.tsslStepHint = new System.Windows.Forms.ToolStripStatusLabel();
+            this.tsslInfo = new System.Windows.Forms.ToolStripStatusLabel();
+            ((System.ComponentModel.ISupportInitialize)(this.splitMain)).BeginInit();
+            this.splitMain.Panel1.SuspendLayout();
+            this.splitMain.Panel2.SuspendLayout();
+            this.splitMain.SuspendLayout();
+            this.plOpBtns.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.splitConfig)).BeginInit();
+            this.splitConfig.Panel1.SuspendLayout();
+            this.splitConfig.Panel2.SuspendLayout();
+            this.splitConfig.SuspendLayout();
+            this.stspStatus.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // splitMain
+            // 
+            this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
+            this.splitMain.Location = new System.Drawing.Point(0, 0);
+            this.splitMain.Name = "splitMain";
+            // 
+            // splitMain.Panel1
+            // 
+            this.splitMain.Panel1.Controls.Add(this.plOpBtns);
+            this.splitMain.Panel1.Controls.Add(this.splitConfig);
+            // 
+            // splitMain.Panel2
+            // 
+            this.splitMain.Panel2.Controls.Add(this.plImage);
+            this.splitMain.Panel2.Controls.Add(this.stspStatus);
+            this.splitMain.Size = new System.Drawing.Size(836, 514);
+            this.splitMain.SplitterDistance = 278;
+            this.splitMain.TabIndex = 0;
+            // 
+            // plOpBtns
+            // 
+            this.plOpBtns.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.plOpBtns.Controls.Add(this.chkManualConfirm);
+            this.plOpBtns.Controls.Add(this.label3);
+            this.plOpBtns.Controls.Add(this.label2);
+            this.plOpBtns.Controls.Add(this.label1);
+            this.plOpBtns.Controls.Add(this.chkContinueMode);
+            this.plOpBtns.Controls.Add(this.chkOfflineRun);
+            this.plOpBtns.Controls.Add(this.chkOfflineCalib);
+            this.plOpBtns.Controls.Add(this.btnCalcuMatrix);
+            this.plOpBtns.Controls.Add(this.btnContinueCalib);
+            this.plOpBtns.Controls.Add(this.btnSnap);
+            this.plOpBtns.Controls.Add(this.btnLoadOfflineImages);
+            this.plOpBtns.Controls.Add(this.btnStepRun);
+            this.plOpBtns.Controls.Add(this.btnStartCalib);
+            this.plOpBtns.Location = new System.Drawing.Point(3, 354);
+            this.plOpBtns.Name = "plOpBtns";
+            this.plOpBtns.Size = new System.Drawing.Size(272, 158);
+            this.plOpBtns.TabIndex = 1;
+            // 
+            // chkManualConfirm
+            // 
+            this.chkManualConfirm.AutoSize = true;
+            this.chkManualConfirm.Location = new System.Drawing.Point(11, 132);
+            this.chkManualConfirm.Name = "chkManualConfirm";
+            this.chkManualConfirm.Size = new System.Drawing.Size(72, 16);
+            this.chkManualConfirm.TabIndex = 3;
+            this.chkManualConfirm.Text = "浜哄伐纭";
+            this.chkManualConfirm.UseVisualStyleBackColor = true;
+            // 
+            // label3
+            // 
+            this.label3.AutoSize = true;
+            this.label3.Location = new System.Drawing.Point(6, 117);
+            this.label3.Name = "label3";
+            this.label3.Size = new System.Drawing.Size(257, 12);
+            this.label3.TabIndex = 2;
+            this.label3.Text = "------------------------------------------";
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Location = new System.Drawing.Point(6, 76);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(257, 12);
+            this.label2.TabIndex = 2;
+            this.label2.Text = "------------------------------------------";
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Location = new System.Drawing.Point(7, 35);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(257, 12);
+            this.label1.TabIndex = 2;
+            this.label1.Text = "------------------------------------------";
+            // 
+            // chkContinueMode
+            // 
+            this.chkContinueMode.AutoSize = true;
+            this.chkContinueMode.Location = new System.Drawing.Point(90, 95);
+            this.chkContinueMode.Name = "chkContinueMode";
+            this.chkContinueMode.Size = new System.Drawing.Size(72, 16);
+            this.chkContinueMode.TabIndex = 1;
+            this.chkContinueMode.Text = "杩炵画鎷嶇収";
+            this.chkContinueMode.UseVisualStyleBackColor = true;
+            this.chkContinueMode.CheckedChanged += new System.EventHandler(this.chkContinueMode_CheckedChanged);
+            // 
+            // chkOfflineRun
+            // 
+            this.chkOfflineRun.AutoSize = true;
+            this.chkOfflineRun.Location = new System.Drawing.Point(90, 56);
+            this.chkOfflineRun.Name = "chkOfflineRun";
+            this.chkOfflineRun.Size = new System.Drawing.Size(72, 16);
+            this.chkOfflineRun.TabIndex = 1;
+            this.chkOfflineRun.Text = "绂荤嚎妯″紡";
+            this.chkOfflineRun.UseVisualStyleBackColor = true;
+            // 
+            // chkOfflineCalib
+            // 
+            this.chkOfflineCalib.AutoSize = true;
+            this.chkOfflineCalib.Location = new System.Drawing.Point(90, 15);
+            this.chkOfflineCalib.Name = "chkOfflineCalib";
+            this.chkOfflineCalib.Size = new System.Drawing.Size(72, 16);
+            this.chkOfflineCalib.TabIndex = 1;
+            this.chkOfflineCalib.Text = "绂荤嚎妯″紡";
+            this.chkOfflineCalib.UseVisualStyleBackColor = true;
+            // 
+            // btnCalcuMatrix
+            // 
+            this.btnCalcuMatrix.Location = new System.Drawing.Point(168, 50);
+            this.btnCalcuMatrix.Name = "btnCalcuMatrix";
+            this.btnCalcuMatrix.Size = new System.Drawing.Size(93, 23);
+            this.btnCalcuMatrix.TabIndex = 0;
+            this.btnCalcuMatrix.Text = "鏍囧畾缁撴灉杩愮畻";
+            this.btnCalcuMatrix.UseVisualStyleBackColor = true;
+            this.btnCalcuMatrix.Click += new System.EventHandler(this.btnCalcuMatrix_Click);
+            // 
+            // btnContinueCalib
+            // 
+            this.btnContinueCalib.Location = new System.Drawing.Point(87, 128);
+            this.btnContinueCalib.Name = "btnContinueCalib";
+            this.btnContinueCalib.Size = new System.Drawing.Size(75, 23);
+            this.btnContinueCalib.TabIndex = 0;
+            this.btnContinueCalib.Text = "缁х画鏍囧畾";
+            this.btnContinueCalib.UseVisualStyleBackColor = true;
+            this.btnContinueCalib.Visible = false;
+            this.btnContinueCalib.Click += new System.EventHandler(this.btnContinueCalib_Click);
+            // 
+            // btnSnap
+            // 
+            this.btnSnap.Location = new System.Drawing.Point(8, 91);
+            this.btnSnap.Name = "btnSnap";
+            this.btnSnap.Size = new System.Drawing.Size(75, 23);
+            this.btnSnap.TabIndex = 0;
+            this.btnSnap.Text = "閲嶆柊鎷嶇収";
+            this.btnSnap.UseVisualStyleBackColor = true;
+            this.btnSnap.Click += new System.EventHandler(this.btnSnap_Click);
+            // 
+            // btnLoadOfflineImages
+            // 
+            this.btnLoadOfflineImages.Location = new System.Drawing.Point(168, 9);
+            this.btnLoadOfflineImages.Name = "btnLoadOfflineImages";
+            this.btnLoadOfflineImages.Size = new System.Drawing.Size(93, 23);
+            this.btnLoadOfflineImages.TabIndex = 0;
+            this.btnLoadOfflineImages.Text = "杞藉叆绂荤嚎鍥剧墖";
+            this.btnLoadOfflineImages.UseVisualStyleBackColor = true;
+            this.btnLoadOfflineImages.Click += new System.EventHandler(this.btnLoadOfflineImages_Click);
+            // 
+            // btnStepRun
+            // 
+            this.btnStepRun.Location = new System.Drawing.Point(8, 50);
+            this.btnStepRun.Name = "btnStepRun";
+            this.btnStepRun.Size = new System.Drawing.Size(75, 23);
+            this.btnStepRun.TabIndex = 0;
+            this.btnStepRun.Text = "鍗曟杩愮畻";
+            this.btnStepRun.UseVisualStyleBackColor = true;
+            this.btnStepRun.Click += new System.EventHandler(this.btnStepRun_Click);
+            // 
+            // btnStartCalib
+            // 
+            this.btnStartCalib.Location = new System.Drawing.Point(8, 9);
+            this.btnStartCalib.Name = "btnStartCalib";
+            this.btnStartCalib.Size = new System.Drawing.Size(75, 23);
+            this.btnStartCalib.TabIndex = 0;
+            this.btnStartCalib.Text = "寮�濮嬫爣瀹�";
+            this.btnStartCalib.UseVisualStyleBackColor = true;
+            this.btnStartCalib.Click += new System.EventHandler(this.btnStartCalib_Click);
+            // 
+            // splitConfig
+            // 
+            this.splitConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.splitConfig.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
+            this.splitConfig.Location = new System.Drawing.Point(3, 0);
+            this.splitConfig.Name = "splitConfig";
+            this.splitConfig.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // splitConfig.Panel1
+            // 
+            this.splitConfig.Panel1.Controls.Add(this.lvConfigs);
+            // 
+            // splitConfig.Panel2
+            // 
+            this.splitConfig.Panel2.Controls.Add(this.propGridConfig);
+            this.splitConfig.Size = new System.Drawing.Size(272, 348);
+            this.splitConfig.SplitterDistance = 61;
+            this.splitConfig.TabIndex = 0;
+            // 
+            // lvConfigs
+            // 
+            this.lvConfigs.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.clIndex,
+            this.clConfigData});
+            this.lvConfigs.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.lvConfigs.FullRowSelect = true;
+            this.lvConfigs.Location = new System.Drawing.Point(0, 0);
+            this.lvConfigs.MultiSelect = false;
+            this.lvConfigs.Name = "lvConfigs";
+            this.lvConfigs.Size = new System.Drawing.Size(272, 61);
+            this.lvConfigs.TabIndex = 0;
+            this.lvConfigs.UseCompatibleStateImageBehavior = false;
+            this.lvConfigs.View = System.Windows.Forms.View.Details;
+            this.lvConfigs.SelectedIndexChanged += new System.EventHandler(this.lvConfigs_SelectedIndexChanged);
+            // 
+            // clIndex
+            // 
+            this.clIndex.Text = "搴忓彿";
+            // 
+            // clConfigData
+            // 
+            this.clConfigData.Text = "閰嶇疆";
+            this.clConfigData.Width = 200;
+            // 
+            // propGridConfig
+            // 
+            this.propGridConfig.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.propGridConfig.Location = new System.Drawing.Point(0, 0);
+            this.propGridConfig.Name = "propGridConfig";
+            this.propGridConfig.Size = new System.Drawing.Size(272, 283);
+            this.propGridConfig.TabIndex = 0;
+            this.propGridConfig.ToolbarVisible = false;
+            // 
+            // plImage
+            // 
+            this.plImage.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.plImage.Location = new System.Drawing.Point(-1, 0);
+            this.plImage.Name = "plImage";
+            this.plImage.Size = new System.Drawing.Size(555, 489);
+            this.plImage.TabIndex = 1;
+            // 
+            // stspStatus
+            // 
+            this.stspStatus.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.tsslStepHint,
+            this.tsslInfo});
+            this.stspStatus.Location = new System.Drawing.Point(0, 492);
+            this.stspStatus.Name = "stspStatus";
+            this.stspStatus.Size = new System.Drawing.Size(554, 22);
+            this.stspStatus.TabIndex = 0;
+            this.stspStatus.Text = "statusStrip1";
+            // 
+            // tsslStepHint
+            // 
+            this.tsslStepHint.Name = "tsslStepHint";
+            this.tsslStepHint.Size = new System.Drawing.Size(28, 17);
+            this.tsslStepHint.Text = "     ";
+            // 
+            // tsslInfo
+            // 
+            this.tsslInfo.Name = "tsslInfo";
+            this.tsslInfo.Size = new System.Drawing.Size(28, 17);
+            this.tsslInfo.Text = "     ";
+            // 
+            // CtrlCalib9PDynamic
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.splitMain);
+            this.Name = "CtrlCalib9PDynamic";
+            this.Size = new System.Drawing.Size(836, 514);
+            this.Load += new System.EventHandler(this.CtrlCalib9PDynamic_Load);
+            this.splitMain.Panel1.ResumeLayout(false);
+            this.splitMain.Panel2.ResumeLayout(false);
+            this.splitMain.Panel2.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.splitMain)).EndInit();
+            this.splitMain.ResumeLayout(false);
+            this.plOpBtns.ResumeLayout(false);
+            this.plOpBtns.PerformLayout();
+            this.splitConfig.Panel1.ResumeLayout(false);
+            this.splitConfig.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.splitConfig)).EndInit();
+            this.splitConfig.ResumeLayout(false);
+            this.stspStatus.ResumeLayout(false);
+            this.stspStatus.PerformLayout();
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.SplitContainer splitMain;
+        private System.Windows.Forms.Panel plOpBtns;
+        private System.Windows.Forms.SplitContainer splitConfig;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.CheckBox chkContinueMode;
+        private System.Windows.Forms.CheckBox chkOfflineRun;
+        private System.Windows.Forms.CheckBox chkOfflineCalib;
+        private System.Windows.Forms.Button btnCalcuMatrix;
+        private System.Windows.Forms.Button btnSnap;
+        private System.Windows.Forms.Button btnLoadOfflineImages;
+        private System.Windows.Forms.Button btnStepRun;
+        private System.Windows.Forms.Button btnStartCalib;
+        private System.Windows.Forms.Panel plImage;
+        private System.Windows.Forms.StatusStrip stspStatus;
+        private System.Windows.Forms.ToolStripStatusLabel tsslStepHint;
+        private System.Windows.Forms.ToolStripStatusLabel tsslInfo;
+        private System.Windows.Forms.ListView lvConfigs;
+        private System.Windows.Forms.ColumnHeader clIndex;
+        private System.Windows.Forms.ColumnHeader clConfigData;
+        private System.Windows.Forms.PropertyGrid propGridConfig;
+        private System.Windows.Forms.CheckBox chkManualConfirm;
+        private System.Windows.Forms.Label label3;
+        private System.Windows.Forms.Button btnContinueCalib;
+    }
+}
diff --git a/src/A032.Process/Calibration/CtrlCalib9PDynamic.cs b/src/A032.Process/Calibration/CtrlCalib9PDynamic.cs
new file mode 100644
index 0000000..dcf6c73
--- /dev/null
+++ b/src/A032.Process/Calibration/CtrlCalib9PDynamic.cs
@@ -0,0 +1,277 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using Bro.Common.Helper;
+using Bro.Common.Interface;
+using Bro.Common.Base;
+using Bro.Common.UI;
+using Bro.Common.PubSub;
+using static Bro.Common.Helper.EnumHelper;
+using Bro.Common.Model;
+using HalconDotNet;
+using System.Threading;
+
+namespace A032.Process.Calibration
+{
+    [Device("Calibration_9P_Dynamic", "鍔ㄦ��9鐐规爣瀹�", EnumHelper.DeviceAttributeType.OperationConfigCtrl)]
+    public partial class CtrlCalib9PDynamic : UserControl, IConfigCtrl<CameraBase, CalibrationConfigCollection>
+    {
+        PubSubCenter PubSubCenter = PubSubCenter.GetInstance();
+        AutoResetEvent _confirmHandle = new AutoResetEvent(false);
+
+        public CtrlCalib9PDynamic()
+        {
+            InitializeComponent();
+        }
+
+        public CtrlCalib9PDynamic(ProcessControl process, IDevice device, IOperationConfig config, Action<List<CalibrationConfig>> finalCalculation)
+        {
+            InitializeComponent();
+
+            ProcessControl = process;
+            Camera = device as CameraBase;
+            Config = config as CalibrationConfigCollection;
+            FinalCalculation = finalCalculation;
+        }
+
+        public CalibrationConfigCollection Config { get; set; }
+        public CameraBase Camera { get; set; }
+        public ProcessControl ProcessControl { get; set; }
+        public Action<List<CalibrationConfig>> FinalCalculation { get; set; }
+
+        public CalibrationConfigCollection GetConfig()
+        {
+            return null;
+        }
+
+        public void LoadConfig(CalibrationConfigCollection config)
+        {
+            lvConfigs.Items.Clear();
+
+            for (int i = 0; i < Config.Configs.Count; i++)
+            {
+                ListViewItem item = new ListViewItem((i + 1).ToString());
+                item.SubItems.Add(Config.Configs[i].GetDisplayText());
+
+                lvConfigs.Items.Add(item);
+
+                Config.Configs[i].PropertyChanged -= CtrlCalib9PDynamic_PropertyChanged;
+                Config.Configs[i].PropertyChanged += CtrlCalib9PDynamic_PropertyChanged;
+            }
+
+            if (lvConfigs.Items.Count > 0)
+            {
+                lvConfigs.Items[0].Selected = true;
+            }
+        }
+
+        private void CtrlCalib9PDynamic_PropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            OnCalibPropertyChanged();
+        }
+
+        private void OnCalibPropertyChanged()
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(new Action(() => OnCalibPropertyChanged()));
+            }
+            else
+            {
+                for (int i = 0; i < Config.Configs.Count; i++)
+                {
+                    if (Config.Configs[i].Image != null)
+                    {
+                        lvConfigs.Items[i].BackColor = Color.GreenYellow;
+                    }
+                    else
+                    {
+                        lvConfigs.Items[i].BackColor = Color.Transparent;
+                    }
+                }
+            }
+        }
+
+        Canvas _canvas = new Canvas();
+        private void CtrlCalib9PDynamic_Load(object sender, EventArgs e)
+        {
+            _canvas.IsShowElementList = true;
+            _canvas.Dock = DockStyle.Fill;
+            plImage.Controls.Add(_canvas);
+
+            RemoveHandles();
+
+            if (Camera != null)
+            {
+                Camera.UpdateShowImage -= Camera_UpdateShowImage;
+                Camera.UpdateShowImage += Camera_UpdateShowImage;
+            }
+
+            LoadConfig(Config);
+
+            PubSubCenter.Subscribe(PubTag.CalibStepDone.ToString(), CalibStepDone);
+            PubSubCenter.Subscribe(PubTag.CalibAllDone.ToString(), CalibAllDone);
+        }
+
+        public void RemoveHandles()
+        {
+            if (Camera != null)
+            {
+                Camera.UpdateShowImage -= Camera_UpdateShowImage;
+            }
+            PubSubCenter.RemoveSubscriber(PubTag.CalibStepDone.ToString(), CalibStepDone);
+            PubSubCenter.RemoveSubscriber(PubTag.CalibAllDone.ToString(), CalibAllDone);
+
+            chkContinueMode.Checked = false;
+        }
+
+        AutoResetEvent _imageShowedHandle = new AutoResetEvent(false);
+        private void Camera_UpdateShowImage(CameraBase camera, Bitmap image, string imagePath)
+        {
+            _canvas.LoadImage(image);
+            _imageShowedHandle.Set();
+        }
+
+        private object CalibAllDone(ISubscriber arg1, object arg2, object arg3)
+        {
+            string msg = arg3.ToString();
+            tsslInfo.Text = msg;
+
+            return null;
+        }
+
+        private object CalibStepDone(ISubscriber arg1, object arg2, object arg3)
+        {
+            int index = Convert.ToInt32(arg2);
+
+            _canvas.LoadImage(Config.Configs[index].Image);
+            _canvas.Elements.Clear();
+            CrossHair ch = new CrossHair(new CalibrationPoint(Config.Configs[index].ImageMarkPoint, Config.Configs[index].CurrentPlatPoint));
+            _canvas.Elements.Add(ch);
+
+            tsslInfo.Text = $"姝ラ{index + 1}瀹屾垚";
+
+            if (chkManualConfirm.Checked)
+            {
+                this.Invoke(new Action(() => btnContinueCalib.Visible = true));                ;
+                _confirmHandle.WaitOne();
+            }
+
+            return null;
+        }
+
+        int _selectedStepIndex = -1;
+        private void lvConfigs_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            if (lvConfigs.SelectedItems.Count <= 0)
+                return;
+
+            _selectedStepIndex = lvConfigs.SelectedIndices[0];
+            var stepConfig = Config.Configs[_selectedStepIndex];
+            propGridConfig.SelectedObject = stepConfig;
+
+            tsslStepHint.Text = $"{(_selectedStepIndex + 1).ToString()}/{Config.Configs.Count}";
+
+            if (stepConfig.Image != null)
+            {
+                _canvas.LoadImage(stepConfig.Image);
+                _canvas.Elements.Clear();
+                _canvas.Elements.Add(new CrossHair(new CalibrationPoint(stepConfig.ImageMarkPoint, stepConfig.CurrentPlatPoint)));
+            }
+        }
+
+        private void btnStartCalib_Click(object sender, EventArgs e)
+        {
+            if (chkOfflineCalib.Checked)
+            {
+                //ProcessControl.Calibration_Pick_9P_Dynamic_Offline(Config.Configs);
+            }
+            else
+            {
+                //ProcessControl.SendCalibStartSignal(Config.TriggerAddress);
+            }
+        }
+
+        private void btnLoadOfflineImages_Click(object sender, EventArgs e)
+        {
+            Config.Configs.ForEach(c =>
+            {
+                if (!string.IsNullOrWhiteSpace(c.OfflineImagePath))
+                {
+                    c.Image = (Bitmap)Image.FromFile(c.OfflineImagePath);
+                }
+            });
+
+            tsslInfo.Text = "绂荤嚎鍥剧墖杞藉叆瀹屾垚";
+        }
+
+        private void btnStepRun_Click(object sender, EventArgs e)
+        {
+            //CalibrationConfig config = propGridConfig.SelectedObject as CalibrationConfig;
+            //if (!chkOfflineRun.Checked)
+            //{
+            //    ProcessControl.CalibMarkPoint(Camera, config, _selectedStepIndex + 1);
+            //}
+            //else
+            //{
+            //    ProcessControl.CalibMarkPoint(_canvas.MAP, config, _selectedStepIndex + 1);
+            //}
+
+            //tsslInfo.Text = $"鍗曟杩愮畻瀹屾垚銆傛爣璁扮偣鍧愭爣锛歿config.ImageMarkPoint.X},{config.ImageMarkPoint.Y}";
+        }
+
+        private void btnCalcuMatrix_Click(object sender, EventArgs e)
+        {
+            FinalCalculation.Invoke(Config.Configs);
+        }
+
+        private void btnSnap_Click(object sender, EventArgs e)
+        {
+            CalibrationConfig config = propGridConfig.SelectedObject as CalibrationConfig;
+            Camera.UploadOperationConfig(config.CameraOpConfig);
+            Camera.Snapshot(config.CameraOpConfig, out HObject hImage);
+            hImage.Dispose();
+        }
+
+        private void chkContinueMode_CheckedChanged(object sender, EventArgs e)
+        {
+            if (chkContinueMode.Checked)
+            {
+                Task.Run(() =>
+                {
+                    HalconRelatedCameraOprerationConfigBase config = (propGridConfig.SelectedObject as CalibrationConfig).CameraOpConfig;
+                    bool temp = config.IsSaveImage;
+                    config.IsSaveImage = false;
+
+                    while (chkContinueMode.Checked)
+                    {
+                        try
+                        {
+                            Camera.UploadOperationConfig(config);
+                            Camera.Snapshot();
+
+                            _imageShowedHandle.WaitOne(3000);
+                        }
+                        catch (Exception)
+                        {
+                        }
+                    }
+
+                    config.IsSaveImage = temp;
+                });
+            }
+        }
+
+        private void btnContinueCalib_Click(object sender, EventArgs e)
+        {
+            _confirmHandle.Set();
+            btnContinueCalib.Visible = false;
+        }
+    }
+}
diff --git a/src/A032.Process/Calibration/CtrlCalib9PDynamic.resx b/src/A032.Process/Calibration/CtrlCalib9PDynamic.resx
new file mode 100644
index 0000000..1b481a6
--- /dev/null
+++ b/src/A032.Process/Calibration/CtrlCalib9PDynamic.resx
@@ -0,0 +1,123 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="stspStatus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
\ No newline at end of file
diff --git a/src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs b/src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs
new file mode 100644
index 0000000..1c2fab9
--- /dev/null
+++ b/src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs
@@ -0,0 +1,47 @@
+锘縩amespace A032.Process.Calibration
+{
+    partial class FrmCalib9PDynamic
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            // 
+            // FrmCalib9PDynamic
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Name = "FrmCalib9PDynamic";
+            this.Text = "鏍囧畾鐣岄潰";
+            this.Load += new System.EventHandler(this.FrmCalib9PDynamic_Load);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+    }
+}
\ No newline at end of file
diff --git a/src/A032.Process/Calibration/FrmCalib9PDynamic.cs b/src/A032.Process/Calibration/FrmCalib9PDynamic.cs
new file mode 100644
index 0000000..e780681
--- /dev/null
+++ b/src/A032.Process/Calibration/FrmCalib9PDynamic.cs
@@ -0,0 +1,50 @@
+锘縰sing Bro.Common.Base;
+using Bro.Common.Interface;
+using Bro.Common.UI;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace A032.Process.Calibration
+{
+    public partial class FrmCalib9PDynamic : Form
+    {
+        public FrmCalib9PDynamic()
+        {
+            InitializeComponent();
+        }
+
+        public FrmCalib9PDynamic(ProcessControl process, IDevice device, IOperationConfig config, Action<List<CalibrationConfig>> finalCalculation)
+        {
+            InitializeComponent();
+
+            Device = device as CameraBase;
+            Config = config as CalibrationConfigCollection;
+            FinalCalculation = finalCalculation;
+            CtrlCalib9PDynamic = new CtrlCalib9PDynamic(process, device, config, finalCalculation);
+        }
+
+        CameraBase Device { get; set; }
+        CalibrationConfigCollection Config { get; set; }
+        CtrlCalib9PDynamic CtrlCalib9PDynamic { get; set; }
+        Action<List<CalibrationConfig>> FinalCalculation { get; set; }
+
+        private void FrmCalib9PDynamic_Load(object sender, EventArgs e)
+        {
+            CtrlCalib9PDynamic.Dock = DockStyle.Fill;
+            this.Controls.Add(CtrlCalib9PDynamic);
+
+            this.FormClosing += (send, ee) =>
+              {
+                  CtrlCalib9PDynamic.RemoveHandles();
+              };
+        }
+    }
+}
diff --git a/src/A032.Process/Calibration/FrmCalib9PDynamic.resx b/src/A032.Process/Calibration/FrmCalib9PDynamic.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/src/A032.Process/Calibration/FrmCalib9PDynamic.resx
@@ -0,0 +1,120 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/src/A032.Process/ProcessConfig.cs b/src/A032.Process/ProcessConfig.cs
index 3640397..200811f 100644
--- a/src/A032.Process/ProcessConfig.cs
+++ b/src/A032.Process/ProcessConfig.cs
@@ -46,6 +46,12 @@
         [TypeConverter(typeof(CollectionCountConvert))]
         [Editor(typeof(ComplexCollectionEditor<HikCameraInitialConfig>), typeof(UITypeEditor))]
         public List<HikCameraInitialConfig> CameraConfigCollection { get; set; } = new List<HikCameraInitialConfig>();
+
+        [Category("璁惧閰嶇疆")]
+        [Description("AGV灏忚溅璁惧缁戝畾閰嶇疆锛岄厤缃粦瀹氱殑AGV锛屾満鍣ㄤ汉鍜岀浉鏈轰俊鎭�")]
+        [TypeConverter(typeof(CollectionCountConvert))]
+        [Editor(typeof(ComplexCollectionEditor<AGVBindUnit>), typeof(UITypeEditor))]
+        public List<AGVBindUnit> AGVBindCollection { get; set; } = new List<AGVBindUnit>();
         #endregion
 
         #region 鎿嶄綔閰嶇疆
@@ -55,15 +61,15 @@
         //[Editor(typeof(MonitorSetBindEditor), typeof(UITypeEditor))]
         //public Dictionary<string, MonitorSet> PLCMonitorSet { get; set; } = new Dictionary<string, MonitorSet>();
 
-        ///// <summary>
-        ///// 鎿嶄綔閰嶇疆鐨勫瓧鍏搁泦鍚�
-        ///// Key锛歁ethodCode锛孷alue锛氭搷浣滈厤缃�
-        ///// </summary>
-        //[Category("鎿嶄綔閰嶇疆")]
-        //[Description("鎿嶄綔閰嶇疆闆嗗悎")]
-        //[TypeConverter(typeof(CollectionCountConvert))]
-        //[Editor(typeof(OperationConfigBindEditor), typeof(UITypeEditor))]
-        //public Dictionary<string, IOperationConfig> ProcessOpConfigDict { get; set; } = new Dictionary<string, IOperationConfig>();
+        /// <summary>
+        /// 鎿嶄綔閰嶇疆鐨勫瓧鍏搁泦鍚�
+        /// Key锛歁ethodCode锛孷alue锛氭搷浣滈厤缃�
+        /// </summary>
+        [Category("鎿嶄綔閰嶇疆")]
+        [Description("榛樿鎿嶄綔閰嶇疆闆嗗悎")]
+        [TypeConverter(typeof(CollectionCountConvert))]
+        [Editor(typeof(OperationConfigBindEditor), typeof(UITypeEditor))]
+        public Dictionary<string, IOperationConfig> ProcessOpConfigDict { get; set; } = new Dictionary<string, IOperationConfig>();
 
         //[Category("鐩戝惉鍜屾搷浣滈厤缃�")]
         //[Description("鐩戝惉鎿嶄綔閰嶇疆闆嗗悎")]
@@ -140,8 +146,44 @@
         [Category("璺緞鐩稿叧")]
         [Description("鍚勪綅缃爣瀹氱煩闃�")]
         [TypeConverter(typeof(CollectionCountConvert))]
-        [Editor(typeof(ComplexCollectionEditor<PositionMatrix>), typeof(UITypeEditor))]
-        public List<PositionMatrix> MatrixCollection { get; set; } = new List<PositionMatrix>();
+        [Editor(typeof(ComplexCollectionEditor<PositionVisionConfig>), typeof(UITypeEditor))]
+        public List<PositionVisionConfig> VisionConfigCollection { get; set; } = new List<PositionVisionConfig>();
+
+        /// <summary>
+        /// 绌篢ray涓婃枡闃堝�硷紝AGV涓婄殑绌簍ray鏁伴噺涓嶅ぇ浜庤鏁板�兼椂锛孉GV鍙互鎵ц绌篢ray涓婃枡浠诲姟
+        /// </summary>
+        [Category("闃堝�艰缃�")]
+        [Description("绌篢ray涓婃枡闃堝�硷紝AGV涓婄殑绌簍ray鏁伴噺涓嶅ぇ浜庤鏁板�兼椂锛孉GV鍙互鎵ц绌篢ray涓婃枡浠诲姟")]
+        public int AGV_EmptyTrayThreshold { get; set; } = 0;
+
+        /// <summary>
+        /// 婊ray涓嬫枡闃堝�硷紝AGV涓婄殑婊ray鏁伴噺涓嶅皬浜庤鏁板�兼椂锛孉GV鍙互鎵ц婊ray涓嬫枡浠诲姟
+        /// </summary>
+        [Category("闃堝�艰缃�")]
+        [Description("婊ray涓嬫枡闃堝�硷紝AGV涓婄殑婊ray鏁伴噺涓嶅皬浜庤鏁板�兼椂锛孉GV鍙互鎵ц婊ray涓嬫枡浠诲姟")]
+        public int AGV_FullTrayThreshold { get; set; } = 10;
+
+        /// <summary>
+        /// 浜х嚎蹇欐椂鎷嶇収纭绛夊緟闂撮殧锛屼互绉掍负鍗曚綅
+        /// </summary>
+        [Category("闃堝�艰缃�")]
+        [Description("浜х嚎蹇欐椂鎷嶇収纭绛夊緟闂撮殧锛屼互绉掍负鍗曚綅")]
+        public int LineBusyWaitInterval { get; set; } = 60;
+
+        /// <summary>
+        /// 浜х嚎蹇欐椂鎷嶇収閲嶈瘯娆℃暟
+        /// </summary>
+        [Category("闃堝�艰缃�")]
+        [Description("浜х嚎蹇欐椂鎷嶇収閲嶈瘯娆℃暟")]
+        public int LineBusyRetryTimes { get; set; } = 10;
+
+        [Category("闃堝�艰缃�")]
+        [Description("鏈哄彴鍘嬫満婊ray鏁伴噺")]
+        public int Machine_FullTrayNum { get; set; }
+
+        [Category("闃堝�艰缃�")]
+        [Description("鏈哄彴鍘嬫満绌篢ray鏁伴噺")]
+        public int Machine_EmptyTrayNum { get; set; }
 
         #region Ignore
         [Browsable(false)]
@@ -190,55 +232,5 @@
         [JsonIgnore]
         public virtual bool IsDBSave { get; set; } = false;
         #endregion
-    }
-
-    public class PathPosition : IComplexDisplay
-    {
-        [Category("瀵艰埅璺緞")]
-        [Description("璺緞鑺傜偣浠g爜")]
-        public string PositionCode { get; set; }
-
-        [Category("瀵艰埅璺緞")]
-        [Description("璺緞鑺傜偣鎻忚堪")]
-        public string Description { get; set; }
-
-        public string GetDisplayText()
-        {
-            return $"{PositionCode}-{Description}";
-        }
-    }
-
-    public class PositionMatrix : IComplexDisplay
-    {
-        [Category("浣嶇疆鐭╅樀")]
-        [Description("浣嶇疆浠g爜")]
-        [TypeConverter(typeof(PositionCodeConverter))]
-        public string PositionCode { get; set; }
-
-        [Category("浣嶇疆鐭╅樀")]
-        [Description("璇ヤ綅缃爣瀹氱煩闃�")]
-        [TypeConverter(typeof(SimpleCollectionConvert<double>))]
-        public List<double> Matrix { get; set; } = new List<double>();
-
-        public string GetDisplayText()
-        {
-            return $"{PositionCode}:{string.Join(",", Matrix)}";
-        }
-    }
-
-    public class PositionCodeConverter : ComboBoxItemTypeConvert
-    {
-        public override void GetConvertHash()
-        {
-            using (var scope = GlobalVar.Container.BeginLifetimeScope())
-            {
-                var config = scope.Resolve<ProcessConfig>();
-
-                config.PositionCollection.ForEach(p =>
-                {
-                    _hash[p.PositionCode] = $"{p.PositionCode}-{p.Description}";
-                });
-            }
-        }
     }
 }
diff --git a/src/A032.Process/ProcessControl.cs b/src/A032.Process/ProcessControl.cs
index 7d26329..04638ee 100644
--- a/src/A032.Process/ProcessControl.cs
+++ b/src/A032.Process/ProcessControl.cs
@@ -179,7 +179,7 @@
 
             ProcessState = DeviceState.DSOpen;
 
-            QueryRobotIO();
+            //QueryRobotIO();
 
             //Task.Run(() =>
             //{
@@ -298,9 +298,43 @@
             InitialAGVs();
             InitialCameras();
 
+            InitialAGVBindUnit();
+
+            InitialMachineTrayNums();
+
             AutoFacRegister();
 
             LogAsync(DateTime.Now, "Process Initialized", "");
+        }
+
+        private void InitialMachineTrayNums()
+        {
+            machineEmptyTrayDict = Config.PositionCollection.Where(u => u.Description == PathPositionDefinition.UnloadEmptyTray).ToDictionary(p => p.PositionNo, p => 0);
+
+            machineFullTrayDict = Config.PositionCollection.Where(u => u.Description == PathPositionDefinition.LoadFullTray).ToDictionary(p => p.PositionNo, p => 0);
+        }
+
+        private void InitialAGVBindUnit()
+        {
+            Config.AGVBindCollection.ForEach(u =>
+            {
+                if (AGVDict.ContainsKey(u.AGVId))
+                {
+                    u.AGV = AGVDict[u.AGVId];
+                }
+
+                if (RobotDict.ContainsKey(u.RobotId))
+                {
+                    u.Robot = RobotDict[u.RobotId];
+                }
+
+                if (CameraDict.ContainsKey(u.CameraId))
+                {
+                    u.Camera = CameraDict[u.CameraId];
+                }
+
+                u.OnMethodInvoke = OnBindUnitTaskInvoke;
+            });
         }
 
         private void InitialCameras()
@@ -321,11 +355,11 @@
                 plc.InitialConfig = c;
                 PLCDict[plc.InitialConfig.ID] = plc;
 
-                plc.OnMonitorAlarm -= Plc_OnMonitorAlarm;
-                plc.OnMonitorInvoke -= Plc_OnMonitorInvoke;
+                plc.OnMonitorAlarm -= OnMonitorAlarm;
+                plc.OnMonitorInvoke -= OnMonitorInvoke;
 
-                plc.OnMonitorAlarm += Plc_OnMonitorAlarm;
-                plc.OnMonitorInvoke += Plc_OnMonitorInvoke;
+                plc.OnMonitorAlarm += OnMonitorAlarm;
+                plc.OnMonitorInvoke += OnMonitorInvoke;
             });
         }
 
@@ -336,6 +370,14 @@
                 AuboRobotDriver robot = new AuboRobotDriver();
                 robot.InitialConfig = c;
                 RobotDict[robot.InitialConfig.ID] = robot;
+
+                robot.OnMsgReceived = OnRobotMsgReceived;
+
+                robot.OnMonitorAlarm -= OnMonitorAlarm;
+                robot.OnMonitorInvoke -= OnMonitorInvoke;
+
+                robot.OnMonitorAlarm += OnMonitorAlarm;
+                robot.OnMonitorInvoke += OnMonitorInvoke;
             });
         }
 
@@ -346,6 +388,9 @@
                 SeerAGVDriver agv = new SeerAGVDriver();
                 agv.InitialConfig = c;
                 AGVDict[agv.InitialConfig.ID] = agv;
+
+                agv.OnAGVPositoinChanged = OnAGVPositionChanged;
+                agv.OnAGVTaskStatusChanged = OnAGVTaskStatusChanged;
             });
         }
 
@@ -447,7 +492,7 @@
         /// </summary>
         protected Dictionary<string, HDevEngineTool> _halconToolDict = new Dictionary<string, HDevEngineTool>();
 
-        private void InitialProcessMethods()
+        public virtual void InitialProcessMethods()
         {
             _processMethodDict = new Dictionary<string, MethodInfo>();
             var methods = this.GetType().GetMethods().ToList();
@@ -458,7 +503,7 @@
                 {
                     _processMethodDict[attr.MethodCode] = m;
 
-                    #region 鍒濆鍖朒alconTool
+                    #region 鍒濆鍖朒alconTool 鏍规嵁processMethod鐨勭壒鎬ф潵閰嶇疆
                     //if (attr.DeviceType.EndsWith("Camera"))
                     //{
                     //    if (StationConfig.ProcessOpConfigDict.Keys.Contains(attr.MethodCode))
@@ -484,29 +529,39 @@
                 }
             });
 
-            #region 鍒濆鍖朒alconTool
+            #region 鍒濆鍖朒alconTool 鏍规嵁閰嶇疆鐨勬帴鍙g被鍨嬫潵閰嶇疆
             _halconToolDict = new Dictionary<string, HDevEngineTool>();
             Config.PLCConfigCollection.SelectMany(plcConfig => plcConfig.MonitorSetCollection).Select(ms => ms.OpConfig).ToList().ForEach(c =>
+            {
+                InitialHalconTool(c as IHalconToolPath);
+            });
+
+            Config.VisionConfigCollection.ForEach(c =>
+            {
+                InitialHalconTool(c as IHalconToolPath);
+            });
+            #endregion
+        }
+
+        private void InitialHalconTool(IHalconToolPath toolPath)
+        {
+            //IHalconToolPath toolPath = c as IHalconToolPath;
+            if (toolPath != null)
+            {
+                toolPath.GetHalconToolPathList().ForEach(path =>
                 {
-                    IHalconToolPath toolPath = c as IHalconToolPath;
-                    if (toolPath != null)
+                    if (!string.IsNullOrWhiteSpace(path) && !_halconToolDict.ContainsKey(path))
                     {
-                        toolPath.GetHalconToolPathList().ForEach(path =>
-                        {
-                            if (!string.IsNullOrWhiteSpace(path))
-                            {
-                                string directoryPath = Path.GetDirectoryName(path);
-                                string fileName = Path.GetFileNameWithoutExtension(path);
+                        string directoryPath = Path.GetDirectoryName(path);
+                        string fileName = Path.GetFileNameWithoutExtension(path);
 
-                                HDevEngineTool tool = new HDevEngineTool(directoryPath);
-                                tool.LoadProcedure(fileName);
+                        HDevEngineTool tool = new HDevEngineTool(directoryPath);
+                        tool.LoadProcedure(fileName);
 
-                                _halconToolDict[path] = tool;
-                            }
-                        });
+                        _halconToolDict[path] = tool;
                     }
                 });
-            #endregion
+            }
         }
 
         public List<IDevice> GetDeviceList()
@@ -521,9 +576,8 @@
             return list;
         }
 
-        #region PLC鐩戝惉
-
-        private void Plc_OnMonitorInvoke(DateTime dt, MonitorSet monitorSet)
+        #region IMonitor鐩戝惉
+        private void OnMonitorInvoke(DateTime dt, IDevice device, MonitorSet monitorSet)
         {
             IOperationConfig config = monitorSet.OpConfig;
             string methodCode = monitorSet.MethodCode;
@@ -535,7 +589,7 @@
                 try
                 {
                     //鏈塈OperationConfig鍙傛暟鐨勮皟鐢�
-                    res = _processMethodDict[methodCode].Invoke(this, new object[] { config });
+                    res = _processMethodDict[methodCode].Invoke(this, new object[] { config, device });
                     reTryTimes = -1;
                 }
                 catch (Exception invokeEX)  //娴佺▼鍔ㄤ綔寮傚父澶辫触
@@ -653,7 +707,7 @@
             #endregion
         }
 
-        private void Plc_OnMonitorAlarm(DateTime dt, WarningSet warning, bool isAlarmRaised)
+        private void OnMonitorAlarm(DateTime dt, IDevice device, WarningSet warning, bool isAlarmRaised)
         {
         }
 
@@ -1099,7 +1153,7 @@
         protected Dictionary<string, Queue<string>> CameraBitmapDict = new Dictionary<string, Queue<string>>();
         //protected Dictionary<string, Bitmap> CameraBitmapDict = new Dictionary<string, Bitmap>();
 
-        protected HObject CollectHImage(CameraBase camera, IOperationConfig opConfig, string cameraId, string methodCode)
+        protected HObject CollectHImage(CameraBase camera, IOperationConfig opConfig, string methodCode)
         {
             HObject hImage = null;
 
@@ -1131,9 +1185,6 @@
 
                 camera.UploadOperationConfig(opConfig);
                 camera.Snapshot(opConfig, out hImage);
-
-                //SaveTempImage(cameraName, camera.ImageFilePath);
-                //SaveTempImage(camera, cameraId);
 
                 if (cameraConifg.DelayAfter > 0)
                 {
diff --git a/src/A032.Process/ProcessControl_Calibration.cs b/src/A032.Process/ProcessControl_Calibration.cs
new file mode 100644
index 0000000..49fa9ee
--- /dev/null
+++ b/src/A032.Process/ProcessControl_Calibration.cs
@@ -0,0 +1,101 @@
+锘縰sing A032.Process.Calibration;
+using Bro.Common.Base;
+using Bro.Common.Helper;
+using Bro.Common.Interface;
+using Bro.Common.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace A032.Process
+{
+    public partial class ProcessControl
+    {
+        [ProcessMethod("CalibrationCollection", "RobotCalibration", "鏈哄櫒浜�9鐐规爣瀹�", true)]
+        public ProcessResponse RobotCalibration(IOperationConfig config)
+        {
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("CalibrationCollection", "StandardPointCalibration", "鏍囧噯鐐逛綅鏍囧畾", true)]
+        public ProcessResponse StandardPointCalibration(IOperationConfig config)
+        {
+            return new ProcessResponse(true);
+        }
+
+        public void SendCalibrationStartSignal(int stepNum)
+        {
+
+        }
+
+        private int MultipleStepsCalibration(CalibrationConfigCollection configs, Action<List<CalibrationConfig>> FinalCalculation)
+        {
+            throw new NotImplementedException();
+
+            //configs.Configs.ForEach(c =>
+            //{
+            //    c.Image = null;
+            //});
+
+            //if (string.IsNullOrWhiteSpace(configs.CameraId) || !CameraDict.ContainsKey(configs.CameraId))
+            //{
+            //    throw new ProcessException("鏍囧畾閰嶇疆鏈厤缃纭殑鐩告満缂栧彿");
+            //}
+
+            //if (string.IsNullOrWhiteSpace(configs.PositionCode))
+            //{
+            //    throw new ProcessException("鏍囧畾閰嶇疆鏈寚瀹氳矾寰勪綅缃�");
+            //}
+
+            //CameraBase camera = CameraDict[configs.CameraId];
+            //FrmCalib9PDynamic frm9PDynamic = new FrmCalib9PDynamic(this, camera, configs, FinalCalculation);
+            //frm9PDynamic.ShowDialog();
+
+            //if (configs.InputPara == null || configs.InputPara.Count <= 0)
+            //{
+            //    return (int)PLCReplyValue.NG;
+            //}
+
+            //if (configs.InputPara[0] <= 0 || configs.InputPara[0] > configs.Configs.Count)
+            //{
+            //    configs.InputPara = null;
+            //    return (int)PLCReplyValue.IGNORE;
+            //}
+
+            //int sequence = configs.InputPara[0];
+
+            ////绗竴娆�
+            //if (sequence == 1)
+            //{
+            //    configs.Configs.ForEach(c =>
+            //    {
+            //        c.Image = null;
+            //        c.OfflineImagePath = "";
+            //        c.CurrentPlatPoint = new CustomizedPoint();
+            //        c.ImageMarkPoint = new CustomizedPoint();
+            //    });
+            //}
+
+            //CalibrationConfig stepConfig = configs.Configs[sequence - 1];
+
+            //HDevEngineTool tool = _halconToolDict[]
+
+            //CalibMarkPoint(camera, stepConfig, sequence);
+
+            ////鑾峰彇褰撳墠骞冲彴鐐逛綅
+            //stepConfig.CurrentPlatPoint = new CustomizedPoint(_monitorList.Skip(locationStartIndex).Take(4).ToList());
+            ////stepConfig.CurrentPlatPoint = new CustomizedPoint(configs.InputPara.Skip(1).Take(4).ToList());
+
+            ////鏈�鍚庝竴娆�
+            //if (sequence == configs.Configs.Count)
+            //{
+            //    FinalCalculation?.Invoke(configs.Configs);
+            //}
+
+            //configs.InputPara = null;
+            //return (int)PLCReplyValue.OK;
+        }
+    }
+}
diff --git a/src/A032.Process/ProcessControl_Method.cs b/src/A032.Process/ProcessControl_Method.cs
index 1ff4d8d..61fb662 100644
--- a/src/A032.Process/ProcessControl_Method.cs
+++ b/src/A032.Process/ProcessControl_Method.cs
@@ -1,7 +1,10 @@
-锘縰sing Bro.Common.Helper;
+锘縰sing Bro.Common.Base;
+using Bro.Common.Helper;
 using Bro.Common.Interface;
 using Bro.Common.Model;
+using Bro.Device.AuboRobot;
 using Bro.Device.HikCamera;
+using Bro.Device.SeerAGV;
 using HalconDotNet;
 using System;
 using System.Collections.Generic;
@@ -14,24 +17,1063 @@
 
 namespace A032.Process
 {
-    public enum ResultState
-    {
-        OK = 1,
-        NG = -1,
-        Undetermined = -2,
-    }
-
     public partial class ProcessControl
     {
+        Dictionary<int, int> machineFullTrayDict = new Dictionary<int, int>();
+        Dictionary<int, int> machineEmptyTrayDict = new Dictionary<int, int>();
+        List<TaskAssignInfo> taskAssignedList = new List<TaskAssignInfo>();
+
+        private void OnAGVTaskStatusChanged(SeerAGVDriver agv, AGVTaskStatus taskStatus)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == agv.Id);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁AGV淇℃伅鑾峰彇缁戝畾璁惧淇℃伅", null);
+            }
+
+            if (bind.AGVDest == agv.CurrentPosition && taskStatus == AGVTaskStatus.Completed)
+            {
+                //PathPosition loadEmptyTrayPosition = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
+
+                //if (bind.AGVDest == loadEmptyTrayPosition.PositionCode)
+                //{
+                //    bind.RobotStatus = TaskStatus.Running;
+                //}
+
+                bind.AGVStatus = TaskStatus.Available;
+            }
+        }
+
+        private void OnAGVPositionChanged(SeerAGVDriver agv, string positionCode)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == agv.Id);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁AGV淇℃伅鑾峰彇缁戝畾璁惧淇℃伅", null);
+            }
+
+            if (bind.AGVDest == positionCode && agv.TaskStatus == AGVTaskStatus.Completed)
+            {
+                //PathPosition loadEmptyTrayPosition = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
+
+                //if (bind.AGVDest == loadEmptyTrayPosition.PositionCode)
+                //{
+                //    bind.RobotStatus = TaskStatus.Running;
+                //}
+
+                bind.AGVStatus = TaskStatus.Available;
+            }
+        }
+
+        private void OnRobotMsgReceived(DateTime dt, AuboRobotDriver robot, RobotMsg msg)
+        {
+            LogAsync(dt, robot.Name + "鎺ユ敹淇℃伅", msg.GetDisplayText());
+
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robot.Id);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁鏈哄櫒浜轰俊鎭幏鍙栫粦瀹氳澶囦俊鎭�", null);
+            }
+
+            List<AGVTaskModel> models = new List<AGVTaskModel>();
+
+            switch (msg.Action)
+            {
+                case RobotMsgAction.Load:
+                    {
+                        switch (msg.Para1)
+                        {
+                            case RobotMsgParas.EmptyTray:
+                                {
+                                    bind.RobotStatus = bind.AGVStatus = TaskStatus.Available;
+                                }
+                                break;
+                            case RobotMsgParas.FullTray:
+                                {
+                                    machineFullTrayDict[msg.Para2]--;
+                                    bind.CurrentFullTray = int.Parse(msg.Datas[1]);
+
+                                    if (machineFullTrayDict[msg.Para2] > 0 && !bind.IsFullTrayFull)
+                                    {
+                                        RobotMsg_UnloadEmptyTray.Para2 = msg.Para2;
+                                        robot.SendMsg(RobotMsg_UnloadEmptyTray, true);
+                                    }
+                                    else
+                                    {
+                                        bind.RobotStatus = TaskStatus.Available;
+                                    }
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                    break;
+                case RobotMsgAction.Unload:
+                    {
+                        switch (msg.Para1)
+                        {
+                            case RobotMsgParas.EmptyTray:
+                                {
+                                    machineEmptyTrayDict[msg.Para2]++;
+                                    bind.CurrentEmptyTray = int.Parse(msg.Datas[0]);
+
+                                    if (machineEmptyTrayDict[msg.Para2] < Config.Machine_EmptyTrayNum)
+                                    {
+                                        bind.RobotIOHandle.Reset();
+                                        bind.RobotIOHandle.WaitOne();
+
+                                        if (!bind.IsFullTrayFull)
+                                        {
+                                            RobotMsg_UnloadEmptyTray.Para2 = msg.Para2;
+                                            robot.SendMsg(RobotMsg_UnloadEmptyTray, true);
+                                        }
+                                    }
+                                    else
+                                    {
+                                        bind.RobotStatus = TaskStatus.Available;
+                                    }
+                                }
+                                break;
+                            case RobotMsgParas.FullTray:
+                                {
+                                    bind.CurrentFullTray = int.Parse(msg.Datas[1]);
+
+                                    bind.RobotIOHandle.Reset();
+                                    bind.RobotIOHandle.WaitOne();
+
+                                    if (!bind.IsFullTrayEmpty)
+                                    {
+                                        Camera_UnloadFullTray(robot.Id, msg.Para2);
+                                    }
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                    break;
+                case RobotMsgAction.Move:
+                    {
+                        switch (msg.Para1)
+                        {
+                            case RobotMsgParas.LineSnap:
+                                {
+                                    Camera_UnloadFullTray(robot.Id, msg.Para2);
+                                }
+                                break;
+                            case RobotMsgParas.LoadFullTraySnap:
+                                {
+                                    Camera_LoadFullTray(robot.Id, msg.Para2);
+                                }
+                                break;
+                            case RobotMsgParas.UnloadEmptyTraySnap:
+                                {
+                                    Camera_UnloadEmptyTray(robot.Id, msg.Para2);
+                                }
+                                break;
+                            case RobotMsgParas.Home:
+                                {
+                                    bind.RobotStatus = TaskStatus.Available;
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                    break;
+                case RobotMsgAction.Calibration:
+                    {
+
+                    }
+                    break;
+                case RobotMsgAction.StandardPoint:
+                    {
+
+                    }
+                    break;
+                default:
+                    break;
+            }
+
+            if (models.Count > 0)
+            {
+                models.ForEach(model =>
+                {
+                    if (!bind.TaskList.Any(t => t.MethodFunc.Method.Name == model.MethodFunc.Method.Name))
+                    {
+                        model.OpConfig = new AGVBindOpConfig(bind.Id);
+                        bind.TaskList.Add(model);
+                    }
+                });
+            }
+        }
+
         public void QueryRobotIO()
         {
-
+            RobotDict.Values.ToList().ForEach(r =>
+            {
+                r.SendMsg(RobotMsgAction.IO, RobotMsgParas.Query, 0);
+            });
         }
 
-        [ProcessMethod("HikCamera", "RobotCorrection", "鎷嶆憚锛岀‘璁ゆ満鍣ㄤ汉璋冩暣浣嶇疆", true)]
-        public ProcessResponse RobotCorrection(IOperationConfig config)
+        private void OnBindUnitTaskInvoke(AGVBindUnit bind)
         {
+            var task = bind.TaskList[0];
+            var response = task.MethodFunc.Invoke(task.OpConfig, task.Device);
+        }
+
+        #region Robot鐩戝惉浜嬩欢
+        private void AddNewTaskToBind(string robotId, List<AGVTaskModel> models)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁鏈哄櫒浜轰俊鎭幏鍙栫粦瀹氳澶囦俊鎭�", null);
+            }
+
+            AddNewTaskToBind(bind, models);
+        }
+
+        private void AddNewTaskToBind(AGVBindUnit bind, List<AGVTaskModel> models)
+        {
+            if (models.Count > 0)
+            {
+                models.ForEach(model =>
+                {
+                    if (!bind.TaskList.Any(t => t.MethodFunc.Method.Name == model.MethodFunc.Method.Name))
+                    {
+                        model.OpConfig = new AGVBindOpConfig(bind.Id);
+                        bind.TaskList.Add(model);
+                    }
+                });
+            }
+        }
+
+        [ProcessMethod("", "Robot_Monitor_Alarm", "鏈哄櫒浜虹洃鍚簨浠�-鎶ヨ", true)]
+        public ProcessResponse Robot_Monitor_Alarm(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁鏈哄櫒浜轰俊鎭幏鍙栫粦瀹氳澶囦俊鎭�", null);
+            }
+
+            bind.AGV.PauseTask();
+            bind.RobotStatus = TaskStatus.Warning;
+
             return new ProcessResponse(true);
         }
+
+        [ProcessMethod("", "Robot_Monitor_EmptyTrayEmpty", "鏈哄櫒浜虹洃鍚簨浠�-绌篢ray鍖哄煙娓呯┖", true)]
+        public ProcessResponse Robot_Monitor_EmptyTrayEmpty(IOperationConfig config, IDevice device)
+        {
+            bool isEmptyTrayEmpty = config.InputPara[0] == 1;
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+            if (isEmptyTrayEmpty)
+            {
+                bind.IsEmptyTrayEmpty = true;
+
+                Task.Run(() =>
+                {
+                    Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_LoadEmptyTray;
+                    while (bind.IsEmptyTrayEmpty && bind.TaskList.Count == 0 && !bind.TaskList.Any(u => u.MethodFunc.Method.Name == action.Method.Name))
+                    {
+                        if (bind.TaskList.Count == 0)
+                        {
+                            List<AGVTaskModel> models = new List<AGVTaskModel>();
+                            models.Add(new AGVTaskModel(TaskAvailableLevel.Both, AGV_LoadEmptyTray));
+                            models.Add(new AGVTaskModel(TaskAvailableLevel.AGV, AfterEmptyTrayPositionArrived));
+
+                            AddNewTaskToBind(device.Id, models);
+                        }
+                        else
+                        {
+                            Thread.Sleep(500);
+                        }
+                    }
+                });
+
+            }
+            else
+            {
+                bind.IsEmptyTrayEmpty = false;
+            }
+
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("", "Robot_Monitor_EmptyTrayEmpty", "鏈哄櫒浜虹洃鍚簨浠�-婊ray鍖哄煙鏀炬弧", true)]
+        public ProcessResponse Robot_Monitor_FullTrayFull(IOperationConfig config, IDevice device)
+        {
+            bool isFullTrayFull = config.InputPara[0] == 1;
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+            if (isFullTrayFull)
+            {
+                bind.IsFullTrayFull = true;
+
+                Task.Run(() =>
+                {
+                    Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_UnloadFullTray;
+                    while (bind.IsFullTrayFull && !bind.TaskList.Any(u => u.MethodFunc.Method.Name == action.Method.Name))
+                    {
+                        if (bind.TaskList.Count == 0)
+                        {
+                            List<AGVTaskModel> models = new List<AGVTaskModel>();
+                            models.Add(new AGVTaskModel(TaskAvailableLevel.Both, AGV_UnloadFullTray));
+                            models.Add(new AGVTaskModel(TaskAvailableLevel.AGV, Robot_UnloadFullTray));
+
+                            AddNewTaskToBind(device.Id, models);
+                        }
+                        else
+                        {
+                            Thread.Sleep(500);
+                        }
+                    }
+                });
+            }
+            else
+            {
+                bind.IsFullTrayFull = false;
+            }
+
+            bind.RobotIOHandle.Set();
+
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("", "Robot_Monitor_FullTrayEmpty", "鏈哄櫒浜虹洃鍚簨浠�-婊ray鍖哄煙娓呯┖", true)]
+        public ProcessResponse Robot_Monitor_FullTrayEmpty(IOperationConfig config, IDevice device)
+        {
+            bool isFullTrayEmpty = config.InputPara[0] == 1;
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+            bind.IsFullTrayEmpty = isFullTrayEmpty;
+
+            bind.RobotIOHandle.Set();
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "Robot_Monitor_LoadEmptyTrayReady", "鏈哄櫒浜虹洃鍚簨浠�-杞藉叆绌篢ray灏辩华", true)]
+        //public ProcessResponse Robot_Monitor_LoadEmptyTrayReady(IOperationConfig config, IDevice device)
+        //{
+        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+
+        //    if (bind == null)
+        //    {
+        //        throw new ProcessException("鏈兘鏍规嵁鏈哄櫒浜轰俊鎭幏鍙栫粦瀹氳澶囦俊鎭�", null);
+        //    }
+
+        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
+        //    if (bind.AGV.CurrentPosition != position.PositionCode)
+        //    {
+        //        new ProcessException("涓婄┖Tray瀹屾垚淇″彿浠呭湪涓婄┖Tray鍦扮偣鏈夋晥", null);
+        //        return new ProcessResponse(true);
+        //    }
+
+        //    List<AGVTaskModel> models = new List<AGVTaskModel>();
+        //    models.Add(new AGVTaskModel(TaskAvailableLevel.AGV, EmptyTrayReady));
+        //    AddNewTaskToBind(bind, models);
+
+        //    return new ProcessResponse(true);
+        //}
+
+        [ProcessMethod("", "Robot_Monitor_Reset", "鏈哄櫒浜虹洃鍚簨浠�-Reset澶嶄綅", true)]
+        public ProcessResponse Robot_Monitor_Reset(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
+
+            if (bind == null)
+            {
+                throw new ProcessException("鏈兘鏍规嵁鏈哄櫒浜轰俊鎭幏鍙栫粦瀹氳澶囦俊鎭�", null);
+            }
+            bind.AGV.CancelTask();
+
+            //isEmptyTrayTaskAssigned = false;
+            //isFullTrayTaskAssigned = false;
+
+            taskAssignedList.RemoveAll(u => u.AgvId == device.Id);
+
+            bind.TaskList.Clear();
+            bind.RobotStatus = bind.AGVStatus = TaskStatus.Available;
+
+            return new ProcessResponse(true);
+        }
+        #endregion
+
+        #region 绌篢ray涓婃枡
+        [ProcessMethod("", "AGV_LoadEmptyTray", "AGV鍘诲線绌篢ray涓婃枡", true)]
+        public ProcessResponse AGV_LoadEmptyTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓婃枡鐐�");
+            }
+
+            bind.AGVDest = position.PositionCode;
+            bind.AGV.TaskOrder(position.PositionCode);
+
+            bind.AGVStatus = TaskStatus.Running;
+
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("", "AfterEmptyTrayPositionArrived", "鍒拌揪绌篢ray涓婃枡鐐�", true)]
+        public ProcessResponse AfterEmptyTrayPositionArrived(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓婃枡鐐�", null);
+            }
+
+            if (bind.AGV.CurrentPosition != position.PositionCode)
+            {
+                throw new ProcessException("AGV灏氭湭鍒拌揪绌篢ray涓婃枡鐐�", null);
+            }
+
+            bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.EmptyTray, 0);
+            bind.RobotStatus = TaskStatus.Running;
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "EmptyTrayReady", "绌篢ray涓婃枡瀹屾垚", true)]
+        //public ProcessResponse EmptyTrayReady(IOperationConfig config, IDevice device)
+        //{
+        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+        //    bind.RobotStatus = bind.AGVStatus = TaskStatus.Available;
+
+        //    return new ProcessResponse(true);
+        //}
+        #endregion
+
+        #region 绌篢ray寰�鏈哄彴涓嬫枡
+        //bool isEmptyTrayNeed = false;
+        //bool isEmptyTrayTaskAssigned = false;
+        RobotMsg RobotMsg_UnloadEmptyTray = new RobotMsg();
+
+        [ProcessMethod("", "PLC_NoticeEmptyTray", "PLC閫氱煡闇�瑕佷笂绌篢ray", true)]
+        public ProcessResponse PLC_NoticeEmptyTray(IOperationConfig config, IDevice device)
+        {
+            if (config.InputPara == null || config.InputPara.Count == 0)
+            {
+                throw new ProcessException("涓婄┖Tray鏂规硶鏈厤缃緭鍏ュ弬鏁�", null);
+            }
+
+            bool isEmptyTrayNeed = config.InputPara[0] == 1;
+
+            if (isEmptyTrayNeed)
+            {
+                var position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadEmptyTray && u.DeviceOwner == device.Id);
+
+                machineEmptyTrayDict[position.PositionNo] = 0;
+
+                if (!taskAssignedList.Any(u => u.PositionNo == position.PositionNo))
+                {
+                    taskAssignedList.Add(new TaskAssignInfo()
+                    {
+                        IsTaskNeed = true,
+                        IsTaskAssgined = false,
+                        PositionNo = position.PositionNo,
+                    });
+                }
+
+                CheckEmptyTrayTask(position.PositionNo);
+            }
+
+            return new ProcessResponse(true);
+        }
+
+        private async void CheckEmptyTrayTask(int positionNo)
+        {
+            await Task.Run(() =>
+            {
+                var taskStatus = taskAssignedList.FirstOrDefault(u => u.PositionNo == positionNo);
+
+                if (taskStatus == null)
+                    return;
+
+                while (taskStatus.IsTaskNeed && !taskStatus.IsTaskAssgined)
+                {
+                    Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_UnloadEmptyTray;
+
+                    //if (!Config.AGVBindCollection.Any(b => b.TaskList.Any(t => t.MethodFunc.Method.Name == action.Method.Name)))
+                    {
+                        var bind = Config.AGVBindCollection.FirstOrDefault(u => u.UnitStatus == TaskStatus.Available);
+                        if (bind != null)
+                        {
+                            var position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNo);
+                            AGVTaskModel model_AGV = new AGVTaskModel(TaskAvailableLevel.Both, action, new AGVBindOpConfig(bind.Id, position));
+                            AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, Robot_UnloadEmptyTray, new AGVBindOpConfig(bind.Id));
+
+                            bind.TaskList.Add(model_AGV);
+                            bind.TaskList.Add(model_Robot);
+
+                            taskStatus.IsTaskAssgined = true;
+                            taskStatus.AgvId = bind.AGVId;
+                        }
+                    }
+
+                    Thread.Sleep(300);
+                }
+            });
+        }
+
+        //[ProcessMethod("", "AGV_UnloadEmptyTray", "AGV鍘诲線鍗歌浇绌篢ray鏂欎綅缃�", true)]
+        public ProcessResponse AGV_UnloadEmptyTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+
+            PathPosition position = (config as AGVBindOpConfig).Position;
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓嬫枡鐐�");
+            }
+
+            bind.AGVDest = position.PositionCode;
+            bind.AGV.TaskOrder(position.PositionCode);
+
+            bind.AGVStatus = TaskStatus.Running;
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "Robot_UnloadEmptyTray", "鏈哄櫒浜鸿繍鍔ㄨ嚦绌篢ray鎷嶇収浣嶇疆", true)]
+        public ProcessResponse Robot_UnloadEmptyTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadEmptyTray && u.PositionCode == bind.AGV.CurrentPosition);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓嬫枡鐐�");
+            }
+
+            taskAssignedList.RemoveAll(u => u.AgvId == bind.AGVId && u.PositionNo == position.PositionNo);
+
+            bind.RobotStatus = TaskStatus.Running;
+            bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.UnloadEmptyTraySnap, position.PositionNo);
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "Camera_UnloadEmptyTray", "鐩告満纭绌篢ray鍗歌浇鏈哄櫒浜轰綅缃皟鏁�", true)]
+        //public ProcessResponse Camera_UnloadEmptyTray(IOperationConfig config, IDevice device)
+        //{
+        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+
+        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadEmptyTray);
+
+        //    if (position == null)
+        //    {
+        //        throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓嬫枡鐐�");
+        //    }
+
+        //    if (bind.AGV.CurrentPosition != position.PositionCode)
+        //    {
+        //        throw new ProcessException("AGV褰撳墠鏈浜庣┖Tray涓嬫枡鐐�");
+        //    }
+
+        //    PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
+
+        //    if (visionConfig == null)
+        //    {
+        //        throw new ProcessException("鏈厤缃鐩告満鐨勭┖Tray涓嬫枡鐐圭殑瑙嗚鎿嶄綔閰嶇疆");
+        //    }
+
+        //    float x = 0;
+        //    float y = 0;
+        //    float angle = 0;
+
+        //    using (HObject hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, "Camera_UnloadEmptyTray"))
+        //    {
+        //        string toolPath = visionConfig.CameraOpConfig.AlgorithemPath;
+        //        if (!_halconToolDict.ContainsKey(toolPath))
+        //        {
+        //            throw new ProcessException($"鏈厤缃瓹amera_UnloadEmptyTray鐨勮瑙夌畻娉曡矾寰�");
+        //        }
+
+        //        var tool = _halconToolDict[toolPath];
+        //        tool.SetDictionary(new Dictionary<string, HTuple>() { { "OUTPUT_X", new HTuple() }, { "OUTPUT_Y", new HTuple() }, { "OUTPUT_Angle", new HTuple() } }, new Dictionary<string, HObject>() { { "INPUT_Image", hImage } });
+        //        tool.RunProcedure();
+
+        //        x = (float)tool.GetResultTuple("OUTPUT_X").D;
+        //        y = (float)tool.GetResultTuple("OUTPUT_Y").D;
+        //        angle = (float)tool.GetResultTuple("OUTPUT_Angle").D;
+        //    }
+
+        //    if (x <= 0 || y <= 0)
+        //    {
+        //        throw new ProcessException("Camera_UnloadEmptyTray瑙嗚璁$畻鑾峰彇鐐逛綅涓嶅彲灏忎簬0");
+        //    }
+
+        //    float dx = visionConfig.StandardPoint.X - x;
+        //    float dy = visionConfig.StandardPoint.Y - y;
+
+        //    HOperatorSet.AffineTransPoint2d(new HTuple(visionConfig.Matrix[0], visionConfig.Matrix[1], 0, visionConfig.Matrix[3], visionConfig.Matrix[4], 0), dx, dy, out HTuple dx_Robot, out HTuple dy_Robot);
+
+        //    bind.Robot.SendMsg(RobotMsgAction.Unload, RobotMsgParas.EmptyTray, position.PositionNo, new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle });
+
+        //    return new ProcessResponse(true);
+        //}
+
+        public ProcessResponse Camera_UnloadEmptyTray(string robotId, int positionNum)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
+
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNum);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃┖Tray涓嬫枡鐐�");
+            }
+
+            if (bind.AGV.CurrentPosition != position.PositionCode)
+            {
+                throw new ProcessException("AGV褰撳墠鏈浜庣┖Tray涓嬫枡鐐�");
+            }
+
+            PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
+
+            if (visionConfig == null)
+            {
+                throw new ProcessException("鏈厤缃鐩告満鐨勭┖Tray涓嬫枡鐐圭殑瑙嗚鎿嶄綔閰嶇疆");
+            }
+
+            float x = 0;
+            float y = 0;
+            float angle = 0;
+
+            using (HObject hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, "Camera_UnloadEmptyTray"))
+            {
+                string toolPath = visionConfig.CameraOpConfig.AlgorithemPath;
+                if (!_halconToolDict.ContainsKey(toolPath))
+                {
+                    throw new ProcessException($"鏈厤缃瓹amera_UnloadEmptyTray鐨勮瑙夌畻娉曡矾寰�");
+                }
+
+                var tool = _halconToolDict[toolPath];
+                tool.SetDictionary(new Dictionary<string, HTuple>() { { "OUTPUT_X", new HTuple() }, { "OUTPUT_Y", new HTuple() }, { "OUTPUT_Angle", new HTuple() } }, new Dictionary<string, HObject>() { { "INPUT_Image", hImage } });
+                tool.RunProcedure();
+
+                x = (float)tool.GetResultTuple("OUTPUT_X").D;
+                y = (float)tool.GetResultTuple("OUTPUT_Y").D;
+                angle = (float)tool.GetResultTuple("OUTPUT_Angle").D;
+            }
+
+            if (x <= 0 || y <= 0)
+            {
+                throw new ProcessException("Camera_UnloadEmptyTray瑙嗚璁$畻鑾峰彇鐐逛綅涓嶅彲灏忎簬0");
+            }
+
+            float dx = visionConfig.StandardPoint.X - x;
+            float dy = visionConfig.StandardPoint.Y - y;
+
+            HOperatorSet.AffineTransPoint2d(new HTuple(visionConfig.Matrix[0], visionConfig.Matrix[1], 0, visionConfig.Matrix[3], visionConfig.Matrix[4], 0), dx, dy, out HTuple dx_Robot, out HTuple dy_Robot);
+
+            //bind.Robot.SendMsg(RobotMsgAction.Unload, RobotMsgParas.EmptyTray, position.PositionNo, new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle });
+            RobotMsg_UnloadEmptyTray.Action = RobotMsgAction.Unload;
+            RobotMsg_UnloadEmptyTray.Para1 = RobotMsgParas.EmptyTray;
+            RobotMsg_UnloadEmptyTray.Para2 = position.PositionNo;
+            RobotMsg_UnloadEmptyTray.Datas = new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle }.ConvertAll(s => s.ToString()).ToList();
+            bind.Robot.SendMsg(RobotMsg_UnloadEmptyTray, true);
+
+            return new ProcessResponse(true);
+        }
+        #endregion
+
+        #region 浠庢満鍙颁笂婊ray
+        //bool isFullTrayNeed = false;
+        //bool isFullTrayTaskAssigned = false;
+        RobotMsg RobotMsg_LoadFullTray = new RobotMsg();
+
+        [ProcessMethod("", "PLC_NoticeFullTray", "PLC閫氱煡婊ray闇�瑕佸彇璧�", true)]
+        public ProcessResponse PLC_NoticeFullTray(IOperationConfig config, IDevice device)
+        {
+            if (config.InputPara == null || config.InputPara.Count == 0)
+            {
+                throw new ProcessException("涓婄┖Tray鏂规硶鏈厤缃緭鍏ュ弬鏁�", null);
+            }
+
+            bool isFullTrayNeed = config.InputPara[0] == 1;
+            if (isFullTrayNeed)
+            {
+                var position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray && u.DeviceOwner == device.Id);
+
+                machineFullTrayDict[position.PositionNo] = Config.Machine_FullTrayNum;
+
+                if (!taskAssignedList.Any(u => u.PositionNo == position.PositionNo))
+                {
+                    taskAssignedList.Add(new TaskAssignInfo()
+                    {
+                        IsTaskNeed = true,
+                        IsTaskAssgined = false,
+                        PositionNo = position.PositionNo,
+                    });
+                }
+
+                CheckFullTrayTask(position.PositionNo);
+            }
+
+            return new ProcessResponse(true);
+        }
+
+        private async void CheckFullTrayTask(int positionNo)
+        {
+            await Task.Run(() =>
+            {
+                var taskStatus = taskAssignedList.FirstOrDefault(u => u.PositionNo == positionNo);
+
+                if (taskStatus == null)
+                    return;
+
+                while (taskStatus.IsTaskNeed && !taskStatus.IsTaskAssgined)
+                {
+                    Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_LoadFullTray;
+
+                    //if (!Config.AGVBindCollection.Any(b => b.TaskList.Any(t => t.MethodFunc.Method.Name == action.Method.Name)))
+                    {
+                        var bind = Config.AGVBindCollection.FirstOrDefault(u => u.UnitStatus == TaskStatus.Available);
+                        if (bind != null)
+                        {
+                            var position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNo);
+                            AGVTaskModel model_AGV = new AGVTaskModel(TaskAvailableLevel.Both, action, new AGVBindOpConfig(bind.Id, position));
+                            AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, Robot_LoadFullTray, new AGVBindOpConfig(bind.Id));
+
+                            bind.TaskList.Add(model_AGV);
+                            bind.TaskList.Add(model_Robot);
+
+                            taskStatus.IsTaskAssgined = true;
+                            taskStatus.AgvId = bind.AGVId;
+                        }
+                    }
+
+                    Thread.Sleep(300);
+                }
+            });
+        }
+
+        [ProcessMethod("", "AGV_LoadFullTray", "AGV鍘诲線婊ray涓婃枡浣嶇疆", true)]
+        public ProcessResponse AGV_LoadFullTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+
+            PathPosition position = (config as AGVBindOpConfig).Position;
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓婃枡鐐�");
+            }
+
+            bind.AGVDest = position.PositionCode;
+            bind.AGV.TaskOrder(position.PositionCode);
+
+            bind.AGVStatus = TaskStatus.Running;
+
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("", "Robot_LoadFullTray", "鏈哄櫒浜鸿繍鍔ㄨ嚦婊ray鎷嶇収浣嶇疆", true)]
+        public ProcessResponse Robot_LoadFullTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray && u.PositionCode == bind.AGV.CurrentPosition);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓婃枡鐐�");
+            }
+
+            //if (bind.AGV.CurrentPosition != position.PositionCode)
+            //{
+            //    throw new ProcessException("AGV褰撳墠鏈浜庢弧Tray涓婃枡鐐�");
+            //}
+
+            bind.RobotStatus = TaskStatus.Running;
+            bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LoadFullTraySnap, position.PositionNo);
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "Camera_LoadFullTray", "鐩告満纭婊ray涓婃枡鏈哄櫒浜轰綅缃皟鏁�", true)]
+        //public ProcessResponse Camera_LoadFullTray(IOperationConfig config, IDevice device)
+        //{
+        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+
+        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray);
+
+        //    if (position == null)
+        //    {
+        //        throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓婃枡鐐�");
+        //    }
+
+        //    if (bind.AGV.CurrentPosition != position.PositionCode)
+        //    {
+        //        throw new ProcessException("AGV褰撳墠鏈浜庢弧Tray涓婃枡鐐�");
+        //    }
+
+        //    PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
+
+        //    if (visionConfig == null)
+        //    {
+        //        throw new ProcessException("鏈厤缃鐩告満鐨勬弧Tray涓婃枡鐐圭殑瑙嗚鎿嶄綔閰嶇疆");
+        //    }
+
+        //    float x = 0;
+        //    float y = 0;
+        //    float angle = 0;
+
+        //    using (HObject hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, "Camera_LoadFullTray"))
+        //    {
+        //        string toolPath = visionConfig.CameraOpConfig.AlgorithemPath;
+        //        if (!_halconToolDict.ContainsKey(toolPath))
+        //        {
+        //            throw new ProcessException($"鏈厤缃瓹amera_LoadFullTray鐨勮瑙夌畻娉曡矾寰�");
+        //        }
+
+        //        var tool = _halconToolDict[toolPath];
+        //        tool.SetDictionary(new Dictionary<string, HTuple>() { { "OUTPUT_X", new HTuple() }, { "OUTPUT_Y", new HTuple() }, { "OUTPUT_Angle", new HTuple() } }, new Dictionary<string, HObject>() { { "INPUT_Image", hImage } });
+        //        tool.RunProcedure();
+
+        //        x = (float)tool.GetResultTuple("OUTPUT_X").D;
+        //        y = (float)tool.GetResultTuple("OUTPUT_Y").D;
+        //        angle = (float)tool.GetResultTuple("OUTPUT_Angle").D;
+        //    }
+
+        //    if (x <= 0 || y <= 0)
+        //    {
+        //        throw new ProcessException("Camera_LoadFullTray瑙嗚璁$畻鑾峰彇鐐逛綅涓嶅彲灏忎簬0");
+        //    }
+
+        //    float dx = visionConfig.StandardPoint.X - x;
+        //    float dy = visionConfig.StandardPoint.Y - y;
+
+        //    HOperatorSet.AffineTransPoint2d(new HTuple(visionConfig.Matrix[0], visionConfig.Matrix[1], 0, visionConfig.Matrix[3], visionConfig.Matrix[4], 0), dx, dy, out HTuple dx_Robot, out HTuple dy_Robot);
+
+        //    bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.FullTray, position.PositionNo, new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle });
+
+        //    return new ProcessResponse(true);
+        //}
+
+        public ProcessResponse Camera_LoadFullTray(string robotId, int positionNum)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
+
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNum);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓婃枡鐐�");
+            }
+
+            if (bind.AGV.CurrentPosition != position.PositionCode)
+            {
+                throw new ProcessException("AGV褰撳墠鏈浜庢弧Tray涓婃枡鐐�");
+            }
+
+            PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
+
+            if (visionConfig == null)
+            {
+                throw new ProcessException("鏈厤缃鐩告満鐨勬弧Tray涓婃枡鐐圭殑瑙嗚鎿嶄綔閰嶇疆");
+            }
+
+            float x = 0;
+            float y = 0;
+            float angle = 0;
+
+            using (HObject hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, "Camera_LoadFullTray"))
+            {
+                string toolPath = visionConfig.CameraOpConfig.AlgorithemPath;
+                if (!_halconToolDict.ContainsKey(toolPath))
+                {
+                    throw new ProcessException($"鏈厤缃瓹amera_LoadFullTray鐨勮瑙夌畻娉曡矾寰�");
+                }
+
+                var tool = _halconToolDict[toolPath];
+                tool.SetDictionary(new Dictionary<string, HTuple>() { { "OUTPUT_X", new HTuple() }, { "OUTPUT_Y", new HTuple() }, { "OUTPUT_Angle", new HTuple() } }, new Dictionary<string, HObject>() { { "INPUT_Image", hImage } });
+                tool.RunProcedure();
+
+                x = (float)tool.GetResultTuple("OUTPUT_X").D;
+                y = (float)tool.GetResultTuple("OUTPUT_Y").D;
+                angle = (float)tool.GetResultTuple("OUTPUT_Angle").D;
+            }
+
+            if (x <= 0 || y <= 0)
+            {
+                throw new ProcessException("Camera_LoadFullTray瑙嗚璁$畻鑾峰彇鐐逛綅涓嶅彲灏忎簬0");
+            }
+
+            float dx = visionConfig.StandardPoint.X - x;
+            float dy = visionConfig.StandardPoint.Y - y;
+
+            HOperatorSet.AffineTransPoint2d(new HTuple(visionConfig.Matrix[0], visionConfig.Matrix[1], 0, visionConfig.Matrix[3], visionConfig.Matrix[4], 0), dx, dy, out HTuple dx_Robot, out HTuple dy_Robot);
+
+            //bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.FullTray, position.PositionNo, new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle });
+            RobotMsg_LoadFullTray.Action = RobotMsgAction.Load;
+            RobotMsg_LoadFullTray.Para1 = RobotMsgParas.FullTray;
+            RobotMsg_LoadFullTray.Para2 = position.PositionNo;
+            RobotMsg_LoadFullTray.Datas = new List<float>() { (float)dx_Robot.D, (float)dy_Robot.D, angle }.ConvertAll(s => s.ToString()).ToList();
+            bind.Robot.SendMsg(RobotMsg_LoadFullTray, true);
+
+            return new ProcessResponse(true);
+        }
+        #endregion
+
+        #region 婊ray浜х嚎涓嬫枡
+        [ProcessMethod("", "AGV_UnloadFullTray", "AGV鍘诲線鍗歌浇婊ray鏂�", true)]
+        public ProcessResponse AGV_UnloadFullTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓嬫枡鐐�");
+            }
+
+            bind.AGVDest = position.PositionCode;
+            bind.AGV.TaskOrder(position.PositionCode);
+
+            bind.AGVStatus = TaskStatus.Running;
+
+            return new ProcessResponse(true);
+        }
+
+        [ProcessMethod("", "Robot_UnloadFullTray", "鏈哄櫒浜哄嵏杞芥弧Tray", true)]
+        public ProcessResponse Robot_UnloadFullTray(IOperationConfig config, IDevice device)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓嬫枡鐐�");
+            }
+
+            if (bind.AGV.CurrentPosition != position.PositionCode)
+            {
+                throw new ProcessException("AGV褰撳墠鏈浜庢弧Tray涓嬫枡鐐�");
+            }
+
+            bind.RobotStatus = TaskStatus.Running;
+            bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LineSnap, position.PositionNo);
+
+            return new ProcessResponse(true);
+        }
+
+        //[ProcessMethod("", "Camera_UnloadFullTray", "鐩告満鎿嶄綔鍗歌浇婊ray", true)]
+        //public ProcessResponse Camera_UnloadFullTray(IOperationConfig config, IDevice device)
+        public ProcessResponse Camera_UnloadFullTray(string robotId, int positionNum)
+        {
+            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
+            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray && u.PositionNo == positionNum);
+
+            if (position == null)
+            {
+                throw new ProcessException("璺緞閰嶇疆鏈缃弧Tray涓嬫枡鐐�");
+            }
+
+            if (bind.AGV.CurrentPosition != position.PositionCode)
+            {
+                throw new ProcessException("AGV褰撳墠鏈浜庢弧Tray涓嬫枡鐐�");
+            }
+
+            PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
+
+            if (visionConfig == null)
+            {
+                throw new ProcessException("鏈厤缃鐩告満鐨勬弧Tray涓嬫枡鐐圭殑瑙嗚鎿嶄綔閰嶇疆");
+            }
+
+            bool isLineReady = false;
+            int reTryTime = Config.LineBusyRetryTimes;
+
+            do
+            {
+                using (HObject hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, "Camera_UnloadFullTray"))
+                {
+                    string toolPath = visionConfig.CameraOpConfig.AlgorithemPath;
+                    if (!_halconToolDict.ContainsKey(toolPath))
+                    {
+                        throw new ProcessException($"鏈厤缃瓹amera_UnloadFullTray鐨勮瑙夌畻娉曡矾寰�");
+                    }
+
+                    _halconToolDict[toolPath].SetDictionary(new Dictionary<string, HTuple>() { { "OUTPUT_Result", new HTuple() } }, new Dictionary<string, HObject>() { { "INPUT_Image", hImage } });
+                    _halconToolDict[toolPath].RunProcedure();
+
+                    isLineReady = _halconToolDict[toolPath].GetResultTuple("OUTPUT_Result").I == 1;
+                }
+
+                if (!isLineReady)
+                {
+                    Thread.Sleep(Config.LineBusyWaitInterval * 1000);
+                    reTryTime--;
+                }
+                else
+                {
+                    reTryTime = 0;
+                }
+            } while (reTryTime > 0);
+
+            //if (!isLineReady)
+            //{
+            //    bind.Robot.SendMsg(RobotMsgType.Send, -1, true, RobotMsgAction.State, RobotMsgParas.LineSnap, new List<string>() { "-1" });
+            //    throw new ProcessException("浜х嚎蹇欙紝绛夊緟瓒呮椂");
+            //}
+            //else
+            //{
+            //    bind.Robot.SendMsg(RobotMsgType.Send, -1, true, RobotMsgAction.State, RobotMsgParas.LineSnap, new List<string>() { "1" });
+            //}
+
+            if (isLineReady)
+            {
+                bind.Robot.SendMsg(RobotMsgAction.Unload, RobotMsgParas.FullTray, position.PositionNo);
+            }
+            else
+            {
+                bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.Home, position.PositionNo);
+            }
+
+            return new ProcessResponse(true);
+        }
+        #endregion
+    }
+
+    public class AGVBindOpConfig : OperationConfigBase
+    {
+        public string BindId { get; set; }
+        public PathPosition Position { get; set; }
+
+        public AGVBindOpConfig() { }
+
+        public AGVBindOpConfig(string bindId, PathPosition position = null)
+        {
+            BindId = bindId;
+            Position = position;
+        }
+    }
+
+    public class TaskAssignInfo
+    {
+        public int PositionNo { get; set; }
+
+        public bool IsTaskNeed { get; set; } = false;
+
+        public bool IsTaskAssgined { get; set; } = false;
+
+        public string AgvId { get; set; }
     }
 }
diff --git a/src/Bro.Common.Model/Helper/ExceptionHelper.cs b/src/Bro.Common.Model/Helper/ExceptionHelper.cs
index 446c170..db85703 100644
--- a/src/Bro.Common.Model/Helper/ExceptionHelper.cs
+++ b/src/Bro.Common.Model/Helper/ExceptionHelper.cs
@@ -50,7 +50,7 @@
         //    PositionIndex = positionIndex;
         //}
 
-        public ProcessException(string error, Exception ex) : base(error, ex)
+        public ProcessException(string error, Exception ex = null) : base(error, ex)
         {
             ErrorCode = error;
             OriginalException = ex;
diff --git a/src/Bro.Common.Model/Interface/IMonitor.cs b/src/Bro.Common.Model/Interface/IMonitor.cs
index 5747279..2d9e1b3 100644
--- a/src/Bro.Common.Model/Interface/IMonitor.cs
+++ b/src/Bro.Common.Model/Interface/IMonitor.cs
@@ -1,4 +1,5 @@
-锘縰sing System;
+锘縰sing Bro.Common.Interface;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -6,8 +7,8 @@
 
 namespace Bro.Common.Model.Interface
 {
-    public delegate void OnMonitorInvokeDelegate(DateTime dt, MonitorSet monitorSet);
-    public delegate void OnMonitorAlarmDelegate(DateTime dt, WarningSet warning, bool isAlarmRaised);
+    public delegate void OnMonitorInvokeDelegate(DateTime dt, IDevice device, MonitorSet monitorSet);
+    public delegate void OnMonitorAlarmDelegate(DateTime dt, IDevice device, WarningSet warning, bool isAlarmRaised);
     public interface IMonitor
     {
         List<int> GetMonitorValues(int startAddress, int length);
diff --git a/src/Bro.Common.Model/Interface/IStationProcess.cs b/src/Bro.Common.Model/Interface/IStationProcess.cs
index 8e526d6..64d39df 100644
--- a/src/Bro.Common.Model/Interface/IStationProcess.cs
+++ b/src/Bro.Common.Model/Interface/IStationProcess.cs
@@ -48,6 +48,8 @@
         /// </summary>
         /// <returns></returns>
         List<ProcessMethodAttribute> CollectProcessMethods();
+
+        void InitialProcessMethods();
         #endregion
 
         #region 浜嬩欢
diff --git a/src/Bro.Device.AuboRobot/AuboRobotConfig.cs b/src/Bro.Device.AuboRobot/AuboRobotConfig.cs
index 190ee8b..50cfc4a 100644
--- a/src/Bro.Device.AuboRobot/AuboRobotConfig.cs
+++ b/src/Bro.Device.AuboRobot/AuboRobotConfig.cs
@@ -1,8 +1,11 @@
 锘縰sing Bro.Common.Base;
 using Bro.Common.Helper;
+using Bro.Common.Model;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Drawing.Design;
+using System.Linq;
 
 namespace Bro.Device.AuboRobot
 {
@@ -28,6 +31,16 @@
         [Category("閫氫俊璁剧疆")]
         [Description("鍗忚鍐呭鍒嗛殧瀛楃")]
         public string Seperator { get; set; } = ",";
+
+        [Category("IO鐩戝惉璁剧疆")]
+        [Description("IO鐩戝惉鎿嶄綔閰嶇疆闆嗗悎")]
+        [TypeConverter(typeof(CollectionCountConvert))]
+        [Editor(typeof(ComplexCollectionEditor<MonitorSet>), typeof(UITypeEditor))]
+        public List<MonitorSet> MonitorSetCollection { get; set; } = new List<MonitorSet>();
+
+        [Category("IO鐩戝惉璁剧疆")]
+        [Description("IO鐩戝惉闂撮殧锛屼互ms涓哄崟浣�")]
+        public int ScanInterval { get; set; } = 100;
     }
 
     [Device("AuboRobot", "濂ュ崥鏈哄櫒浜�", EnumHelper.DeviceAttributeType.OperationConfig)]
@@ -45,22 +58,44 @@
 
         public RobotMsgParas Para1 { get; set; } = RobotMsgParas.None;
 
-        public List<string> Paras { get; set; } = new List<string>();
+        public int Para2 { get; set; } = 0;
+
+        /// <summary>
+        /// Paras涓嶅寘鍚玃ara1,Para2鍐呭
+        /// </summary>
+        public List<string> Datas { get; set; } = new List<string>();
 
         public byte[] GetMsgBytes(string seperator, string endChar)
         {
-            List<string> list = new List<string>() { Type.ToString(), ID.ToString() };
+            List<string> list = new List<string>() { ((int)Type).ToString("D2"), ID.ToString("D2") };
 
             if (Type == RobotMsgType.Send)
             {
-                list.Add(Action.ToString());
+                list.Add(((int)Action).ToString("D2"));
 
-                if (Para1 != RobotMsgParas.None)
+                list.Add(((int)Para1).ToString("D2"));
+                list.Add(Para2.ToString("D2"));
+
+                if (Datas == null)
                 {
-                    list.Add(Para1.ToString());
+                    Datas = new List<string>();
                 }
 
-                list.AddRange(Paras);
+                while (Datas.Count < 5)
+                {
+                    Datas.Add("0");
+                }
+
+                list.AddRange(Datas.ConvertAll(s =>
+                {
+                    string res = float.Parse(s).ToString("F7");
+
+                    while (res.Length < 11)
+                    {
+                        res = "0" + s;
+                    }
+                    return res;
+                }));
             }
 
             string msg = string.Join(seperator, list);
@@ -74,31 +109,17 @@
 
             RobotMsg msg = new RobotMsg();
 
-            msg.Type = (RobotMsgType)Enum.Parse(typeof(RobotMsgType), datas[0]);
+            msg.Type = (RobotMsgType)int.Parse(datas[0]);
             msg.ID = int.Parse(datas[1]);
 
             if (msg.Type == RobotMsgType.Send)
             {
-                msg.Action = (RobotMsgAction)Enum.Parse(typeof(RobotMsgAction), datas[2]);
+                msg.Action = (RobotMsgAction)int.Parse(datas[2]);
 
-                if (int.TryParse(datas[3], out int para1))
-                {
-                    msg.Para1 = RobotMsgParas.None;
+                msg.Para1 = (RobotMsgParas)int.Parse(datas[3]);
+                msg.Para2 = int.Parse(datas[4]);
 
-                    for (int i = 3; i < datas.Length; i++)
-                    {
-                        msg.Paras.Add(datas[i]);
-                    }
-                }
-                else
-                {
-                    msg.Para1 = (RobotMsgParas)Enum.Parse(typeof(RobotMsgParas), datas[3]);
-
-                    for (int i = 4; i < datas.Length; i++)
-                    {
-                        msg.Paras.Add(datas[i]);
-                    }
-                }
+                msg.Datas = datas.Skip(5).ToList();
             }
 
             return msg;
@@ -106,38 +127,36 @@
 
         public string GetDisplayText()
         {
-            string msg = $"搴忓彿锛歿ID},{Action.ToString()}_{Para1.ToString()}{(Paras.Count > 0 ? ("_" + string.Join(",", Paras)) : "")}";
+            string msg = $"搴忓彿锛歿ID},{Action.ToString()}_{Para1.ToString()}_{Para2.ToString()}_{(Datas.Count > 0 ? ("_" + string.Join(",", Datas)) : "")}";
             return msg;
         }
     }
 
     public enum RobotMsgType
     {
-        Send,
-        Rec
+        Send = 1,
+        Rec = 2,
     }
 
     public enum RobotMsgAction
     {
-        Move,
-        State,
-        Adjust,
-        IO,
+        Move = 1,
+        Unload = 2,
+        Load = 3,
+        IO = 6,
+        Calibration = 9,
+        StandardPoint = 10,
     }
 
     public enum RobotMsgParas
     {
-        None,
-        LineSnap,
-        Line,
-        Robot,
-        LoadEmptyTraySnap,
-        LoadEmptyTrayDone,
-        LoadFullTraySnap,
-        LoadFullTrayDone,
-        EmptyTrayReady,
-        EmptyTrayEmpty,
-        FullTrayFull,
-        Query,
+        None = 0,
+        Home = 1,
+        LineSnap = 2,
+        EmptyTray = 3,
+        FullTray = 4,
+        UnloadEmptyTraySnap = 5,
+        LoadFullTraySnap = 6,
+        Query = 7,
     }
 }
diff --git a/src/Bro.Device.AuboRobot/AuboRobotDriver.cs b/src/Bro.Device.AuboRobot/AuboRobotDriver.cs
index 26e7324..7007dc8 100644
--- a/src/Bro.Device.AuboRobot/AuboRobotDriver.cs
+++ b/src/Bro.Device.AuboRobot/AuboRobotDriver.cs
@@ -1,6 +1,8 @@
 锘縰sing Bro.Common.Base;
 using Bro.Common.Helper;
 using Bro.Common.Interface;
+using Bro.Common.Model;
+using Bro.Common.Model.Interface;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -12,9 +14,9 @@
 namespace Bro.Device.AuboRobot
 {
     [Device("AuboRobot", "濂ュ崥鏈哄櫒浜�", EnumHelper.DeviceAttributeType.Device)]
-    public class AuboRobotDriver : DeviceBase
+    public class AuboRobotDriver : DeviceBase, IMonitor
     {
-        public Action<RobotMsg> OnMsgReceived { get; set; }
+        public Action<DateTime, AuboRobotDriver, RobotMsg> OnMsgReceived { get; set; }
 
         AuboRobotInitialConfig IConfig
         {
@@ -52,10 +54,20 @@
         {
         }
 
+        RobotMsg scanMsg = new RobotMsg();
         protected override void Start()
         {
             //Query Robot IOs
             //SendMsg(RobotMsgType.Send, 0, true, RobotMsgAction.IO, RobotMsgParas.Query, new List<string>());
+
+            scanMsg = new RobotMsg();
+            scanMsg.Action = RobotMsgAction.IO;
+            scanMsg.Para1 = RobotMsgParas.Query;
+
+            Task.Run(() =>
+            {
+                Monitor();
+            });
         }
 
         protected override void Stop()
@@ -152,8 +164,24 @@
                      }
                      else
                      {
-                         SendMsg(RobotMsgType.Rec, msg.ID, false);
-                         OnMsgReceived?.Invoke(msg);                         
+                         canMonitor = true;
+
+                         if (msg.Action == RobotMsgAction.IO && msg.Para1 == RobotMsgParas.Query)
+                         {
+                             string resultStr = msg.Datas[0];
+                             newValues = new List<int>();
+
+                             for (int i = resultStr.Length - 1; i >= 0; i--)
+                             {
+                                 newValues.Add(resultStr[i]);
+                             }
+
+                             monitorHandle.Set();
+                         }
+                         else
+                         {
+                             OnMsgReceived?.BeginInvoke(DateTime.Now, this, msg, null, null);
+                         }
                      }
                  });
             });
@@ -164,7 +192,7 @@
         {
             get
             {
-                if (sid > 999)
+                if (sid > 99)
                 {
                     sid = 1;
                 }
@@ -176,27 +204,33 @@
         List<int> replyHandleList = new List<int>();
         Dictionary<int, AutoResetEvent> replyHandleDict = new Dictionary<int, AutoResetEvent>();
 
-        public void SendMsg(RobotMsgType type, int replyId, bool isWaitReply = true, RobotMsgAction action = RobotMsgAction.Move, RobotMsgParas para1 = RobotMsgParas.None, List<string> paras = null)
+        public void SendMsg(RobotMsgAction action, RobotMsgParas para1, int para2, List<float> paras = null)
+        {
+            RobotMsg msg = new RobotMsg();
+            msg.Type = RobotMsgType.Send;
+            msg.ID = SID;
+
+            msg.Action = action;
+            msg.Para1 = para1;
+            msg.Para2 = para2;
+
+            msg.Datas = new List<string>((paras ?? new List<float>()).ConvertAll(i => i.ToString()));
+
+            SendMsg(msg, true);
+        }
+
+        public void SendReplyMsg(int replyId)
         {
             RobotMsg msg = new RobotMsg();
 
-            msg.Type = type;
-            if (msg.Type == RobotMsgType.Send)
-            {
-                msg.ID = SID;
-            }
-            else
-            {
-                msg.ID = replyId;
-            }
+            msg.Type = RobotMsgType.Rec;
+            msg.ID = replyId;
 
-            msg.Para1 = para1;
-            msg.Paras = new List<string>(paras ?? new List<string>());
-
-            SendMsg(msg, isWaitReply);
+            SendMsg(msg, false);
         }
 
-        public void SendMsg(RobotMsg msg, bool isWaitReply = true)
+        bool canMonitor = true;
+        public void SendMsg(RobotMsg msg, bool isWaitReply = true, bool isMonitorMsg = false)
         {
             if (isWaitReply)
             {
@@ -205,17 +239,99 @@
             }
 
             byte[] bytes = msg.GetMsgBytes(IConfig.Seperator, IConfig.EndChar);
-            client.GetStream().Write(bytes, 0, bytes.Length);
 
-            if (isWaitReply)
+            if (!isMonitorMsg)
             {
-                replyHandleDict[msg.ID].WaitOne(IConfig.ReplyTimeout);
+                canMonitor = false;
+            }
 
-                if (replyHandleList.Contains(msg.ID))
+            if (isMonitorMsg && !canMonitor)
+                return;
+
+            lock (this)
+            {
+                client.GetStream().Write(bytes, 0, bytes.Length);
+                if (isWaitReply)
                 {
-                    throw new ProcessException("鍙嶉鏁版嵁瓒呮椂\r\n" + msg.GetDisplayText(), null);
+                    replyHandleDict[msg.ID].WaitOne(IConfig.ReplyTimeout);
+
+                    if (replyHandleList.Contains(msg.ID))
+                    {
+                        throw new ProcessException("鍙嶉鏁版嵁瓒呮椂\r\n" + msg.GetDisplayText(), null);
+                    }
                 }
             }
         }
+
+        #region IMonitor
+        public event OnMonitorInvokeDelegate OnMonitorInvoke;
+        public event OnMonitorAlarmDelegate OnMonitorAlarm;
+
+        protected List<int> oldValues = new List<int>();
+        List<int> newValues = new List<int>();
+        AutoResetEvent monitorHandle = new AutoResetEvent(false);
+        public List<int> GetMonitorValues(int startAddress, int length)
+        {
+            SendMsg(scanMsg, true, true);
+            monitorHandle.WaitOne();
+
+            return newValues;
+        }
+
+        public virtual void Monitor()
+        {
+            while (CurrentState != EnumHelper.DeviceState.DSClose && CurrentState != EnumHelper.DeviceState.DSExcept)
+            {
+                try
+                {
+                    List<int> newValues = GetMonitorValues(0, 0);
+
+                    if (newValues == null || newValues.Count == 0)
+                        continue;
+
+                    if (oldValues.Count == newValues.Count)
+                    {
+                        var tempNew = new List<int>(newValues);
+                        var tempOld = new List<int>(oldValues);
+                        MonitorCheckAndInvoke(tempNew, tempOld);
+                    }
+                    oldValues = new List<int>(newValues);
+
+                    Thread.Sleep(IConfig.ScanInterval);
+                }
+                catch (Exception ex)
+                {
+                    OnLog?.Invoke(DateTime.Now, this, $"{Name}鐩戝惉寮傚父:{ex.GetExceptionMessage()}");
+                }
+            }
+        }
+
+        protected virtual void MonitorCheckAndInvoke(List<int> tempNew, List<int> tempOld)
+        {
+            IConfig.MonitorSetCollection.ForEach(m =>
+            {
+                int newValue = tempNew[m.TriggerIndex];
+                int oldValue = tempOld[m.TriggerIndex];
+
+                if (newValue != oldValue)
+                {
+                    if (m.TriggerValue == -999 || newValue == m.TriggerValue)
+                    {
+                        if (m.OpConfig == null)
+                        {
+                            m.OpConfig = new OperationConfigBase();
+                        }
+
+                        m.OpConfig.InputPara = m.InputDataIndex.ConvertAll(index =>
+                        {
+                            return tempNew[index];
+                        }).ToList();
+
+                        OnMonitorInvoke?.BeginInvoke(DateTime.Now, this, m, null, null);
+                    }
+                }
+            });
+        }
+        #endregion
     }
 }
diff --git a/src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj b/src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj
index bea6b68..0af4af5 100644
--- a/src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj
+++ b/src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj
@@ -36,6 +36,7 @@
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />
diff --git a/src/Bro.Device.Common/DeviceBase/PLCBase.cs b/src/Bro.Device.Common/DeviceBase/PLCBase.cs
index c1e99b9..10d42f1 100644
--- a/src/Bro.Device.Common/DeviceBase/PLCBase.cs
+++ b/src/Bro.Device.Common/DeviceBase/PLCBase.cs
@@ -79,7 +79,7 @@
                     }
                     catch (Exception ex)
                     {
-                        OnLog?.Invoke(DateTime.Now, this, "PLC鐩戝惉寮傚父:" + ex.GetExceptionMessage());
+                        OnLog?.Invoke(DateTime.Now, this, $"{Name}鐩戝惉寮傚父:{ex.GetExceptionMessage()}");
                     }
                 }
             }
@@ -141,7 +141,7 @@
                               return tempNew[index];
                           }).ToList();
 
-                        OnMonitorInvoke?.BeginInvoke(DateTime.Now, m, OnMethodInvoked, m);
+                        OnMonitorInvoke?.BeginInvoke(DateTime.Now, this, m, OnMethodInvoked, m);
                     }
                 }
             });
diff --git a/src/Bro.Device.OmronFins/OmronFinsConfig.cs b/src/Bro.Device.OmronFins/OmronFinsConfig.cs
index a4d31ad..6d2c675 100644
--- a/src/Bro.Device.OmronFins/OmronFinsConfig.cs
+++ b/src/Bro.Device.OmronFins/OmronFinsConfig.cs
@@ -35,6 +35,7 @@
         /// </summary>
         [Description("PLC缃戠粶鍙�")]
         [Category("閫氫俊璁剧疆-PLC")]
+        [Browsable(false)]
         public byte DNA { get; set; } = 0;
 
         /// <summary>
@@ -42,6 +43,7 @@
         /// </summary>
         [Description("PLC鑺傜偣鍙�")]
         [Category("閫氫俊璁剧疆-PLC")]
+        [Browsable(false)]
         public byte DA1 { get; set; } = 1;
 
         /// <summary>
@@ -49,6 +51,7 @@
         /// </summary>
         [Description("PLC鍗曞厓鍙�")]
         [Category("閫氫俊璁剧疆-PLC")]
+        [Browsable(false)]
         public byte DA2 { get; set; } = 0;
 
         /// <summary>
@@ -70,6 +73,7 @@
         /// </summary>
         [Description("PC缃戠粶鍙�")]
         [Category("閫氫俊璁剧疆-鏈満")]
+        [Browsable(false)]
         public byte SNA { get; set; } = 0;
 
         /// <summary>
@@ -77,6 +81,7 @@
         /// </summary>
         [Description("PC鑺傜偣鍙�")]
         [Category("閫氫俊璁剧疆-鏈満")]
+        [Browsable(false)]
         public byte SA1 { get; set; } = 10;
 
         /// <summary>
@@ -84,9 +89,8 @@
         /// </summary>
         [Description("PC鍗曞厓鍙�")]
         [Category("閫氫俊璁剧疆-鏈満")]
+        [Browsable(false)]
         public byte SA2 { get; set; } = 0;
-
-
     }
 
     public class OmronFinsInputConfig : PLCInputConfigBase
diff --git a/src/Bro.Device.SeerAGV/SeerAGVConfig.cs b/src/Bro.Device.SeerAGV/SeerAGVConfig.cs
index 88e3b3f..67482a1 100644
--- a/src/Bro.Device.SeerAGV/SeerAGVConfig.cs
+++ b/src/Bro.Device.SeerAGV/SeerAGVConfig.cs
@@ -136,6 +136,7 @@
         QueryTaskStatus = 0x03FC,
 
         CancelTask = 0x0BBB,
+        PauseTask = 0x0BB9,
         TaskOrder = 0x0BEB,
     }
 
diff --git a/src/Bro.Device.SeerAGV/SeerAGVDriver.cs b/src/Bro.Device.SeerAGV/SeerAGVDriver.cs
index e64d17c..c5247c7 100644
--- a/src/Bro.Device.SeerAGV/SeerAGVDriver.cs
+++ b/src/Bro.Device.SeerAGV/SeerAGVDriver.cs
@@ -96,7 +96,7 @@
         byte[] buffer = new byte[1024];
 
         string currentPosition = "";
-        string CurrentPosition
+        public string CurrentPosition
         {
             get => currentPosition;
             set
@@ -105,13 +105,16 @@
                 {
                     currentPosition = value;
 
-                    OnAGVPositoinChanged?.Invoke(this, currentPosition);
+                    if (!string.IsNullOrWhiteSpace(currentPosition))
+                    {
+                        OnAGVPositoinChanged?.Invoke(this, currentPosition);
+                    }
                 }
             }
         }
 
         AGVTaskStatus taskStatus = AGVTaskStatus.None;
-        AGVTaskStatus TaskStatus
+        public AGVTaskStatus TaskStatus
         {
             get => taskStatus;
             set
@@ -119,7 +122,11 @@
                 if (taskStatus != value)
                 {
                     taskStatus = value;
-                    OnAGVTaskStatusChanged?.Invoke(this, taskStatus);
+
+                    if (taskStatus != AGVTaskStatus.None)
+                    {
+                        OnAGVTaskStatusChanged?.Invoke(this, taskStatus);
+                    }
                 }
             }
         }
@@ -219,8 +226,15 @@
             SendMsg(client_Guide, IConfig.GuidePort, msg);
         }
 
+        public void PauseTask()
+        {
+            SeerMessage msg = new SeerMessage((int)AGVCode.PauseTask, SID);
+            SendMsg(client_Guide, IConfig.GuidePort, msg);
+        }
+
         public void TaskOrder(string dest)
         {
+            CurrentPosition = "";
             SeerMessage msg = new SeerMessage((int)AGVCode.TaskOrder, SID, JsonConvert.SerializeObject(new { id = dest }));
             SendMsg(client_Guide, IConfig.GuidePort, msg);
         }

--
Gitblit v1.8.0