using AmrControl.ADS; using AmrControl.Common; using AmrControl.Common.HttpClients; using AmrControl.DB; using AmrControl.DB.Models; using AmrControl.Dto; using AmrControl.exceptions; using AmrControl.mq; using AmrControl.services; using AmrControl.Vo; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Newtonsoft.Json; using NPOI.HSSF.Record; using NPOI.SS.Formula.Functions; using NPOI.SS.UserModel; using NPOI.Util; using NPOI.XSSF.UserModel; using NPOI.XWPF.UserModel; using Org.BouncyCastle.Crypto.Paddings; using Org.BouncyCastle.Ocsp; using Org.BouncyCastle.Utilities; using System.Collections.Concurrent; using System.Diagnostics.Metrics; using System.Drawing; using System.IO; using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using TwinCAT.Ads.TypeSystem; using TwinCAT.PlcOpen; using static NPOI.HSSF.Util.HSSFColor; namespace AmrControl.JGR { /// /// 车辆控制器,用来控制车辆,通过ADS-EtherCAT系统 /// public class JGRManager : IJGR, IDisposable { protected static JGRManager Instance = null; private readonly static object balanceLock = new object(); private IHubContext hubContext; private MyDbHandler dbHandler; private int endpointX = 0, endpointY = 0; /// /// 仓储入库位 /// public readonly int[] inPos = new int[2] { 1, 4 }; /// /// 仓储出库位 /// public readonly int[] outPos = new int[2] { 1, 4 }; /// /// 轨道车入库位 /// public readonly int[] inAmrPos = new int[2] { 30, 3 }; /// /// 轨道车出库位 /// public readonly int[] outAmrPos = new int[2] { 30, 3 }; /// /// 拣选工位坐标 /// public readonly int[] selectStationPos = new int[2] {29, 7 }; /// /// 服务地址 /// private string serviceAddr; private readonly IConfiguration _configuration; private readonly ITaskExecManager _taskExecManager; private readonly IRegistryService _registryService; /// /// 车辆任务占用处理 /// private volatile ConcurrentDictionary jgrTaskMap = new(); /// /// 取消任务 /// private volatile List cancelTaskIds = new(); /// /// 启动 /// private volatile bool started; ///// ///// 车的状态更新时间 ///// //private readonly double querySeconds = 5; //包含的车和车的信息,会实时更新 ConcurrentDictionary jgrs = new ConcurrentDictionary(); Dictionary chargers = new Dictionary(); Dictionary stations = new Dictionary(); int maxDisplayX = 0, maxDisplayY = 0; /// /// 轨道的格子定义 /// Dictionary railGridCells = new Dictionary(); /// /// 仓储的格子定义 /// Dictionary storageGridCells = new Dictionary(); private string[] disableStorageCells = new string[] { "1,3","1,4","1,5","1,6","2,3","2,4","2,5" }; /// /// 锁对象 /// Mutex mutex = new Mutex(); /// /// 这种signalr注入的方式和实际使用chathub并不是同一个,实际上并不齐作用 /// 这种方式是新建了一个ChatHub /// 此处留在这里做个记忆,调了半天。 /// /// public JGRManager(IHubContext context, MyDbHandler dbHandler, IConfiguration configuration, ITaskExecManager taskExecManager, IRegistryService registryService) { Instance = this; hubContext = context; this.dbHandler = dbHandler; this._configuration = configuration; this._taskExecManager = taskExecManager; this._registryService = registryService; } public static JGRManager GetInstance() { //lock (balanceLock) //{ // if (Instance == null) // Instance = new JGRManager(); //} return Instance; } public void Dispose() { //关闭前记录最终位置信息 dbHandler.saveJGRs(GetJgrs()); //关闭定时器 cts.Cancel(); ctsTask.Cancel(); started = false; _registryService.Stop(); //关闭任务 _taskExecManager.stop(); } public bool Init(IServiceProvider services) { started = true; //从数据库加载信息做初始化动作 InitDB(); ////初始化车辆坐标 //foreach(var car in jgrs.Values) //{ // SetNewXY(car.jgrSN, car.currLocation_X, car.currLocation_Y); //} //加载地图数据 LoadGridConfig(); //异步,执行定期查询车辆状态的事情 queryJgrSatus(); //异步,执行车的调度 movingJGRS(); //监控工站状态 watchStationState(); //注册心跳服务启动,此服务包含心跳, //同步储位信息, //后期同步库存也是从该方法 _registryService.Start(); //启动任务 _taskExecManager.start(); return started; } async Task watchStationState() { while (true) { try { byte[] datas = TcClient.GetInstance().ReadBytes("GVL_STATION.station", 110); List stationList = new(); int stationPos = 1; for (var i=0; i /// 设置sianalr集线器 /// /// public void SetHub(IHubContext hub) { bool ok = hubContext == hub; hubContext = hub; // Console.WriteLine(ok.ToString()); } public bool ResetADS() { return TcClient.GetInstance().InitAds(); } /// /// 给界面上返回单元格的定义 /// /// public GridCellsDto getCells() { GridCellsDto dto = new GridCellsDto(); dto.maxRow = maxDisplayY; dto.maxCol = maxDisplayX; dto.railCells = new List(this.railGridCells.Values.ToList()); dto.storageCells = new List(this.storageGridCells.Values.ToList()); return dto; } /// /// 获得车的清单,目前充电桩和车是一个结构 /// /// public List GetJgrs() { return jgrs.Values.ToList(); } public List getStations() { return this.stations.Values.ToList(); } public StationDataModel? getStation(string stanCode) { return this.stations.Values.ToList().Where(item=>item.stationID == stanCode).FirstOrDefault(); } public List getCharges() { return this.chargers.Values.ToList(); } /// /// 定时查询jgr的状态,并推送到前端 /// /// /// CancellationTokenSource cts = new CancellationTokenSource(); async Task queryJgrSatus() { //uint jgrCycleTime = 2; //uint chargerCycleTime = 3; //uint stationCycleTime = 4; //uint tick = 0; while (started) { List lsJgrs = Get_Tc_Models(); string strJgr = JsonConvert.SerializeObject(lsJgrs); hubContext.Clients.All.SendAsync("JgrStatus", strJgr); await Task.Delay(TimeSpan.FromMilliseconds(500)); } ////间隔时间1秒 //using (var timer = new PeriodicTimer(TimeSpan.FromSeconds(1))) //{ // int x = 1, y = 1; // //在到达指定周期后执行方法 // while (await timer.WaitForNextTickAsync(cts.Token)) // { // //遍历每辆车和充电桩,在线的就发,状态变更的也发 // tick = (tick + 1) % uint.MaxValue; // if (tick % jgrCycleTime == 0) // { // List lsJgrs = Get_Tc_Models(); // string strJgr = JsonConvert.SerializeObject(lsJgrs); // hubContext.Clients.All.SendAsync("JgrStatus", strJgr); // //dbHandler.saveJGRs(lsJgrs); // } ////发送工位信息 //if (tick % stationCycleTime == 0) //{ //} ////发送充电桩信息 //if (tick % chargerCycleTime == 0) //{ // List lsJgrs = getChargers(); // string strCharger = JsonConvert.SerializeObject(lsJgrs); // hubContext.Clients.All.SendAsync("ChargingStatus", strCharger); //} //发送车的信息 //x = (x + 1) % 6; //if(x ==0) //{ // x = 1; //} //List jgrs = this.GetJgrs(); //JGR_Tc_Model xx = jgrs[0]; //xx.currLocation_X = (short)x; ////转成displayX //long key = 0; //key = (jgrs[0].currLocation_X & 0xffff); //key = key + ((jgrs[0].currLocation_Y & 0xffff) << 16); //if(this.storageGridCells.ContainsKey(key)) //{ // xx.displayX = this.storageGridCells[key].displayX; //} //xx = jgrs[1]; //xx.currLocation_X = (short)x; ////转成displayX //key = 0; //key = (jgrs[1].currLocation_X & 0xffff); //key = key + ((jgrs[1].currLocation_Y & 0xffff) << 16); //if (this.railGridCells.ContainsKey(key)) //{ // xx.displayX = this.railGridCells[key].displayX; //} //string str = JsonConvert.SerializeObject(jgrs); //hubContext.Clients.All.SendAsync("JgrStatus", str); //List stations = this.getStations(); //str = JsonConvert.SerializeObject(stations); //hubContext.Clients.All.SendAsync("StationStatus", str); //var item = this.getCharges(); //str = JsonConvert.SerializeObject(item); //hubContext.Clients.All.SendAsync("ChargingStatus", str); //} //} } /// /// 初始化数据库信息 /// void InitDB() { if (dbHandler == null) return; //sqlite数据库初始化 dbHandler.InitSqlite(); //加载车的信息 var dbjgrs = dbHandler.getJGRs(); this.jgrs.Clear(); foreach (var item in dbjgrs) { this.jgrs[item.jgrSN] = item; } //加载充电桩的信息 var charges = dbHandler.getChargers(); this.chargers.Clear(); foreach (var charge in charges) { this.chargers.Add(charge.csSN, charge); } //加载工位信息 var station = dbHandler.getSatations(); this.stations.Clear(); foreach (var st in station) { this.stations.Add(st.stationID, st); } } /// /// 加载地图数据 /// void LoadGridConfig() { string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "files", "grid.xlsx"); if (File.Exists(filePath)) { try { railGridCells.Clear(); storageGridCells.Clear(); FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); IWorkbook workbook = new XSSFWorkbook(fs); ISheet sheet = workbook.GetSheet("位置定义"); if (sheet != null) { //先判断行列范围, int rowLen = 0, colLen = 0; for (int i = 0; i < 100; i++) { IRow row = sheet.GetRow(i); if (row == null) break; NPOI.SS.UserModel.ICell cell = row.Cells[0]; string cellValue = ""; if (cell.CellType == CellType.String) { cellValue = cell.StringCellValue; } else if (cell.CellType == CellType.Numeric) { cellValue = cell.NumericCellValue.ToString(); } else { cellValue = ""; } if (string.IsNullOrEmpty(cellValue) == false) { rowLen = i; } } List cells = sheet.GetRow(0).Cells; for (int i = 1; i < cells.Count; i++) { NPOI.SS.UserModel.ICell cell = cells[i]; string cellValue = ""; if (cell.CellType == CellType.String) { cellValue = cell.StringCellValue; } else if (cell.CellType == CellType.Numeric) { cellValue = cell.NumericCellValue.ToString(); } else { cellValue = ""; } if (string.IsNullOrEmpty(cellValue) == false) { colLen = i; } } maxDisplayX = colLen + 1; maxDisplayY = rowLen + 1; //显示的X\Y坐标位置的内容 int displayX = 0, displayY = 0; for (int i = 1; i <= rowLen; i++) { displayY = i; for (int j = 1; j <= colLen; j++) { displayX = j; string cellValue = ""; NPOI.SS.UserModel.ICell cell = sheet.GetRow(i).GetCell(j, MissingCellPolicy.RETURN_NULL_AND_BLANK); if (cell == null) { continue; } else if (cell.CellType == CellType.String) { cellValue = cell.StringCellValue; } else if (cell.CellType == CellType.Numeric) { cellValue = cell.NumericCellValue.ToString(); } else { continue; } List strValues = new List(); //解析内容 if (cellValue.Contains('#') == false) { strValues.Add(cellValue); } else { //有多个重叠定义 string[] strs = cellValue.Split('#'); foreach (var item in strs) { strValues.Add(item.Trim()); } } foreach (string item in strValues) { //单元格只有一种定义 GridCell gcell = new GridCell(); gcell.displayX = displayX; gcell.displayY = displayY; int indexX = 0, indexY = 0, indexS = 0; indexX = item.IndexOf('X') + 1; indexY = item.IndexOf('Y'); indexS = item.IndexOf('/'); string strX = item.Substring(indexX, indexY - indexX); string strY = item.Substring(indexY + 1, indexS - indexY - 1); if (strX != null && strY != null) { gcell.positionX = Convert.ToInt32(strX); gcell.positionY = Convert.ToInt32(strY); } if (item.Contains("R-")) { if (railGridCells.Keys.Contains(gcell.GetKey())) { Console.WriteLine($"轨道数据中有重复定义的格子:x:{gcell.positionX} y:{gcell.positionY} "); continue; } railGridCells.Add(gcell.GetKey(), gcell); } else if (item.Contains("S-")) { if (storageGridCells.Keys.Contains(gcell.GetKey())) { Console.WriteLine($"仓储数据中有重复定义的格子:x:{gcell.positionX} y:{gcell.positionY} "); continue; } string pos = gcell.positionX.ToString() + "," + gcell.positionY.ToString(); if(disableStorageCells.Contains(pos)) { gcell.enableUse = false; } else { gcell.enableUse = true; } storageGridCells.Add(gcell.GetKey(), gcell); } if (item.Contains("/1")) { gcell.isEnable = true; } else { gcell.isEnable = false; gcell.enableUse = false; } } } } } //关闭文件 fs.Close(); } catch (Exception e) { Console.WriteLine("轨道位置定义文件没能正常解析:" + e.Message); } } } /// /// 从TC中读取小车的信息 /// /// public List Get_Tc_Models() { List list = new List(); try { //List railCars = GetJgrs().Where(item=> item.jgrType == 1).ToList(); //if(railCars !=null && railCars.Count > 0) //{ // foreach (JGR_Tc_Model car in railCars) // { // byte[] readRFIDCmd = new byte[5]; // readRFIDCmd[0] = 0x05; // readRFIDCmd[1] = 0x0F; // WriteCmd(car.jgrSN, readRFIDCmd); // Thread.Sleep(50); // } //} //先获取小车的数量 //在读取信息,并分段为小车的数据结构 int jgrCounts = TcClient.GetInstance().ReadValue("GVL_EXPORT.jgrAvailableTotal"); int size = Marshal.SizeOf(); byte[] datas = TcClient.GetInstance().ReadBytes("GVL_EXPORT.jgrStatusArr", jgrCounts * size); if (datas == null || datas.Length < jgrCounts * size) { return list; } byte[] buf = new byte[size]; for (int i = 0; i < jgrCounts; i++) { Array.Clear(buf); Array.Copy(datas, i * size, buf, 0, size); JGR_Ads_Struct model = AppHelper.BytesToStruct(buf); //出厂编号,30个长度带'\0',有效最多29个字节 string jgrSN = getString(model.jgrSN); if (this.jgrs.ContainsKey(jgrSN) == false) { Console.WriteLine("发现的车没有在数据库中配置,车的编号:"+jgrSN); continue; } //JGR_Tc_Model jgr = new JGR_Tc_Model(); //jgr.jgrSN = jgrSN; //车的数量以数据库配置为准 JGR_Tc_Model jgr = this.jgrs[jgrSN]; // 0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器 //jgr.jgrType = model.jgrType; //车ID,取值范围0~63,不同类型的车/充电器的jgrID可以重复 //jgr.jgrID = model.jgrID; //无线模块的地址,长度2字节 //jgr.rcAddr = model.rcAddr; //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50) //jgr.rcFreqCode = model.rcFreqCode; //在线/离线状态 是否在线(通讯是否正常) jgr.isOnline = model.isOnline; //健康状态 =0:正常; >=1:异常,异常编码看接口给 jgr.isHealthy = model.isHealthy; //充电标志,1:充电中,0:未在充电 jgr.isCharging = model.isCharging; //充电电压,单位:V jgr.chargeVoltage = model.chargeVoltage; //充电电流,单位:mA jgr.chargeCurrent = model.chargeCurrent; //电池剩余电量,取值 0~100 , 100表示满电量 jgr.electricity = model.electricity; //驱动器通电/断电状态 jgr.isDriverPowerOn = model.isDriverPowerOn; //(*工作模式 //1: 正常工作模式,即自动控制模式,只需给出运动的目标坐标,动作序列小车自己控制,正常工作时使用; //2:调试模式,即手动控制模式,用于调试,每一个动作都需要发送指令*) jgr.workMode = model.workMode; //行进/停止状态 //(* 车体运动状态 //0:静止状态 //1:X负向移动 //2:X正向移动 //3:Y负向移动 //4:Y正向移动*) jgr.moveStatus = model.moveStatus; //移动速度,单位:m/s jgr.moveSpeed = model.moveSpeed; //当前位置X jgr.currLocation_X = model.currLocation_X; //当前位置Y jgr.currLocation_Y = model.currLocation_Y; //目标位置X jgr.destLocation_X = model.destLocation_X; //目标位置Y jgr.destLocation_Y = model.destLocation_Y; /// /// 显示位置X,Y坐标,需要根据实际物理坐标转为显示坐标 /// long key = 0; key = (model.currLocation_X & 0xffff); key = key + ((model.currLocation_Y & 0xffff) << 16); if (model.jgrType == (byte)JgrType.RailCar) { if (railGridCells.ContainsKey(key)) { jgr.displayX = railGridCells[key].displayX; jgr.displayY = railGridCells[key].displayY; } } else if (model.jgrType == (byte)JgrType.StorageCar) { if (storageGridCells.ContainsKey(key)) { jgr.displayX = storageGridCells[key].displayX; jgr.displayY = storageGridCells[key].displayY; } } //车轮是否在位置切换中 jgr.isWheelAltering = model.isWheelAltering; //车轮位置 jgr.wheelPosition = model.wheelPosition; //4个碰撞传感器,true:碰撞,false:未碰撞 jgr.collideSensor = new byte[4]; Array.Copy(model.collideSensor, jgr.collideSensor, 4); //X加速度,单位 m/s2 jgr.acceleration_X = model.acceleration_X; //Y加速度,单位 m/s2 jgr.acceleration_Y = model.acceleration_Y; //Z加速度,单位 m/s2 jgr.acceleration_Z = model.acceleration_Z; //XZ角度,单位:° jgr.angleXZ = model.angleXZ; //YZ角度,单位:° jgr.angleYZ = model.angleYZ; //吊篮限位标志,吊篮处于的位置 1:最高点; 2:最低点 jgr.basketPosition = model.basketPosition; //吊篮抓夹状态 1:闭合状态; 2:张开状态 jgr.basketGripperStatus = model.basketGripperStatus; //吊篮是否在移动,吊篮运动状态 吊篮运动状态 0:静止; 1:正在上提; 2:正在下放 jgr.basketMoveStatus = model.basketMoveStatus; //RFID string rfId = getString(model.rfid); jgr.rfid = rfId.StartsWith("RFID") ? rfId : ""; list.Add(jgr); } return list; } catch(Exception e) { Console.WriteLine("从TC读取小车消息异常:" + e.Message); return list; } } List getChargers() { List list = new List(); //先获取小车的数量 //在读取信息,并分段为小车的数据结构 int counts = TcClient.GetInstance().ReadValue("GVL_EXPORT.chargerAvailableTotal"); int size = Marshal.SizeOf(); try { byte[] datas = TcClient.GetInstance().ReadBytes("GVL_EXPORT.chargerStatusArr", counts * size); if (datas == null || datas.Length < counts * size) { return list; } byte[] buf = new byte[size]; for (int i = 0; i < counts; i++) { Array.Clear(buf); Array.Copy(datas, i * size, buf, 0, size); Charge_Ads_Struct model = AppHelper.BytesToStruct(buf); ChargingStationDataModel item = new ChargingStationDataModel(); // 0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器 item.csType = model.chargeType; //出厂编号,30个长度带'\0',有效最多29个字节 item.csSN = getString(model.chargeSN); //车ID,取值范围0~63,不同类型的车/充电器的jgrID可以重复 item.csID = model.chargeID; //无线模块的地址,长度2字节 item.rcAddr = model.rcAddr; //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50) item.rcFreqCode = model.rcFreqCode; //在线/离线状态 是否在线(通讯是否正常) item.isOnline = model.isOnline; //健康状态 =0:正常; >=1:异常,异常编码看接口给 item.isHealthy = model.isHealthy; //充电标志,1:充电中,0:未在充电 item.isCharging = model.isCharging; item.chargerStatus = model.chargerStatus; //充电电压,单位:V item.chargeVoltage = model.chargeVoltage; //充电电流,单位:mA item.chargeCurrent = model.chargeCurrent; if(this.chargers.ContainsKey(item.csSN)) { item.positionX = chargers[item.csSN].positionX; item.positionY = chargers[item.csSN].positionY; item.displayX = chargers[item.csSN].displayX; item.displayY = chargers[item.csSN].displayY; } list.Add(item); } return list; } catch (Exception e) { Console.WriteLine("从TC读取充电桩的消息异常:" + e.Message); return list; } } string getString(byte[] data) { //判断非0的长度,0是字符串结尾 int index = 0; for (int i = 0; i < data.Length; i++) { if (data[index] == 0) { break; } index++; } return Encoding.ASCII.GetString(data, 0, index).Trim(); } /// /// 设置新车或者充电桩的新位置 /// /// /// /// /// public string SetNewXY(string jgrSN, int x , int y) { string ret = "0"; if(this.jgrs.ContainsKey(jgrSN) || this.chargers.ContainsKey(jgrSN)) { short sx = Convert.ToInt16(x.ToString()); short sy = Convert.ToInt16(y.ToString()); byte[] data = new byte[9]; data[1] = 0x11; //有正负 data[3] = (byte)((sx >> 8) & 0xff); data[4] = (byte)(sx & 0xff); data[5] = (byte)((sy >> 8) & 0xff); data[6] = (byte)(sy & 0xff); ret = WriteCmd(jgrSN, data); } return ret; } /// /// 执行命令 /// /// /// ="0",正常;="xx"异常信息 public string SetCmd(JgrDebugDto debugDto) { string ret = "0"; byte[] data = null; //如果没有这辆车,或者车不是正常模式,则不让发车 if (this.jgrs.ContainsKey(debugDto.SN) == false) return "未找到车,车的编号:" + debugDto.SN; if (this.jgrs[debugDto.SN].isOnline == 0) return "小车没有在线"; if (this.jgrs[debugDto.SN].isHealthy > 0) return "车处于异常状态,需要人工复位"; var currJGR = this.jgrs[debugDto.SN]; //如果车处于刹车状态,则解除刹车 //处理各自动作,最理想的做法是把动作指令统一映射成ADS通信协议,写入ADS switch (debugDto.CmdCode) { /// /// 小车系统重启 /// case JgrCmdCode.Restart: if(currJGR.workMode != 2 ) { return "小车没有处于调试模式"; } break; /// /// 设置电源 /// case JgrCmdCode.SetPower: //if (debugDto.Parameters.ContainsKey("switch") == false) //{ // ret = "缺少参数\"switch\""; // break; //} //else //{ // data = new byte[8]; // data[1] = 5; // data[3] = (byte)((debugDto.Parameters["switch"] == "on" || debugDto.Parameters["switch"] == "1") ? 1 : 2); // ret = WriteCmd(debugDto.SN, data); //} break; /// /// 设置终点 /// case JgrCmdCode.SetEndPoint: if (debugDto.Parameters.ContainsKey("X") == false || debugDto.Parameters.ContainsKey("Y") == false) { ret = "缺少参数\"X、Y\""; break; } else { if (int.TryParse(debugDto.Parameters["X"], out endpointX) == false) { endpointX = 0; } if (int.TryParse(debugDto.Parameters["Y"], out endpointY) == false) { endpointY = 0; } if (endpointX == 0 || endpointY == 0) { endpointX = endpointY = 0; ret = "X-Y坐标参数格式有问题!"; } else { MovingPath path = new MovingPath(); path.jgrSN = debugDto.SN; path.startLocationX = this.jgrs[debugDto.SN].currLocation_X; path.startLocationY = this.jgrs[debugDto.SN].currLocation_Y; path.destLocationX = endpointX; path.destLocationY = endpointY; ret = TryAddPath(debugDto.SN, path); } } break; /// /// 移动到设置的终点 /// case JgrCmdCode.StartMove: //在设置终点中就完成动作了,不需要独立的触发命令 break; /// /// 正常中止,停在前进方向最近的一格上 /// case JgrCmdCode.StopNormal: data = new byte[8]; data[1] = 0x12; data[3] = 2; ret = WriteCmd(debugDto.SN, data); break; /// /// 急停,最短距离停下 /// case JgrCmdCode.StopEmergency: data = new byte[8]; data[1] = 0x12; data[3] = 1; ret = WriteCmd(debugDto.SN, data); break; /// /// 前进方向移动到下一格,X增加 /// case JgrCmdCode.MoveForward: if (this.jgrs[debugDto.SN].GetPathCounts() == 0) { int newX = this.jgrs[debugDto.SN].currLocation_X + 1; int newY = this.jgrs[debugDto.SN].currLocation_Y; //检测这一个格子是否可以前进,如果可以前进,则加入到路径中 MovingPath path = new MovingPath(); path.jgrSN = debugDto.SN; path.startLocationX = this.jgrs[debugDto.SN].currLocation_X; path.startLocationY = this.jgrs[debugDto.SN].currLocation_Y; path.destLocationX = newX; path.destLocationY = newY; ret = TryAddPath(debugDto.SN, path); } break; /// /// 后退一格,X减少 /// case JgrCmdCode.MoveBack: if (this.jgrs[debugDto.SN].GetPathCounts() == 0) { int newX = this.jgrs[debugDto.SN].currLocation_X - 1; int newY = this.jgrs[debugDto.SN].currLocation_Y; //检测这一个格子是否可以前进,如果可以前进,则加入到路径中 MovingPath path = new MovingPath(); path.jgrSN = debugDto.SN; path.startLocationX = this.jgrs[debugDto.SN].currLocation_X; path.startLocationY = this.jgrs[debugDto.SN].currLocation_Y; path.destLocationX = newX; path.destLocationY = newY; ret = TryAddPath(debugDto.SN, path); } break; /// /// 前进方向左侧移动一格,Y减少 /// case JgrCmdCode.MoveLeft: if (this.jgrs[debugDto.SN].GetPathCounts() == 0) { int newX = this.jgrs[debugDto.SN].currLocation_X; int newY = this.jgrs[debugDto.SN].currLocation_Y - 1; //检测这一个格子是否可以前进,如果可以前进,则加入到路径中 MovingPath path = new MovingPath(); path.jgrSN = debugDto.SN; path.startLocationX = this.jgrs[debugDto.SN].currLocation_X; path.startLocationY = this.jgrs[debugDto.SN].currLocation_Y; path.destLocationX = newX; path.destLocationY = newY; ret = TryAddPath(debugDto.SN, path); } break; /// /// 前进方向右侧移动一格,Y增加 /// case JgrCmdCode.MoveRight: if (this.jgrs[debugDto.SN].GetPathCounts() == 0) { int newX = this.jgrs[debugDto.SN].currLocation_X; int newY = this.jgrs[debugDto.SN].currLocation_Y + 1; //检测这一个格子是否可以前进,如果可以前进,则加入到路径中 MovingPath path = new MovingPath(); path.jgrSN = debugDto.SN; path.startLocationX = this.jgrs[debugDto.SN].currLocation_X; path.startLocationY = this.jgrs[debugDto.SN].currLocation_Y; path.destLocationX = newX; path.destLocationY = newY; ret = TryAddPath(debugDto.SN, path); } break; /// /// 车轮置顶 /// case JgrCmdCode.SetWheelTop: data = new byte[8]; data[1] = 6; data[3] = 1; ret = WriteCmd(debugDto.SN, data); break; /// /// 车轮置底 /// case JgrCmdCode.SetWheelBottom: data = new byte[8]; data[1] = 6; data[3] = 3; ret = WriteCmd(debugDto.SN, data); break; /// /// 车轮置在中间,四轮着地 /// case JgrCmdCode.SetWheelMiddle: data = new byte[8]; data[1] = 6; data[3] = 2; ret = WriteCmd(debugDto.SN, data); break; /// /// 吊篮抓取 /// case JgrCmdCode.BasketUp: data = new byte[8]; data[1] = 7; data[3] = 5; data[4] = Convert.ToByte(debugDto.Parameters["Z"]); ret = WriteCmd(debugDto.SN, data); break; /// /// 吊篮释放 /// case JgrCmdCode.BasketDown: data = new byte[8]; data[1] = 7; data[3] = 6; data[4] = Convert.ToByte(debugDto.Parameters["Z"]); ret = WriteCmd(debugDto.SN, data); break; /// /// 吊篮停止运动 /// case JgrCmdCode.BasketStopMoving: data = new byte[8]; data[1] = 7; data[3] = 0; ret = WriteCmd(debugDto.SN, data); break; /// /// 吊篮夹子张开 /// case JgrCmdCode.GripperOpen: data = new byte[8]; data[1] = 7; data[3] = 3; ret = WriteCmd(debugDto.SN, data); break; /// /// 吊篮夹子闭合 /// case JgrCmdCode.GripperClose: data = new byte[8]; data[1] = 7; data[3] = 4; ret = WriteCmd(debugDto.SN, data); break; /// /// 开始充电 /// case JgrCmdCode.StartCharging: //先检查车的位置与充电桩的位置重叠 //然后给指定充电桩发送指令 foreach (var item in this.chargers) { if (item.Value.positionX == this.jgrs[debugDto.SN].currLocation_X && item.Value.positionY == this.jgrs[debugDto.SN].currLocation_Y) { //给充电桩发送伸出指令 data = new byte[8]; data[1] = 8; data[3] = 1; data[4] = 1; ret = WriteCmd(item.Value.csSN, data); if (ret != "0") return ret; //给充电桩发送充电指令 data[1] = 8; data[3] = 0; data[4] = 1; ret = WriteCmd(item.Value.csSN, data); if (ret != "0") return ret; //给车发送充电指令 data[1] = 8; data[3] = 0; data[4] = 1; ret = WriteCmd(debugDto.SN, data); if (ret != "0") return ret; break; } } break; /// /// 停止充电 /// case JgrCmdCode.StopCharging: //先检查车的位置与充电桩的位置重叠 //然后给指定充电桩发送指令 foreach (var item in this.chargers) { if (item.Value.positionX == this.jgrs[debugDto.SN].currLocation_X && item.Value.positionY == this.jgrs[debugDto.SN].currLocation_Y) { //给充电桩发送停止充电指令 data = new byte[8]; data[1] = 8; data[3] = 0; data[4] = 0; ret = WriteCmd(item.Value.csSN, data); if (ret != "0") return ret; //给车发送停止充电指令 data[1] = 8; data[3] = 0; data[4] = 0; ret = WriteCmd(debugDto.SN, data); if (ret != "0") return ret; //给充电桩发送缩回指令 data[1] = 8; data[3] = 1; data[4] = 0; ret = WriteCmd(item.Value.csSN, data); if (ret != "0") return ret; break; } } break; //调试模式 case JgrCmdCode.DebugModel: data = new byte[8]; data[1] = 9; data[3] = 2; ret = WriteCmd(debugDto.SN, data); break; //正常模式 case JgrCmdCode.NormalModel: data = new byte[8]; data[1] = 9; data[3] = 1; ret = WriteCmd(debugDto.SN, data); break; //车的驱动器通电 case JgrCmdCode.MonitorPowerOn: data = new byte[8]; data[1] = 5; data[3] = 1; ret = WriteCmd(debugDto.SN, data); break; //车的驱动器断电 case JgrCmdCode.MonitorPowerOff: data = new byte[8]; data[1] = 5; data[3] = 2; ret = WriteCmd(debugDto.SN, data); break; default: //不带参数 break; } return ret; } /// /// 返回"0"是正常 /// /// /// /// public string WriteCmd(string sn , byte[] data) { string? ret = null; try { mutex.WaitOne(); ret = "0"; //1.先写车或充电桩的序列号 //2.再写命令字节数据 //3.再写触发标志位 //4.再回读状态位 TcClient.GetInstance().WriteValue("insert_random_frame.SN", sn); TcClient.GetInstance().WriteValue("insert_random_frame.FrameContentToSendVar", data); TcClient.GetInstance().WriteValue("insert_random_frame.insert", 1); //数据读走,状态位置为0 int timeout = 20; while(timeout-- > 0) { Thread.Sleep(30); byte val = TcClient.GetInstance().ReadValue("insert_random_frame.insert"); ret = val.ToString(); if (val == 0) { break; } } } catch(Exception e) { ret = "发送命令失败:"+e.Message; } finally { mutex.ReleaseMutex(); } return ret; } /// /// 尝试为车添加路径,路径需要拆解,并判断是否可行 /// //目前假设路径上没有禁用的格子 /// /// /// /// string TryAddPath(string jgrSN, MovingPath path) { string ret = "0"; if (path.startLocationX == path.destLocationX && path.startLocationY == path.destLocationY) { //目标一样 return "起点与终点一致,不用移动"; } List paths = new List(); //检查路径是否合理,如果不算直线,则需要拆解为多条路径 if(path.startLocationX == path.destLocationX || path.startLocationY == path.destLocationY) { //检查路径是否可行 if(CheckPathAvaible(jgrSN,path)) { paths.Add(path); this.jgrs[jgrSN].AddPath(paths); return ret; } else { return "移动路径没有被接受"; } } else { //拆成多段路径 //如果X轴能走到位置,则走X轴 MovingPath path1 = new MovingPath(); MovingPath path2 = new MovingPath(); MovingPath path3 = new MovingPath(); if (path.startLocationY > path.destLocationY) { path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = path.destLocationY; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path.destLocationY; if(CheckPathAvaible(jgrSN,path1) && CheckPathAvaible(jgrSN, path2)) { paths.Add(path1); paths.Add(path2); this.jgrs[jgrSN].AddPath(paths); return ret; } } else { path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.destLocationX; path1.destLocationY = path.startLocationY; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2)) { paths.Add(path1); paths.Add(path2); this.jgrs[jgrSN].AddPath(paths); return ret; } } //从当前位置的上边,下边位置尝试移动 for (int i = path.startLocationY -1; i >= 1; i--) { path1 = new MovingPath(); path2 = new MovingPath(); path3 = new MovingPath(); path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = i; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path1.destLocationY; path3.startLocationX = path2.destLocationX; path3.startLocationY = path2.destLocationY; path3.destLocationX = path.destLocationX; path3.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2) && CheckPathAvaible(jgrSN, path3)) { paths.Add(path1); paths.Add(path2); paths.Add(path3); this.jgrs[jgrSN].AddPath(paths); return ret; } } for (int i = path.startLocationY + 1; i <= 8 ; i++) { path1 = new MovingPath(); path2 = new MovingPath(); path3 = new MovingPath(); path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = i; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path1.destLocationY; path3.startLocationX = path2.destLocationX; path3.startLocationY = path2.destLocationY; path3.destLocationX = path.destLocationX; path3.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2) && CheckPathAvaible(jgrSN, path3)) { paths.Add(path1); paths.Add(path2); paths.Add(path3); this.jgrs[jgrSN].AddPath(paths); return ret; } } } return "移动路径没有被接受"; } bool CheckPathAvaible(string jgrSN,MovingPath path) { Dictionary cells = null; bool ok = true; if (jgrs[jgrSN].jgrType == (byte)JgrType.StorageCar) { cells = storageGridCells; } else if (jgrs[jgrSN].jgrType == (byte)JgrType.RailCar) { cells = railGridCells; } else { return false; } if (path.startLocationX == path.destLocationX) { //Y轴移动 int startY = path.startLocationY < path.destLocationY ? path.startLocationY : path.destLocationY; int stopY = path.startLocationY > path.destLocationY ? path.startLocationY : path.destLocationY; //检查路径是否可行 for (int i = startY; i <= stopY; i++) { long key = 0; key = (path.startLocationX & 0xffff); key = key + ((i & 0xffff) << 16); if(cells.ContainsKey(key)==false || cells[key].isEnable == false) { return false; } } return true; } else if (path.startLocationY == path.destLocationY) { //X轴移动 int startX = path.startLocationX < path.destLocationX ? path.startLocationX : path.destLocationX; int stopX = path.startLocationX > path.destLocationX ? path.startLocationX : path.destLocationX; //检查路径是否可行 for (int i = startX; i <= stopX; i++) { long key = 0; key = (i & 0xffff); key = key + ((path.startLocationY & 0xffff) << 16); if (cells.ContainsKey(key)==false || cells[key].isEnable == false) { return false; } } return true; } else { return false; } } /// /// 定时执行车辆移动的指令 /// CancellationTokenSource ctsTask = new CancellationTokenSource(); async Task movingJGRS() { ///选择没有冲突的路径,执行路径 ///锁定执行的路径和周边的单元格 ///读取车的实时位置,解锁已经经过的单元格 ///没有冲突的路径可以同时执行 //间隔时间1秒 /*************************/ //因为目前每个区域只有一辆车,所以不存在过多的调度问题,有路径可以直接运行 //以下操作只是针对区域中只有一辆车的情况 //以下操作只是针对区域中只有一辆车的情况 //以下操作只是针对区域中只有一辆车的情况 //以下操作只是针对区域中只有一辆车的情况 /*************************/ using (var timer = new PeriodicTimer(TimeSpan.FromSeconds(1))) { while (await timer.WaitForNextTickAsync(ctsTask.Token)) { var lsJgrs = jgrs.Values.ToList(); for(int i = 0; i< lsJgrs.Count;i++) { var item = Get_Tc_Models().Where(m=>m.jgrSN == lsJgrs[i].jgrSN).First(); //如果车正在运动,则不处理 //if(item.isOnline == 0 || item.isHealthy > 0 || item.isCharging == 1 || item.isDriverPowerOn == 0 || item.moveStatus != 0 ) //{ // continue; //} if (item.isOnline == 0 || item.isHealthy > 0 || item.isCharging == 1 || item.moveStatus != 0) { continue; } MovingPath path = item.GetFirstPath(); if (path != null) { if (path.IsRuning == false) { //没开始则开始,如果已经到位置,则移除 if (path.startLocationX == (int)(item.currLocation_X) && path.startLocationY == (int)(item.currLocation_Y)) { //如果车的起始位置与路径起始位置一致,则发起移动 //下指令移动 path.IsRuning = true; path.StartTime = DateTime.Now; byte[] data = new byte[8]; data[1] = 0x0a; if(path.destLocationX == path.startLocationX) { //移动Y short len = Convert.ToInt16((path.destLocationY - path.startLocationY).ToString()); //有正负 data[5] = (byte)((len >> 8) & 0xff); data[6] = (byte)(len & 0xff); WriteCmd(item.jgrSN, data); } else if(path.destLocationY == path.startLocationY) { //移动X short len = Convert.ToInt16((path.destLocationX - path.startLocationX).ToString()); //有正负 data[3] = (byte)((len >> 8) & 0xff); data[4] = (byte)(len & 0xff); WriteCmd(item.jgrSN, data); } else { //异常,正常是不会出现的 item.RemoveFirstPath(); i--; } } else { //如果车的起始位置与路径起始位置不一致,则是有异常发生,移除路径 item.RemoveFirstPath(); i--; Console.WriteLine("规划的路径起点与当前位置不一致,规划的路径被抛弃"); } } else { //如果已经移动到位 if (path.destLocationX == (int)(item.currLocation_X) && path.destLocationY == (int)(item.currLocation_Y)) { item.RemoveFirstPath(); //下以次还是检查这辆车,好发起新的路径移动 i--; } else { //如果移动过程中超时 if (DateTime.Now - path.StartTime > TimeSpan.FromSeconds(120)) { Console.WriteLine("小车移动超时,小车编号:" + item.jgrSN); //把超时的移动路径移走 item.RemoveFirstPath(); i--; } } } } //end if (path != null) }//end for }//end while } } public string moveJGCar(string jgrSN, int startLocationX, int startLocationY, int destLocationX, int destLocationY) { JGR_Tc_Model curCarState = GetJgrs().Where(item => item.jgrSN == jgrSN).First(); //没开始则开始,如果已经到位置,则移除 if (startLocationX == (int)(curCarState.currLocation_X) && startLocationY == (int)(curCarState.currLocation_Y)) { byte[] data = new byte[8]; data[1] = 0x0a; if (destLocationX == startLocationX) { //移动Y short len = Convert.ToInt16((destLocationY - startLocationY).ToString()); //有正负 data[5] = (byte)((len >> 8) & 0xff); data[6] = (byte)(len & 0xff); //移动仓储车到出库位 Console.WriteLine(jgrSN + "移动到位置: " + destLocationX + "," + destLocationY); try { return WriteCmd(jgrSN, data); } finally { Console.WriteLine(jgrSN + "移动到位置: " + destLocationX + "," + destLocationY+",指令发送完成"); } } else if (destLocationY == startLocationY) { //移动X short len = Convert.ToInt16((destLocationX - startLocationX).ToString()); //有正负 data[3] = (byte)((len >> 8) & 0xff); data[4] = (byte)(len & 0xff); try { //移动仓储车到出库位 Console.WriteLine(jgrSN + "移动到位置: " + destLocationX + "," + destLocationY); return WriteCmd(jgrSN, data); } finally { Console.WriteLine(jgrSN + "移动到位置: " + destLocationX + "," + destLocationY + ",指令发送完成"); } } } return "没有进行路径规划"; } public StationDataModel? getStationByNo(string stanCode) { return dbHandler.getStationByNo(stanCode); } /// /// 移动指定车辆到指定位置 /// /// /// /// public async void moveCarToDestAsync(JGR_Tc_Model free_car, int positionX, int positionY) { try { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return; } await waitAndMoveCar(free_car, positionX, positionY); //等待到达终点 await waitArrived(free_car.jgrSN, (short)positionX, (short)positionY); //通知完成 notifyTaskCompleted(new string[] { free_car.jgrSN }); } catch (CancelTaskException ex) { Console.WriteLine($"异常信息:{ex.Message}"); if (free_car != null) { //发送取消任务成功通知 sendCancelTaskSuccess(new string[] { free_car.jgrSN }); } } } /// /// 发送任务取消成功的消息 /// private void sendCancelTaskSuccess(string[] jgrSNs) { RabbitmqService.getInstance().SendMsg(new MqMessage() { msgId = _taskExecManager.genTaskId(), msgType = 5, msgData = new CancelTaskNotify() { taskId = ThreadLocalManager.Get().taskId, cancelTime = DateTime.Now } }); removeCancelTask(ThreadLocalManager.Get().taskId); if(jgrSNs !=null && jgrSNs.Length>0) { //移除车辆占用 foreach (var sn in jgrSNs) { jgrTaskMap.Remove(sn, out _); } } } /// /// 移动车 /// /// /// /// /// async Task waitAndMoveCar(JGR_Tc_Model free_car, int positionX, int positionY) { //判断车状态是否被其他任务占用 await waitCarToFree(free_car); //规划路径并执行 await planPathAndRun(free_car.jgrSN, positionX, positionY); } /// /// 等待车辆为空闲状态 /// /// /// async Task waitCarToFree(JGR_Tc_Model free_car) { int times = 0; bool rs; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times) % 500 == 0) { //发送超时消息 sendExceptNotify("等待指定车辆超时"); } await Task.Delay(TimeSpan.FromMilliseconds(200)); rs = checkIsFree(free_car, false); } while (!rs); //占用车辆 jgrTaskMap[free_car.jgrSN] = ThreadLocalManager.Get().taskId; } /// /// 发送异常通知消息 /// /// private void sendExceptNotify(string reason) { RabbitmqService.getInstance().SendMsg(new MqMessage() { msgId = _taskExecManager.genTaskId(), msgType = 4, msgData = new TaskExceptNotify() { taskId = ThreadLocalManager.Get().taskId, exceptReason = reason } }); } /// /// 移动车到目标位置 /// /// /// /// public async void moveCarToDestAsync(JgrType jgrType, int positionX, int positionY) { JGR_Tc_Model? free_car = null; try { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return; } free_car = await waitFreeCar(jgrType); notifyCarInfo(free_car); //规划路径并执行 await planPathAndRun(free_car.jgrSN, positionX, positionY); await waitArrived(free_car.jgrSN, (short)positionX, (short)positionY); //int current_x = free_car.currLocation_X; //int current_y = free_car.currLocation_Y; //if (free_car.currLocation_Y > 3) //{ // if (positionY > 3) // { // current_y -= 1; // } // else // { // current_y = (short)positionY; // } //} //else //{ // current_x = (short)positionX; //} ////首次运动 //JGR_Tc_Model curCarState = Get_Tc_Models().Where(item => item.jgrSN == free_car.jgrSN).First(); //if (curCarState.currLocation_X != (short)current_x || curCarState.currLocation_Y != (short)current_y) //{ // await retryMoveCar(free_car.jgrSN, current_x, current_y); // await waitArrived(free_car.jgrSN, (short)current_x, (short)current_y); //} //if (free_car.currLocation_X != (short)positionX) //{ // current_x = (short)positionX; // curCarState = Get_Tc_Models().Where(item => item.jgrSN == free_car.jgrSN).First(); // await retryMoveCar(free_car.jgrSN, current_x, current_y); // await waitArrived(free_car.jgrSN, (short)current_x, (short)current_y); //} ////再次运动到达目的地 //curCarState = Get_Tc_Models().Where(item => item.jgrSN == free_car.jgrSN).First(); //await retryMoveCar(free_car.jgrSN, positionX, positionY); notifyTaskCompleted(new string[] { free_car.jgrSN }); } catch (CancelTaskException ex) { Console.WriteLine($"异常信息:{ex.Message}"); if(free_car != null) { //发送取消任务成功通知 sendCancelTaskSuccess(new string[] { free_car.jgrSN }); } } } /// /// 通知获取车辆信息给业务端 /// private void notifyCarInfo(JGR_Tc_Model carInfo, string rfId="") { //发送消息给业务端 RabbitmqService.getInstance().SendMsg(new MqMessage() { msgId = _taskExecManager.genTaskId(), msgType = 1, msgData = new GetCarNotify() { taskId = ThreadLocalManager.Get().taskId, carId = carInfo.jgrSN, carType = carInfo.jgrType, rfId = rfId } }) ; } /// /// 通知任务完成处理 /// /// private void notifyTaskCompleted(string[] jgrSNs) { //移除车辆占用 foreach(var sn in jgrSNs) { jgrTaskMap.Remove(sn, out _); } //通知任务完成 RabbitmqService.getInstance().SendMsg(new MqMessage() { msgId = _taskExecManager.genTaskId(), msgType = 2, msgData = new TaskCompletedNotify() { taskId = ThreadLocalManager.Get().taskId } }); _taskExecManager.notifyTaskCompleted(ThreadLocalManager.Get().taskId); } /// /// 等待空闲车辆 /// /// /// async Task waitFreeCar(JgrType jgrType) { int times = 0; JGR_Tc_Model? curCarState = null; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times)%500 == 0) { sendExceptNotify("等待空闲车超时"); } await Task.Delay(TimeSpan.FromMilliseconds(200)); curCarState = findFreeCar(jgrType); } while (curCarState == null); if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } return curCarState; } /// /// 仓储车入库操作 /// /// 入库参数 public async void storageCarToInAsync(JGR_Tc_Model railCar, InStorageDTO dto) { JGR_Tc_Model? storageCarInfo = null; try { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return; } //等待仓盒信息 string rfId = await awaitStorage(railCar); //移动车辆 waitAndMoveCar(railCar, inAmrPos[0], inAmrPos[1]); //等待直到获取仓储车 storageCarInfo = await waitFreeCar(JgrType.StorageCar); notifyCarInfo(storageCarInfo, rfId); //int current_x = storageCarInfo.currLocation_X; //int current_y = storageCarInfo.currLocation_Y; //规划路径并执行 await planPathAndRun(storageCarInfo.jgrSN, inPos[0], inPos[1] - 1); //到位判断等待 await waitArrived(storageCarInfo.jgrSN, (short)inPos[0], (short)(inPos[1] - 1)); ////根据仓储车朝向原则上y轴减去1为车的位置 //if (current_x != inPos[0] || current_y != inPos[1] - 1) //{ // //移动车到入库位置 // await retryMoveCar(storageCarInfo.jgrSN, inPos[0], inPos[1] - 1); // //到位判断等待 // await waitArrived(storageCarInfo.jgrSN, (short)inPos[0], (short)(inPos[1] - 1)); //} //读取轨道车是否到达入库位置 await waitArrived(railCar.jgrSN, (short)inAmrPos[0], (short)inAmrPos[1]); //发送抓取指令 byte[] data = new byte[8]; data[1] = 7; data[3] = 5; data[4] = 0x80; await retrySendCmd(storageCarInfo.jgrSN, data); //读取吊篮状态是否已恢复到0的位置 await waitBasketPositionReset(storageCarInfo.jgrSN, 1); //移动仓储车到入料位 int dest_x = int.Parse(dto.storagePos.Split(",")[0]); //仓储车相对位置减1 int dest_y = int.Parse(dto.storagePos.Split(",")[1]) - 1; //目标层数 int dest_z = int.Parse(dto.storagePos.Split(",")[2]); //移动车到入库位置 await planPathAndRun(storageCarInfo.jgrSN, dest_x, dest_y); // await retryMoveCar(storageCarInfo.jgrSN, dest_x, dest_y); await waitArrived(storageCarInfo.jgrSN, (short)dest_x, (short)dest_y); //执行下放抓斗命令 data = new byte[8]; data[1] = 7; data[3] = 6; data[4] = Convert.ToByte(dest_z); await retrySendCmd(storageCarInfo.jgrSN, data); //读取吊篮状态 await waitBasketPositionReset(storageCarInfo.jgrSN, 2); //将任务完成状态放到集合中等待查询读取 notifyTaskCompleted(new string[] { dto.carId, storageCarInfo.jgrSN }); } catch (CancelTaskException ex) { Console.WriteLine($"异常信息:{ex.Message}"); string[] cars = new string[1]; cars[0] = dto.carId; if (storageCarInfo != null) { cars = new string[2]; cars[0] = dto.carId; cars[1] = storageCarInfo.jgrSN; } //发送取消任务成功通知 sendCancelTaskSuccess(cars); } } async Task awaitStorage(JGR_Tc_Model railCar) { int times = 0; string rfId = ""; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times) % 1000 == 0) { //发送超时消息 sendExceptNotify("等待放置仓盒超时"); } await Task.Delay(TimeSpan.FromSeconds(1)); rfId = GetJgrs().Where(car => railCar.jgrSN.Equals(car.jgrSN)).First().rfid; } while(string.IsNullOrEmpty(rfId) || string.IsNullOrWhiteSpace(rfId)); return rfId; } /// /// 等待仓储车吊篮状态复位到0的位置 /// /// 仓储车id /// 验证gripper状态 /// async Task waitBasketPositionReset(string jgrSN, int checkGripperState) { int times = 0; JGR_Tc_Model? carState = null; bool isReady = false; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times) % 1000 == 0) { //发送超时消息 sendExceptNotify("等待仓储车吊篮状态复位超时"); } await Task.Delay(TimeSpan.FromMilliseconds(100)); carState = Get_Tc_Models().Where(item => item.jgrSN == jgrSN).First(); isReady = carState.basketPosition == 0 && carState.basketGripperStatus == checkGripperState; } while (!isReady); } /// /// 重试指令 /// /// /// /// async Task retrySendCmd(string jgrSN, byte[] data) { int times = 0; string ret; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times) % 1000 == 0) { //发送超时消息 sendExceptNotify("发送车辆操作指令超时"); } await Task.Delay(TimeSpan.FromMilliseconds(100)); //车辆到达入料位置,发起下放抓取指令 ret = WriteCmd(jgrSN, data); } while (ret != "0"); } /// /// 可重试移动车位置,暂不考虑退出 /// /// /// /// /// async Task retryMoveCar(string jgrSN, int dx, int dy) { int times = 0; string str; JGR_Tc_Model carState = Get_Tc_Models().Where(item => item.jgrSN == jgrSN).First(); int current_x = carState.currLocation_X; int current_y = carState.currLocation_Y; do { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } if ((++times) % 1000 == 0) { //发送超时消息 sendExceptNotify("移动车辆指令重试超时"); } await Task.Delay(TimeSpan.FromMilliseconds(100)); str = moveJGCar(carState.jgrSN, current_x, current_y, dx, dy); } while (!"0".Equals(str)); } /// /// 判断是否达到指定坐标位置,此处暂不考虑长时间未到达的情况,后期可于此做退出机制 /// /// /// /// /// public async Task waitArrived(string jgrSN, short dx, short dy) { int times = 0; JGR_Tc_Model curCarState = Get_Tc_Models().Where(item => item.jgrSN == jgrSN).First(); while (curCarState.currLocation_X != dx || curCarState.currLocation_Y != dy) { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消指令操作"); } if ((++times) % 500 == 0) { //发送超时消息 sendExceptNotify("等待车辆到达超时"); } await Task.Delay(TimeSpan.FromMilliseconds(200)); curCarState = Get_Tc_Models().Where(item => item.jgrSN == jgrSN).First(); } } /// /// 出库操作逻辑 /// /// 存储位置 /// amr运动目标坐标位置x /// amr运动目标坐标位置y /// public async void storageCarToOutAsync(string storagePos, int amrDx, int amrDy) { JGR_Tc_Model? railCar = null; JGR_Tc_Model? storageCarInfo = null; try { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return; } //呼叫轨道车到出库位 railCar = await waitFreeCar(JgrType.RailCar); notifyCarInfo(railCar); //移动车辆到出库位 planPathAndRun(railCar.jgrSN, outAmrPos[0], outAmrPos[1]); //获取仓储车 storageCarInfo = await waitFreeCar(JgrType.StorageCar); //移动仓储车到储位拿取 //int current_x = storageCarInfo.currLocation_X; //int current_y = storageCarInfo.currLocation_Y; int dx = int.Parse(storagePos.Split(',')[0]); int dy = int.Parse(storagePos.Split(',')[1]) - 1; int dz = int.Parse(storagePos.Split(',')[2]); await planPathAndRun(storageCarInfo.jgrSN, dx, dy); //到位判断等待 await waitArrived(storageCarInfo.jgrSN, (short)dx, (short)dy); Console.WriteLine(storageCarInfo.jgrSN + "已经到达位置: " + dx + "," + dy); ////根据仓储车朝向原则上y轴减去1为车的位置 //if (current_x != dx || current_y != dy) //{ // //移动车到抓取位置 // await retryMoveCar(storageCarInfo.jgrSN, dx, dy); // //到位判断等待 // await waitArrived(storageCarInfo.jgrSN, (short)dx, (short)dy); //} //移动到位发送抓取指令 byte[] data = new byte[8]; data[1] = 7; data[3] = 5; data[4] = Convert.ToByte(dz); await retrySendCmd(storageCarInfo.jgrSN, data); Console.WriteLine(storageCarInfo.jgrSN + "发送抓取指令,指令内容:", string.Join(',', data)); //读取吊篮状态 await waitBasketPositionReset(storageCarInfo.jgrSN, 1); Console.WriteLine(storageCarInfo.jgrSN + "等待吊篮上升到最高层,状态为抓取状态:"); await planPathAndRun(storageCarInfo.jgrSN, outPos[0], outPos[1] - 1); ////移动仓储车到出库位 //await retryMoveCar(storageCarInfo.jgrSN, outPos[0], outPos[1] - 1); //到位判断等待 await waitArrived(storageCarInfo.jgrSN, (short)outPos[0], (short)(outPos[1] - 1)); Console.WriteLine(storageCarInfo.jgrSN + "已经到达位置: " + outPos[0].ToString() + "," + (outPos[1] - 1).ToString()); //等待轨道车是否到达出库位 await waitArrived(railCar.jgrSN, (short)outAmrPos[0], (short)outAmrPos[1]); Console.WriteLine(railCar.jgrSN + "已经到达位置: " + outAmrPos[0].ToString() + "," + outAmrPos[1].ToString()); //发送下放指令 data = new byte[8]; data[1] = 7; data[3] = 6; data[4] = 0x80; await retrySendCmd(storageCarInfo.jgrSN, data); Console.WriteLine(storageCarInfo.jgrSN + "发送下放指令,指令内容:", string.Join(",", data)); //读取吊篮状态 await waitBasketPositionReset(storageCarInfo.jgrSN, 2); Console.WriteLine(storageCarInfo.jgrSN + "吊篮复位,状态为闭合状态。"); //先移动一格等待 //await planPathAndRun(jgrSN, outAmrPos[0]-1, outAmrPos[1]); //await waitArrived(jgrSN, (short)(outAmrPos[0] - 1), (short)outAmrPos[1]); //移动到指定位置 await planPathAndRun(railCar.jgrSN, amrDx, amrDy); //发送指令让车移动到拣料工位 //发送指令让车移动到拣料工位 //await retryMoveCar(jgrSN, selectStationPos[0], selectStationPos[1]); await waitArrived(railCar.jgrSN, (short)amrDx, (short)amrDy); Console.WriteLine(railCar.jgrSN + "移动车辆到指定工位,工位坐标:" + amrDx.ToString() + "," + amrDy.ToString()); //将任务完成状态放到集合中等待查询读取 notifyTaskCompleted(new string[] { storageCarInfo.jgrSN, railCar.jgrSN }); Console.WriteLine("完成出库任务"); } catch (CancelTaskException ex) { Console.WriteLine($"异常信息:{ex.Message}"); string[] cars; if (railCar == null && storageCarInfo == null) { cars = Array.Empty(); } else if (railCar == null && storageCarInfo != null) { cars = new string[1] { storageCarInfo.jgrSN }; } else if (railCar != null && storageCarInfo == null) { cars = new string[1] { railCar.jgrSN }; } else { cars = new string[2] { railCar.jgrSN, storageCarInfo.jgrSN }; } //发送取消任务成功通知 sendCancelTaskSuccess(cars); } } /// /// 移库操作 /// /// /// public async void moveStorageAsync(MoveStorageDTO dto) { JGR_Tc_Model? storageCarInfo = null; try { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return; } storageCarInfo = await waitFreeCar(JgrType.StorageCar); notifyCarInfo(storageCarInfo); int dx = int.Parse(dto.sourcePos.Split(',')[0]); int dy = int.Parse(dto.sourcePos.Split(',')[1]) - 1; int dz = int.Parse(dto.sourcePos.Split(',')[2]); await planPathAndRun(storageCarInfo.jgrSN, dx, dy); //到位判断等待 await waitArrived(storageCarInfo.jgrSN, (short)dx, (short)dy); ////获取仓储车当前位置 //int current_x = storageCarInfo.currLocation_X; //int current_y = storageCarInfo.currLocation_Y; ////移动仓储车到源仓位位置 //int dx = int.Parse(dto.sourcePos.Split(',')[0]); //int dy = int.Parse(dto.sourcePos.Split(',')[1]) - 1; //int dz = int.Parse(dto.sourcePos.Split(',')[2]); ////根据仓储车朝向原则上y轴减去1为车的位置 //if (current_x != dx || current_y != dy) //{ // //移动车到抓取位置 // await retryMoveCar(storageCarInfo.jgrSN, dx, dy); // //到位判断等待 // await waitArrived(storageCarInfo.jgrSN, (short)dx, (short)dy); //} //移动到位发送抓取指令 byte[] data = new byte[8]; data[1] = 7; data[3] = 5; data[4] = Convert.ToByte(dz); await retrySendCmd(storageCarInfo.jgrSN, data); //读取吊篮状态 await waitBasketPositionReset(storageCarInfo.jgrSN, 1); //更新目标位置 dx = int.Parse(dto.destPos.Split(',')[0]); dy = int.Parse(dto.destPos.Split(',')[1]) - 1; dz = int.Parse(dto.destPos.Split(',')[2]); await planPathAndRun(storageCarInfo.jgrSN, dx, dy); //移动车到抓取位置 //await retryMoveCar(storageCarInfo.jgrSN, dx, dy); //到位判断等待 await waitArrived(storageCarInfo.jgrSN, (short)dx, (short)dy); //移动到位发送抓取指令 data = new byte[8]; data[1] = 7; data[3] = 6; data[4] = Convert.ToByte(dz); await retrySendCmd(storageCarInfo.jgrSN, data); //读取吊篮状态 await waitBasketPositionReset(storageCarInfo.jgrSN, 2); //将任务完成状态放到集合中等待查询读取 notifyTaskCompleted(new string[] { storageCarInfo.jgrSN }); } catch(CancelTaskException ex) { Console.WriteLine($"异常信息:{ex.Message}"); string[] cars = Array.Empty(); if(storageCarInfo != null) { cars = new string[1] { storageCarInfo.jgrSN }; } //发送取消任务成功通知 sendCancelTaskSuccess(cars); } } /// /// 规划路线并下发路线 /// /// /// /// /// public async Task planPathAndRun(string jgrSN, int destX, int destY) { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } List paths = tryPlanPath(jgrSN, new() { jgrSN = jgrSN, startLocationX = this.jgrs[jgrSN].currLocation_X, startLocationY = this.jgrs[jgrSN].currLocation_Y, destLocationX = destX, destLocationY = destY }); if (paths != null && paths.Count > 0) { for(int i=0; i /// 规划路线 /// /// /// /// public List tryPlanPath(string jgrSN, MovingPath path) { try { if (cancelTaskIds.Contains(ThreadLocalManager.Get().taskId)) { throw new CancelTaskException("取消任务处理"); } return planPath(jgrSN, path); } catch { Task.Delay(TimeSpan.FromSeconds(2)).Wait(); return tryPlanPath(jgrSN, path); } } List planPath(string jgrSN, MovingPath path) { if (path.startLocationX == path.destLocationX && path.startLocationY == path.destLocationY) { //目标一样 return new(); } List paths = new List(); //检查路径是否合理,如果不算直线,则需要拆解为多条路径 if (path.startLocationX == path.destLocationX || (path.startLocationY == path.destLocationY && path.startLocationY<4)) { //检查路径是否可行 if (CheckPathAvaible(jgrSN, path)) { paths.Add(path); return paths; } } //拆成多段路径 //如果X轴能走到位置,则走X轴 MovingPath path1 = new MovingPath(); MovingPath path2 = new MovingPath(); if (path.startLocationY > path.destLocationY) { path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = path.destLocationY; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2)) { paths.Add(path1); paths.Add(path2); return paths; } } else { path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.destLocationX; path1.destLocationY = path.startLocationY; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2)) { paths.Add(path1); paths.Add(path2); return paths; } } //从当前位置的上边,下边位置尝试移动 for (int i = path.startLocationY - 1; i >= 1; i--) { path1 = new MovingPath(); path2 = new MovingPath(); MovingPath path3 = new MovingPath(); path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = i; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path1.destLocationY; path3.startLocationX = path2.destLocationX; path3.startLocationY = path2.destLocationY; path3.destLocationX = path.destLocationX; path3.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2) && CheckPathAvaible(jgrSN, path3)) { paths.Add(path1); paths.Add(path2); paths.Add(path3); return paths; } } for (int i = path.startLocationY + 1; i <= 8; i++) { path1 = new MovingPath(); path2 = new MovingPath(); MovingPath path3 = new MovingPath(); path1.startLocationX = path.startLocationX; path1.startLocationY = path.startLocationY; path1.destLocationX = path.startLocationX; path1.destLocationY = i; path2.startLocationX = path1.destLocationX; path2.startLocationY = path1.destLocationY; path2.destLocationX = path.destLocationX; path2.destLocationY = path1.destLocationY; path3.startLocationX = path2.destLocationX; path3.startLocationY = path2.destLocationY; path3.destLocationX = path.destLocationX; path3.destLocationY = path.destLocationY; if (CheckPathAvaible(jgrSN, path1) && CheckPathAvaible(jgrSN, path2) && CheckPathAvaible(jgrSN, path3)) { paths.Add(path1); paths.Add(path2); paths.Add(path3); return paths; } } throw new Exception("路线拥挤,请等待"); } /// /// 查找空车 /// /// /// public JGR_Tc_Model? findFreeCar(JgrType jgrType) { if (ThreadLocalManager.Get() == null || string.IsNullOrEmpty(ThreadLocalManager.Get().taskId) || string.IsNullOrWhiteSpace(ThreadLocalManager.Get().taskId)) { Console.WriteLine("没有任务id,不能执行当前业务"); return null; } JGR_Tc_Model? free_car = null; //查找空车 List carStatus = GetJgrs(); if (carStatus != null && carStatus.Count > 0) { foreach (var car in carStatus) { if (car.jgrType == (byte)jgrType && checkIsFree(car, true)) { free_car = car; if (!jgrTaskMap.ContainsKey(car.jgrSN)) { jgrTaskMap[car.jgrSN] = ThreadLocalManager.Get().taskId; } break; } } } return free_car; } /// /// 车辆是否空闲 /// /// /// private bool checkIsFree(JGR_Tc_Model car, bool isCheckRfId) { if(car.jgrType == (byte)JgrType.RailCar) { return car.moveStatus == 0 && (!isCheckRfId || (string.IsNullOrEmpty(car.rfid) || string.IsNullOrWhiteSpace(car.rfid))) && (!jgrTaskMap.ContainsKey(car.jgrSN) || jgrTaskMap[car.jgrSN] == ThreadLocalManager.Get().taskId); } else if(car.jgrType == (byte)JgrType.StorageCar) { return car.moveStatus == 0 && car.basketPosition == 0 && car.basketGripperStatus == 2 && (!jgrTaskMap.ContainsKey(car.jgrSN) || jgrTaskMap[car.jgrSN] == ThreadLocalManager.Get().taskId); } return false; } /// /// 获取仓储结构 /// /// public Dictionary getStorageGridCells() { return storageGridCells; } public bool checkStoragePos(int x, int y) { var storageGridCells = getStorageGridCells(); if (storageGridCells != null && storageGridCells.Count > 0) { //判断储位是否为不能出入库的储位 foreach (var cell in storageGridCells) { if (cell.Value.positionX == x && cell.Value.positionY == y) { if (!cell.Value.enableUse) { return false; } return true; } } } return false; } public void notifyCancelTask(string taskId) { try { mutex.WaitOne(); cancelTaskIds.Add(taskId); } finally { mutex.ReleaseMutex(); } } public void removeCancelTask(string taskId) { try { mutex.WaitOne(); cancelTaskIds.Remove(taskId); } finally { mutex.ReleaseMutex(); } } } public interface IJGR { bool Init(IServiceProvider services); bool ResetADS(); void SetHub(IHubContext hub); /// /// 获得车的清单,目前充电桩和车是一个结构 /// /// List GetJgrs(); } }