patrick
2019-10-18 4e8bf084f8a04617a9f542099183b8d829fa7c4b
1. 修改机器人通信协议及相关流程触发和操作
14个文件已修改
10个文件已添加
3221 ■■■■■ 已修改文件
src/A032.Process/A032.Process.csproj 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/AGVBindUnit.cs 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/AGVPath.cs 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CalibrationConfig.cs 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CtrlCalib9PDynamic.cs 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CtrlCalib9PDynamic.resx 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/FrmCalib9PDynamic.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/FrmCalib9PDynamic.resx 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessConfig.cs 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl.cs 113 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Calibration.cs 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Method.cs 1064 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Helper/ExceptionHelper.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Interface/IMonitor.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Interface/IStationProcess.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.AuboRobot/AuboRobotConfig.cs 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.AuboRobot/AuboRobotDriver.cs 166 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.AuboRobot/Bro.Device.AuboRobot.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/DeviceBase/PLCBase.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.OmronFins/OmronFinsConfig.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.SeerAGV/SeerAGVConfig.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.SeerAGV/SeerAGVDriver.cs 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>
src/A032.Process/AGVBindUnit.cs
New file
@@ -0,0 +1,295 @@
using 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 ?? "未绑定AGV";
                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;
                });
            }
        }
    }
}
src/A032.Process/AGVPath.cs
New file
@@ -0,0 +1,110 @@
using 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("卸载空Tray地点")]
        UnloadEmptyTray = 2,
        [Description("上满Tray地点")]
        LoadFullTray = 3,
        [Description("卸载满Tray地点")]
        UnloadFullTray = 4,
    }
    public class PathPosition : IComplexDisplay
    {
        [Category("导航路径")]
        [Description("AGV路径节点代码")]
        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("位置代码")]
        [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()}";
                });
            }
        }
    }
}
src/A032.Process/Calibration/CalibrationConfig.cs
New file
@@ -0,0 +1,87 @@
using 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("位置代码")]
        [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);
        }
    }
}
src/A032.Process/Calibration/CtrlCalib9PDynamic.Designer.cs
New file
@@ -0,0 +1,378 @@
namespace 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 组件设计器生成的代码
        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </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;
    }
}
src/A032.Process/Calibration/CtrlCalib9PDynamic.cs
New file
@@ -0,0 +1,277 @@
using 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;
        }
    }
}
src/A032.Process/Calibration/CtrlCalib9PDynamic.resx
New file
@@ -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>
src/A032.Process/Calibration/FrmCalib9PDynamic.Designer.cs
New file
@@ -0,0 +1,47 @@
namespace 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
    }
}
src/A032.Process/Calibration/FrmCalib9PDynamic.cs
New file
@@ -0,0 +1,50 @@
using 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();
              };
        }
    }
}
src/A032.Process/Calibration/FrmCalib9PDynamic.resx
New file
@@ -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>
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:MethodCode,Value:操作配置
        ///// </summary>
        //[Category("操作配置")]
        //[Description("操作配置集合")]
        //[TypeConverter(typeof(CollectionCountConvert))]
        //[Editor(typeof(OperationConfigBindEditor), typeof(UITypeEditor))]
        //public Dictionary<string, IOperationConfig> ProcessOpConfigDict { get; set; } = new Dictionary<string, IOperationConfig>();
        /// <summary>
        /// 操作配置的字典集合
        /// Key:MethodCode,Value:操作配置
        /// </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>
        /// 空Tray上料阈值,AGV上的空tray数量不大于该数值时,AGV可以执行空Tray上料任务
        /// </summary>
        [Category("阈值设置")]
        [Description("空Tray上料阈值,AGV上的空tray数量不大于该数值时,AGV可以执行空Tray上料任务")]
        public int AGV_EmptyTrayThreshold { get; set; } = 0;
        /// <summary>
        /// 满Tray下料阈值,AGV上的满tray数量不小于该数值时,AGV可以执行满Tray下料任务
        /// </summary>
        [Category("阈值设置")]
        [Description("满Tray下料阈值,AGV上的满tray数量不小于该数值时,AGV可以执行满Tray下料任务")]
        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("机台压机满Tray数量")]
        public int Machine_FullTrayNum { get; set; }
        [Category("阈值设置")]
        [Description("机台压机空Tray数量")]
        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("路径节点代码")]
        public string PositionCode { get; set; }
        [Category("导航路径")]
        [Description("路径节点描述")]
        public string Description { get; set; }
        public string GetDisplayText()
        {
            return $"{PositionCode}-{Description}";
        }
    }
    public class PositionMatrix : IComplexDisplay
    {
        [Category("位置矩阵")]
        [Description("位置代码")]
        [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}";
                });
            }
        }
    }
}
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 初始化HalconTool
                    #region 初始化HalconTool 根据processMethod的特性来配置
                    //if (attr.DeviceType.EndsWith("Camera"))
                    //{
                    //    if (StationConfig.ProcessOpConfigDict.Keys.Contains(attr.MethodCode))
@@ -484,29 +529,39 @@
                }
            });
            #region 初始化HalconTool
            #region 初始化HalconTool 根据配置的接口类型来配置
            _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
                {
                    //有IOperationConfig参数的调用
                    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)
                {
src/A032.Process/ProcessControl_Calibration.cs
New file
@@ -0,0 +1,101 @@
using 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;
        }
    }
}
src/A032.Process/ProcessControl_Method.cs
@@ -1,7 +1,10 @@
using Bro.Common.Helper;
using 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", "机器人监听事件-空Tray区域清空", 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", "机器人监听事件-满Tray区域放满", 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", "机器人监听事件-满Tray区域清空", 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", "机器人监听事件-载入空Tray就绪", 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 空Tray上料
        [ProcessMethod("", "AGV_LoadEmptyTray", "AGV去往空Tray上料", 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", "到达空Tray上料点", 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尚未到达空Tray上料点", null);
            }
            bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.EmptyTray, 0);
            bind.RobotStatus = TaskStatus.Running;
            return new ProcessResponse(true);
        }
        //[ProcessMethod("", "EmptyTrayReady", "空Tray上料完成", 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 空Tray往机台下料
        //bool isEmptyTrayNeed = false;
        //bool isEmptyTrayTaskAssigned = false;
        RobotMsg RobotMsg_UnloadEmptyTray = new RobotMsg();
        [ProcessMethod("", "PLC_NoticeEmptyTray", "PLC通知需要上空Tray", 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去往卸载空Tray料位置", 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", "机器人运动至空Tray拍照位置", 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", "相机确认空Tray卸载机器人位置调整", 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($"未配置Camera_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($"未配置Camera_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 从机台上满Tray
        //bool isFullTrayNeed = false;
        //bool isFullTrayTaskAssigned = false;
        RobotMsg RobotMsg_LoadFullTray = new RobotMsg();
        [ProcessMethod("", "PLC_NoticeFullTray", "PLC通知满Tray需要取走", 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去往满Tray上料位置", 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", "机器人运动至满Tray拍照位置", 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", "相机确认满Tray上料机器人位置调整", 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($"未配置Camera_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($"未配置Camera_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 满Tray产线下料
        [ProcessMethod("", "AGV_UnloadFullTray", "AGV去往卸载满Tray料", 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", "相机操作卸载满Tray", 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($"未配置Camera_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; }
    }
}
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;
src/Bro.Common.Model/Interface/IMonitor.cs
@@ -1,4 +1,5 @@
using System;
using 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);
src/Bro.Common.Model/Interface/IStationProcess.cs
@@ -48,6 +48,8 @@
        /// </summary>
        /// <returns></returns>
        List<ProcessMethodAttribute> CollectProcessMethods();
        void InitialProcessMethods();
        #endregion
        #region 事件
src/Bro.Device.AuboRobot/AuboRobotConfig.cs
@@ -1,8 +1,11 @@
using 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不包含Para1,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,
    }
}
src/Bro.Device.AuboRobot/AuboRobotDriver.cs
@@ -1,6 +1,8 @@
using 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
    }
}
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" />
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);
                    }
                }
            });
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
src/Bro.Device.SeerAGV/SeerAGVConfig.cs
@@ -136,6 +136,7 @@
        QueryTaskStatus = 0x03FC,
        CancelTask = 0x0BBB,
        PauseTask = 0x0BB9,
        TaskOrder = 0x0BEB,
    }
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);
        }