using HslCommunication; using ProjectManagementSystem.Common.Config; using ProjectManagementSystem.Common.Extenions; using ProjectManagementSystem.Common.Logger; using ProjectManagementSystem.Common.Models; using ProjectManagementSystem.Common.WebApi; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ProjectManagementSystem.Common.Core { public class PackControl { private static PackControl m_instance; private bool checkOnline = true; private bool firstStart = true; public ConcurrentDictionary StationDictionary { get; set; } = new ConcurrentDictionary(); public static PackControl Instance { get { if (m_instance == null) { m_instance = new PackControl(); if (!m_instance.Initialize()) { m_instance = null; } } return m_instance; } } private PackControl() { } private bool Initialize() { try { var configList = ExcelConfig.Instance.RouteConfigList; for (int i = 0; i < configList.Count; i++) { var config = configList[i]; PackStation data = StationDictionary.ContainsKey(config.LocationCode) ? StationDictionary[config.LocationCode] : new PackStation(); data.Id = config.LocationCode; data.routeConfig = config; data.请求进入导航点 = config.LocationCode.GetLocationMember("AGV请求进入导航点").ToValue(); data.EnteringEdges = config.LocationCode.GetLocationMember("AGV正在进入路段").ToValueArray(); data.EnteringVertexs = config.LocationCode.GetLocationMember("AGV正在进入导航点").ToValueArray(); data.工位导航点 = config.GraphVertex; data.LeavingEdges = config.LocationCode.GetLocationMember("AGV正在离开路段").ToValueArray(); data.LeavingVertexs = config.LocationCode.GetLocationMember("AGV正在离开导航点").ToValueArray(); StationDictionary.AddOrUpdate(data.Id, data, (key, value) => data); } Task.Factory.StartNew(UserThread); Task.Factory.StartNew(UserThread2); return true; } catch (Exception ex) { CLog.Instance.SystemLog.WriteException("PackControl Initialize", ex); } return false; } private void TaskCustomProc() { //return; //AGV状态 //0:空闲, //1:配送中, //3:完成 //4:充电中 //5:发生异常 //默认值:65535 //return; var agvDataList1 = PmsApi.GetAllCarrier(); if (agvDataList1 == null) return; var agvDataList = checkOnline ? agvDataList1.Where(d => d.Online) : agvDataList1.Where(d => true); var configList = ExcelConfig.Instance.RouteConfigList; for (int i = 0; i < configList.Count; i++) { var config = configList[i]; if (!config.PackTaskAddCooled) { //CLog.Instance.TaskLog.WriteInfo($"{config.LocationCode} 限制添加任务(未冷却)"); continue; } //已到位的AGV可以出发后任务模板 var agvData = agvDataList?.FirstOrDefault(d => /*d.Status == 0 && */d.GraphVertex == config.GraphVertex); if (agvData != null) { var taskList = PmsApi.GetTaskList(); if (taskList != null && taskList.FirstOrDefault(d => d.Carrier == agvData.AgvID || d.BindingAGVNumber == agvData.AgvID) == null) { //添加任务 string nextLocation = GetPlcNextLocation(config, agvData.AgvID, out string templateName); if (!string.IsNullOrEmpty(nextLocation) && !string.IsNullOrEmpty(templateName)) { AddPackTask(templateName, agvData.AgvID, config.LocationCode, nextLocation); } } } //工位前的任务导航点:可根据前任务导航点,匹配不一样的任务模板 var vertexArray = config.LocationCode.GetLocationMember("前任务导航点").ToValueArray(); for (int index = 0; index < vertexArray.Length; index++) { int vertex = vertexArray[index]; agvData = agvDataList?.FirstOrDefault(d => d.Status == 0 && vertex == d.GraphVertex); if (agvData != null) { //判断是否直通 var passToNextLocation = GetPlcAllowToPassNextLocation(config, agvData.AgvID); if (!string.IsNullOrEmpty(passToNextLocation)) { string preTemplateName = config.LocationCode.GetLocationMember("直通任务模板").GetArrayValue(index); AddPackTask(preTemplateName, agvData.AgvID, config.LocationCode, passToNextLocation); } else { string preTemplateName = config.LocationCode.GetLocationMember("前任务模板").GetArrayValue(index); AddPackTask(preTemplateName, agvData.AgvID, config.LocationCode); } } } } } private string GetPlcAllowToPassNextLocation(RouteConfig config, int agvId) { string plcAllowToPass = config.LocationCode.GetLocationMember("PLC允许通过"); if (string.IsNullOrEmpty(plcAllowToPass)) return null; var plc = config.PlcReadWrite; var read = plc.ReadBool(plcAllowToPass); if (!read.IsSuccess && !read.Content) { return null; } string nextLocation = config.LocationCode.GetLocationMember("下个工位"); if (string.IsNullOrEmpty(nextLocation)) { CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 没配置下个工位"); return null; } return nextLocation.ToValueArray()[0]; } private string GetPlcNextLocation(RouteConfig config, int agvId, out string templateName) { if (!checkOnline) { //模拟测试 templateName = config.LocationCode.GetLocationMember("后任务模板"); return config.LocationCode.GetLocationMember("下个工位").ToValueArray()[0]; } templateName = null; if (string.IsNullOrEmpty(config.PlcIpAddr)) { return null; } //var plc放行 = config.LocationCode.GetLocationMember("PLC放行"); //产生任务的地址改成允许上升地址 var plc放行 = config.LocationCode.GetLocationMember("PLC允许上升"); if (string.IsNullOrEmpty(plc放行)) { plc放行 = config.LocationCode.GetLocationMember("PLC放行"); if (string.IsNullOrEmpty(plc放行)) { return null; } } var plc = config.PlcReadWrite; var read = plc.ReadBool(plc放行); if (!read.IsSuccess || !read.Content) { return null; } //已有放行信号 string nextLocation = config.LocationCode.GetLocationMember("下个工位"); if (string.IsNullOrEmpty(nextLocation)) { CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 没配置下个工位"); return null; } templateName = config.LocationCode.GetLocationMember("后任务模板"); nextLocation = nextLocation.ToValueArray()[0]; var plc返修 = config.LocationCode.GetLocationMember("PLC返修"); if (string.IsNullOrEmpty(plc返修)) { //没有配置返修地址,直接去下个工位 CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 没配置返修地址,去默认下个工位 {nextLocation} (读取{plc放行} = {read.Content})"); return nextLocation; } read = plc.ReadBool(plc返修); if (!read.IsSuccess) { return null; } if (read.Content == false) { //不需要返修去下个工位 CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 不需要返修,去默认下个工位 {nextLocation} (读取{plc返修} = {read.Content})"); return nextLocation; } //已有需要返修的信号 templateName = config.LocationCode.GetLocationMember("返修任务模板"); if (string.IsNullOrEmpty(templateName)) { CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 需要返修,但没配置返修任务模板"); return null; } //需要返修去固定返修位置 string ngLocation = config.LocationCode.GetLocationMember("返修工位"); if (string.IsNullOrEmpty(ngLocation)) { CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 需要返修,但没配置返修工位"); return null; } CLog.Instance.GetAgvLog(agvId).WriteInfo($"{config.LocationCode} 需要返修,去返修工位 {ngLocation} (读取{plc返修} = {read.Content})"); return ngLocation; } private bool AddPackTask(string templateName, int agvId, params string[] locations) { //添加任务 if (string.IsNullOrEmpty(templateName)) return false; var taskList = PmsApi.GetTaskList(); if (taskList == null) return false; var taskData = taskList.FirstOrDefault(d => d.Carrier == agvId || d.BindingAGVNumber == agvId); if (taskData != null) { //指定AGV已经有任务,就不添任务 return false; } MidTaskInfoEx data = new MidTaskInfoEx(); data.TemplateName = templateName; for (int i = 0; i < locations.Length; i++) { data.ParametersDic.Add($"U{i + 1}", locations[i]); } data.AgvType = "0"; data.AgvId = agvId; data.Priority = 0; data.CreateTime = DateTime.Now; bool result = PmsApi.TaskAdd(data); if (result) { for (int i = 0; i < locations.Length; i++) { var location = locations[i]; var config = ExcelConfig.Instance.GetRouteConfig(location); if (config != null) { config.LastPackTaskAddTime = DateTime.Now; } } } return result; } private void UpdateStationDict(bool[] forceWrite) { var agvDataList1 = PmsApi.GetAllCarrier(); if (agvDataList1 == null) return; var agvDataList = checkOnline ? agvDataList1.Where(d => d.Online) : agvDataList1.Where(d => true); DateTime start = DateTime.Now; Parallel.ForEach(StationDictionary.Values, delegate (PackStation data, ParallelLoopState loopState) { try { var agvData = agvDataList.FirstOrDefault(d => data.请求进入导航点 > 0 && d.GraphVertex == data.请求进入导航点); bool result = agvData != null; data.请求进入AGV信息 = result ? $"({agvData.AgvID}# 路段:{agvData.GraphEdge} 导航点:{agvData.GraphVertex})" : null; if (data.请求进入 != result || forceWrite[0]) { data.请求进入 = result; HandleAgvToPlc(data.routeConfig, "AGV请求进入", data.请求进入, data.请求进入AGV信息); } var agvDataAtStation = agvDataList.FirstOrDefault(d => data.工位导航点 > 0 && d.GraphVertex == data.工位导航点); result = agvDataAtStation != null; data.到位AGV信息 = result ? $"({agvDataAtStation.AgvID}# 路段:{agvDataAtStation.GraphEdge} 导航点:{agvDataAtStation.GraphVertex})" : null; if (data.到位 != result || forceWrite[1]) { data.到位 = result; HandleAgvToPlc(data.routeConfig, "AGV到位", data.到位, data.到位AGV信息); } var agvDataEntering = agvDataList.FirstOrDefault(d => (d.GraphEdge > 0 && data.EnteringEdges.Length > 0 && data.EnteringEdges.Contains(d.GraphEdge)) || (d.GraphVertex > 0 && data.EnteringVertexs.Length > 0 && data.EnteringVertexs.Contains(d.GraphVertex))); result = agvDataEntering != null; data.正在进入AGV信息 = result ? $"({agvDataEntering.AgvID}# 路段:{agvDataEntering.GraphEdge} 导航点:{agvDataEntering.GraphVertex})" : null; if (data.正在进入 != result || firstStart) { data.正在进入 = result; HandleAgvToPlc(data.routeConfig, "AGV正在进入", data.正在进入, data.正在进入AGV信息); } if (forceWrite[2]) { data.正在进入 = result; if (data.正在进入) { HandleAgvToPlc(data.routeConfig, "AGV正在进入", data.正在进入, data.正在进入AGV信息); } else { //清零信号需要已经离开前面的导航点 if (data.请求进入 == false) { HandleAgvToPlc(data.routeConfig, "AGV正在进入", data.正在进入, data.正在进入AGV信息); } } } var agvDataLeaving = agvDataList.FirstOrDefault(d => (d.GraphEdge > 0 && data.LeavingEdges.Length > 0 && data.LeavingEdges.Contains(d.GraphEdge)) || (d.GraphVertex > 0 && data.LeavingVertexs.Length > 0 && data.LeavingVertexs.Contains(d.GraphVertex))); result = agvDataLeaving != null; data.正在离开AGV信息 = result ? $"({agvDataLeaving.AgvID}# 路段:{agvDataLeaving.GraphEdge} 导航点:{agvDataLeaving.GraphVertex})" : null; if (data.正在离开 != result || firstStart) { data.正在离开 = result; HandleAgvToPlc(data.routeConfig, "AGV正在离开", data.正在离开, data.正在离开AGV信息); } if (forceWrite[3]) { data.正在离开 = result; if (data.正在离开) { HandleAgvToPlc(data.routeConfig, "AGV正在离开", data.正在离开, data.正在离开AGV信息); } else { //清零信号需要已经离开前面的导航点 if (data.到位 == false) { HandleAgvToPlc(data.routeConfig, "AGV正在离开", data.正在离开, data.正在离开AGV信息); } } } //LogicBit 9 上升到位状态 512 //LogicBit 10 下降到位状态 1024 result = agvDataAtStation != null ? agvDataAtStation.LogicBits.GetBitVaule(9) : false; data.上升到位AGV信息 = result ? $"({agvDataAtStation.AgvID}# 路段:{agvDataAtStation.GraphEdge} 导航点:{agvDataAtStation.GraphVertex}) LogicBits:{agvDataAtStation.LogicBits}" : null; if (data.AGV上升到位 != result || forceWrite[4]) { data.AGV上升到位 = result; HandleAgvToPlc(data.routeConfig, nameof(data.AGV上升到位), data.AGV上升到位, data.上升到位AGV信息); } result = agvDataAtStation != null ? agvDataAtStation.LogicBits.GetBitVaule(10) : false; data.下降到位AGV信息 = result ? $"({agvDataAtStation.AgvID}# 路段:{agvDataAtStation.GraphEdge} 导航点:{agvDataAtStation.GraphVertex}) LogicBits:{agvDataAtStation.LogicBits}" : null; if (data.AGV下降到位 != result || forceWrite[5]) { data.AGV下降到位 = result; HandleAgvToPlc(data.routeConfig, nameof(data.AGV下降到位), data.AGV下降到位, data.下降到位AGV信息); } var agvId = agvDataAtStation == null ? 0 : agvDataAtStation.AgvID; if (data.AGV编号 != agvId || forceWrite[1]) { data.AGV编号 = agvId; HandleAgvToPlc(data.routeConfig, nameof(data.AGV编号), (ushort)data.AGV编号, data.到位AGV信息); } var alarmId = agvDataAtStation == null || agvDataAtStation.AlarmList == null || agvDataAtStation.AlarmList.Count == 0 ? 0 : agvDataAtStation.AlarmList.OrderByDescending(d => d.alarmLevel).FirstOrDefault().alarmId; if (data.AGV报警代码 != alarmId || forceWrite[1]) { data.AGV报警代码 = alarmId; HandleAgvToPlc(data.routeConfig, nameof(data.AGV报警代码), (ushort)data.AGV报警代码, data.到位AGV信息); } data.异常消息 = null; } catch (Exception ex) { data.异常消息 = ex.Message; } data.耗时 = Math.Round((DateTime.Now - start).TotalMilliseconds); }); } //private void HandleAgvToPlc(RouteConfig routeConfig, string plcAddrDesc, bool agvValue, string agvInfo) //{ // string plcAddr = routeConfig.LocationCode.GetLocationMember(plcAddrDesc); // if (!string.IsNullOrEmpty(plcAddr) // && routeConfig.PlcReadWrite != null // && routeConfig.PlcConnected) // { // var readResult = routeConfig.PlcReadWrite.ReadBool(plcAddr); // if (readResult.IsSuccess // && readResult.Content != agvValue) // { // var result = routeConfig.PlcReadWrite.Write(plcAddr, agvValue); // string strLog = $"{routeConfig.LocationCode} {routeConfig.PlcIpAddr} {plcAddrDesc}:{plcAddr} = {agvValue} 写入{result.IsSuccess.ToChineseString()} {agvInfo}"; // CLog.Instance.TaskLog.WriteInfo(strLog); // } // } //} private void HandleAgvToPlc(RouteConfig routeConfig, string plcAddrDesc, T agvValue, string agvInfo) { string plcAddr = routeConfig.LocationCode.GetLocationMember(plcAddrDesc); if (!string.IsNullOrEmpty(plcAddr) && routeConfig.PlcReadWrite != null && routeConfig.PlcConnected) { var readResult = routeConfig.PlcReadWrite.ReadValue(plcAddr, agvValue, out OperateResult operateResult); if (!readResult) { var result = routeConfig.PlcReadWrite.WriteValue(plcAddr, agvValue); string strLog = $"{routeConfig.LocationCode} {routeConfig.PlcIpAddr} {plcAddrDesc}:{plcAddr} = {agvValue} 写入{result.IsSuccess.ToChineseString()} {agvInfo}"; CLog.Instance.TaskLog.WriteInfo(strLog); } } } private void UserThread() { CLog.Instance.SystemLog.WriteDebug($"PackControl已启动"); uint couter = 0; bool[] forces = new bool[6]; while (true) { try { //错开刷新 var mod = couter % 10; if (mod >= 0 && mod <= forces.Length) { for (int i = 0; i < forces.Length; i++) { forces[i] = (mod == i); } } DateTime start = DateTime.Now; UpdateStationDict(forces); System.Diagnostics.Trace.WriteLine($"UpdateStationDict {string.Join(" ", forces.Select(d => { return d ? "1" : "0"; }))} 耗时:{(DateTime.Now - start).TotalMilliseconds} ms"); } catch (Exception ex) { CLog.Instance.SystemLog.WriteException("PackControl", ex); Thread.Sleep(5000); } firstStart = false; couter++; Thread.Sleep(1000); } } private void UserThread2() { CLog.Instance.SystemLog.WriteDebug($"PackControl2已启动"); while (true) { try { TaskCustomProc(); } catch (Exception ex) { CLog.Instance.SystemLog.WriteException("PackControl2", ex); Thread.Sleep(5000); } Thread.Sleep(500); } } } public class PackStation { public string Id { get; set; } internal RouteConfig routeConfig { get; set; } public int 工位导航点 { get; set; } public int 请求进入导航点 { get; set; } public int[] EnteringVertexs { get; set; } public int[] EnteringEdges { get; set; } public int[] LeavingVertexs { get; set; } public int[] LeavingEdges { get; set; } public bool 请求进入 { get; set; } public bool 正在进入 { get; set; } public bool 到位 { get; set; } public bool 正在离开 { get; set; } public bool 区域有Agv { get { return 正在进入 || 到位 || 正在离开; } } public bool AGV上升到位 { get; set; } public bool AGV下降到位 { get; set; } public int AGV编号 { get; set; } public int AGV报警代码 { get; set; } public string 请求进入AGV信息 { get; set; } public string 正在进入AGV信息 { get; set; } public string 到位AGV信息 { get; set; } public string 正在离开AGV信息 { get; set; } public string 上升到位AGV信息 { get; set; } public string 下降到位AGV信息 { get; set; } public double 耗时 { get; set; } public string 异常消息 { get; set; } } }