1. 重新整理项目,按照A034模式,将设备异步操作修改为类同步操作。使用任务队列来存储和分配任务。
30个文件已修改
4个文件已添加
4519 ■■■■■ 已修改文件
src/A032.Process/A032.Process.csproj 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/AGVBindUnit.cs 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/AGVPath.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CalibrationConfig.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/CtrlCalib9PDynamic.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/Calibration/FrmCalib9PDynamic.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessConfig.cs 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl.cs 160 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_AGV.cs 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Calibration.cs 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Method.cs 1623 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Robot.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/ProcessControl_Task.cs 759 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Bro.Common.Model.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Helper/EnumHelper.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Helper/ExceptionHelper.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Helper/ListHelper.cs 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Interface/IDeviceConfig.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Interface/IMonitor.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Model/CustomizedPoint.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Model/IODefinition.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Model/Model/MonitorSet.cs 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.AuboRobot/AuboRobotConfig.cs 191 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.AuboRobot/AuboRobotDriver.cs 270 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/Base/DeviceBase.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/Base/DeviceConfigBase.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/DeviceBase/HDevEngineTool.cs 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/DeviceBase/PLCBase.cs 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Common/Helper/AspectHelper.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.OmronFins/FinsFrame.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.OmronFins/OmronFinsDriver.cs 143 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.SeerAGV/Bro.Device.SeerAGV.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.SeerAGV/SeerAGVConfig.cs 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.SeerAGV/SeerAGVDriver.cs 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/A032.Process/A032.Process.csproj
@@ -90,9 +90,12 @@
    </Compile>
    <Compile Include="Helper\PropertyConvertHelper.cs" />
    <Compile Include="ProcessConfig.cs" />
    <Compile Include="ProcessControl_AGV.cs" />
    <Compile Include="ProcessControl_Calibration.cs" />
    <Compile Include="ProcessControl_Method.cs" />
    <Compile Include="ProcessControl.cs" />
    <Compile Include="ProcessControl_Robot.cs" />
    <Compile Include="ProcessControl_Task.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
src/A032.Process/AGVBindUnit.cs
@@ -15,42 +15,84 @@
namespace A032.Process
{
    public enum TaskStatus
    public enum TaskState
    {
        Available = 1,
        /// <summary>
        /// 新任务
        /// </summary>
        New = 0,
        /// <summary>
        /// 任务完成
        /// </summary>
        Done = 1,
        /// <summary>
        /// 任务失败
        /// </summary>
        Failed = 2,
        /// <summary>
        /// 任务已添加队列,等待执行
        /// </summary>
        Queueing = 3,
        /// <summary>
        /// 任务已指派
        /// </summary>
        Assigned = 4,
        /// <summary>
        /// 任务未指派,已取消
        /// </summary>
        Cancelled = 5,
    }
    public enum AGVState
    {
        /// <summary>
        /// AGV状态不可知
        /// </summary>
        Unknown = 0,
        /// <summary>
        /// 空闲状态,可接收任务
        /// </summary>
        Idle = 1,
        /// <summary>
        /// 任务执行中
        /// </summary>
        Running = 2,
        /// <summary>
        /// 报警中,不可执行任务
        /// </summary>
        Warning = 3,
        /// <summary>
        /// 充电中,不可执行任务
        /// </summary>
        InCharge = 4,
        /// <summary>
        /// 空闲充电状态,可接收任务
        /// </summary>
        IdleCharge = 5,
    }
    public enum TaskAvailableLevel
    {
        Robot = 1,
        AGV = 2,
        Both = 3,
    }
    //public class AGVTaskModel
    //{
    //    public TaskAvailableLevel Level { get; set; } = TaskAvailableLevel.Robot;
    public class AGVTaskModel
    {
        public TaskAvailableLevel Level { get; set; } = TaskAvailableLevel.Robot;
    //    public string MethodName { get; set; }
        public string MethodName { get; set; }
    //    public IOperationConfig OpConfig { get; set; }
        public IOperationConfig OpConfig { get; set; }
    //    public IDevice Device { get; set; }
        public IDevice Device { get; set; }
    //    //public int Priority { get; set; } = 10;
        //public int Priority { get; set; } = 10;
    //    public AGVTaskModel() { }
        public AGVTaskModel() { }
        public AGVTaskModel(TaskAvailableLevel level, string methodName, IOperationConfig opConfig = null, IDevice device = null)
        {
            Level = level;
            MethodName = methodName;
            OpConfig = opConfig ?? new OperationConfigBase();
            Device = device;
        }
    }
    //    public AGVTaskModel(TaskAvailableLevel level, string methodName, IOperationConfig opConfig = null, IDevice device = null)
    //    {
    //        Level = level;
    //        MethodName = methodName;
    //        OpConfig = opConfig ?? new OperationConfigBase();
    //        Device = device;
    //    }
    //}
    public class AGVBindUnit : IComplexDisplay
    {
@@ -90,60 +132,32 @@
        }
        #endregion
        #region 后台属性
        #region 状态
        private AGVState unitState = AGVState.Idle;
        [Browsable(false)]
        [JsonIgnore]
        public Action<AGVTaskModel> OnMethodInvoke { get; set; }
        public string AGVDest { get; set; } = "";
        private TaskStatus agvStatus = TaskStatus.Available;
        [Browsable(false)]
        [JsonIgnore]
        public TaskStatus AGVStatus
        public AGVState UnitState
        {
            get => agvStatus;
            get => unitState;
            set
            {
                agvStatus = value;
                //InvokeTaskCheck();
            }
        }
                if (value != unitState)
                {
                    var preState = unitState;
                    unitState = value;
        private TaskStatus robotStatus = TaskStatus.Available;
        [Browsable(false)]
        [JsonIgnore]
        public TaskStatus RobotStatus
        {
            get => robotStatus;
            set
            {
                robotStatus = value;
                //InvokeTaskCheck();
                    OnUnitStateChanged?.Invoke(this.Id, preState, unitState);
                }
            }
        }
        [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;
                    }
                }
        public string WarningMsg { get; set; } = "";
        #endregion
                return TaskStatus.Running;
            }
        }
        #region 设备
        [Browsable(false)]
        [JsonIgnore]
        public SeerAGVDriver AGV { get; set; } = null;
@@ -153,150 +167,42 @@
        [Browsable(false)]
        [JsonIgnore]
        public CameraBase Camera { get; set; } = null;
        #endregion
        //[Browsable(false)]
        //[JsonIgnore]
        //public ObservableCollection<AGVTaskModel> TaskList { get; set; } = new ObservableCollection<AGVTaskModel>();
        #region 任务信息
        [JsonIgnore]
        [Browsable(false)]
        public string CurrentTaskId { get; set; }
        [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; }
        public bool IsTaskCancelled { get; set; } = false;
        [Browsable(false)]
        [JsonIgnore]
        public bool IsFullTrayTaskAssigned { get; set; }
        [Browsable(false)]
        public bool IsTaskCancelling { get; set; } = false;
        #endregion
        #region 事件
        [JsonIgnore]
        public bool IsEmptyTrayTaskAssigned { get; set; }
        [Browsable(false)]
        public Action<string, AGVState, AGVState> OnUnitStateChanged { get; set; }
        #endregion
        //[Browsable(false)]
        //[JsonIgnore]
        //public ManualResetEvent FullTrayFullHandle { get; set; } = new ManualResetEvent(false);
        #region 托盘信息
        [JsonIgnore]
        [Browsable(false)]
        public int FullTrayNum { get; set; }
        //[Browsable(false)]
        //[JsonIgnore]
        //public ManualResetEvent FullTrayEmptyHandle { get; set; } = new ManualResetEvent(false);
        public ManualResetEvent RobotIOHandle { get; set; } = new ManualResetEvent(false);
        [JsonIgnore]
        [Browsable(false)]
        public int EmptyTrayNum { get; set; }
        #endregion
        #endregion
        public AGVBindUnit()
        {
            //TaskList.CollectionChanged += TaskList_CollectionChanged;
        }
        object agvStatusLock = new object();
        public bool SetAGVStatus(TaskStatus status)
        {
            lock (agvStatusLock)
            {
                switch (status)
                {
                    case TaskStatus.Available:
                        break;
                    case TaskStatus.Running:
                        if (AGVStatus == TaskStatus.Available)
                        {
                            AGVStatus = status;
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    case TaskStatus.Warning:
                        break;
                    default:
                        break;
                }
                AGVStatus = status;
                return true;
            }
        }
        //private void TaskList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        //{
        //    if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
        //    {
        //        InvokeTaskCheck();
        //    }
        //}
        //private void InvokeTaskCheck()
        //{
        //    lock (taskLock)
        //    {
        //        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(TaskList[i]);
        //                TaskList.RemoveAt(i);
        //                break;
        //            }
        //        }
        //    }
        //}
        //object taskLock = new object();
        //public void AddTask(AGVTaskModel task)
        //{
        //    lock (taskLock)
        //    {
        //        TaskList.Add(task);
        //    }
        //}
        //public void ClearTask()
        //{
        //    lock (taskLock)
        //    {
        //        TaskList.Clear();
        //    }
        //}
        //public AGVTaskModel GetTask(int index)
        //{
        //    lock (taskLock)
        //    {
        //        return TaskList[index];
        //    }
        //}
    }
    public class AGVDeviceConverter : ComboBoxItemTypeConvert
@@ -364,4 +270,40 @@
            }
        }
    }
    public class AllDeviceIdConverter : ComboBoxItemTypeConvert
    {
        public override void GetConvertHash()
        {
            using (var scope = GlobalVar.Container.BeginLifetimeScope())
            {
                _hash[""] = "未指定";
                List<IDevice> deviceList = scope.Resolve<List<IDevice>>();
                deviceList.ForEach(d =>
                {
                    _hash[d.Id] = d.Name;
                });
            }
        }
    }
    public class AllDeviceNameConverter : ComboBoxItemTypeConvert
    {
        public override void GetConvertHash()
        {
            using (var scope = GlobalVar.Container.BeginLifetimeScope())
            {
                _hash[""] = "未指定";
                List<IDevice> deviceList = scope.Resolve<List<IDevice>>();
                deviceList.ForEach(d =>
                {
                    _hash[d.Name] = d.Name;
                });
            }
        }
    }
}
src/A032.Process/AGVPath.cs
@@ -3,6 +3,7 @@
using Bro.Common.Model;
using Bro.Common.Model.Interface;
using Bro.Device.HikCamera;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -48,6 +49,10 @@
        [TypeConverter(typeof(PLCDeviceConverter))]
        public string DeviceOwner { get; set; }
        [Browsable(false)]
        [JsonIgnore]
        public bool IsOccupied { get; set; } = false;
        public string GetDisplayText()
        {
            return $"{PositionCode}-{Description}";
@@ -83,6 +88,18 @@
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public HikCameraOperationConfig CameraOpConfig { get; set; } = new HikCameraOperationConfig();
        [Category("示教配置")]
        [Description("机器人拍照位置")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public RobotPoint RobotSnapshotPoint { get; set; } = new RobotPoint();
        [Category("示教配置")]
        [Description("机器人示教位置到实际抓取位置的偏移")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public RobotPoint RobotShift { get; set; } = new RobotPoint();
        public string GetDisplayText()
        {
            return $"{PositionCode}:曝光:{CameraOpConfig.Exposure};标定矩阵:{string.Join(",", Matrix)}";
src/A032.Process/Calibration/CalibrationConfig.cs
@@ -19,8 +19,8 @@
    {
        [Category("关联配置")]
        [Description("位置序号")]
        [TypeConverter(typeof(PositionNoConverter))]
        public int PositionNo { get; set; }
        [TypeConverter(typeof(PositionCodeConverter))]
        public string PositionCode { get; set; }
        [Category("关联配置")]
        [Description("适用相机编号")]
@@ -39,7 +39,7 @@
        public string GetDisplayText()
        {
            return $"PositionNo:{PositionNo}; Configs:{Configs.Count}";
            return $"PositionNo:{PositionCode}; Configs:{Configs.Count}";
        }
        public List<string> GetHalconToolPathList()
@@ -86,11 +86,17 @@
        [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();
        [Category("运动平台设置")]
        [Description("平台当前坐标")]
        [Description("机器人运动坐标")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public CustomizedPoint CurrentPlatPoint { get; set; } = new CustomizedPoint();
        public RobotPoint PlatPoint { get; set; } = new RobotPoint();
        public event PropertyChangedEventHandler PropertyChanged;
src/A032.Process/Calibration/CtrlCalib9PDynamic.cs
@@ -30,7 +30,7 @@
            InitializeComponent();
        }
        public CtrlCalib9PDynamic(ProcessControl process, CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position, Action<AGVBindUnit, int, int> commuAction, Action<CalibrationConfigCollection, AGVBindUnit, PathPosition> finalCalculation)
        public CtrlCalib9PDynamic(ProcessControl process, CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position, Action<CalibrationConfigCollection, AGVBindUnit, PathPosition> finalCalculation)
        {
            InitializeComponent();
@@ -42,7 +42,6 @@
            Bind = bind;
            Position = position;
            CommuAction = commuAction;
            FinalCalculation = finalCalculation;
        }
@@ -161,7 +160,8 @@
            _canvas.LoadImage(Config.Configs[index].Image);
            _canvas.Elements.Clear();
            CrossHair ch = new CrossHair(new CalibrationPoint(Config.Configs[index].ImageMarkPoint, Config.Configs[index].CurrentPlatPoint));
            var config = Config.Configs[index];
            CrossHair ch = new CrossHair(new CalibrationPoint(new CustomizedPoint(config.ImageMarkPoint.X, config.ImageMarkPoint.Y), new CustomizedPoint(config.PlatPoint.X, config.PlatPoint.Y)));
            _canvas.Elements.Add(ch);
            tsslInfo.Text = $"步骤{index + 1}完成";
@@ -191,7 +191,9 @@
            {
                _canvas.LoadImage(stepConfig.Image);
                _canvas.Elements.Clear();
                _canvas.Elements.Add(new CrossHair(new CalibrationPoint(stepConfig.ImageMarkPoint, stepConfig.CurrentPlatPoint)));
                CrossHair ch = new CrossHair(new CalibrationPoint(new CustomizedPoint(stepConfig.ImageMarkPoint.X, stepConfig.ImageMarkPoint.Y), new CustomizedPoint(stepConfig.PlatPoint.X, stepConfig.PlatPoint.Y)));
                _canvas.Elements.Add(ch);
            }
        }
@@ -206,7 +208,7 @@
            //    //ProcessControl.SendCalibStartSignal(Config.TriggerAddress);
            //}
            ProcessControl.MultipleStepsProcess(Config, Bind, CommuAction);
            ProcessControl.MultipleStepsProcess(Config, Bind);
        }
        private void btnLoadOfflineImages_Click(object sender, EventArgs e)
@@ -236,7 +238,7 @@
            //tsslInfo.Text = $"单步运算完成。标记点坐标:{config.ImageMarkPoint.X},{config.ImageMarkPoint.Y}";
            ProcessControl.SingleStepProcess(config, CommuAction, Bind, Position.PositionNo, _selectedStepIndex);
            ProcessControl.SingleStepProcess(config, Bind, _selectedStepIndex);
            tsslInfo.Text = $"单步运算完成。标记点坐标:{config.ImageMarkPoint.X},{config.ImageMarkPoint.Y}";
        }
src/A032.Process/Calibration/FrmCalib9PDynamic.cs
@@ -37,7 +37,7 @@
        //    CtrlCalib9PDynamic = new CtrlCalib9PDynamic(process, device, config, finalCalculation);
        //}
        public FrmCalib9PDynamic(ProcessControl process, CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position, Action<AGVBindUnit, int, int> commuAction, Action<CalibrationConfigCollection, AGVBindUnit, PathPosition> finalCalculation)
        public FrmCalib9PDynamic(ProcessControl process, CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position, Action<CalibrationConfigCollection, AGVBindUnit, PathPosition> finalCalculation)
        {
            InitializeComponent();
@@ -48,7 +48,7 @@
            //CommuAction = commuAction;
            //FinalCalculation = finalCalculation;
            CtrlCalib9PDynamic = new CtrlCalib9PDynamic(process, calibConfig, bind, position, commuAction, finalCalculation);
            CtrlCalib9PDynamic = new CtrlCalib9PDynamic(process, calibConfig, bind, position, finalCalculation);
        }
        //AGVBindUnit Bind { get; set; }
src/A032.Process/ProcessConfig.cs
@@ -3,6 +3,7 @@
using Bro.Common.Helper;
using Bro.Common.Interface;
using Bro.Common.Model;
using Bro.Common.Model.Interface;
using Bro.Device.AuboRobot;
using Bro.Device.HikCamera;
using Bro.Device.OmronFins;
@@ -20,7 +21,7 @@
namespace A032.Process
{
    public class ProcessConfig : IStationConfig
    public class ProcessConfig : IStationConfig, IHalconToolPath
    {
        #region 设备配置
        [Category("设备配置")]
@@ -70,6 +71,10 @@
        [TypeConverter(typeof(CollectionCountConvert))]
        [Editor(typeof(OperationConfigBindEditor), typeof(UITypeEditor))]
        public Dictionary<string, IOperationConfig> ProcessOpConfigDict { get; set; } = new Dictionary<string, IOperationConfig>();
        [Category("操作配置")]
        [Description("操作超时设置,单位min")]
        public int OperationTimeout { get; set; } = 10;
        //[Category("监听和操作配置")]
        //[Description("监听操作配置集合")]
@@ -139,6 +144,7 @@
        //[Description("是否采用外部算子。true:采用外部算子,false:使用内部算法")]
        //public bool IsUsingExternalAlgorithem { get; set; } = true;
        #region A032
        [Category("路径相关")]
        [Description("路径节点配置")]
        [TypeConverter(typeof(CollectionCountConvert))]
@@ -155,19 +161,21 @@
        [Description("是否启用视觉引导")]
        public bool IsEnableVisionGuide { get; set; } = false;
        /// <summary>
        /// 空Tray上料阈值,AGV上的空tray数量不大于该数值时,AGV可以执行空Tray上料任务
        /// </summary>
        [Category("阈值设置")]
        [Description("空Tray上料阈值,AGV上的空tray数量不大于该数值时,AGV可以执行空Tray上料任务")]
        public int AGV_EmptyTrayThreshold { get; set; } = 0;
        [Category("视觉配置")]
        [Description("视觉引导次数")]
        public int VisionGuideTimes { get; set; } = 2;
        /// <summary>
        /// 满Tray下料阈值,AGV上的满tray数量不小于该数值时,AGV可以执行满Tray下料任务
        /// </summary>
        [Category("阈值设置")]
        [Description("满Tray下料阈值,AGV上的满tray数量不小于该数值时,AGV可以执行满Tray下料任务")]
        public int AGV_FullTrayThreshold { get; set; } = 10;
        [Category("设备参数配置")]
        [Description("光源开关索引配置")]
        public int LightOutputIndex { get; set; }
        [Category("设备参数配置")]
        [Description("AGV满载满Tray/空Tray数量")]
        public int AGVAvailableTrayNums { get; set; } = 6;
        [Category("设备参数配置")]
        [Description("默认等待任务轮数。当某些条件不满足当前任务执行前提,当前任务会等待若干任务数后执行")]
        public int DefaultWaitShift { get; set; } = 3;
        /// <summary>
        /// 产线忙时拍照确认等待间隔,以秒为单位
@@ -185,11 +193,43 @@
        [Category("阈值设置")]
        [Description("机台压机满Tray数量")]
        public int Machine_FullTrayNum { get; set; }
        public int Machine_FullTrayNum { get; set; } = 6;
        [Category("阈值设置")]
        [Description("机台压机空Tray数量")]
        public int Machine_EmptyTrayNum { get; set; }
        public int Machine_EmptyTrayNum { get; set; } = 6;
        #endregion
        #region IHalconToolPath
        public List<string> GetHalconToolPathList()
        {
            List<string> list = new List<string>();
            ProcessOpConfigDict.Values.ToList().ForEach(c =>
            {
                if (c is IHalconToolPath)
                {
                    list.AddRange((c as IHalconToolPath).GetHalconToolPathList());
                }
            });
            this.GetType().GetProperties().ToList().ForEach(p =>
            {
                var pValue = p.GetValue(this);
                if (pValue is IHalconToolPath)
                {
                    list.AddRange((pValue as IHalconToolPath).GetHalconToolPathList());
                }
                else if (pValue is IEnumerable<IHalconToolPath>)
                {
                    list.AddRange((pValue as IEnumerable<IHalconToolPath>).SelectMany(u => u.GetHalconToolPathList()));
                }
            });
            return list.Distinct().ToList();
        }
        #endregion
        #region Ignore
        [Browsable(false)]
@@ -239,4 +279,23 @@
        public virtual bool IsDBSave { get; set; } = false;
        #endregion
    }
    [Device("OperationTest", "测试方法配置", DeviceAttributeType.OperationConfig)]
    public class OperationTestConfig : OperationConfigBase
    {
        //[Category("测试配置")]
        //[Description("方法类型")]
        //public TaskType TaskType { get; set; } = TaskType.LoadEmptyTrayToAGV;
        [Category("测试配置")]
        [Description("方法信息")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public TrayTask TaskInfo { get; set; } = new TrayTask();
        [Category("测试配置")]
        [Description("执行AGV设备")]
        [TypeConverter(typeof(AGVDeviceConverter))]
        public string AGVId { get; set; }
    }
}
src/A032.Process/ProcessControl.cs
@@ -5,7 +5,6 @@
using Bro.Common.Model;
using Bro.Common.Model.Interface;
using Bro.Common.PubSub;
using Bro.Common.UI;
using Bro.Device.AuboRobot;
using Bro.Device.OmronFins;
using Bro.Device.SeerAGV;
@@ -14,18 +13,14 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using static Bro.Common.Helper.EnumHelper;
@@ -152,6 +147,11 @@
            if (ProcessState == DeviceState.DSClose)
                return;
            List<string> currentTaskIds = Config.AGVBindCollection.Select(u => u.CurrentTaskId).Where(s => !string.IsNullOrWhiteSpace(s)).ToList();
            TrayTaskCollection.RemoveAll(t => !currentTaskIds.Contains(t.TaskId));
            _bindTaskDoneHandleDict.Values.ToList().ForEach(h => h.WaitOne());
            CloseDevice(PLCDict.Values.ToList());
            CloseDevice(RobotDict.Values.ToList());
            CloseDevice(AGVDict.Values.ToList());
@@ -168,6 +168,7 @@
                return;
            InitialProcessMethods();
            TrayTaskCollection.OnItemChangedWithItemInfo = OnTaskListChanged;
            OpenDevices(RobotDict.Values.ToList());
            OpenDevices(AGVDict.Values.ToList());
@@ -177,14 +178,15 @@
            OpenDevices(PLCDict.Values.ToList());
            //加载AGVUnit状态事件
            Config.AGVBindCollection.ForEach(b =>
            {
                b.OnUnitStateChanged = OnUnitStateChanged;
                _bindTaskDoneHandleDict[b.Id] = new AutoResetEvent(true);
            });
            ProcessState = DeviceState.DSOpen;
            //QueryRobotIO();
            //Task.Run(() =>
            //{
            //    //PLCMonitor();
            //});
            LogAsync(DateTime.Now, "Process Opened", "");
        }
@@ -291,27 +293,27 @@
            #region 个别配置的特别处理
            #endregion
            _warningRemains.CollectionChanged -= _warningRemains_CollectionChanged;
            _warningRemains.CollectionChanged += _warningRemains_CollectionChanged;
            WarningRemains.CollectionChanged -= _warningRemains_CollectionChanged;
            WarningRemains.CollectionChanged += _warningRemains_CollectionChanged;
            InitialPLCs();
            InitialAGVs();
            InitialRobots();
            InitialCameras();
            InitialAGVBindUnit();
            InitialMachineTrayNums();
            //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);
        //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);
        }
        //    machineFullTrayDict = Config.PositionCollection.Where(u => u.Description == PathPositionDefinition.LoadFullTray).ToDictionary(p => p.PositionNo, p => 0);
        //}
        private void InitialAGVBindUnit()
        {
@@ -390,6 +392,9 @@
                agv.InitialConfig = c;
                AGVDict[agv.InitialConfig.ID] = agv;
                agv.OnMonitorAlarm -= OnMonitorAlarm;
                agv.OnMonitorInvoke -= OnMonitorInvoke;
                agv.OnLog = OnDeviceLog;
                agv.OnAGVPositoinChanged = OnAGVPositionChanged;
                agv.OnAGVTaskStatusChanged = OnAGVTaskStatusChanged;
@@ -448,7 +453,7 @@
            ProcessConfig pConfig = config as ProcessConfig;
            if (pConfig == null)
                throw new ProcessException("目前只支持ProcessConfig类型的非空内容保存", null);
                throw new ProcessException("目前只支持ProcessConfig类型的非空内容保存");
            string newConfig = JsonConvert.SerializeObject(pConfig, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });
            using (StreamWriter writer = new StreamWriter(CONFIG_PATH, false, System.Text.Encoding.UTF8))
@@ -505,58 +510,24 @@
                if (attr != null)
                {
                    _processMethodDict[attr.MethodCode] = m;
                    #region 初始化HalconTool 根据processMethod的特性来配置
                    //if (attr.DeviceType.EndsWith("Camera"))
                    //{
                    //    if (StationConfig.ProcessOpConfigDict.Keys.Contains(attr.MethodCode))
                    //    {
                    //        var opConfig = StationConfig.ProcessOpConfigDict[attr.MethodCode] as HalconRelatedCameraOprerationConfigBase;
                    //        if (opConfig != null)
                    //        {
                    //            if (!string.IsNullOrWhiteSpace(opConfig.AlgorithemPath))
                    //            {
                    //                string directoryPath = Path.GetDirectoryName(opConfig.AlgorithemPath);
                    //                string fileName = Path.GetFileNameWithoutExtension(opConfig.AlgorithemPath);
                    //                HDevEngineTool tool = new HDevEngineTool(directoryPath);
                    //                tool.LoadProcedure(fileName);
                    //                _halconToolDict[attr.MethodCode] = tool;
                    //            }
                    //        }
                    //    }
                    //}
                    #endregion
                }
            });
            #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);
            });
            Config.ProcessOpConfigDict.Values.ToList().ForEach(c =>
            {
                InitialHalconTool(c as IHalconToolPath);
            });
            InitialHalconTool();
            #endregion
        }
        private void InitialHalconTool(IHalconToolPath toolPath)
        private void InitialHalconTool()
        {
            //IHalconToolPath toolPath = c as IHalconToolPath;
            if (toolPath != null)
            foreach (HDevEngineTool tool in _halconToolDict.Values)
            {
                toolPath.GetHalconToolPathList().ForEach(path =>
                tool?.Dispose();
            }
            _halconToolDict = new Dictionary<string, HDevEngineTool>();
            Config.GetHalconToolPathList().ForEach(path =>
                {
                    if (!string.IsNullOrWhiteSpace(path) && !_halconToolDict.ContainsKey(path))
                    {
@@ -569,7 +540,6 @@
                        _halconToolDict[path] = tool;
                    }
                });
            }
        }
        public List<IDevice> GetDeviceList()
@@ -621,7 +591,7 @@
                                    res = new ProcessResponse((int)ReturnValue.EXCEPTIONVALUE);
                                }
                                var newEx = new ProcessException("函数" + methodCode + "执行异常", ex);
                                var newEx = new ProcessException("函数" + methodCode + "执行异常", ExceptionLevel.Warning, ex);
                            }
                            else
                            {
@@ -658,60 +628,6 @@
            {
                monitorSet.Response.ResultValue = (int)ReturnValue.OKVALUE;
            }
            #endregion
            //sw.Stop();
            //LogAsync(DateTime.Now, methodCode + " 调用耗时: " + sw.ElapsedMilliseconds.ToString() + "ms", "");
            //TimeRecordCSV(DateTime.Now, methodCode + "调用", (int)sw.ElapsedMilliseconds);
            //sw.Start();
            #region 原有PLC写入结果操作,现转到异步调用后回调去执行
            //ProcessResponse resValues = res as ProcessResponse;
            //if (resValues.ResultValue == (int)PLCReplyValue.IGNORE)
            //{
            //    return;
            //}
            //if (monitorSet.ReplyDataAddress != -1 && resValues.DataList.Count > 0)
            //{
            //    PLC_ITEM item = new PLC_ITEM();
            //    item.OP_TYPE = 2;
            //    item.ITEM_LENGTH = resValues.DataList.Count;
            //    item.ADDRESS = monitorSet.ReplyDataAddress.ToString();
            //    item.ITEM_VALUE = String.Join(",", resValues.DataList);
            //    PLC.WriteItem(item, false);
            //}
            //if (monitorSet.NoticeAddress != -1)
            //{
            //    //测试模式下始终反馈OK信号
            //    if (StationConfig.IsDemoMode && resValues.ResultValue <= 0)
            //    {
            //        resValues.ResultValue = (int)ReturnValue.OKVALUE;
            //    }
            //    int repeatTime = 5;
            //    //LogAsync(DateTime.Now, methodCode + "开始反馈", "");
            //    do
            //    {
            //        try
            //        {
            //            PLC.WriteSingleAddress(set.NoticeAddress, resValues.ResultValue, false);
            //            repeatTime = 0;
            //        }
            //        catch (Exception ex)
            //        {
            //            repeatTime--;
            //            if (repeatTime <= 0)
            //            {
            //                new ProcessException("PLC反馈写入异常", ex);
            //            }
            //        }
            //    } while (repeatTime > 0);
            //}
            #endregion
        }
@@ -1176,7 +1092,7 @@
                    else
                    {
                        //MessageBox.Show("未能获取离线图片!");
                        throw new ProcessException("未能获取离线图片!", null);
                        throw new ProcessException("未能获取离线图片!");
                    }
                }
            }
@@ -1440,7 +1356,7 @@
        #endregion
        #region 报警和DownTime
        public ObservableCollection<string> _warningRemains = new ObservableCollection<string>();
        public ObservableCollection<string> WarningRemains { get; set; } = new ObservableCollection<string>();
        bool warningRemainFlag = false;
        bool WarningRemainFlag
src/A032.Process/ProcessControl_AGV.cs
New file
@@ -0,0 +1,102 @@
using Bro.Common.Helper;
using Bro.Device.SeerAGV;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace A032.Process
{
    public partial class ProcessControl
    {
        private async void OnAGVBatteryLvlChanged(SeerAGVDriver agv, float preBatteryLvl, float batteryLvl)
        {
            await Task.Run(() =>
            {
                var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == agv.Id);
                SeerAGVInitialConfig iConfig = bind.AGV.InitialConfig as SeerAGVInitialConfig;
                if (bind.UnitState == AGVState.Idle)
                {
                    if (batteryLvl <= iConfig.BatteryLvlToCharge)
                    {
                        bind.CurrentTaskId = "InCharge";
                        bind.UnitState = AGVState.InCharge;
                        var chargePosition = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.Charge);
                        if (chargePosition == null)
                        {
                            bind.WarningMsg = $"{bind.AGV.Name}目前无可用充电地址";
                            new ProcessException(bind.WarningMsg);
                            bind.UnitState = AGVState.Warning;
                        }
                        else
                        {
                            bind.AGV.TaskOrder(chargePosition.PositionCode, true);
                        }
                    }
                    else if (batteryLvl <= iConfig.BatteryLvlToCharge_Recommand)
                    {
                        ChargeWhenIdle(bind);
                    }
                }
                else if (bind.UnitState == AGVState.InCharge)
                {
                    if (batteryLvl >= iConfig.BatteryLvlChargeDone)
                    {
                        bind.UnitState = AGVState.Idle;
                    }
                    else if (batteryLvl >= iConfig.BatteryLvlToCharge_Recommand)
                    {
                        bind.UnitState = AGVState.IdleCharge;
                    }
                }
            });
        }
        private async void OnAGVTaskStatusChanged(SeerAGVDriver agv, AGVTaskStatus taskStatus)
        {
            await Task.Run(() =>
            {
                //如果成功到达充电地点,开始充电
                if (taskStatus == AGVTaskStatus.Completed && Config.PositionCollection.Any(u => u.Description == PathPositionDefinition.Charge && u.PositionCode == agv.CurrentPosition))
                {
                    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == agv.Id);
                    if (bind != null)
                    {
                        //表示充电动作任务完成
                        bind.CurrentTaskId = "";
                    }
                }
            });
        }
        private async void OnAGVPositionChanged(SeerAGVDriver agv, string positionCode)
        {
            await Task.Run(() =>
            {
            });
        }
        private void ChargeWhenIdle(AGVBindUnit bind)
        {
            bind.CurrentTaskId = "IdleCharge";
            bind.UnitState = AGVState.IdleCharge;
            //设置为无需等待是为了方便执行过程中可以取消当前操作,执行任务
            var chargePosition = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.Charge);
            if (chargePosition != null)
            {
                bind.AGV.TaskOrder(chargePosition.PositionCode, true);
            }
            else
            {
                new ProcessException($"{bind.AGV.Name}未能获取充电地址");
            }
        }
    }
}
src/A032.Process/ProcessControl_Calibration.cs
@@ -3,6 +3,7 @@
using Bro.Common.Helper;
using Bro.Common.Interface;
using Bro.Common.Model;
using Bro.Device.AuboRobot;
using HalconDotNet;
using System;
using System.Collections.Generic;
@@ -16,8 +17,6 @@
{
    public partial class ProcessControl
    {
        CalibReplyMsg _calibReply = new CalibReplyMsg();
        [ProcessMethod("CalibrationCollection", "RobotCalibration", "机器人9点标定", true)]
        public ProcessResponse RobotCalibration(IOperationConfig config, IDevice device)
        {
@@ -29,7 +28,7 @@
                throw new ProcessException("未能获取绑定设备信息");
            }
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == calibConfig.PositionNo);
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionCode == calibConfig.PositionCode);
            if (position == null)
            {
                throw new ProcessException("未能获取正确位置信息");
@@ -42,64 +41,15 @@
            if (calibConfig.IsStartedFromUI)
            {
                FrmCalib9PDynamic frm = new FrmCalib9PDynamic(this, calibConfig, bind, position, SendMessageToRobot_Calibration, CalculateMatrix);
                FrmCalib9PDynamic frm = new FrmCalib9PDynamic(this, calibConfig, bind, position, CalculateMatrix);
                frm.ShowDialog();
            }
            else
            {
                MultipleStepsProcess(calibConfig, bind, SendMessageToRobot_Calibration);
                MultipleStepsProcess(calibConfig, bind);
                CalculateMatrix(calibConfig, bind, position);
            }
            //for (int i = 0; i < calibConfig.Configs.Count; i++)
            //{
            //    bind.Robot.SendMsg(Bro.Device.AuboRobot.RobotMsgAction.Calibration, Bro.Device.AuboRobot.RobotMsgParas.None, calibConfig.PositionNo, new List<float>() { i + 1 });
            //    _calibReply.CalibHandle.WaitOne();
            //    if (_calibReply.CalibIndex != (i + 1) || _calibReply.CalibPositionNo != calibConfig.PositionNo)
            //    {
            //        throw new ProcessException("标定反馈的索引或位置信息不一致");
            //    }
            //    calibConfig.Configs[i].CurrentPlatPoint = new CustomizedPoint(_calibReply.RobotPosition.X, _calibReply.RobotPosition.Y);
            //    using (HObject hImage = CollectHImage(bind.Camera, calibConfig.Configs[i].CameraOpConfig, "RobotCalibration"))
            //    {
            //        var tool = _halconToolDict[calibConfig.Configs[i].CameraOpConfig.AlgorithemPath];
            //        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();
            //        float x = (float)tool.GetResultTuple("OUTPUT_X").D;
            //        float y = (float)tool.GetResultTuple("OUTPUT_Y").D;
            //        float angel = (float)tool.GetResultTuple("OUTPUT_Angle").D;
            //        if (x < 0 || y < 0)
            //        {
            //            throw new ProcessException("获取点位信息不正确");
            //        }
            //        calibConfig.Configs[i].ImageMarkPoint = new CustomizedPointWithAngle(x, y, angel);
            //    }
            //}
            //HOperatorSet.VectorToHomMat2d(new HTuple(calibConfig.Configs.Select(u => u.ImageMarkPoint.X).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.ImageMarkPoint.Y).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.CurrentPlatPoint.X).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.CurrentPlatPoint.Y).ToArray()), out HTuple matrix);
            //var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == position.PositionCode);
            //if (visionConfig != null)
            //{
            //    visionConfig.Matrix = new List<double>() { matrix[0], matrix[1], 0, matrix[3], matrix[4], 0 };
            //}
            //else
            //{
            //    Config.VisionConfigCollection.Add(new PositionVisionConfig()
            //    {
            //        CameraId = bind.CameraId,
            //        PositionCode = position.PositionCode,
            //        Matrix = new List<double>() { matrix[0], matrix[1], 0, matrix[3], matrix[4], 0 },
            //    });
            //}
            return new ProcessResponse(true);
        }
@@ -115,7 +65,7 @@
                throw new ProcessException("未能获取绑定设备信息");
            }
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == calibConfig.PositionNo);
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionCode == calibConfig.PositionCode);
            if (position == null)
            {
                throw new ProcessException("未能获取正确位置信息");
@@ -128,72 +78,22 @@
            if (calibConfig.IsStartedFromUI)
            {
                FrmCalib9PDynamic frm = new FrmCalib9PDynamic(this, calibConfig, bind, position, SendMessageToRobot_Standard, CalculateStandardPoint);
                FrmCalib9PDynamic frm = new FrmCalib9PDynamic(this, calibConfig, bind, position, CalculateStandardPoint);
                frm.ShowDialog();
            }
            else
            {
                MultipleStepsProcess(calibConfig, bind, SendMessageToRobot_Standard);
                MultipleStepsProcess(calibConfig, bind);
                CalculateStandardPoint(calibConfig, bind, position);
            }
            //for (int i = 0; i < calibConfig.Configs.Count; i++)
            //{
            //    bind.Robot.SendMsg(Bro.Device.AuboRobot.RobotMsgAction.Calibration, Bro.Device.AuboRobot.RobotMsgParas.None, calibConfig.PositionNo, new List<float>() { i + 1 });
            //    _calibReply.CalibHandle.WaitOne();
            //    if (_calibReply.CalibPositionNo != calibConfig.PositionNo)
            //    {
            //        throw new ProcessException("标定反馈的位置信息不一致");
            //    }
            //    calibConfig.Configs[i].CurrentPlatPoint = new CustomizedPoint(_calibReply.RobotPosition.X, _calibReply.RobotPosition.Y);
            //    using (HObject hImage = CollectHImage(bind.Camera, calibConfig.Configs[i].CameraOpConfig, "RobotCalibration"))
            //    {
            //        var tool = _halconToolDict[calibConfig.Configs[i].CameraOpConfig.AlgorithemPath];
            //        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();
            //        float x = (float)tool.GetResultTuple("OUTPUT_X").D;
            //        float y = (float)tool.GetResultTuple("OUTPUT_Y").D;
            //        float angel = (float)tool.GetResultTuple("OUTPUT_Angle").D;
            //        if (x < 0 || y < 0)
            //        {
            //            throw new ProcessException("获取点位信息不正确");
            //        }
            //        calibConfig.Configs[i].ImageMarkPoint = new CustomizedPointWithAngle(x, y, angel);
            //    }
            //}
            //CustomizedPointWithAngle markPoint = calibConfig.Configs[0].ImageMarkPoint;
            //var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == position.PositionCode);
            //if (visionConfig != null)
            //{
            //    visionConfig.StandardPoint = new CustomizedPointWithAngle(markPoint.X, markPoint.Y, markPoint.Angle);
            //}
            //else
            //{
            //    Config.VisionConfigCollection.Add(new PositionVisionConfig()
            //    {
            //        CameraId = bind.CameraId,
            //        PositionCode = position.PositionCode,
            //        StandardPoint = new CustomizedPointWithAngle(markPoint.X, markPoint.Y, markPoint.Angle),
            //    });
            //}
            return new ProcessResponse(true);
        }
        public void CalculateMatrix(CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position)
        {
            //HOperatorSet.VectorToHomMat2d(new HTuple(calibConfig.Configs.Select(u => u.ImageMarkPoint.X).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.ImageMarkPoint.Y).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.CurrentPlatPoint.X).ToArray()), new HTuple(calibConfig.Configs.Select(u => u.CurrentPlatPoint.Y).ToArray()), out HTuple matrix);
            List<double> matrix = GetMovementMatrix(calibConfig.Configs.Select(u => u.ImageMarkPoint as CustomizedPoint).ToList(), calibConfig.Configs.Select(u => u.CurrentPlatPoint).ToList(), out string msg);
            List<double> matrix = GetMovementMatrix(calibConfig.Configs.Select(u => u.ImageMarkPoint as CustomizedPoint).ToList(), calibConfig.Configs.Select(u => new CustomizedPoint(u.PlatPoint.X, u.PlatPoint.Y)).ToList(), out string msg);
            var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == position.PositionCode);
            if (visionConfig != null)
@@ -235,7 +135,6 @@
                sum += Math.Sqrt((Math.Pow((m.D - platPoints[i].X), 2) + Math.Pow((n.D - platPoints[i].Y), 2)));
            }
            //sum = ((sum / (double)Config.LengthPulseRatio) * 100.0) / ((double)imagePoints.Count);
            sum = sum / (double)imagePoints.Count;
            msg = $"标定点数量:{imagePoints.Count};单点误差:{sum.ToString()}脉冲";
@@ -251,18 +150,6 @@
        public void CalculateStandardPoint(CalibrationConfigCollection calibConfig, AGVBindUnit bind, PathPosition position)
        {
            //var bind = Config.AGVBindCollection.FirstOrDefault(u => u.CameraId == calibConfig.CameraId);
            //if (bind == null)
            //{
            //    throw new ProcessException("未能获取绑定设备信息");
            //}
            //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == calibConfig.PositionNo);
            //if (position == null)
            //{
            //    throw new ProcessException("未能获取正确位置信息");
            //}
            CustomizedPointWithAngle markPoint = calibConfig.Configs[0].ImageMarkPoint;
            var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == position.PositionCode);
            if (visionConfig != null)
@@ -282,51 +169,25 @@
            PubSubCenter.Publish(PubTag.CalibAllDone.ToString(), "", $"标定完成,标准点:{markPoint.GetDisplayText()}", true);
        }
        public void MultipleStepsProcess(CalibrationConfigCollection calibConfig, AGVBindUnit bind, Action<AGVBindUnit, int, int> sendMessageToRobot)
        public void MultipleStepsProcess(CalibrationConfigCollection calibConfig, AGVBindUnit bind)
        {
            for (int i = 0; i < calibConfig.Configs.Count; i++)
            {
                SingleStepProcess(calibConfig.Configs[i], sendMessageToRobot, bind, calibConfig.PositionNo, i);
                SingleStepProcess(calibConfig.Configs[i], bind, i);
            }
        }
        public void SendMessageToRobot_Calibration(AGVBindUnit bind, int positionNo, int index)
        public void SingleStepProcess(CalibrationConfig config, AGVBindUnit bind, int index)
        {
            bind.Robot.SendMsg(Bro.Device.AuboRobot.RobotMsgAction.Calibration, Bro.Device.AuboRobot.RobotMsgParas.None, positionNo, new List<float>() { index + 1 });
            _calibReply.CalibHandle.WaitOne();
            if (_calibReply.CalibIndex != (index + 1) || _calibReply.CalibPositionNo != positionNo)
            {
                throw new ProcessException("标定反馈的索引或位置信息不一致");
            }
        }
        public void SendMessageToRobot_Standard(AGVBindUnit bind, int positionNo, int index)
        {
            bind.Robot.SendMsg(Bro.Device.AuboRobot.RobotMsgAction.StandardPoint, Bro.Device.AuboRobot.RobotMsgParas.None, positionNo);
            _calibReply.CalibHandle.WaitOne();
            if (_calibReply.CalibPositionNo != positionNo)
            {
                throw new ProcessException("标定反馈的位置信息不一致");
            }
        }
        //PubSubCenter.Subscribe(PubTag.CalibStepDone.ToString(), CalibStepDone);
        //PubSubCenter.Subscribe(PubTag.CalibAllDone.ToString(), CalibAllDone);
        public void SingleStepProcess(CalibrationConfig config, Action<AGVBindUnit, int, int> sendMessageToRobot, AGVBindUnit bind, int positionNo, int index)
        {
            sendMessageToRobot.Invoke(bind, positionNo, index);
            config.CurrentPlatPoint = new CustomizedPoint(_calibReply.RobotPosition.X, _calibReply.RobotPosition.Y);
            bind.Robot.Move(config.PlatPoint, MoveType.AbsoluteMove, true);
            using (HObject hImage = CollectHImage(bind.Camera, config.CameraOpConfig, "RobotCalibration"))
            {
                var tool = _halconToolDict[config.CameraOpConfig.AlgorithemPath];
                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.InputImageDic.Clear();
                tool.InputImageDic["INPUT_Image"] = hImage;
                tool.RunProcedure();
                float x = (float)tool.GetResultTuple("OUTPUT_X").D;
@@ -342,16 +203,5 @@
            PubSubCenter.Publish(PubTag.CalibStepDone.ToString(), index, "", true);
        }
    }
    public class CalibReplyMsg
    {
        public AutoResetEvent CalibHandle { get; set; } = new AutoResetEvent(false);
        public int CalibIndex { get; set; } = 0;
        public int CalibPositionNo { get; set; }
        public CustomizedPoint RobotPosition { get; set; } = new CustomizedPoint();
    }
}
src/A032.Process/ProcessControl_Method.cs
@@ -8,6 +8,7 @@
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.IO;
@@ -20,331 +21,7 @@
{
    public partial class ProcessControl
    {
        const int WAITTIME = 5000;
        Dictionary<int, int> machineFullTrayDict = new Dictionary<int, int>();
        Dictionary<int, int> machineEmptyTrayDict = new Dictionary<int, int>();
        List<TaskAssignInfo> taskAssignedList = new List<TaskAssignInfo>();
        #region AGV事件
        private void OnAGVBatteryLvlChanged(SeerAGVDriver agv, float preBatteryLvl, float batteryLvl)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == agv.Id);
            SeerAGVInitialConfig iConfig = agv.InitialConfig as SeerAGVInitialConfig;
            if (batteryLvl <= iConfig.BatteryLvlToCharge && preBatteryLvl > iConfig.BatteryLvlToCharge)
            {
                Task.Run(() =>
                {
                    var position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.Charge);
                    if (position == null)
                    {
                        throw new ProcessException("未找到充电地点");
                    }
                    while (bind.UnitStatus != TaskStatus.Available)
                    {
                        if (bind.SetAGVStatus(TaskStatus.Running))
                        {
                            bind.AGV.TaskOrder(position.PositionCode);
                            break;
                        }
                        Thread.Sleep(WAITTIME);
                    }
                });
                return;
            }
            if (batteryLvl >= iConfig.BatteryLvlChargeDone && preBatteryLvl < iConfig.BatteryLvlChargeDone)
            {
                var position = Config.PositionCollection.FirstOrDefault(u => u.PositionCode == agv.CurrentPosition);
                if (position != null && position.Description == PathPositionDefinition.Charge)
                {
                    bind.SetAGVStatus(TaskStatus.Available);
                }
                return;
            }
        }
        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.SetAGVStatus(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.SetAGVStatus(TaskStatus.Available);
                LogAsync(DateTime.Now, $"AGV到位{positionCode}", "");
                PathPosition position = Config.PositionCollection.FirstOrDefault(p => p.PositionCode == bind.AGVDest);
                switch (position.Description)
                {
                    case PathPositionDefinition.LoadEmptyTray:
                        LogAsync(DateTime.Now, $"AGV完成,准备上空Tray", "");
                        Robot_LoadEmptyTray(bind.Id, position);
                        break;
                    case PathPositionDefinition.LoadFullTray:
                        LogAsync(DateTime.Now, $"AGV完成,准备上满Tray", "");
                        Robot_LoadFullTraySnap(bind.Id, position);
                        break;
                    case PathPositionDefinition.UnloadEmptyTray:
                        LogAsync(DateTime.Now, $"AGV完成,准备下空Tray", "");
                        Robot_UnloadEmptyTraySnap(bind.Id, position);
                        break;
                    case PathPositionDefinition.UnloadFullTray:
                        LogAsync(DateTime.Now, $"AGV完成,准备下满Tray", "");
                        Robot_UnloadFullTraySnap(bind.Id, position);
                        break;
                    default:
                        break;
                }
            }
        }
        #endregion
        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();
                                        bind.Robot.MonitorHandle.WaitOne();
                                        //bind.Robot.IOChangedHandle.WaitOne();
                                        Thread.Sleep((bind.Robot.InitialConfig as AuboRobotInitialConfig).ScanInterval);
                                        if (!bind.IsFullTrayFull)
                                        {
                                            RobotMsg_UnloadEmptyTray.Para2 = msg.Para2;
                                            robot.SendMsg(RobotMsg_UnloadEmptyTray, true);
                                        }
                                        else
                                        {
                                            bind.RobotStatus = TaskStatus.Available;
                                        }
                                    }
                                    else
                                    {
                                        bind.RobotStatus = TaskStatus.Available;
                                    }
                                }
                                break;
                            case RobotMsgParas.FullTray:
                                {
                                    bind.CurrentFullTray = int.Parse(msg.Datas[1]);
                                    //bind.RobotIOHandle.Reset();
                                    //bind.RobotIOHandle.WaitOne();
                                    bind.Robot.MonitorHandle.WaitOne();
                                    //bind.Robot.IOChangedHandle.WaitOne();
                                    Thread.Sleep((bind.Robot.InitialConfig as AuboRobotInitialConfig).ScanInterval);
                                    if (!bind.IsFullTrayEmpty)
                                    {
                                        Camera_UnloadFullTray(robot.Id, msg.Para2);
                                    }
                                    else
                                    {
                                        bind.RobotStatus = TaskStatus.Available;
                                    }
                                }
                                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:
                    {
                        _calibReply.CalibIndex = int.Parse(msg.Datas[4]);
                        _calibReply.CalibPositionNo = msg.Para2;
                        _calibReply.RobotPosition = new CustomizedPoint(float.Parse(msg.Datas[0]), float.Parse(msg.Datas[1]));
                        _calibReply.CalibHandle.Set();
                    }
                    break;
                case RobotMsgAction.StandardPoint:
                    {
                        _calibReply.CalibPositionNo = msg.Para2;
                        _calibReply.CalibHandle.Set();
                    }
                    break;
                default:
                    break;
            }
            //if (models.Count > 0)
            //{
            //    models.ForEach(model =>
            //    {
            //        if (!bind.TaskList.Any(t => t.MethodName == model.MethodName))
            //        {
            //            model.OpConfig = new AGVBindOpConfig(bind.Id);
            //            bind.AddTask(model);
            //        }
            //    });
            //}
        }
        public void QueryRobotIO()
        {
            RobotDict.Values.ToList().ForEach(r =>
            {
                r.SendMsg(RobotMsgAction.IO, RobotMsgParas.Query, 0);
            });
        }
        //private void OnBindUnitTaskInvoke(AGVTaskModel task)
        //{
        //    InvokeMethodDict[task.MethodName].Invoke(this, new object[] { task.OpConfig, task.Device });
        //    //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.MethodName == model.MethodName))
        //            {
        //                model.OpConfig = new AGVBindOpConfig(bind.Id);
        //                bind.AddTask(model);
        //            }
        //        });
        //    }
        //}
        [ProcessMethod("", "Robot_Monitor_Alarm", "机器人监听事件-报警", true)]
        public ProcessResponse Robot_Monitor_Alarm(IOperationConfig config, IDevice device)
        {
@@ -352,11 +29,11 @@
            if (bind == null)
            {
                throw new ProcessException("未能根据机器人信息获取绑定设备信息", null);
                throw new ProcessException($"未能获取{device.Name}的绑定设备信息");
            }
            bind.AGV.PauseTask();
            bind.RobotStatus = TaskStatus.Warning;
            bind.AGV.CancelTask();
            bind.UnitState = AGVState.Warning;
            return new ProcessResponse(true);
        }
@@ -364,92 +41,58 @@
        [ProcessMethod("", "Robot_Monitor_EmptyTrayEmpty", "机器人监听事件-空Tray区域清空", true)]
        public ProcessResponse Robot_Monitor_EmptyTrayEmpty(IOperationConfig config, IDevice device)
        {
            bool isEmptyTrayEmpty = config.InputPara[0] == 0;
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
            if (isEmptyTrayEmpty)
            {
                bind.IsEmptyTrayEmpty = true;
                Task.Run(() =>
            if (bind == null)
                {
                    //Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_LoadEmptyTray;
                    while (bind.IsEmptyTrayEmpty && !bind.IsEmptyTrayTaskAssigned)
                    {
                        //if (bind.TaskList.Count == 0)
                        if (bind.UnitStatus == TaskStatus.Available)
                        {
                            //List<AGVTaskModel> models = new List<AGVTaskModel>();
                            //models.Add(new AGVTaskModel(TaskAvailableLevel.Both, "AGV_LoadEmptyTray"));
                            //models.Add(new AGVTaskModel(TaskAvailableLevel.AGV, "AfterEmptyTrayPositionArrived"));
                throw new ProcessException($"未能获取{device.Name}的绑定设备信息");
            }
                            //AddNewTaskToBind(device.Id, models);
            bind.EmptyTrayNum = 0;
                            if (AGV_LoadEmptyTray(bind.Id))
                            {
                                bind.IsEmptyTrayTaskAssigned = true;
                            }
                        }
                        else
                        {
                            Thread.Sleep(WAITTIME);
                        }
                    }
                });
            }
            else
            {
                bind.IsEmptyTrayEmpty = false;
                bind.IsEmptyTrayTaskAssigned = false;
            }
            TrayTask task = new TrayTask();
            task.TaskType = TaskType.LoadEmptyTrayToAGV;
            //task.Priority = 10;
            task.SourceDeviceId = device.Id;
            //task.SourceDeviceName = device.Name;
            //如果目前地址被占用,地址有可能为空,需要在任务指派时再次确认
            task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.LoadEmptyTray);
            InsertTask(task);
            return new ProcessResponse(true);
        }
        /// <summary>
        /// 信号要求长信号触发,避免误触发
        /// </summary>
        /// <param name="config"></param>
        /// <param name="device"></param>
        /// <returns></returns>
        [ProcessMethod("", "Robot_Monitor_FullTrayFull", "机器人监听事件-满Tray区域放满", true)]
        public ProcessResponse Robot_Monitor_FullTrayFull(IOperationConfig config, IDevice device)
        {
            //(device as AuboRobotDriver).IOChangedHandle.Reset();
            bool isFullTrayFull = config.InputPara[0] == 1;
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
            if (isFullTrayFull)
            {
                bind.IsFullTrayFull = true;
                Task.Run(() =>
            if (bind == null)
                {
                    //Func<IOperationConfig, IDevice, ProcessResponse> action = AGV_UnloadFullTray;
                    while (bind.IsFullTrayFull && !bind.IsFullTrayTaskAssigned)
                    {
                        //if (bind.TaskList.Count == 0)
                        if (bind.UnitStatus == TaskStatus.Available)
                        {
                            //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);
                            if (AGV_UnloadFullTray(bind.Id))
                            {
                                bind.IsFullTrayTaskAssigned = true;
                            }
                        }
                        else
                        {
                            Thread.Sleep(WAITTIME);
                        }
                    }
                });
            }
            else
            {
                bind.IsFullTrayFull = false;
                bind.IsFullTrayTaskAssigned = false;
                throw new ProcessException($"未能获取{device.Name}的绑定设备信息");
            }
            //(device as AuboRobotDriver).IOChangedHandle.Set();
            //if (bind.FullTrayNum >= Config.AGVAvailableTrayNums)
            {
                bind.FullTrayNum = Config.AGVAvailableTrayNums;
            //bind.RobotIOHandle.Set();
                TrayTask task = new TrayTask();
                task.TaskType = TaskType.UnloadFullTrayToLine;
                task.SourceDeviceId = device.Id;
                //如果目前地址被占用,地址有可能为空,需要在任务指派时再次确认
                task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.UnloadFullTray);
                InsertTask(task);
            }
            return new ProcessResponse(true);
        }
@@ -457,13 +100,14 @@
        [ProcessMethod("", "Robot_Monitor_FullTrayEmpty", "机器人监听事件-满Tray区域清空", true)]
        public ProcessResponse Robot_Monitor_FullTrayEmpty(IOperationConfig config, IDevice device)
        {
            //(device as AuboRobotDriver).IOChangedHandle.Reset();
            bool isFullTrayEmpty = config.InputPara[0] == 0;
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == device.Id);
            bind.IsFullTrayEmpty = isFullTrayEmpty;
            //(device as AuboRobotDriver).IOChangedHandle.Set();
            //bind.RobotIOHandle.Set();
            if (bind == null)
            {
                throw new ProcessException($"未能获取{device.Name}的绑定设备信息");
            }
            bind.FullTrayNum = 0;
            return new ProcessResponse(true);
        }
@@ -499,27 +143,103 @@
            if (bind == null)
            {
                throw new ProcessException("未能根据机器人信息获取绑定设备信息", null);
                throw new ProcessException($"未能获取{device.Name}的绑定设备信息");
            }
            bind.AGV.CancelTask();
            //isEmptyTrayTaskAssigned = false;
            //isFullTrayTaskAssigned = false;
            taskAssignedList.RemoveAll(u => u.AgvId == device.Id);
            //bind.ClearTask();
            bind.RobotStatus = bind.AGVStatus = TaskStatus.Available;
            Reset(bind.Id);
            return new ProcessResponse(true);
        }
        #endregion
        #region 空Tray上料
        //[ProcessMethod("", "AGV_LoadEmptyTray", "AGV去往空Tray上料", true)]
        //public ProcessResponse AGV_LoadEmptyTray(IOperationConfig config, IDevice device)
        #region PLC监听事件
        [ProcessMethod("", "PLC_NoticeEmptyTray", "PLC通知需要上空Tray", true)]
        public ProcessResponse PLC_NoticeEmptyTray(IOperationConfig config, IDevice device)
        {
            TrayTask task = new TrayTask();
            task.TaskType = TaskType.UnloadEmptyTrayToMachine;
            task.SourceDeviceId = device.Id;
            //task.SourceDeviceName = device.Name;
            task.Location = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadEmptyTray && u.DeviceOwner == device.Id);
            InsertTask(task);
            return new ProcessResponse(true);
        }
        [ProcessMethod("", "PLC_NoticeFullTray", "PLC通知满Tray需要取走", true)]
        public ProcessResponse PLC_NoticeFullTray(IOperationConfig config, IDevice device)
        {
            TrayTask task = new TrayTask();
            task.TaskType = TaskType.LoadFullTrayFromMachine;
            task.SourceDeviceId = device.Id;
            //task.SourceDeviceName = device.Name;
            task.Location = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray && u.DeviceOwner == device.Id);
            InsertTask(task);
            return new ProcessResponse(true);
        }
        #endregion
        [ProcessMethod("OperationTest", "OperationDemo", "单步测试方法", true)]
        public ProcessResponse OperationDemo(IOperationConfig config, IDevice device)
        {
            string s = (-1).ToString("D2");
            string a = (1).ToString("D2");
            OperationTestConfig opConfig = config as OperationTestConfig;
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.AGVId == opConfig.AGVId);
            if (bind == null)
                throw new ProcessException("未能获取指定AGV信息或AGV绑定的设备信息");
            switch (opConfig.TaskInfo.TaskType)
            {
                case TaskType.LoadEmptyTrayToAGV:
                    LoadEmptyTrayToAGV(opConfig.TaskInfo, bind);
                    break;
                case TaskType.LoadFullTrayFromMachine:
                    LoadFullTrayFromMachine(opConfig.TaskInfo, bind);
                    break;
                case TaskType.UnloadEmptyTrayToMachine:
                    UnloadEmptyTrayToMachine(opConfig.TaskInfo, bind);
                    break;
                case TaskType.UnloadFullTrayToLine:
                    UnloadFullTrayToLine(opConfig.TaskInfo, bind);
                    break;
                default:
                    throw new ProcessException($"未能指定{opConfig.TaskInfo.TaskType.ToString()}的对应方法");
            }
            return new ProcessResponse(true);
        }
        #region old
        //#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);
        ////}
        //public bool AGV_LoadEmptyTray(string bindId)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
        //    if (position == null)
@@ -527,42 +247,45 @@
        //        throw new ProcessException("路径配置未设置空Tray上料点");
        //    }
        //    if (bind.SetAGVStatus(TaskStatus.Running))
        //    {
        //    bind.AGVDest = position.PositionCode;
        //    bind.AGV.TaskOrder(position.PositionCode);
        //    bind.AGVStatus = TaskStatus.Running;
        //    return new ProcessResponse(true);
        //        return true;
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //}
        public bool AGV_LoadEmptyTray(string bindId)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
        ////[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上料点");
            }
        ////    if (position == null)
        ////    {
        ////        throw new ProcessException("路径配置未设置空Tray上料点", null);
        ////    }
            if (bind.SetAGVStatus(TaskStatus.Running))
            {
                bind.AGVDest = position.PositionCode;
                bind.AGV.TaskOrder(position.PositionCode);
        ////    if (bind.AGV.CurrentPosition != position.PositionCode)
        ////    {
        ////        throw new ProcessException("AGV尚未到达空Tray上料点", null);
        ////    }
                return true;
            }
            else
            {
                return false;
            }
        }
        ////    bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.EmptyTray, 0);
        ////    bind.RobotStatus = TaskStatus.Running;
        //[ProcessMethod("", "AfterEmptyTrayPositionArrived", "到达空Tray上料点", true)]
        //public ProcessResponse AfterEmptyTrayPositionArrived(IOperationConfig config, IDevice device)
        ////    return new ProcessResponse(true);
        ////}
        //public void Robot_LoadEmptyTray(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
        //    if (position == null)
        //    {
@@ -576,160 +299,166 @@
        //    bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.EmptyTray, 0);
        //    bind.RobotStatus = TaskStatus.Running;
        //    return new ProcessResponse(true);
        //}
        public void Robot_LoadEmptyTray(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadEmptyTray);
        ////[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;
            if (position == null)
            {
                throw new ProcessException("路径配置未设置空Tray上料点", null);
            }
        ////    return new ProcessResponse(true);
        ////}
        //#endregion
            if (bind.AGV.CurrentPosition != position.PositionCode)
            {
                throw new ProcessException("AGV尚未到达空Tray上料点", null);
            }
        //#region 空Tray往机台下料
        ////bool isEmptyTrayNeed = false;
        ////bool isEmptyTrayTaskAssigned = false;
        //RobotMsg RobotMsg_UnloadEmptyTray = new RobotMsg();
            bind.Robot.SendMsg(RobotMsgAction.Load, RobotMsgParas.EmptyTray, 0);
            bind.RobotStatus = TaskStatus.Running;
        }
        //[ProcessMethod("", "EmptyTrayReady", "空Tray上料完成", true)]
        //public ProcessResponse EmptyTrayReady(IOperationConfig config, IDevice device)
        //private async void CheckUnloadEmptyTrayTask(int positionNo)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    bind.RobotStatus = bind.AGVStatus = TaskStatus.Available;
        //    await Task.Run(() =>
        //    {
        //        var taskStatus = taskAssignedList.FirstOrDefault(u => u.PositionNo == positionNo);
        //    return new ProcessResponse(true);
        //        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.MethodName == "AGV_UnloadEmptyTray")))
        //            {
        //                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, "AGV_UnloadEmptyTray", new AGVBindOpConfig(bind.Id, position));
        //                    //AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, "Robot_UnloadEmptyTray", new AGVBindOpConfig(bind.Id));
        //                    //bind.AddTask(model_AGV);
        //                    //bind.AddTask(model_Robot);
        //                    if (AGV_UnloadEmptyTray(bind.Id, position))
        //                    {
        //                        taskStatus.IsTaskAssgined = true;
        //                        taskStatus.AgvId = bind.AGVId;
        //}
        #endregion
        //                }
        //            }
        #region 空Tray往机台下料
        //bool isEmptyTrayNeed = false;
        //bool isEmptyTrayTaskAssigned = false;
        RobotMsg RobotMsg_UnloadEmptyTray = new RobotMsg();
        //            Thread.Sleep(WAITTIME);
        //        }
        //    });
        //}
        [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,
                    });
                }
                CheckUnloadEmptyTrayTask(position.PositionNo);
            }
            return new ProcessResponse(true);
        }
        private async void CheckUnloadEmptyTrayTask(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.MethodName == "AGV_UnloadEmptyTray")))
                    {
                        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, "AGV_UnloadEmptyTray", new AGVBindOpConfig(bind.Id, position));
                            //AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, "Robot_UnloadEmptyTray", new AGVBindOpConfig(bind.Id));
                            //bind.AddTask(model_AGV);
                            //bind.AddTask(model_Robot);
                            if (AGV_UnloadEmptyTray(bind.Id, position))
                            {
                                taskStatus.IsTaskAssgined = true;
                                taskStatus.AgvId = bind.AGVId;
                            }
                        }
                    }
                    Thread.Sleep(WAITTIME);
                }
            });
        }
        //[ProcessMethod("", "AGV_UnloadEmptyTray", "AGV去往卸载空Tray料位置", true)]
        public bool AGV_UnloadEmptyTray(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            if (position == null)
            {
                throw new ProcessException("路径配置未设置空Tray下料点");
            }
            if (bind.SetAGVStatus(TaskStatus.Running))
            {
                bind.AGVDest = position.PositionCode;
                bind.AGV.TaskOrder(position.PositionCode);
                return true;
            }
            else
            {
                return false;
            }
        }
        //[ProcessMethod("", "Robot_UnloadEmptyTray", "机器人运动至空Tray拍照位置", true)]
        public void Robot_UnloadEmptyTraySnap(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == 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);
        }
        //[ProcessMethod("", "Camera_UnloadEmptyTray", "相机确认空Tray卸载机器人位置调整", true)]
        //public ProcessResponse Camera_UnloadEmptyTray(IOperationConfig config, IDevice device)
        ////[ProcessMethod("", "AGV_UnloadEmptyTray", "AGV去往卸载空Tray料位置", true)]
        //public bool AGV_UnloadEmptyTray(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadEmptyTray);
        //    if (position == null)
        //    {
        //        throw new ProcessException("路径配置未设置空Tray下料点");
        //    }
        //    if (bind.SetAGVStatus(TaskStatus.Running))
        //    {
        //        bind.AGVDest = position.PositionCode;
        //        bind.AGV.TaskOrder(position.PositionCode);
        //        return true;
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //}
        ////[ProcessMethod("", "Robot_UnloadEmptyTray", "机器人运动至空Tray拍照位置", true)]
        //public void Robot_UnloadEmptyTraySnap(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == 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);
        //}
        ////[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)
        //    {
@@ -741,6 +470,12 @@
        //        throw new ProcessException("AGV当前未处于空Tray下料点");
        //    }
        //    float adjust_X = 0.0f;
        //    float adjust_Y = 0.0f;
        //    float adjust_Angle = 0.0f;
        //    if (Config.IsEnableVisionGuide)
        //    {
        //    PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
        //    if (visionConfig == null)
@@ -779,193 +514,118 @@
        //    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 });
        //        adjust_X = (float)dx_Robot.D;
        //        adjust_Y = (float)dy_Robot.D;
        //        adjust_Angle = visionConfig.StandardPoint.Angle - angle;
        //    }
        //    //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>() { adjust_X, adjust_Y, 0, adjust_Angle }.ConvertAll(s => s.ToString()).ToList();
        //    bind.Robot.SendMsg(RobotMsg_UnloadEmptyTray, true);
        //    return new ProcessResponse(true);
        //}
        //#endregion
        public ProcessResponse Camera_UnloadEmptyTray(string robotId, int positionNum)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
        //#region 从机台上满Tray
        ////bool isFullTrayNeed = false;
        ////bool isFullTrayTaskAssigned = false;
        //RobotMsg RobotMsg_LoadFullTray = new RobotMsg();
            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下料点");
            }
            float adjust_X = 0.0f;
            float adjust_Y = 0.0f;
            float adjust_Angle = 0.0f;
            if (Config.IsEnableVisionGuide)
            {
                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);
                adjust_X = (float)dx_Robot.D;
                adjust_Y = (float)dy_Robot.D;
                adjust_Angle = visionConfig.StandardPoint.Angle - angle;
            }
            //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>() { adjust_X, adjust_Y, 0, adjust_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, "AGV_LoadFullTray", new AGVBindOpConfig(bind.Id, position));
                            //AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, "Robot_LoadFullTray", new AGVBindOpConfig(bind.Id));
                            //bind.AddTask(model_AGV);
                            //bind.AddTask(model_Robot);
                            if (AGV_LoadFullTray(bind.Id, position))
                            {
                                taskStatus.IsTaskAssgined = true;
                                taskStatus.AgvId = bind.AGVId;
                            }
                        }
                    }
                    Thread.Sleep(300);
                }
            });
        }
        //[ProcessMethod("", "AGV_LoadFullTray", "AGV去往满Tray上料位置", true)]
        public bool AGV_LoadFullTray(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            //PathPosition position = (config as AGVBindOpConfig).Position;
            if (position == null)
            {
                throw new ProcessException("路径配置未设置满Tray上料点");
            }
            if (bind.SetAGVStatus(TaskStatus.Running))
            {
                bind.AGVDest = position.PositionCode;
                bind.AGV.TaskOrder(position.PositionCode);
                return true;
            }
            else
            {
                return false;
            }
            //return new ProcessResponse(true);
        }
        //[ProcessMethod("", "Robot_LoadFullTray", "机器人运动至满Tray拍照位置", true)]
        //public ProcessResponse Robot_LoadFullTray(IOperationConfig config, IDevice device)
        //private async void CheckFullTrayTask(int positionNo)
        //{
        //    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);
        //    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, "AGV_LoadFullTray", new AGVBindOpConfig(bind.Id, position));
        //                    //AGVTaskModel model_Robot = new AGVTaskModel(TaskAvailableLevel.AGV, "Robot_LoadFullTray", new AGVBindOpConfig(bind.Id));
        //                    //bind.AddTask(model_AGV);
        //                    //bind.AddTask(model_Robot);
        //                    if (AGV_LoadFullTray(bind.Id, position))
        //                    {
        //                        taskStatus.IsTaskAssgined = true;
        //                        taskStatus.AgvId = bind.AGVId;
        //                    }
        //                }
        //            }
        //            Thread.Sleep(300);
        //        }
        //    });
        //}
        ////[ProcessMethod("", "AGV_LoadFullTray", "AGV去往满Tray上料位置", true)]
        //public bool AGV_LoadFullTray(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    //PathPosition position = (config as AGVBindOpConfig).Position;
        //    if (position == null)
        //    {
        //        throw new ProcessException("路径配置未设置满Tray上料点");
        //    }
        //    if (bind.SetAGVStatus(TaskStatus.Running))
        //    {
        //        bind.AGVDest = position.PositionCode;
        //        bind.AGV.TaskOrder(position.PositionCode);
        //        return true;
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //    //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);
        ////}
        //public void Robot_LoadFullTraySnap(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray && u.PositionCode == bind.AGV.CurrentPosition);
        //    if (position == null)
        //    {
@@ -979,35 +639,73 @@
        //    bind.RobotStatus = TaskStatus.Running;
        //    bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LoadFullTraySnap, position.PositionNo);
        //    return new ProcessResponse(true);
        //}
        public void Robot_LoadFullTraySnap(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray && u.PositionCode == bind.AGV.CurrentPosition);
        ////[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);
            if (position == null)
            {
                throw new ProcessException("路径配置未设置满Tray上料点");
            }
        ////    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.LoadFullTray);
            //if (bind.AGV.CurrentPosition != position.PositionCode)
        ////    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)
            //{
            //    throw new ProcessException("AGV当前未处于满Tray上料点");
            //}
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
            bind.RobotStatus = TaskStatus.Running;
            bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LoadFullTraySnap, position.PositionNo);
        }
        //[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);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNum);
        //    if (position == null)
        //    {
@@ -1019,6 +717,12 @@
        //        throw new ProcessException("AGV当前未处于满Tray上料点");
        //    }
        //    float adjust_X = 0.0f;
        //    float adjust_Y = 0.0f;
        //    float adjust_Angle = 0.0f;
        //    if (Config.IsEnableVisionGuide)
        //    {
        //    PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
        //    if (visionConfig == null)
@@ -1057,92 +761,45 @@
        //    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 });
        //        adjust_X = (float)dx_Robot.D;
        //        adjust_Y = (float)dy_Robot.D;
        //        adjust_Angle = visionConfig.StandardPoint.Angle - angle;
        //    }
        //    //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>() { adjust_X, adjust_Y, 0, adjust_Angle }.ConvertAll(s => s.ToString()).ToList();
        //    bind.Robot.SendMsg(RobotMsg_LoadFullTray, true);
        //    return new ProcessResponse(true);
        //}
        //#endregion
        public ProcessResponse Camera_LoadFullTray(string robotId, int positionNum)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.RobotId == robotId);
        //#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);
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.PositionNo == positionNum);
        ////    if (position == null)
        ////    {
        ////        throw new ProcessException("路径配置未设置满Tray下料点");
        ////    }
            if (position == null)
            {
                throw new ProcessException("路径配置未设置满Tray上料点");
            }
        ////    bind.AGVDest = position.PositionCode;
        ////    bind.AGV.TaskOrder(position.PositionCode);
            if (bind.AGV.CurrentPosition != position.PositionCode)
            {
                throw new ProcessException("AGV当前未处于满Tray上料点");
            }
        ////    bind.AGVStatus = TaskStatus.Running;
            float adjust_X = 0.0f;
            float adjust_Y = 0.0f;
            float adjust_Angle = 0.0f;
        ////    return new ProcessResponse(true);
        ////}
            if (Config.IsEnableVisionGuide)
            {
                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);
                adjust_X = (float)dx_Robot.D;
                adjust_Y = (float)dy_Robot.D;
                adjust_Angle = visionConfig.StandardPoint.Angle - angle;
            }
            //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>() { adjust_X, adjust_Y, 0, adjust_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)
        //public bool AGV_UnloadFullTray(string bindId)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
        //    if (position == null)
@@ -1150,42 +807,45 @@
        //        throw new ProcessException("路径配置未设置满Tray下料点");
        //    }
        //    if (bind.SetAGVStatus(TaskStatus.Running))
        //    {
        //    bind.AGVDest = position.PositionCode;
        //    bind.AGV.TaskOrder(position.PositionCode);
        //    bind.AGVStatus = TaskStatus.Running;
        //    return new ProcessResponse(true);
        //        return true;
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //}
        public bool AGV_UnloadFullTray(string bindId)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
        ////[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 (position == null)
        ////    {
        ////        throw new ProcessException("路径配置未设置满Tray下料点");
        ////    }
            if (bind.SetAGVStatus(TaskStatus.Running))
            {
                bind.AGVDest = position.PositionCode;
                bind.AGV.TaskOrder(position.PositionCode);
        ////    if (bind.AGV.CurrentPosition != position.PositionCode)
        ////    {
        ////        throw new ProcessException("AGV当前未处于满Tray下料点");
        ////    }
                return true;
            }
            else
            {
                return false;
            }
        }
        ////    bind.RobotStatus = TaskStatus.Running;
        ////    bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LineSnap, position.PositionNo);
        //[ProcessMethod("", "Robot_UnloadFullTray", "机器人卸载满Tray", true)]
        //public ProcessResponse Robot_UnloadFullTray(IOperationConfig config, IDevice device)
        ////    return new ProcessResponse(true);
        ////}
        //public void Robot_UnloadFullTraySnap(string bindId, PathPosition position)
        //{
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == (config as AGVBindOpConfig).BindId);
        //    PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
        //    var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
        //    //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
        //    if (position == null)
        //    {
@@ -1199,148 +859,97 @@
        //    bind.RobotStatus = TaskStatus.Running;
        //    bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.LineSnap, position.PositionNo);
        //    return new ProcessResponse(true);
        //    //LogAsync(DateTime.Now, "Robot运动至下满Tray拍照", "");
        //}
        public void Robot_UnloadFullTraySnap(string bindId, PathPosition position)
        {
            var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
            //PathPosition position = Config.PositionCollection.FirstOrDefault(u => u.Description == PathPositionDefinition.UnloadFullTray);
        ////[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 (position == null)
        //    {
        //        throw new ProcessException("路径配置未设置满Tray下料点");
        //    }
            if (bind.AGV.CurrentPosition != position.PositionCode)
            {
                throw new ProcessException("AGV当前未处于满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);
            //LogAsync(DateTime.Now, "Robot运动至下满Tray拍照", "");
        }
        //    //float adjust_X = 0.0f;
        //    //float adjust_Y = 0.0f;
        //    //float adjust_Angle = 0.0f;
        //    bool isLineReady = false;
        //[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 (Config.IsEnableVisionGuide)
        //    {
        //        PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
            if (position == null)
            {
                throw new ProcessException("路径配置未设置满Tray下料点");
            }
            if (bind.AGV.CurrentPosition != position.PositionCode)
            {
                throw new ProcessException("AGV当前未处于满Tray下料点");
            }
            //float adjust_X = 0.0f;
            //float adjust_Y = 0.0f;
            //float adjust_Angle = 0.0f;
            bool isLineReady = false;
            if (Config.IsEnableVisionGuide)
            {
                PositionVisionConfig visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.PositionCode == position.PositionCode && u.CameraId == bind.CameraId);
                if (visionConfig == null)
                {
                    throw new ProcessException("未配置该相机的满Tray下料点的视觉操作配置");
                }
        //        if (visionConfig == null)
        //        {
        //            throw new ProcessException("未配置该相机的满Tray下料点的视觉操作配置");
        //        }
                int reTryTime = Config.LineBusyRetryTimes;
        //        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的视觉算法路径");
                        }
        //        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();
        //                _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);
        //                isLineReady = _halconToolDict[toolPath].GetResultTuple("OUTPUT_Result").I == 1;
        //            }
                //if (!isLineReady)
                //{
                //    bind.Robot.SendMsg(RobotMsgType.Send, -1, true, RobotMsgAction.State, RobotMsgParas.LineSnap, new List<string>() { "-1" });
                //    throw new ProcessException("产线忙,等待超时");
        //                Thread.Sleep(Config.LineBusyWaitInterval * 1000);
        //                reTryTime--;
                //}
                //else
                //{
                //    bind.Robot.SendMsg(RobotMsgType.Send, -1, true, RobotMsgAction.State, RobotMsgParas.LineSnap, new List<string>() { "1" });
        //                reTryTime = 0;
                //}
            }
            else
            {
                isLineReady = true;
            }
        //        } while (reTryTime > 0);
            if (isLineReady)
            {
                bind.Robot.SendMsg(RobotMsgAction.Unload, RobotMsgParas.FullTray, position.PositionNo);
            }
            else
            {
                bind.Robot.SendMsg(RobotMsgAction.Move, RobotMsgParas.Home, position.PositionNo);
            }
        //        //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" });
        //        //}
        //    }
        //    else
        //    {
        //        isLineReady = true;
        //    }
            return new ProcessResponse(true);
        }
        //    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
        #endregion
    }
    //[Device("AGVBind", "AGVBind", EnumHelper.DeviceAttributeType.OperationConfig)]
    public class AGVBindOpConfig : OperationConfigBase
    {
        [Category("设备信息")]
        [Description("操作相关的设备编号")]
        public string BindId { get; set; }
        [Category("位置信息")]
        [Description("操作相关的位置")]
        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/A032.Process/ProcessControl_Robot.cs
New file
@@ -0,0 +1,24 @@
using Bro.Device.AuboRobot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace A032.Process
{
    public partial class ProcessControl
    {
        private void OnRobotMsgReceived(DateTime dt, AuboRobotDriver robot, RobotMsg msg)
        {
        }
        private void SwitchLight(AuboRobotDriver robot, bool isOn)
        {
            if (Config.LightOutputIndex > 0)
            {
                robot.SetIO(Config.LightOutputIndex, isOn);
            }
        }
    }
}
src/A032.Process/ProcessControl_Task.cs
New file
@@ -0,0 +1,759 @@
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 HalconDotNet;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace A032.Process
{
    public class TrayTask : IComplexDisplay
    {
        [Browsable(false)]
        public string TaskId { get; set; } = Guid.NewGuid().ToString();
        /// <summary>
        /// 优先级越高,越快执行
        /// </summary>
        [Category("任务配置")]
        [Description("优先级。优先级越高,越快执行")]
        public int Priority { get; set; }
        private string locationCode = "";
        [Category("任务配置")]
        [Description("任务执行地址代码")]
        [TypeConverter(typeof(PositionCodeConverter))]
        public string LocationCode
        {
            get => locationCode;
            set
            {
                if (locationCode != value)
                {
                    locationCode = value;
                    using (var scope = GlobalVar.Container.BeginLifetimeScope())
                    {
                        ProcessConfig config = scope.Resolve<ProcessConfig>();
                        Location = config.PositionCollection.FirstOrDefault(u => u.PositionCode == locationCode);
                    }
                }
            }
        }
        [Browsable(false)]
        public PathPosition Location { get; set; } = new PathPosition();
        private TaskType taskType = TaskType.LoadEmptyTrayToAGV;
        [Category("任务配置")]
        [Description("任务类型")]
        public TaskType TaskType
        {
            get => taskType;
            set
            {
                taskType = value;
                var attr = taskType.GetEnumAttribute<PriorityAttribute>();
                if (attr != null)
                {
                    Priority = attr.Priority;
                }
            }
        }
        private string sourceDeviceId = "";
        [Category("任务配置")]
        [Description("任务来源设备")]
        [TypeConverter(typeof(AllDeviceIdConverter))]
        public string SourceDeviceId
        {
            get => sourceDeviceId;
            set
            {
                if (sourceDeviceId != value)
                {
                    sourceDeviceId = value;
                    using (var scope = GlobalVar.Container.BeginLifetimeScope())
                    {
                        List<IDevice> deviceList = scope.Resolve<List<IDevice>>();
                        var device = deviceList.FirstOrDefault(u => u.Id == value);
                        SourceDeviceName = device.Name;
                    }
                }
            }
        }
        [Browsable(false)]
        [JsonIgnore]
        public string SourceDeviceName { get; set; } = "";
        [Browsable(false)]
        [JsonIgnore]
        public string BindId { get; set; } = "";
        [Browsable(false)]
        [JsonIgnore]
        public int WaitShift { get; set; } = 0;
        public string GetDisplayText()
        {
            return $"{SourceDeviceName}发起的{TaskType.ToString()}任务";
        }
    }
    public enum TaskType
    {
        [Priority(30)]
        UnloadEmptyTrayToMachine,
        [Priority(20)]
        LoadEmptyTrayToAGV,
        [Priority(30)]
        LoadFullTrayFromMachine,
        [Priority(20)]
        UnloadFullTrayToLine,
        [Priority(50)]
        Charge,
        [Priority(10)]
        IdleCharge,
    }
    public class PriorityAttribute : Attribute
    {
        public int Priority { get; set; }
        public PriorityAttribute(int priority)
        {
            Priority = priority;
        }
    }
    public partial class ProcessControl
    {
        private bool isEnableExecuteTask = true;
        public bool IsEnableExecuteTask
        {
            get => isEnableExecuteTask;
            set
            {
                isEnableExecuteTask = value;
                LogAsync(DateTime.Now, $"Set IsEnableExecuteTask {value.ToString()}", $"{(value ? "打开" : "关闭")}任务执行");
            }
        }
        List<AGVState> _disableStates = new List<AGVState>() { AGVState.InCharge, AGVState.Warning, AGVState.Unknown };
        private void Reset(string bindId = "")
        {
            WarningRemains.Clear();
            IsEnableExecuteTask = true;
            if (string.IsNullOrWhiteSpace(bindId))
            {
                Config.AGVBindCollection.ForEach(bind =>
                {
                    if (bind.UnitState == AGVState.Warning)
                    {
                        bind.WarningMsg = "";
                        bind.UnitState = AGVState.Idle;
                    }
                });
                LogAsync(DateTime.Now, "Reset", "执行全体复位操作");
            }
            else
            {
                var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId);
                if (bind != null && bind.UnitState == AGVState.Warning)
                {
                    bind.WarningMsg = "";
                    bind.UnitState = AGVState.Idle;
                    LogAsync(DateTime.Now, "Reset", $"执行{bind.AGV.Name}复位操作");
                }
            }
        }
        static object _taskLock = new object();
        NoticedList<TrayTask> TrayTaskCollection = new NoticedList<TrayTask>();
        private void InsertTask(TrayTask task)
        {
            lock (_taskLock)
            {
                if (TrayTaskCollection.Any(u => u.TaskType == task.TaskType && u.SourceDeviceId == task.SourceDeviceId))
                {
                    LogAsync(DateTime.Now, "重复任务", $"{task.SourceDeviceName}发起的{task.TaskType.ToString()}任务已存在任务列表中");
                    return;
                }
                int insertIndex = TrayTaskCollection.Count;
                var nextTask = TrayTaskCollection.FirstOrDefault(u => u.Priority < task.Priority && u.WaitShift == 0);
                if (nextTask != null)
                {
                    insertIndex = TrayTaskCollection.IndexOf(nextTask);
                }
                TrayTaskCollection.Insert(insertIndex, task);
            }
        }
        #region 任务触发
        private async void OnTaskListChanged(NotifyCollectionChangedAction action, List<TrayTask> task)
        {
            await Task.Run(() =>
            {
                if (action == NotifyCollectionChangedAction.Add)
                {
                    ExecuteTask();
                }
            });
        }
        private async void OnUnitStateChanged(string unitId, AGVState preState, AGVState currentState)
        {
            await Task.Run(() =>
            {
                if (currentState == AGVState.Idle || currentState == AGVState.IdleCharge)
                {
                    ExecuteTask(unitId);
                }
            });
        }
        private void ExecuteTask(string unitId = "")
        {
            TrayTask task = null;
            lock (_taskLock)
            {
                if (TrayTaskCollection.Count <= 0)
                {
                    return;
                }
                task = TrayTaskCollection.FirstOrDefault(u => string.IsNullOrWhiteSpace(u.BindId));
            }
            //任务是否已在执行中
            if (Config.AGVBindCollection.Any(u => u.CurrentTaskId == task.TaskId))
            {
                LogAsync(DateTime.Now, "任务执行中", $"{task.GetDisplayText()}正在执行中");
                return;
            }
            AGVBindUnit bind = null;
            if (string.IsNullOrWhiteSpace(unitId))
            {
                var available = Config.AGVBindCollection.Where(u => u.UnitState == AGVState.Idle || u.UnitState == AGVState.IdleCharge);
                switch (task.TaskType)
                {
                    case TaskType.LoadFullTrayFromMachine:
                        available = available.Where(u => u.FullTrayNum < Config.AGVAvailableTrayNums);
                        break;
                    case TaskType.UnloadEmptyTrayToMachine:
                        available = available.Where(u => u.EmptyTrayNum > 0);
                        break;
                    default:
                        break;
                }
                bind = available.FirstOrDefault();
            }
            else
            {
                bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == unitId);
            }
            if (bind == null)
            {
                LogAsync(DateTime.Now, $"暂时没有可执行任务的单元", "");
                ReArrangeTask(task);
                return;
            }
            if (task.Location == null)
            {
                switch (task.TaskType)
                {
                    case TaskType.LoadEmptyTrayToAGV:
                        task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.LoadEmptyTray);
                        break;
                    case TaskType.LoadFullTrayFromMachine:
                        task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.LoadFullTray && u.DeviceOwner == task.SourceDeviceId);
                        break;
                    case TaskType.UnloadEmptyTrayToMachine:
                        task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.UnloadEmptyTray && u.DeviceOwner == task.SourceDeviceId);
                        break;
                    case TaskType.UnloadFullTrayToLine:
                        task.Location = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.UnloadFullTray && u.DeviceOwner == task.SourceDeviceId);
                        break;
                    default:
                        break;
                }
            }
            if (task.Location == null)
            {
                ReArrangeTask(task);
                return;
            }
            else
            {
                task.Location.IsOccupied = true;
            }
            task.BindId = bind.Id;
            if (!string.IsNullOrWhiteSpace(bind.CurrentTaskId))
            {
                bind.AGV.CancelTask();
            }
            LogAsync(DateTime.Now, $"开始执行任务", task.GetDisplayText());
            bind.IsTaskCancelled = false;
            bind.IsTaskCancelling = false;
            bind.UnitState = AGVState.Running;
            bind.CurrentTaskId = task.TaskId;
            try
            {
                switch (task.TaskType)
                {
                    case TaskType.LoadEmptyTrayToAGV:
                        LoadEmptyTrayToAGV(task, bind);
                        break;
                    case TaskType.LoadFullTrayFromMachine:
                        LoadFullTrayFromMachine(task, bind);
                        break;
                    case TaskType.UnloadEmptyTrayToMachine:
                        UnloadEmptyTrayToMachine(task, bind);
                        break;
                    case TaskType.UnloadFullTrayToLine:
                        UnloadFullTrayToLine(task, bind);
                        break;
                    default:
                        break;
                }
                ////任务完成后检查电量
                //bind.AGV.BatteryHandle.Reset();
                //bool isNotTimeout = bind.AGV.BatteryHandle.WaitOne((bind.AGV.InitialConfig as SeerAGVInitialConfig).ScanInterval * 10);
                //if (!isNotTimeout)
                //{
                //    throw new ProcessException($"{bind.AGV.Name}获取电池状态超时");
                //}
            }
            catch (TaskCanceledException ex)
            {
                try
                {
                    CancelTask(bind);
                }
                catch (Exception)
                {
                    ExceptionHandle(task, bind, ex, $"任务{task.TaskId}取消失败,");
                    return;
                }
                finally
                {
                    bind.IsTaskCancelled = false;
                    bind.IsTaskCancelling = false;
                }
            }
            catch (Exception ex)
            {
                ExceptionHandle(task, bind, ex);
                return;
            }
            finally
            {
                //lock (_taskListLock)
                //{
                //    _taskList.RemoveAt(0);
                //}
            }
            LogAsync(DateTime.Now, $"任务{task.TaskId}流程结束", "");
            AfterTaskHandle(task, bind);
        }
        private void ReArrangeTask(TrayTask task)
        {
            task.WaitShift++;
            TrayTaskCollection.Remove(task);
            if (task.WaitShift > Config.DefaultWaitShift)
            {
                LogAsync(DateTime.Now, "任务无法执行", $"{task.GetDisplayText()}等待{task.WaitShift}次无法执行,已移出任务队列");
            }
            else
            {
                InsertTask(task);
            }
        }
        //AutoResetEvent _taskDoneHandle = new AutoResetEvent(true);
        Dictionary<string, AutoResetEvent> _bindTaskDoneHandleDict = new Dictionary<string, AutoResetEvent>();
        private void AfterTaskHandle(TrayTask task, AGVBindUnit bind, string warningMsg = "", bool isWarningRaised = false)
        {
            lock (_taskLock)
            {
                task.Location.IsOccupied = false;
                TrayTaskCollection.Remove(task);
            }
            bind.CurrentTaskId = "";
            if (isWarningRaised)
            {
                bind.WarningMsg = warningMsg;
                bind.UnitState = AGVState.Warning;
            }
            else
            {
                //任务完成后检查电量
                bind.AGV.BatteryHandle.Reset();
                bool isNotTimeout = bind.AGV.BatteryHandle.WaitOne((bind.AGV.InitialConfig as SeerAGVInitialConfig).ScanInterval * 10);
                if (!isNotTimeout)
                {
                    bind.WarningMsg = $"{bind.AGV.Name}获取电池状态超时";
                    new ProcessException(bind.WarningMsg);
                    bind.UnitState = AGVState.Warning;
                }
                else
                {
                    SeerAGVInitialConfig iConfig = bind.AGV.InitialConfig as SeerAGVInitialConfig;
                    if (bind.AGV.BatteryLvl <= iConfig.BatteryLvlToCharge)
                    {
                        bind.UnitState = AGVState.InCharge;
                        var chargePosition = Config.PositionCollection.FirstOrDefault(u => !u.IsOccupied && u.Description == PathPositionDefinition.Charge);
                        if (chargePosition == null)
                        {
                            bind.WarningMsg = $"{bind.AGV.Name}目前无可用充电地址";
                            new ProcessException(bind.WarningMsg);
                            bind.UnitState = AGVState.Warning;
                        }
                        else
                        {
                            bind.AGV.TaskOrder(chargePosition.PositionCode, true);
                        }
                    }
                    else
                    {
                        bind.UnitState = AGVState.Idle;
                    }
                }
            }
            _bindTaskDoneHandleDict[bind.Id].Set();
        }
        /// <summary>
        /// 取消当前任务,如果已取卷宗,归还到原处
        /// </summary>
        /// <param name="bind"></param>
        private void CancelTask(AGVBindUnit bind)
        {
            bind.IsTaskCancelling = true;
            bind.AGV.CancelTask();
            bind.Robot.Move(new RobotPoint(), MoveType.Origin, true);
        }
        /// <summary>
        /// 过程异常处理,反馈异常消息到RabbitMQ
        /// </summary>
        /// <param name="task"></param>
        /// <param name="bind"></param>
        /// <param name="ex"></param>
        /// <param name="errorMsg"></param>
        private void ExceptionHandle(TrayTask task, AGVBindUnit bind, Exception ex, string errorMsg = "")
        {
            bool isWarningEx = false;
            if (ex is ProcessException)
            {
                isWarningEx = (ex as ProcessException).Level > 0;
                errorMsg += (ex as ProcessException).ErrorCode;
            }
            else
            {
                isWarningEx = true;
                errorMsg += ex.Message;
                LogAsync(DateTime.Now, $"{task.TaskId}异常", ex.GetExceptionMessage());
            }
            try
            {
                SwitchLight(bind.Robot, false);
                bind.Robot.Move(new RobotPoint(), MoveType.Origin, true);
            }
            catch (Exception)
            {
            }
            LogAsync(DateTime.Now, $"任务{task.GetDisplayText()}异常反馈", errorMsg);
            AfterTaskHandle(task, bind, errorMsg, isWarningEx);
        }
        #endregion
        #region 任务执行
        /// <summary>
        /// 将满Tray放置到产线上
        /// </summary>
        /// <param name="task"></param>
        /// <param name="bind"></param>
        private void UnloadFullTrayToLine(TrayTask task, AGVBindUnit bind)
        {
            string methodName = "UnloadFullTrayToLine";
            bind.AGV.TaskOrder(task.Location.PositionCode, true);
            var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == task.Location.PositionCode);
            if (visionConfig == null)
                throw new ProcessException($"未能获取{bind.Camera.Name}在{task.Location.PositionCode}的视觉配置");
            while (bind.FullTrayNum > 0)
            {
                bind.Robot.Move(visionConfig.RobotSnapshotPoint, MoveType.AbsoluteMove, true);
                bool isLineReady = false;
                if (Config.IsEnableVisionGuide)
                {
                    int reTryTime = Config.LineBusyRetryTimes;
                    HDevEngineTool tool = null;
                    if (!_halconToolDict.ContainsKey(visionConfig.CameraOpConfig.AlgorithemPath))
                    {
                        throw new ProcessException($"未配置{methodName}的算法工具");
                    }
                    tool = _halconToolDict[visionConfig.CameraOpConfig.AlgorithemPath];
                    SwitchLight(bind.Robot, true);
                    Thread.Sleep(500);//等待灯开
                    do
                    {
                        RobotPoint point = new RobotPoint();
                        using (var hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, methodName))
                        {
                            tool.InputImageDic.Clear();
                            tool.InputImageDic["INPUT_Image"] = hImage;
                            tool.RunProcedure();
                            if (!tool.IsSuccessful)
                            {
                                throw new ProcessException($"{visionConfig.CameraOpConfig.AlgorithemPath}算法运行失败");
                            }
                            isLineReady = tool.GetResultTuple("OUTPUT_Result").I == 1;
                        }
                        if (!isLineReady)
                        {
                            LogAsync(DateTime.Now, $"{task.Location.PositionCode}位置线体繁忙", "");
                            Thread.Sleep(Config.LineBusyWaitInterval * 1000);
                            reTryTime--;
                        }
                        else
                        {
                            reTryTime = 0;
                        }
                    } while (reTryTime > 0);
                    SwitchLight(bind.Robot, false);
                }
                else
                {
                    isLineReady = true;
                }
                if (isLineReady)
                {
                    bind.Robot.UnLoad(bind.Robot.CurrentPoint, TrayType.FullTray, true);
                }
                else
                {
                    bind.Robot.Move(new RobotPoint(), MoveType.Origin, true);
                    break;
                }
            }
        }
        /// <summary>
        /// 将空Tray放置到压机上
        /// </summary>
        /// <param name="task"></param>
        /// <param name="bind"></param>
        private void UnloadEmptyTrayToMachine(TrayTask task, AGVBindUnit bind)
        {
            string methodName = "UnloadEmptyTrayToMachine";
            MachineRelatedOperation(task, bind, methodName);
            RobotPoint point = bind.Robot.CurrentPoint.DeepSerializeClone();
            int emptyTrayNum = 0;
            do
            {
                bind.Robot.UnLoad(point, TrayType.EmptyTray, true);
                emptyTrayNum++;
                bind.Robot.MonitorHandle.Reset();
                var isNotTimeout = bind.Robot.MonitorHandle.WaitOne((bind.Robot.InitialConfig as AuboRobotInitialConfig).ReplyTimeout * 3);
                if (!isNotTimeout)
                    throw new ProcessException($"{bind.Robot.Name}监听操作超时");
            } while (emptyTrayNum < Config.Machine_EmptyTrayNum && bind.EmptyTrayNum > 0);
        }
        /// <summary>
        /// 从压机上取满Tray到AGV
        /// </summary>
        /// <param name="task"></param>
        /// <param name="bind"></param>
        private void LoadFullTrayFromMachine(TrayTask task, AGVBindUnit bind)
        {
            string methodName = "LoadFullTrayFromMachine";
            MachineRelatedOperation(task, bind, methodName);
            RobotPoint point = bind.Robot.CurrentPoint.DeepSerializeClone();
            int fullTrayNum = Config.Machine_FullTrayNum;
            do
            {
                bind.Robot.Load(point, TrayType.FullTray, true);
                fullTrayNum--;
                bind.FullTrayNum++;
                bind.Robot.MonitorHandle.Reset();
                var isNotTimeout = bind.Robot.MonitorHandle.WaitOne((bind.Robot.InitialConfig as AuboRobotInitialConfig).ReplyTimeout * 3);
                if (!isNotTimeout)
                    throw new ProcessException($"{bind.Robot.Name}监听操作超时");
            } while (fullTrayNum > 0 && bind.FullTrayNum < Config.AGVAvailableTrayNums);
        }
        private void MachineRelatedOperation(TrayTask task, AGVBindUnit bind, string methodName)
        {
            bind.AGV.TaskOrder(task.Location.PositionCode, true);
            var visionConfig = Config.VisionConfigCollection.FirstOrDefault(u => u.CameraId == bind.CameraId && u.PositionCode == task.Location.PositionCode);
            if (visionConfig == null)
                throw new ProcessException($"未能获取{bind.Camera.Name}在{task.Location.PositionCode}的视觉配置");
            bind.Robot.Move(visionConfig.RobotSnapshotPoint, MoveType.AbsoluteMove, true);
            if (Config.IsEnableVisionGuide)
            {
                int repeatTime = Config.VisionGuideTimes;
                HDevEngineTool tool = null;
                if (!_halconToolDict.ContainsKey(visionConfig.CameraOpConfig.AlgorithemPath))
                {
                    throw new ProcessException($"未配置{methodName}的算法工具");
                }
                tool = _halconToolDict[visionConfig.CameraOpConfig.AlgorithemPath];
                SwitchLight(bind.Robot, true);
                Thread.Sleep(500);//等待灯开
                do
                {
                    RobotPoint point = new RobotPoint();
                    using (var hImage = CollectHImage(bind.Camera, visionConfig.CameraOpConfig, methodName))
                    {
                        tool.InputImageDic.Clear();
                        tool.InputImageDic["INPUT_Image"] = hImage;
                        tool.RunProcedure();
                        if (!tool.IsSuccessful)
                        {
                            throw new ProcessException($"{visionConfig.CameraOpConfig.AlgorithemPath}算法运行失败");
                        }
                        double du = tool.GetResultTuple("OUTPUT_X").D;
                        double dv = tool.GetResultTuple("OUTPUT_Y").D;
                        if (du < 0 || dv < 0)
                        {
                            throw new ProcessException($"{visionConfig.CameraOpConfig.AlgorithemPath}算法结果异常,点位信息获取失败");
                        }
                        du -= visionConfig.StandardPoint.X;
                        dv -= visionConfig.StandardPoint.Y;
                        HOperatorSet.AffineTransPoint2d(new HTuple(visionConfig.Matrix), du, dv, out HTuple dx, out HTuple dy);
                        point.X = (float)dx.D;
                        point.Y = (float)dy.D;
                        point.A = (float)tool.GetResultTuple("OUTPUT_Angle").D;
                    }
                    LogAsync(DateTime.Now, $"{methodName}引导坐标", point.GetDisplayText());
                    bind.Robot.Move(point, MoveType.RobotRelativeMove, true);
                    repeatTime--;
                } while (repeatTime > 0);
                SwitchLight(bind.Robot, false);
                //视觉导引后再做固定偏移
                bind.Robot.Move(visionConfig.RobotShift, MoveType.RobotRelativeMove, true);
            }
        }
        /// <summary>
        /// 人工装空Tray到AGV
        /// </summary>
        /// <param name="task"></param>
        /// <param name="bind"></param>
        private void LoadEmptyTrayToAGV(TrayTask task, AGVBindUnit bind)
        {
            bind.AGV.TaskOrder(task.Location.PositionCode, true);
            bind.Robot.Load(new RobotPoint(), TrayType.EmptyTray, true);
        }
        #endregion
    }
}
src/Bro.Common.Model/Bro.Common.Model.csproj
@@ -89,6 +89,7 @@
    <Compile Include="Interface\IStationProcess.cs" />
    <Compile Include="Model\CustomizedPoint.cs" />
    <Compile Include="Model\DeviceCmmd.cs" />
    <Compile Include="Model\IODefinition.cs" />
    <Compile Include="Model\ModbusFrame.cs" />
    <Compile Include="Model\MonitorSet.cs" />
    <Compile Include="Model\NGDesc.cs" />
src/Bro.Common.Model/Helper/EnumHelper.cs
@@ -73,6 +73,15 @@
            }
        }
        public static T GetEnumAttribute<T>(this Enum enumObj) where T : Attribute
        {
            Type t = enumObj.GetType();
            FieldInfo f = t.GetField(enumObj.ToString());
            T attr = f.GetCustomAttribute<T>();
            return attr;
        }
        public static string GetPartSFCName(this PartType partType)
        {
            Type t = partType.GetType();
src/Bro.Common.Model/Helper/ExceptionHelper.cs
@@ -8,8 +8,17 @@
namespace Bro.Common.Helper
{
    public enum ExceptionLevel
    {
        Info = 0,
        Warning = 1,
        Fatal = 2,
    }
    public class ProcessException : Exception
    {
        public ExceptionLevel Level { get; set; } = ExceptionLevel.Warning;
        public static Action<DateTime, string, string> OnExceptionNotice;
        //PubSubCenter pubSub = PubSubCenter.GetInstance();
@@ -38,9 +47,11 @@
        {
        }
        public ProcessException(Exception ex)
        public ProcessException(Exception ex, ExceptionLevel lvl = ExceptionLevel.Warning)
        {
            OriginalException = ex;
            Level = lvl;
            ExceptionNotice();
        }
@@ -50,9 +61,10 @@
        //    PositionIndex = positionIndex;
        //}
        public ProcessException(string error, Exception ex = null) : base(error, ex)
        public ProcessException(string error, ExceptionLevel lvl = ExceptionLevel.Warning, Exception ex = null) : base(error, ex)
        {
            ErrorCode = error;
            Level = lvl;
            OriginalException = ex;
            ExceptionNotice();
@@ -74,29 +86,5 @@
            //pubSub.Publish(PubTag.ExceptionUpdate.ToString(), ErrorCode, OriginalException, true);
            OnExceptionNotice?.Invoke(DateTime.Now, ErrorCode, OriginalException?.GetExceptionMessage());
        }
    }
    public class Level3Exception : ProcessException
    {
        public Level3Exception() : base() { }
        public Level3Exception(string error, Exception ex) : base(error, ex) { }
    }
    /// <summary>
    /// 来料检测异常
    /// </summary>
    public class IncomingMaterialException : ProcessException
    {
        public IncomingMaterialException() : base() { }
        public IncomingMaterialException(string error, Exception ex) : base(error, ex) { }
    }
    public class BarcodeScanFailureException : ProcessException
    {
        public BarcodeScanFailureException() : base() { }
        public BarcodeScanFailureException(string msg, Exception ex) : base(msg, ex) { }
    }
}
src/Bro.Common.Model/Helper/ListHelper.cs
@@ -1,18 +1,17 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bro.Common.Helper
{
    public class NoticedList<T> : List<T> where T : class
    {
        public Action OnItemChanged;
        public Action<NotifyCollectionChangedAction> OnItemChanged;
        public Action<List<T>> OnItemChangedWithItemInfo;
        public Action<NotifyCollectionChangedAction, List<T>> OnItemChangedWithItemInfo;
        public Action<NoticedList<T>> OnItemChangedWithSelf;
        //public Action<NotifyCollectionChangedAction, NoticedList<T>> OnItemChangedWithSelf;
        public new T this[int index]
        {
@@ -33,9 +32,8 @@
                if (base[index] != value)
                {
                    base[index] = value;
                    OnItemChanged?.Invoke();
                    OnItemChangedWithItemInfo?.Invoke(new List<T>() { value });
                    OnItemChangedWithSelf?.Invoke(this);
                    OnItemChanged?.Invoke(NotifyCollectionChangedAction.Replace);
                    OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Replace, new List<T>() { value });
                }
            }
        }
@@ -43,40 +41,36 @@
        public new void Add(T item)
        {
            base.Add(item);
            OnItemChanged?.Invoke();
            OnItemChangedWithItemInfo?.Invoke(new List<T>() { item });
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Add);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Add, new List<T>() { item });
        }
        public new void AddRange(IEnumerable<T> collection)
        {
            base.AddRange(collection);
            OnItemChanged?.Invoke();
            OnItemChangedWithItemInfo?.Invoke(collection.ToList());
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Add);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Add, collection.ToList());
        }
        public new void Clear()
        {
            base.Clear();
            OnItemChanged?.Invoke();
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Reset);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Reset, this);
        }
        public new void Insert(int index, T item)
        {
            base.Insert(index, item);
            OnItemChanged?.Invoke();
            OnItemChangedWithItemInfo?.Invoke(new List<T>() { item });
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Add);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Add, new List<T>() { item });
        }
        public new void InsertRange(int index, IEnumerable<T> collection)
        {
            base.InsertRange(index, collection);
            OnItemChanged?.Invoke();
            OnItemChangedWithItemInfo?.Invoke(collection.ToList());
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Add);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Add, collection.ToList());
        }
        public new bool Remove(T item)
@@ -85,9 +79,8 @@
            if (flag)
            {
                OnItemChanged?.Invoke();
                OnItemChangedWithItemInfo?.Invoke(new List<T>() { item });
                OnItemChangedWithSelf?.Invoke(this);
                OnItemChanged?.Invoke(NotifyCollectionChangedAction.Remove);
                OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Remove, new List<T>() { item });
            }
            return flag;
@@ -95,11 +88,12 @@
        public new int RemoveAll(Predicate<T> match)
        {
            List<T> temp = this.Where(u => match.Invoke(u)).ToList();
            int i = base.RemoveAll(match);
            if (i > 0)
            {
                OnItemChanged?.Invoke();
                OnItemChangedWithSelf?.Invoke(this);
                OnItemChanged?.Invoke(NotifyCollectionChangedAction.Remove);
                OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Remove, temp);
            }
            return i;
@@ -107,16 +101,18 @@
        public new void RemoveAt(int index)
        {
            var item = this[index];
            base.RemoveAt(index);
            OnItemChanged?.Invoke();
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Remove);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Remove, new List<T>() { item });
        }
        public new void RemoveRange(int index, int count)
        {
            List<T> temp = this.Skip(index).Take(count).ToList();
            base.RemoveRange(index, count);
            OnItemChanged?.Invoke();
            OnItemChangedWithSelf?.Invoke(this);
            OnItemChanged?.Invoke(NotifyCollectionChangedAction.Remove);
            OnItemChangedWithItemInfo?.Invoke(NotifyCollectionChangedAction.Remove, temp);
        }
    }
}
src/Bro.Common.Model/Interface/IDeviceConfig.cs
@@ -20,7 +20,7 @@
    /// <summary>
    /// 设备初始配置
    /// </summary>
    public interface IInitialConfig
    public interface IInitialConfig : ILog
    {
        /// <summary>
        /// 设备序号 GUID
@@ -31,5 +31,14 @@
        string Name { get; set; }
        bool IsEnabled { get; set; }
        string DriverType { get; set; }
    }
    public interface ILog
    {
        string LogPath { get; set; }
        bool IsEnableLog { get; set; }
    }
}
src/Bro.Common.Model/Interface/IMonitor.cs
@@ -11,7 +11,7 @@
    public delegate void OnMonitorAlarmDelegate(DateTime dt, IDevice device, WarningSet warning, bool isAlarmRaised);
    public interface IMonitor
    {
        List<int> GetMonitorValues(int startAddress, int length);
        //List<int> GetMonitorValues(int startAddress, int length);
        void Monitor();
        event OnMonitorInvokeDelegate OnMonitorInvoke;
src/Bro.Common.Model/Model/CustomizedPoint.cs
@@ -599,4 +599,64 @@
            return data.TrimEnd(new char[] { ',' });
        }
    }
    public class RobotPoint : IComplexDisplay
    {
        [Category("点位信息")]
        [Description("坐标X")]
        public float X { get; set; }
        [Category("点位信息")]
        [Description("坐标Y")]
        public float Y { get; set; }
        [Category("点位信息")]
        [Description("坐标Z")]
        public float Z { get; set; }
        [Category("点位信息")]
        [Description("角度A")]
        public float A { get; set; }
        [Category("点位信息")]
        [Description("角度B")]
        public float B { get; set; }
        [Category("点位信息")]
        [Description("角度C")]
        public float C { get; set; }
        public string GetDisplayText()
        {
            return $"X:{X.ToString()} Y:{Y.ToString()} Z:{Z.ToString()} A:{A.ToString()} B:{B.ToString()} C:{C.ToString()}";
        }
        public double[] GetArray()
        {
            return new double[] { X, Y, Z, A, B, C };
        }
        public static RobotPoint GetRobotPointByArray(double[] array)
        {
            if (array.Length != 6)
            {
                return null;
            }
            RobotPoint point = new RobotPoint();
            point.X = (float)array[0];
            point.Y = (float)array[1];
            point.Z = (float)array[2];
            point.A = (float)array[3];
            point.B = (float)array[4];
            point.C = (float)array[5];
            return point;
        }
        public static float GetDistance(RobotPoint pointA, RobotPoint pointB)
        {
            return (float)Math.Sqrt(Math.Pow(pointA.X - pointB.X, 2) + Math.Pow(pointA.Y - pointB.Y, 2) + Math.Pow(pointA.Z - pointB.Z, 2));
        }
    }
}
src/Bro.Common.Model/Model/IODefinition.cs
New file
@@ -0,0 +1,43 @@
using Bro.Common.Helper;
using Newtonsoft.Json;
using System;
using System.ComponentModel;
namespace Bro.Common.Model
{
    public class IODefinition : IComplexDisplay
    {
        [Category("IO配置")]
        [Description("IO索引,从0开始")]
        public int IOIndex { get; set; }
        [Category("IO配置")]
        [Description("IO描述")]
        public string IODesc { get; set; } = "";
        [Category("IO配置")]
        [Description("true:接通触发  false:断开触发")]
        public bool TriggerValue { get; set; } = true;
        [Browsable(false)]
        [JsonIgnore]
        public bool CurrentValue { get; set; } = true;
        [Category("IO配置")]
        [Description("IO定义类型")]
        public IODefinitionType IOType { get; set; } = IODefinitionType.Warning;
        public string GetDisplayText()
        {
            return $"{IOIndex}{(string.IsNullOrWhiteSpace(IODesc) ? "" : (":" + IODesc))}:{IOType.ToString()}";
        }
    }
    public enum IODefinitionType
    {
        Start,
        Stop,
        Reset,
        Warning,
    }
}
src/Bro.Common.Model/Model/MonitorSet.cs
@@ -47,6 +47,10 @@
        [Description("触发值,设置为-999时变化即触发")]
        public int TriggerValue { get; set; } = -1;
        [Browsable(false)]
        [JsonIgnore]
        public int CurrentValue { get; set; }
        /// <summary>
        /// 传入数据地址的索引 按照PLC监听地址从0开始的索引集合
        /// </summary>
@@ -69,31 +73,17 @@
        [Description("通知地址 实际PLC寄存器的地址,10进制,例如 40012")]
        public int NoticeAddress { get; set; } = -1;
        //[Browsable(false)]
        //[JsonIgnore]
        //public List<int> InputDatas { get; set; } = new List<int>();
        [Browsable(false)]
        [JsonIgnore]
        public ProcessResponse Response { get; set; } = new ProcessResponse();
        public MonitorSet() { }
        //public MonitorSet(int triggerIndex, int triggerValue, List<int> inputDataIndex, int dataIndex, int noticeIndex)
        //{
        //    TriggerIndex = triggerIndex;
        //    TriggerValue = triggerValue;
        //    InputDataIndex = inputDataIndex;
        //    ReplyDataAddress = dataIndex;
        //    NoticeAddress = noticeIndex;
        //}
        public string GetDisplayText()
        {
            using (var scope = GlobalVar.Container.BeginLifetimeScope())
            {
                List<ProcessMethodAttribute> methodList = scope.Resolve<List<ProcessMethodAttribute>>();
                //List<IDevice> deviceList = scope.Resolve<List<IDevice>>();
                string desc = "";
src/Bro.Device.AuboRobot/AuboRobotConfig.cs
@@ -1,7 +1,6 @@
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;
@@ -25,12 +24,20 @@
        public int ReplyTimeout { get; set; } = 1000;
        [Category("通信设置")]
        [Description("反馈超时重试次数")]
        public int TimeoutRetryTimes { get; set; } = 5;
        [Category("通信设置")]
        [Description("协议文本结束字符")]
        public string EndChar { get; set; } = "#";
        [Category("通信设置")]
        [Description("协议内容分隔字符")]
        public string Seperator { get; set; } = ",";
        [Category("动作设置")]
        [Description("动作超时设置,单位min")]
        public float OperationTimeout { get; set; } = 1;
        [Category("IO监听设置")]
        [Description("IO监听操作配置集合")]
@@ -40,12 +47,54 @@
        [Category("IO监听设置")]
        [Description("IO监听间隔,以ms为单位")]
        public int ScanInterval { get; set; } = 100;
        public int ScanInterval { get; set; } = 9999;
        [Category("IO监听设置")]
        [Description("是否启用IO监听,true:监听 false:不监听")]
        public bool IsEnableMonitor { get; set; } = false;
        [Category("报警配置")]
        [Description("报警代码配置")]
        [TypeConverter(typeof(CollectionCountConvert))]
        [Editor(typeof(ComplexCollectionEditor<RobotWarningCode>), typeof(UITypeEditor))]
        public List<RobotWarningCode> RobotWarnings { get; set; } = new List<RobotWarningCode>();
    }
    public class RobotWarningCode
    {
        [Category("报警配置")]
        [Description("报警代码")]
        public int WarningCode { get; set; }
        [Category("报警配置")]
        [Description("报警代码描述")]
        public string WarningDescription { get; set; }
    }
    [Device("AuboRobot", "奥博机器人", EnumHelper.DeviceAttributeType.OperationConfig)]
    public class AuboRobotOperationConfig : OperationConfigBase
    {
        [Category("动作指令")]
        [Description("动作指令")]
        public RobotMsgAction Action { get; set; } = RobotMsgAction.Move;
        [Category("参数")]
        [Description("参数1")]
        public int Para1 { get; set; } = 0;
        [Category("参数")]
        [Description("参数2")]
        public int Para2 { get; set; } = 0;
        [Category("运动配置")]
        [Description("机器人运动点位")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public RobotPoint Point { get; set; } = new RobotPoint();
        [Category("操作配置")]
        [Description("是否等待完成信号")]
        public bool IsWaitFinished { get; set; } = true;
    }
    public class RobotMsg : IComplexDisplay
@@ -56,16 +105,13 @@
        public RobotMsgAction Action { get; set; } = RobotMsgAction.Move;
        public RobotMsgParas Para1 { get; set; } = RobotMsgParas.None;
        public int Para1 { get; set; }
        public int Para2 { get; set; } = 0;
        /// <summary>
        /// Paras不包含Para1,Para2内容
        /// </summary>
        public List<string> Datas { get; set; } = new List<string>();
        public RobotPoint Point { get; set; } = new RobotPoint();
        public byte[] GetMsgBytes(string seperator, string endChar)
        public byte[] GetMsgBytes(string seperator, string endChar, out string msg)
        {
            List<string> list = new List<string>() { ((int)Type).ToString("D2"), ID.ToString("D2") };
@@ -73,34 +119,51 @@
            {
                list.Add(((int)Action).ToString("D2"));
                list.Add(((int)Para1).ToString("D2"));
                list.Add(Para1.ToString("D2"));
                list.Add(Para2.ToString("D2"));
                if (Datas == null)
                {
                    Datas = new List<string>();
                list.Add(GetFormatString(Point.X));
                list.Add(GetFormatString(Point.Y));
                list.Add(GetFormatString(Point.Z));
                list.Add(GetFormatString_Angle(Point.A));
                list.Add(GetFormatString_Angle(Point.B));
                list.Add(GetFormatString_Angle(Point.C));
                }
                while (Datas.Count < 5)
                {
                    Datas.Add("0");
            msg = string.Join(seperator, list) + seperator + endChar;
            return System.Text.Encoding.ASCII.GetBytes(msg);
                }
                list.AddRange(Datas.ConvertAll(s =>
        private string GetFormatString(float x)
                {
                    string res = float.Parse(s).ToString("F7");
            string s = x.ToString("f10");
                    while (res.Length < 11)
            s = s.TrimEnd(new char[] { '0' });
            bool isNegative = x < 0;
            while (s.Length < 11)
                    {
                        res = "0" + res;
                    }
                    return res;
                }));
                s = s.Insert(isNegative ? 1 : 0, "0");
            }
            string msg = string.Join(seperator, list);
            return s;
        }
            return System.Text.Encoding.ASCII.GetBytes(msg + endChar);
        /// <summary>
        /// 将角度数值转换为11位字符串,输入角度区间0~360,输出角度区间-180~+180
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        private string GetFormatString_Angle(float x)
        {
            if (x > 180)
            {
                x = x - 360;
            }
            return GetFormatString(x);
        }
        public static RobotMsg GetRobotMsg(string data, string seperator)
@@ -116,10 +179,22 @@
            {
                msg.Action = (RobotMsgAction)int.Parse(datas[2]);
                msg.Para1 = (RobotMsgParas)int.Parse(datas[3]);
                msg.Para1 = int.Parse(datas[3]);
                msg.Para2 = int.Parse(datas[4]);
                msg.Datas = datas.Skip(5).ToList();
                msg.Point = new RobotPoint()
                {
                    X = float.Parse(datas[5]),
                    Y = float.Parse(datas[6]),
                    Z = float.Parse(datas[7]),
                    A = float.Parse(datas[8]) < 0 ? float.Parse(datas[8]) + 360 : float.Parse(datas[8]),
                    B = float.Parse(datas[9]) < 0 ? float.Parse(datas[9]) + 360 : float.Parse(datas[9]),
                    C = float.Parse(datas[10]) < 0 ? float.Parse(datas[10]) + 360 : float.Parse(datas[10]),
                };
            }
            else
            {
                msg.Para1 = int.Parse(datas[2]);
            }
            return msg;
@@ -127,7 +202,7 @@
        public string GetDisplayText()
        {
            string msg = $"序号:{ID},{Action.ToString()}_{Para1.ToString()}_{Para2.ToString()}_{(Datas.Count > 0 ? ("_" + string.Join(",", Datas)) : "")}";
            string msg = $"序号:{ID},{Action.ToString()}_{Para1.ToString()}_{Para2.ToString()}_{Point.GetDisplayText()}";
            return msg;
        }
    }
@@ -143,20 +218,62 @@
        Move = 1,
        Unload = 2,
        Load = 3,
        IO = 6,
        GetPosition = 4,
        /// <summary>
        /// 指定当前点位为基准点
        /// </summary>
        BasePoint = 5,
        IOSet = 6,
        IOQuery = 7,
        Calibration = 9,
        StandardPoint = 10,
    }
    public enum RobotMsgParas
    public enum MoveType
    {
        None = 0,
        Home = 1,
        LineSnap = 2,
        EmptyTray = 3,
        FullTray = 4,
        UnloadEmptyTraySnap = 5,
        LoadFullTraySnap = 6,
        Query = 7,
        [Description("绝对运动")]
        AbsoluteMove = 1,
        [Description("机器人坐标系相对运动")]
        RobotRelativeMove = 2,
        [Description("相对某个基准点位的相对运动")]
        BasedPointRelativeMove = 3,
        [Description("回原点")]
        Origin = 4,
        [Description("左侧姿势")]
        LeftPose = 6,
        [Description("右侧姿势")]
        RightPose = 5,
        [Description("前侧姿势")]
        FrontPose = 7,
        [Description("相机坐标系相对运动")]
        CameraRelativeMove = 12,
    }
    public enum TrayType
    {
        FullTray = 1,
        EmptyTray = 2,
    }
    //public enum RobotMsgAction
    //{
    //    Move = 1,
    //    Unload = 2,
    //    Load = 3,
    //    IO = 6,
    //    Calibration = 9,
    //    StandardPoint = 10,
    //}
    //public enum RobotMsgParas
    //{
    //    None = 0,
    //    Home = 1,
    //    LineSnap = 2,
    //    EmptyTray = 3,
    //    FullTray = 4,
    //    UnloadEmptyTraySnap = 5,
    //    LoadFullTraySnap = 6,
    //    Query = 7,
    //}
}
src/Bro.Device.AuboRobot/AuboRobotDriver.cs
@@ -10,6 +10,7 @@
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using static Bro.Common.Helper.EnumHelper;
namespace Bro.Device.AuboRobot
{
@@ -38,7 +39,7 @@
            if (string.IsNullOrWhiteSpace(IConfig.EndChar))
            {
                throw new ProcessException("协议文本的结束字符不可为空,请检查配置", null);
                throw new ProcessException("协议文本的结束字符不可为空,请检查配置");
            }
            client = new TcpClient();
@@ -46,6 +47,10 @@
            client.Client.Blocking = true;
            client.NoDelay = true;
            client.BeginConnect(IPAddress.Parse(IConfig.RobotIP), IConfig.RobotPort, OnConnect, null);
            replyHandleDict = new Dictionary<int, AutoResetEvent>();
            replyErrorCodeDict = new Dictionary<int, int>();
            taskHandleDict = new Dictionary<int, ManualResetEvent>();
        }
        protected override void Pause()
@@ -62,18 +67,24 @@
            //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;
            //scanMsg = new RobotMsg();
            //scanMsg.Action = RobotMsgAction.IO;
            //scanMsg.Para1 = RobotMsgParas.Query;
            Task.Run(() =>
            {
                Monitor();
            });
            //Task.Run(() =>
            //{
            //    Monitor();
            //});
        }
        protected override void Stop()
        {
            replyHandleDict.Values.ToList().ForEach(h => h.Set());
            replyHandleDict.Clear();
            taskHandleDict.Values.ToList().ForEach(h => h.Set());
            taskHandleDict.Clear();
            if (client != null && client.Connected)
            {
                client.Close();
@@ -95,7 +106,8 @@
            }
            catch (Exception ex)
            {
                OnLog?.Invoke(DateTime.Now, this, ex.GetExceptionMessage());
                //OnLog?.Invoke(DateTime.Now, this, ex.GetExceptionMessage());
                LogAsync(DateTime.Now, "连接异常", ex.GetExceptionMessage());
                if (client != null && client.Connected)
                {
@@ -110,13 +122,12 @@
            try
            {
                int dataLength = stream.EndRead(ar);
                if (dataLength > 0)
                {
                    byte[] data = buffer.Take(dataLength).ToArray();
                    string dataStr = System.Text.Encoding.ASCII.GetString(data).Trim();
                    //OnLog?.BeginInvoke(DateTime.Now, this, $"{Name}接收数据:{dataStr}", null, null);
                    AnalyzeData(dataStr);
                    stream.BeginRead(buffer, 0, buffer.Length, OnDataReceived, null);
@@ -125,19 +136,21 @@
                {
                    if (!client.Connected)
                    {
                        OnLog?.Invoke(DateTime.Now, this, "返回空数据,连接中断");
                        //OnLog?.Invoke(DateTime.Now, this, "返回空数据,连接中断");
                        LogAsync(DateTime.Now, "连接中断", "返回空数据,连接中断");
                        client.BeginConnect(IPAddress.Parse(IConfig.RobotIP), IConfig.RobotPort, OnConnect, null);
                    }
                }
            }
            catch (Exception ex)
            {
                OnLog?.Invoke(DateTime.Now, this, $"{Name}数据接收异常:{ex.GetExceptionMessage()}");
                //OnLog?.Invoke(DateTime.Now, this, $"{Name}:{ex.GetExceptionMessage()}");
                LogAsync(DateTime.Now, "数据接收异常", ex.GetExceptionMessage());
            }
        }
        string msg = "";
        static object analyzeLock = new object();
        object analyzeLock = new object();
        private async void AnalyzeData(string data)
        {
            await Task.Run(() =>
@@ -173,32 +186,49 @@
                     if (msg.Type == RobotMsgType.Rec)
                     {
                        replyErrorCodeDict[msg.ID] = msg.Para1;
                         if (replyHandleDict.ContainsKey(msg.ID))
                         {
                             replyHandleList.Remove(msg.ID);
                             replyHandleDict[msg.ID].Set();
                             replyHandleDict.Remove(msg.ID);
                         }
                     }
                     else
                     {
                         canMonitor = true;
                         if (msg.Action == RobotMsgAction.IO && msg.Para1 == RobotMsgParas.Query)
                        if (msg.Action == RobotMsgAction.IOQuery)
                         {
                             string resultStr = msg.Datas[0];
                             newValues = new List<int>();
                             for (int i = resultStr.Length - 1; i >= 0; i--)
                            for (int i = msg.Para1.ToString().Length - 1; i >= 0; i--)
                             {
                                 newValues.Add(int.Parse(resultStr[i].ToString()));
                                newValues.Add((msg.Para1 >> i) & 1);
                             }
                             MonitorHandle.Set();
                         }
                         else
                         {
                            canMonitor = true;
                            switch (msg.Action)
                            {
                                case RobotMsgAction.Move:
                                    CurrentPoint = msg.Point;
                                    break;
                                //case RobotMsgAction.Load:
                                //    //SlotIndex = msg.Para1;
                                //    break;
                                case RobotMsgAction.GetPosition:
                                    CurrentPoint = msg.Point;
                                    break;
                                default:
                                    break;
                            }
                            if (taskHandleDict.ContainsKey(msg.ID))
                            {
                                taskHandleDict[msg.ID].Set();
                            }
                            LogAsync(DateTime.Now, "指令反馈", msg.GetDisplayText());
                             OnMsgReceived?.BeginInvoke(DateTime.Now, this, msg, null, null);
                         }
                     }
@@ -220,24 +250,9 @@
            }
        }
        List<int> replyHandleList = new List<int>();
        Dictionary<int, AutoResetEvent> replyHandleDict = new Dictionary<int, AutoResetEvent>();
        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()));
            OnLog?.BeginInvoke(DateTime.Now, this, $"{Name}发送指令:{msg.GetDisplayText()}", null, null);
            SendMsg(msg, true);
        }
        Dictionary<int, int> replyErrorCodeDict = new Dictionary<int, int>();
        Dictionary<int, ManualResetEvent> taskHandleDict = new Dictionary<int, ManualResetEvent>();
        public void SendReplyMsg(int replyId)
        {
@@ -253,14 +268,23 @@
        object monitorLock = new object();
        public void SendMsg(RobotMsg msg, bool isWaitReply = true, bool isMonitorMsg = false)
        {
            replyErrorCodeDict[msg.ID] = 0;
            if (isWaitReply)
            {
                replyHandleList.Add(msg.ID);
                replyHandleDict[msg.ID] = new AutoResetEvent(false);
            }
            byte[] bytes = msg.GetMsgBytes(IConfig.Seperator, IConfig.EndChar);
            byte[] bytes = msg.GetMsgBytes(IConfig.Seperator, IConfig.EndChar, out string dataStr);
            if (!isMonitorMsg)
            {
                LogAsync(DateTime.Now, "指令发送", dataStr);
            }
            bool isNotTimeout = true;
            int reTryTime = IConfig.TimeoutRetryTimes;
            do
            {
            lock (monitorLock)
            {
                if (!isMonitorMsg)
@@ -271,19 +295,156 @@
                if (isMonitorMsg && !canMonitor)
                    return;
                //lock (this)
                {
                    stream.Write(bytes, 0, bytes.Length);
                    if (!isMonitorMsg)
                    {
                        LogAsync(DateTime.Now, "数据发送", BitConverter.ToString(bytes));
                }
            }
            if (isWaitReply)
            {
                replyHandleDict[msg.ID].WaitOne(IConfig.ReplyTimeout);
                    int errorCode = replyErrorCodeDict[msg.ID];
                if (replyHandleList.Contains(msg.ID))
                    if (errorCode != 0)
                {
                    throw new ProcessException("反馈数据超时\r\n" + msg.GetDisplayText(), null);
                        var desc = IConfig.RobotWarnings.FirstOrDefault(u => u.WarningCode == errorCode);
                        throw new ProcessException($"{Name}{msg.ID}任务反馈异常{errorCode},异常描述:{(desc == null ? "无" : desc.WarningDescription)}");
                    }
                    isNotTimeout = replyHandleDict[msg.ID].WaitOne(IConfig.ReplyTimeout);
                    if (!isNotTimeout)
                    {
                        reTryTime--;
                    }
                }
            } while (reTryTime > 0 && !isNotTimeout);
            if (isWaitReply)
            {
                replyHandleDict.Remove(msg.ID);
            }
            if (!isNotTimeout)
            {
                throw new ProcessException($"{Name}反馈数据超时\r\n" + msg.GetDisplayText());
            }
        }
        #region 执行结果属性
        public RobotPoint CurrentPoint { get; set; } = null;
        //public int SlotIndex { get; set; } = 0;
        #endregion
        public void Move(RobotPoint point, MoveType isAbs, bool isWaitFinished)
        {
            CurrentPoint = null;
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.Move;
            msg.Para1 = (int)isAbs;
            msg.Point = point ?? new RobotPoint();
            SendMsg(msg, true);
            TaskTimeoutCheck(isWaitFinished, msg.ID, $"{Name}{(isAbs.GetEnumDescription())}至{point.GetDisplayText()}动作超时");
        }
        public void Load(RobotPoint point, TrayType trayType, bool isWaitFinished)
        {
            //SlotIndex = 0;
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.Load;
            msg.Para1 = (int)trayType;
            msg.Point = point;
            SendMsg(msg, true);
            TaskTimeoutCheck(isWaitFinished, msg.ID, $"{Name}取{trayType.ToString()}{point.GetDisplayText()}动作超时");
            //if (isWaitFinished && SlotIndex <= 0)
            //{
            //    throw new ProcessException($"{Name}没有可用的空白格位,无法放置卷宗");
            //}
        }
        public void UnLoad(RobotPoint point, TrayType trayType, bool isWaitFinished)
        {
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.Unload;
            msg.Para1 = (int)trayType;
            msg.Point = point;
            SendMsg(msg, true);
            TaskTimeoutCheck(isWaitFinished, msg.ID, $"{Name}卸{trayType.ToString()}{point.GetDisplayText()}动作超时");
        }
        public void GetPosition(bool isWaitFinished)
        {
            CurrentPoint = null;
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.GetPosition;
            SendMsg(msg, true);
            TaskTimeoutCheck(isWaitFinished, msg.ID, $"{Name}获取当前坐标超时");
        }
        public void SetBasePoint()
        {
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.BasePoint;
            SendMsg(msg, true);
        }
        public void SetIO(int outputIndex, bool isOn)
        {
            RobotMsg msg = new RobotMsg();
            msg.Type = RobotMsgType.Send;
            msg.ID = SID;
            msg.Action = RobotMsgAction.IOSet;
            msg.Para1 = outputIndex;
            msg.Para2 = isOn ? 1 : 0;
            SendMsg(msg, true);
        }
        private void TaskTimeoutCheck(bool isWaitFinished, int msgId, string errorMsg)
        {
            if (isWaitFinished)
            {
                taskHandleDict[msgId] = new ManualResetEvent(false);
                taskHandleDict[msgId].Reset();
                bool isNotTimeout = taskHandleDict[msgId].WaitOne((int)(IConfig.OperationTimeout * 60 * 1000));
                taskHandleDict.Remove(msgId);
                if (!isNotTimeout)
                {
                    throw new ProcessException(errorMsg);
                }
            }
        }
@@ -295,14 +456,16 @@
        protected List<int> oldValues = new List<int>();
        List<int> newValues = new List<int>();
        public ManualResetEvent MonitorHandle { get; set; } = new ManualResetEvent(false);
        //public ManualResetEvent IOChangedHandle { get; set; } = new ManualResetEvent(true);
        public List<int> GetMonitorValues(int startAddress, int length)
        public List<int> GetIOValues()
        {
            MonitorHandle.Reset();
            scanMsg.ID = SID;
            SendMsg(scanMsg, true, true);
            MonitorHandle.WaitOne(IConfig.ReplyTimeout);
            var isNotTimeout = MonitorHandle.WaitOne(IConfig.ReplyTimeout * 3);
            if (!isNotTimeout)
                throw new ProcessException($"{Name}监听操作超时");
            return newValues;
        }
@@ -313,7 +476,9 @@
            {
                try
                {
                    List<int> newValues = GetMonitorValues(0, 0);
                    if (IConfig.IsEnableMonitor)
                    {
                        List<int> newValues = GetIOValues();
                    if (newValues == null || newValues.Count == 0)
                        continue;
@@ -330,12 +495,13 @@
                        MonitorCheckAndInvoke(tempNew, tempOld);
                    }
                    oldValues = new List<int>(newValues);
                    }
                    Thread.Sleep(IConfig.ScanInterval);
                }
                catch (Exception ex)
                {
                    OnLog?.Invoke(DateTime.Now, this, $"{Name}监听异常:{ex.GetExceptionMessage()}");
                    LogAsync(DateTime.Now, "监听异常", ex.GetExceptionMessage());
                }
            }
        }
src/Bro.Device.Common/Base/DeviceBase.cs
@@ -14,6 +14,7 @@
//using BroCComn.PubSub;
using static Bro.Common.Helper.EnumHelper;
using System.ComponentModel;
using System.IO;
namespace Bro.Common.Base
{
@@ -393,7 +394,7 @@
            {
                string currentStateStr = CurrentStateToBe.GetEnumDescription();
                string stateToBeStr = stateToBe.GetEnumDescription();
                throw new ProcessException($"{InitialConfig.Name}设备的当前状态为{currentStateStr},无法切换至{stateToBeStr}", null);
                throw new ProcessException($"{InitialConfig.Name}设备的当前状态为{currentStateStr},无法切换至{stateToBeStr}");
            }
            CurrentState = EnumHelper.DeviceState.TBD;
@@ -417,7 +418,7 @@
                    }
                    else
                    {
                        throw new ProcessException($"{InitialConfig.Name}设备操作超时", null);
                        throw new ProcessException($"{InitialConfig.Name}设备操作超时");
                    }
                }
@@ -489,7 +490,7 @@
                        //this.CurrentState = EnumHelper.DeviceState.DSExcept;
                        //this.CurrentStateToBe = EnumHelper.DeviceState.DSExcept;
                        throw new ProcessException($"设备{InitialConfig.Name}的{CurrentStateToBe.GetEnumDescription()}操作重复3次失败", ex);
                        throw new ProcessException($"设备{InitialConfig.Name}的{CurrentStateToBe.GetEnumDescription()}操作重复3次失败", ExceptionLevel.Warning, ex);
                    }
                }
            }
@@ -515,5 +516,40 @@
            CurrentUserId = obj;
        }
        #endregion
        #region 日志处理
        object logObject = new object();
        public virtual async void LogAsync(DateTime dt, string prefix, string msg)
        {
            await Task.Run(() =>
            {
                if (InitialConfig.IsEnableLog)
                {
                    OnLog?.Invoke(dt, this, prefix + "\t" + msg);
                    if (string.IsNullOrWhiteSpace(InitialConfig.LogPath) || !Path.IsPathRooted(InitialConfig.LogPath))
                    {
                        return;
                    }
                    if (!Directory.Exists(InitialConfig.LogPath))
                    {
                        Directory.CreateDirectory(InitialConfig.LogPath);
                    }
                    string filePath = Path.Combine(InitialConfig.LogPath, $"Log_{Name}_{DateTime.Now.ToString("yyyyMMdd")}.txt");
                    lock (logObject)
                    {
                        using (StreamWriter writer = new StreamWriter(filePath, true, Encoding.UTF8))
                        {
                            writer.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t{prefix}\t{msg}");
                            writer.Flush();
                            writer.Close();
                        }
                    }
                }
            });
        }
        #endregion
    }
}
src/Bro.Device.Common/Base/DeviceConfigBase.cs
@@ -1,7 +1,9 @@
using Bro.Common.Interface;
using Bro.Common.Helper;
using Bro.Common.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -29,5 +31,17 @@
        [Category("通用配置")]
        [Description("设备是否启用")]
        public bool IsEnabled { get; set; } = false;        
        [Browsable(false)]
        public virtual string DriverType { get; set; } = "";
        [Category("日志配置")]
        [Description("日志记录目录")]
        [Editor(typeof(FoldDialogEditor), typeof(UITypeEditor))]
        public string LogPath { get; set; }
        [Category("日志配置")]
        [Description("true:启用日志记录  false:不启用日志记录")]
        public bool IsEnableLog { get; set; } = false;
    }
}
src/Bro.Device.Common/DeviceBase/HDevEngineTool.cs
@@ -12,7 +12,7 @@
namespace Bro.Common.Base
{
    public class HDevEngineTool
    public class HDevEngineTool : IDisposable
    {
        #region 常量
@@ -41,7 +41,7 @@
        /// <summary>
        /// 程序运行是否成功
        /// </summary>
        private bool isSuccess = false;
        public bool IsSuccessful { get; set; } = false;
        /// <summary>
        /// 控制参数字典
@@ -132,11 +132,11 @@
                procedureCall.Execute();
                isSuccess = true;
                IsSuccessful = true;
            }
            catch (HDevEngineException ex)
            {
                isSuccess = false;
                IsSuccessful = false;
                Trace.TraceInformation("HDevProgram {0} Run fail , Line number: {1}, Halcon error number : {2},ex:{3}", ex.ProcedureName, ex.LineNumber, ex.HalconError, ex.Message);
                return;
            }
@@ -144,7 +144,7 @@
        public HTuple GetResultTuple(string key)
        {
            if (isSuccess)
            if (IsSuccessful)
            {
                return procedureCall.GetOutputCtrlParamTuple(key);
            }
@@ -157,7 +157,7 @@
        public HObject GetResultObject(string key, bool ignoreError = false)
        {
            if (ignoreError || isSuccess)
            if (ignoreError || IsSuccessful)
            {
                return procedureCall.GetOutputIconicParamObject(key);
            }
@@ -166,6 +166,17 @@
                return new HObject();
            }
        }
        public void Dispose()
        {
            //foreach (HObject obj in InputImageDic.Values)
            //{
            //    obj.Dispose();
            //}
            procedureCall.Dispose();
            myEngine.Dispose();
        }
    }
    public static class HalconHelper
src/Bro.Device.Common/DeviceBase/PLCBase.cs
@@ -147,7 +147,7 @@
            });
        }
        private void OnMethodInvoked(IAsyncResult ar)
        protected virtual void OnMethodInvoked(IAsyncResult ar)
        {
            MonitorSet monitorSet = ar.AsyncState as MonitorSet;
@@ -186,7 +186,7 @@
                        if (repeatTime <= 0)
                        {
                            new ProcessException("PLC反馈写入异常", ex);
                            new ProcessException("PLC反馈写入异常", ExceptionLevel.Info, ex);
                        }
                    }
                } while (repeatTime > 0);
@@ -234,13 +234,13 @@
        [Description("超时设置,单位:ms")]
        public int Timeout { get; set; } = 500;
        [Category("输出设置")]
        [Description("是否日志输出")]
        public bool IsEnabelLog { get; set; } = false;
        //[Category("输出设置")]
        //[Description("是否日志输出")]
        //public bool IsEnabelLog { get; set; } = false;
        [Category("输出设置")]
        [Description("输出文件路径")]
        public string LogPath { get; set; } = @"D:\PLCLog.txt";
        //[Category("输出设置")]
        //[Description("输出文件路径")]
        //public string LogPath { get; set; } = @"D:\PLCLog.txt";
        #region 地址设置
        [Category("事件地址设置")]
src/Bro.Device.Common/Helper/AspectHelper.cs
@@ -45,7 +45,7 @@
            IOperationConfig config = args.Arguments.FirstOrDefault() as IOperationConfig;
            if (config == null)
            {
                throw new ProcessException("设备操作转入参数类型错误!", null);
                throw new ProcessException("设备操作转入参数类型错误!");
            }
            if (device.DeviceMode == DeviceMode.Run)
src/Bro.Device.OmronFins/FinsFrame.cs
@@ -292,7 +292,7 @@
                List<byte> errorCode = bytes.Skip(4 + 4 + 4).Take(4).ToList();
                if (errorCode.Any(u => u != 0x00))
                {
                    throw new ProcessException($"返回异常,错误码{string.Join("", errorCode)}", null);
                    throw new ProcessException($"返回异常,错误码{string.Join("", errorCode)}");
                }
                bytes = bytes.Skip(4 + 4 + 4 + 4).ToArray();
@@ -323,7 +323,7 @@
            if (beginIndex == -1)
            {
                throw new ProcessException("欧姆龙PLC通讯帧错误", null);
                throw new ProcessException("欧姆龙PLC通讯帧错误");
            }
            List<byte> singleBytes = bytes.Skip(beginIndex).ToList();
@@ -411,7 +411,7 @@
            if (wordAddress == 0 && byteAddress == 0)
            {
                throw new ProcessException("欧姆龙PLC监听地址设置错误", null);
                throw new ProcessException("欧姆龙PLC监听地址设置错误");
            }
            //字地址
src/Bro.Device.OmronFins/OmronFinsDriver.cs
@@ -13,6 +13,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Bro.Common.Helper.EnumHelper;
namespace Bro.Device.OmronFins
{
@@ -31,11 +32,11 @@
            }
            byte[] data = opFrame.GetSendReadFrameBytes(item, CurrentSid);
            var stream = opClient.GetStream();
            stream.Write(data, 0, data.Length);
            //var stream = opClient.GetStream();
            opStream.Write(data, 0, data.Length);
            byte[] buffer = new byte[2048];
            int readSize = stream.Read(buffer, 0, buffer.Length);
            int readSize = opStream.Read(buffer, 0, buffer.Length);
            if (readSize > 0)
            {
@@ -70,7 +71,7 @@
            Stopwatch sw = new Stopwatch();
            sw.Start();
            NetworkStream stream = null;
            //NetworkStream stream = null;
            lock (opClientLock)
            {
                do
@@ -80,13 +81,13 @@
                        InitialOpClient();
                        byte[] data = opFrame.GetSendWriteFrameBytes(item, CurrentSid);
                        stream = opClient.GetStream();
                        stream.Write(data, 0, data.Length);
                        //stream = opClient.GetStream();
                        opStream.Write(data, 0, data.Length);
                        sw.Stop();
                        if (sw.ElapsedMilliseconds > 10)
                        {
                            LogAsync(DateTime.Now, $"发送完成,耗时:{sw.ElapsedMilliseconds}");
                            LogAsync(DateTime.Now, $"发送完成", $"发送耗时:{sw.ElapsedMilliseconds}");
                        }
                        repeatTime = 0;
@@ -101,7 +102,7 @@
                        }
                        else
                        {
                            LogAsync(DateTime.Now, "Send Exception:" + ex.GetExceptionMessage());
                            LogAsync(DateTime.Now, "Send Exception", ex.GetExceptionMessage());
                        }
                    }
                } while (repeatTime > 0);
@@ -111,7 +112,7 @@
                    try
                    {
                        byte[] buffer = new byte[1024];
                        int bufferCount = stream.Read(buffer, 0, buffer.Length);
                        int bufferCount = opStream.Read(buffer, 0, buffer.Length);
                        if (bufferCount > 0)
                        {
                            opFrame.AnalyseReceivedItems(buffer.Take(bufferCount).ToArray(), item);
@@ -119,7 +120,7 @@
                    }
                    catch (Exception ex)
                    {
                        LogAsync(DateTime.Now, "写入反馈异常\r\n" + ex.GetExceptionMessage());
                        LogAsync(DateTime.Now, "写入反馈异常", ex.GetExceptionMessage());
                    }
                }
                else
@@ -129,7 +130,7 @@
                        try
                        {
                            byte[] buffer = new byte[1024];
                            int bufferCount = stream.Read(buffer, 0, buffer.Length);
                            int bufferCount = opStream.Read(buffer, 0, buffer.Length);
                            if (bufferCount > 0)
                            {
                                opFrame.AnalyseReceivedItems(buffer.Take(bufferCount).ToArray(), item);
@@ -137,7 +138,7 @@
                        }
                        catch (Exception ex)
                        {
                            LogAsync(DateTime.Now, "写入反馈异常\r\n" + ex.GetExceptionMessage());
                            LogAsync(DateTime.Now, "写入反馈异常", ex.GetExceptionMessage());
                        }
                    });
                }
@@ -163,6 +164,7 @@
            item.ADDRESS = "D" + address.ToString();
            item.ITEM_LENGTH = 1;
            item.ITEM_VALUE_TYPE = 1;
            item.ITEM_VALUE = writeValue.ToString();
            //lock (opClient)
@@ -403,7 +405,9 @@
        static object opClientLock = new object();
        TcpClient scanClient = new TcpClient();
        NetworkStream scanStream = null;
        TcpClient opClient = new TcpClient();
        NetworkStream opStream = null;
        IPEndPoint plcEP;
@@ -455,10 +459,10 @@
                scanFrame = new FinsFrame(IConfig.DNA, serverNode, IConfig.DA2, IConfig.SNA, scanNode, IConfig.SA2, false);
                byte[] dataRequest = scanFrame.GetTcpRequestFrame(0);
                var stream = scanClient.GetStream();
                stream.Write(dataRequest, 0, dataRequest.Length);
                scanStream = scanClient.GetStream();
                scanStream.Write(dataRequest, 0, dataRequest.Length);
                byte[] dataRead = new byte[2048];
                int readSize = stream.Read(dataRead, 0, dataRead.Length);
                int readSize = scanStream.Read(dataRead, 0, dataRead.Length);
                if (readSize <= 0)
                {
@@ -489,10 +493,10 @@
                opFrame = new FinsFrame(IConfig.DNA, serverNode, IConfig.DA2, IConfig.SNA, opNode, IConfig.SA2, false);
                byte[] dataRequest = opFrame.GetTcpRequestFrame(0);
                var stream = opClient.GetStream();
                stream.Write(dataRequest, 0, dataRequest.Length);
                opStream = opClient.GetStream();
                opStream.Write(dataRequest, 0, dataRequest.Length);
                byte[] dataRead = new byte[2048];
                int readSize = stream.Read(dataRead, 0, dataRead.Length);
                int readSize = opStream.Read(dataRead, 0, dataRead.Length);
                if (readSize <= 0)
                {
@@ -524,14 +528,15 @@
            PLC_ITEM item = new PLC_ITEM();
            item.ADDRESS = "D" + startAddress;
            item.OP_TYPE = 1;
            item.ITEM_VALUE_TYPE = 1;
            item.ITEM_LENGTH = length;
            var data = opFrame.GetSendReadFrameBytes(item, CurrentSid);
            var stream = opClient.GetStream();
            stream.Write(data, 0, data.Length);
            opStream.Write(data, 0, data.Length);
            opStream.Flush();
            byte[] buffer = new byte[2048];
            int readSize = stream.Read(buffer, 0, buffer.Length);
            int readSize = opStream.Read(buffer, 0, buffer.Length);
            if (readSize > 0)
            {
@@ -555,6 +560,7 @@
            PLC_ITEM item = new PLC_ITEM();
            item.ADDRESS = "D" + startAddress;
            item.OP_TYPE = 1;
            item.ITEM_VALUE_TYPE = 1;
            item.ITEM_LENGTH = length;
            if (scanBuffer == null)
@@ -562,11 +568,11 @@
                scanBuffer = scanFrame.GetSendReadFrameBytes(item, CurrentSid);
            }
            var stream = scanClient.GetStream();
            stream.Write(scanBuffer, 0, scanBuffer.Length);
            //var stream = scanClient.GetStream();
            scanStream.Write(scanBuffer, 0, scanBuffer.Length);
            byte[] buffer = new byte[2048];
            int readSize = stream.Read(buffer, 0, buffer.Length);
            int readSize = scanStream.Read(buffer, 0, buffer.Length);
            if (readSize > 0)
            {
@@ -578,69 +584,52 @@
                return new List<int>();
            }
        }
        #endregion
        #region Log
        object logLock = new object();
        private async void LogAsync(DateTime dt, string info)
        protected override void OnMethodInvoked(IAsyncResult ar)
        {
            await Task.Run(() =>
            MonitorSet monitorSet = ar.AsyncState as MonitorSet;
            ProcessResponse resValues = monitorSet.Response;
            if (resValues.ResultValue == (int)PLCReplyValue.IGNORE)
            {
                if (IConfig.IsEnabelLog)
                {
                    lock (logLock)
                    {
                        DirectoryInfo dir = new DirectoryInfo(IConfig.LogPath);
                        if (!dir.Exists)
                        {
                            dir.Create();
                return;
                        }
                        string filePath = Path.Combine(IConfig.LogPath, "PLC_" + DateTime.Now.ToString("yyyyMMdd") + ".txt");
                        using (StreamWriter writer = new StreamWriter(filePath, true))
            if (monitorSet.ReplyDataAddress != -1 && resValues.DataList.Count > 0)
                        {
                            writer.WriteLine(dt.ToString("HH:mm:ss.fff"));
                            writer.WriteLine(info);
                            writer.WriteLine();
                            writer.Flush();
                            writer.Close();
                        }
                    }
                }
            });
                PLC_ITEM item = new PLC_ITEM();
                item.OP_TYPE = 2;
                item.ITEM_LENGTH = resValues.DataList.Count;
                item.ADDRESS = "D" + monitorSet.ReplyDataAddress.ToString();
                item.ITEM_VALUE = String.Join(",", resValues.DataList);
                item.ITEM_VALUE_TYPE = (int)PLCItemType.Integer;
                WriteItem(item, false);
        }
        //private async void LogAsync(DateTime dt, IModbusFrame frame)
        //{
        //    await Task.Run(() =>
        //    {
        //        if (IConfig.IsEnabelLog)
        //        {
        //            lock (logLock)
        //            {
        //                DirectoryInfo dir = new DirectoryInfo(IConfig.LogPath);
        //                if (!dir.Exists)
        //                {
        //                    dir.Create();
        //                }
            if (monitorSet.NoticeAddress != -1)
            {
                int repeatTime = 5;
        //                string filePath = Path.Combine(IConfig.LogPath, "PLC_" + DateTime.Now.ToString("yyyyMMdd") + ".txt");
                do
                {
                    try
                    {
                        this.WriteSingleAddress(monitorSet.NoticeAddress, resValues.ResultValue, false);
                        repeatTime = 0;
                    }
                    catch (Exception ex)
                    {
                        repeatTime--;
        //                using (StreamWriter writer = new StreamWriter(filePath, true))
        //                {
        //                    writer.WriteLine(dt.ToString("HH:mm:ss.fff"));
        //                    writer.WriteLine(frame.GetFrameString());
        //                    writer.WriteLine();
        //                    writer.Flush();
        //                    writer.Close();
        //                }
        //            }
        //        }
        //    });
        //}
                        if (repeatTime <= 0)
                        {
                            new ProcessException("PLC反馈写入异常", ExceptionLevel.Info, ex);
                        }
                    }
                } while (repeatTime > 0);
            }
        }
        #endregion
    }
}
src/Bro.Device.SeerAGV/Bro.Device.SeerAGV.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.SeerAGV/SeerAGVConfig.cs
@@ -1,16 +1,12 @@
using Bro.Common.Base;
using Bro.Common.Helper;
using Bro.Common.Model.Helper;
using Bro.Common.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Bro.Device.SeerAGV
{
@@ -31,7 +27,19 @@
        [Category("监听配置")]
        [Description("监听间隔,单位ms")]
        public int ScanInterval { get; set; } = 200;
        public int ScanInterval { get; set; } = 500;
        //[Category("监听配置")]
        //[Description("监听的IO信息配置集合")]
        //[TypeConverter(typeof(CollectionCountConvert))]
        //[Editor(typeof(ComplexCollectionEditor<IODefinition>), typeof(UITypeEditor))]
        //public List<IODefinition> IOCollection { get; set; } = new List<IODefinition>();
        [Category("监听设置")]
        [Description("监听操作配置集合")]
        [TypeConverter(typeof(CollectionCountConvert))]
        [Editor(typeof(ComplexCollectionEditor<MonitorSet>), typeof(UITypeEditor))]
        public List<MonitorSet> MonitorSetCollection { get; set; } = new List<MonitorSet>();
        [Category("监听配置")]
        [Description("是否采用简单监听模式。true:简单模式,只获取任务状态;false:全部模式,获取任务所有信息")]
@@ -53,6 +61,22 @@
            }
        }
        private float batteryLvlToCharge_Recommand = 0.3f;
        [Category("充电配置")]
        [Description("充电电池容量,电池容量低于该值时,设备空闲时建议充电")]
        public float BatteryLvlToCharge_Recommand
        {
            get => batteryLvlToCharge_Recommand;
            set
            {
                if (value >= 1 || value <= 0)
                {
                    value = 0.3f;
                }
                batteryLvlToCharge_Recommand = value;
            }
        }
        private float batteryLvlChargeDone = 0.9f;
        [Category("充电配置")]
        [Description("充电完成电池容量,电池容量高于该值时确认充电完成")]
@@ -68,12 +92,22 @@
                batteryLvlChargeDone = value;
            }
        }
        [Category("动作设置")]
        [Description("动作超时设置,单位min")]
        public float OperationTimeout { get; set; } = 5;
    }
    [Device("SeerAGV", "SeerAGV", EnumHelper.DeviceAttributeType.OperationConfig)]
    public class SeerAGVOperationConfig : OperationConfigBase
    {
        [Category("导航配置")]
        [Description("AGV行驶目的地")]
        public string Location { get; set; }
        [Category("导航配置")]
        [Description("是否等待完成信号")]
        public bool IsWaitFinished { get; set; } = true;
    }
    public class SeerMessage : IComplexDisplay
@@ -143,7 +177,7 @@
            if (data.Length < 16 + msg.DataLength)
            {
                throw new ProcessException("数据长度错误", null);
                throw new ProcessException("数据长度错误");
            }
            msg.JsonData = data.Skip(16).Take(msg.DataLength).ToArray();
@@ -171,6 +205,7 @@
    {
        QueryPosition = 0x03EC,
        QueryTaskStatus = 0x03FC,
        QueryIO = 0x03F5,
        CancelTask = 0x0BBB,
        PauseTask = 0x0BB9,
src/Bro.Device.SeerAGV/SeerAGVDriver.cs
@@ -1,25 +1,27 @@
using Bro.Common.Base;
using Bro.Common.Helper;
using Bro.Common.Interface;
using Bro.Common.Model;
using Bro.Common.Model.Interface;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Bro.Device.SeerAGV
{
    [Device("SeerAGV", "SeerAGV", EnumHelper.DeviceAttributeType.Device)]
    public class SeerAGVDriver : DeviceBase
    public class SeerAGVDriver : DeviceBase, IMonitor
    {
        public Action<SeerAGVDriver, string> OnAGVPositoinChanged;
        public Action<SeerAGVDriver, AGVTaskStatus> OnAGVTaskStatusChanged;
        public Action<SeerAGVDriver, float, float> OnAGVBatteryLvlChanged;
        public Action<SeerAGVDriver, IODefinition> OnAGVIOChanged;
        SeerAGVInitialConfig IConfig
        {
@@ -39,6 +41,15 @@
        {
            InitialTcpClient(ref client_State, IConfig.StatusPort);
            InitialTcpClient(ref client_Guide, IConfig.GuidePort);
            StateHandle = new ManualResetEvent(false);
            PositionHandle = new ManualResetEvent(false);
            BatteryHandle = new ManualResetEvent(false);
            IOHandle = new ManualResetEvent(false);
            _taskDoneHandle = new ManualResetEvent(false);
            _monitorLock = new ManualResetEvent(true);
            _isTaskRunning = false;
        }
        private void InitialTcpClient(ref TcpClient client, int port)
@@ -66,6 +77,15 @@
            msg_Position = new SeerMessage((int)AGVCode.QueryPosition, SID);
            msg_GuideStatus = new SeerMessage((int)AGVCode.QueryTaskStatus, SID, IConfig.IsSimpleMonitor ? JsonConvert.SerializeObject(new { simple = true }) : "");
            msg_Battery = new SeerMessage((int)AGVCode.QueryBattery, SID, IConfig.IsSimpleMonitor ? JsonConvert.SerializeObject(new { simple = true }) : "");
            msg_IO = new SeerMessage((int)AGVCode.QueryIO, SID);
            _taskDoneHandle.Reset();
            _monitorLock.Set();
            Task.Run(() =>
            {
                Monitor();
            });
            Task.Run(() =>
            {
@@ -75,6 +95,14 @@
        protected override void Stop()
        {
            StateHandle.Set();
            PositionHandle.Set();
            BatteryHandle.Set();
            IOHandle.Set();
            _taskDoneHandle.Set();
            _monitorLock.Set();
            if (client_Guide != null && client_Guide.Connected)
            {
                CancelTask();
@@ -126,6 +154,8 @@
            }
        }
        List<AGVTaskStatus> _taskRunningStates = new List<AGVTaskStatus>() { AGVTaskStatus.Running, AGVTaskStatus.Waiting, AGVTaskStatus.Suspended };
        List<AGVTaskStatus> _taskDoneStates = new List<AGVTaskStatus>() { AGVTaskStatus.Cancelled, AGVTaskStatus.Completed, AGVTaskStatus.Failed };
        AGVTaskStatus taskStatus = AGVTaskStatus.None;
        public AGVTaskStatus TaskStatus
        {
@@ -138,6 +168,12 @@
                    if (taskStatus != AGVTaskStatus.None)
                    {
                        if (_isTaskRunning && _taskDoneStates.Contains(taskStatus))
                        {
                            _taskDoneHandle.Set();
                            _isTaskRunning = false;
                        }
                        OnAGVTaskStatusChanged?.Invoke(this, taskStatus);
                    }
                }
@@ -159,13 +195,24 @@
            }
        }
        public string LastStation { get; set; }
        public string Destination { get; set; } = "";
        SeerMessage msg_Position = new SeerMessage();
        SeerMessage msg_GuideStatus = new SeerMessage();
        SeerMessage msg_Battery = new SeerMessage();
        SeerMessage msg_IO = new SeerMessage();
        public ManualResetEvent StateHandle { get; set; } = new ManualResetEvent(false);
        public ManualResetEvent PositionHandle { get; set; } = new ManualResetEvent(false);
        public ManualResetEvent BatteryHandle { get; set; } = new ManualResetEvent(false);
        public ManualResetEvent IOHandle { get; set; } = new ManualResetEvent(false);
        private void MonitorAGV()
        {
            while (CurrentState != EnumHelper.DeviceState.DSClose && CurrentState != EnumHelper.DeviceState.DSExcept)
            {
                _monitorLock.WaitOne();
                try
                {
                    SendMsg(client_State, IConfig.StatusPort, msg_Position);
@@ -174,10 +221,12 @@
                    Thread.Sleep(IConfig.ScanInterval);
                    SendMsg(client_State, IConfig.StatusPort, msg_Battery);
                    Thread.Sleep(IConfig.ScanInterval);
                    SendMsg(client_State, IConfig.StatusPort, msg_IO);
                    Thread.Sleep(IConfig.ScanInterval);
                }
                catch (Exception ex)
                {
                    OnLog?.Invoke(DateTime.Now, this, $"{Name}监听异常:{ex.GetExceptionMessage()}");
                    LogAsync(DateTime.Now, "监听异常", ex.GetExceptionMessage());
                }
            }
        }
@@ -194,7 +243,7 @@
                    var stream = client.GetStream();
                    stream.Write(msg.Frame, 0, msg.Frame.Length);
                    stream.Flush();
                    int recSize = stream.Read(buffer, 0, buffer.Length);
                    if (recSize > 0)
                    {
@@ -202,7 +251,7 @@
                        SeerMessage recMsg = SeerMessage.GetSeerMessage(rec);
                        if (recMsg.TypeCode != (10000 + msg.TypeCode) || recMsg.SeqNum != msg.SeqNum)
                        {
                            throw new ProcessException("反馈信息和发送信息不一致", null);
                            throw new ProcessException("反馈信息和发送信息不一致");
                        }
                        if (recMsg.JValues.ContainsKey("ret_code"))
@@ -210,7 +259,7 @@
                            int retCode = recMsg.JValues.Value<int>("ret_code");
                            if (retCode != 0)
                            {
                                throw new ProcessException($"反馈信息异常,异常码:{retCode}", null);
                                throw new ProcessException($"反馈信息异常,异常码:{retCode}");
                            }
                        }
@@ -222,18 +271,18 @@
                catch (SocketException ex)
                {
                    repeatTime--;
                    new ProcessException("AGV网络通信异常", ex);
                    new ProcessException("AGV网络通信异常", ExceptionLevel.Warning, ex);
                    if (repeatTime <= 0)
                    {
                        throw new ProcessException("AGV网络通信失败", ex);
                        throw new ProcessException("AGV网络通信失败", ExceptionLevel.Warning, ex);
                    }
                    Thread.Sleep(IConfig.ScanInterval);
                }
                catch (Exception ex)
                {
                    throw new ProcessException("AGV信息发送异常", ex);
                    throw new ProcessException("AGV信息发送异常", ExceptionLevel.Warning, ex);
                }
            } while (repeatTime > 0);
@@ -247,12 +296,35 @@
                {
                    case (int)AGVCode.QueryPosition:
                        CurrentPosition = recMsg.JValues.Value<string>("current_station");
                        LastStation = recMsg.JValues.Value<string>("last_station");
                        PositionHandle.Set();
                        break;
                    case (int)AGVCode.QueryTaskStatus:
                        TaskStatus = (AGVTaskStatus)recMsg.JValues.Value<int>("task_status");
                        StateHandle.Set();
                        break;
                    case (int)AGVCode.QueryBattery:
                        BatteryLvl = recMsg.JValues.Value<float>("battery_level");
                        BatteryHandle.Set();
                        break;
                    case (int)AGVCode.QueryIO:
                        //recMsg.JValues.Value<JArray>("DI").ToList().ForEach(j =>
                        //{
                        //    var ioDefinition = IConfig.IOCollection.FirstOrDefault(i => i.IOIndex == j.Value<int>("id"));
                        //    if (ioDefinition != null && j.Value<bool>("status") != ioDefinition.CurrentValue)
                        //    {
                        //        ioDefinition.CurrentValue = j.Value<bool>("status");
                        //        OnAGVIOChanged?.Invoke(this, ioDefinition);
                        //    }
                        //});
                        //高电平 1  低电平 0
                        _ioDict = recMsg.JValues.Value<JArray>("DI").ToDictionary(u => u.Value<int>("id"), u => u.Value<bool>("status") ? 1 : 0);
                        IOHandle.Set();
                        break;
                    default:
                        break;
@@ -272,13 +344,102 @@
            SendMsg(client_Guide, IConfig.GuidePort, msg);
        }
        public void TaskOrder(string dest)
        ManualResetEvent _taskDoneHandle = new ManualResetEvent(false);
        ManualResetEvent _monitorLock = new ManualResetEvent(true);
        bool _isTaskRunning = false;
        public void TaskOrder(string dest, bool isWaitFinished)
        {
            CurrentPosition = "";
            SeerMessage msg = new SeerMessage((int)AGVCode.TaskOrder, SID, JsonConvert.SerializeObject(new { id = dest }));
            _monitorLock.Reset();
            Thread.Sleep(IConfig.ScanInterval);
            OnLog?.BeginInvoke(DateTime.Now, this, $"{Name}行驶向 {dest}", null, null);
            CurrentPosition = "";
            Destination = dest;
            TaskStatus = AGVTaskStatus.None;
            SeerMessage msg = new SeerMessage((int)AGVCode.TaskOrder, SID, JsonConvert.SerializeObject(new { id = dest }));
            //OnLog?.BeginInvoke(DateTime.Now, this, $"{Name}行驶向 {dest}", null, null);
            LogAsync(DateTime.Now, "", $"{Name}行驶向 {dest}");
            SendMsg(client_Guide, IConfig.GuidePort, msg);
            //Thread.Sleep(IConfig.ScanInterval);
            _monitorLock.Set();
            if (isWaitFinished)
            {
                _isTaskRunning = true;
                _taskDoneHandle.Reset();
                bool isNotTimeout = _taskDoneHandle.WaitOne((int)(IConfig.OperationTimeout * 60 * 1000));
                if (!isNotTimeout)
                {
                    throw new ProcessException($"{Name}执行去往{dest}动作超时");
        }
                if (TaskStatus == AGVTaskStatus.Failed)
                {
                    throw new ProcessException($"{Name}执行去往{dest}动作失败");
                }
                else if (TaskStatus == AGVTaskStatus.Cancelled)
                {
                    //OnLog?.Invoke(DateTime.Now, this, $"{Name}执行去往{dest}动作取消");
                    LogAsync(DateTime.Now, "", $"{Name}执行去往{dest}动作取消");
                }
            }
        }
        #region IMonitor
        public event OnMonitorInvokeDelegate OnMonitorInvoke;
        public event OnMonitorAlarmDelegate OnMonitorAlarm;
        Dictionary<int, int> _ioDict = new Dictionary<int, int>();
        public void Monitor()
        {
            while (CurrentState != EnumHelper.DeviceState.DSClose && CurrentState != EnumHelper.DeviceState.DSExcept)
            {
                try
                {
                    IOHandle.Reset();
                    var isNotTimeout = IOHandle.WaitOne(IConfig.ScanInterval * 3);
                    if (!isNotTimeout)
                        throw new ProcessException($"{Name}监听超时");
                    IConfig.MonitorSetCollection.ForEach(m =>
                    {
                        if (_ioDict.ContainsKey(m.TriggerIndex))
                        {
                            if ((m.TriggerValue == _ioDict[m.TriggerIndex] || m.TriggerIndex == -999) && _ioDict[m.TriggerIndex] != m.CurrentValue)
                            {
                                if (m.OpConfig == null)
                                {
                                    m.OpConfig = new OperationConfigBase();
                                }
                                m.OpConfig.InputPara = m.InputDataIndex.ConvertAll(index =>
                                {
                                    if (_ioDict.ContainsKey(index))
                                    {
                                        return _ioDict[index];
                                    }
                                    else
                                    {
                                        return -1;
                                    }
                                }).ToList();
                                OnMonitorInvoke?.BeginInvoke(DateTime.Now, this, m, null, null);
                            }
                            m.CurrentValue = _ioDict[m.TriggerIndex];
                        }
                    });
                }
                catch (Exception ex)
                {
                    OnLog?.Invoke(DateTime.Now, this, $"{Name}监听异常:{ex.GetExceptionMessage()}");
                }
            }
        }
        #endregion
    }
}