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.Specialized; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace A032.Process { public class TrayTask : IComplexDisplay { [Browsable(false)] public string TaskId { get; set; } = Guid.NewGuid().ToString(); /// /// 优先级越高,越快执行 /// [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(); 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(); 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 deviceList = scope.Resolve>(); 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 _disableStates = new List() { 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.Clear(); //bind.UnitState = AGVState.Idle; bind.Reset(); } }); LogAsync(DateTime.Now, "Reset", "执行全体复位操作"); } else { var bind = Config.AGVBindCollection.FirstOrDefault(u => u.Id == bindId); if (bind != null && bind.UnitState == AGVState.Warning) { //bind.WarningMsg.Clear(); //bind.UnitState = AGVState.Idle; bind.Reset(); LogAsync(DateTime.Now, "Reset", $"执行{bind.AGV.Name}复位操作"); } } } static object _taskLock = new object(); NoticedList TrayTaskCollection = new NoticedList(); 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 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 && (u.UnitState == AGVState.Idle || u.UnitState == AGVState.IdleCharge)); } 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 _bindTaskDoneHandleDict = new Dictionary(); 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.Add(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) { string msg = $"{bind.AGV.Name}获取电池状态超时"; bind.WarningMsg.Add(msg); new ProcessException(msg); 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) { string msg = $"{bind.AGV.Name}目前无可用充电地址"; bind.WarningMsg.Add(msg); new ProcessException(msg); bind.UnitState = AGVState.Warning; } else { bind.AGV.TaskOrder(chargePosition.PositionCode, true); } } else { bind.UnitState = AGVState.Idle; } } } _bindTaskDoneHandleDict[bind.Id].Set(); } /// /// 取消当前任务,如果已取卷宗,归还到原处 /// /// private void CancelTask(AGVBindUnit bind) { bind.IsTaskCancelling = true; bind.AGV.CancelTask(); bind.Robot.Move(new RobotPoint(), MoveType.Origin, true); } /// /// 过程异常处理,反馈异常消息到RabbitMQ /// /// /// /// /// 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 任务执行 /// /// 将满Tray放置到产线上 /// /// /// 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; } } } /// /// 将空Tray放置到压机上 /// /// /// 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); } /// /// 从压机上取满Tray到AGV /// /// /// 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); } } /// /// 人工装空Tray到AGV /// /// /// private void LoadEmptyTrayToAGV(TrayTask task, AGVBindUnit bind) { bind.AGV.TaskOrder(task.Location.PositionCode, true); bind.Robot.Load(new RobotPoint(), TrayType.EmptyTray, true); } #endregion } }