using HslCommunication; using Newtonsoft.Json; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Metadata; using System.Text; using System.Threading; using WIDESEA_Common; using WIDESEA_Common.CutomerModel; using WIDESEA_Common.LogEnum; using WIDESEA_Common.TaskEnum; using WIDESEA_Common.Tools; using WIDESEA_Core.Utilities; using WIDESEA_Entity.DomainModels; using WIDESEA_Services; using WIDESEA_Services.IRepositories; using WIDESEA_Services.Repositories; using WIDESEA_Services.Services; using WIDESEA_WCS.WCSClient; namespace WIDESEA_WCS.Jobs.ConveyorLine.OutboundArea { public partial class OutboundAreaDispatch { /// /// 出库的层 /// private static List OutboundStationLayerNo = new List() { "60101", "60102", "60103", "60104", "60105", "60106", "60107" }; /// /// 入库托盘申请站台 /// private static string ReInboundRequestStationNo = "90101"; //出库站台交互 //private static string OutboundStationNo = "90201"; //提升机编号 private static string hoisterResultNo = "90100"; /// /// 出库区调度 /// /// /// public static string currentModel = "Inbound"; static int flag = 0; // 压装台缓存位字典 public static Dictionary PlatformDict = new Dictionary { ["70101"] = "80101", ["70103"] = "80103", ["70104"] = "80104", ["70106"] = "80106" }; // 出库区域校准缓存架校准 public static void OutboundAreaJZ(IDt_TaskWCSinfoRepository taskWCSinfoRepository, IDt_TaskRGVinfoRepository taskRGVinfoRepository, PLCClient client) { // 出库区的出库任务 到缓存架子了 就要把任务号和托盘号写入到对应层的缓存架 List executingTasks = taskWCSinfoRepository.Find(r => ( r.wcstask_state == TaskState.TaskState_Box_Out_RGV_Finished.ToString() || r.wcstask_state == TaskState.TaskState_Empty_Out_RGV_Finished.ToString()) && endStationNo.Contains(r.wcstask_endPoint)).ToList(); foreach (var item in executingTasks) { bool LoadSleep = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_LoadSleep.ToString(), item.wcstask_startPoint.ToString()).ToString()); int ReadNumber = int.Parse(client.ReadValue(CLineInfoDBName.R_Line_TaskNumber.ToString(), item.wcstask_startPoint.ToString()).ToString()); int ReadBarcode = int.Parse(client.ReadValue(CLineInfoDBName.R_Line_Barcode.ToString(), item.wcstask_startPoint.ToString()).ToString()); if (LoadSleep && (ReadNumber == 0 || ReadBarcode == 0)) { //写入托盘条码 bool writeTaskNumber = client.WriteValue(CLineInfoDBName.W_Line_TaskNumber.ToString(), item.wcstask_startPoint, item.wcstask_taskNumber); bool writeBarcode = client.WriteValue(CLineInfoDBName.W_Line_Barcode.ToString(), item.wcstask_startPoint, item.wcstask_barcode); } } foreach (var item in OutboundStationLayerNo) { bool LoadSleep = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_LoadSleep.ToString(), item.ToString()).ToString()); int ReadNumber = int.Parse(client.ReadValue(CLineInfoDBName.R_Line_TaskNumber.ToString(), item.ToString()).ToString()); int ReadBarcode = int.Parse(client.ReadValue(CLineInfoDBName.R_Line_Barcode.ToString(), item.ToString()).ToString()); Dt_TaskWCSinfo dt_TaskWCSinfo = taskWCSinfoRepository.Find(r => r.wcstask_type == TaskType.TaskType_Empty_Pallet_Inbound.ToString() && r.wcstask_state == TaskState.TaskState_Assigned.ToString() && r.wcstask_startPoint == 90101.ToString() && r.wcstask_endPoint == item && r.wcstask_barcode == ReadBarcode.ToString() && r.wcstask_taskNumber == ReadNumber ).FirstOrDefault(); if (LoadSleep && dt_TaskWCSinfo != null) { // 证明货物已经到了缓存架 但任务状态没有改变 还是新建状态 就要改为 TaskState.TaskState_Empty_In_PLC_Finished.ToString() dt_TaskWCSinfo.wcstask_state = TaskState.TaskState_Empty_In_PLC_Finished.ToString(); taskWCSinfoRepository.Update(dt_TaskWCSinfo, true); string str = $"出库区域 空托盘回库 托盘都到缓存架子 但任务状态没变 {DateTime.Now}【{dt_TaskWCSinfo.wcstask_state}】,托盘号:{dt_TaskWCSinfo.wcstask_barcode},任务号:{dt_TaskWCSinfo.wcstask_taskNumber}"; LogRecord.WriteLog(LogEnum.Errer, str.ToString()); } } // 这里要写一个 有一些托盘已经出到外面去了 但状态还是提升机运行中 导致后面的发不出去 Dt_TaskWCSinfo OutTask = taskWCSinfoRepository.Find(r => r.wcstask_state == TaskState.TaskState_HoisterExecuting.ToString() && endStationNo.Contains(r.wcstask_endPoint) ).FirstOrDefault(); if (OutTask != null) { double secondsPassend = (DateTime.Now - OutTask.wcstask_dispatcherTime.Value).TotalSeconds; if (secondsPassend > 38) // 如果大于38秒 还没用提升机完成 就要手动 { string state = TaskState.TaskState_Box_Out_Line_Executing.ToString(); if (OutTask.wcstask_type == TaskType.TaskType_Empty_Pallet_Outbound.ToString()) { state = TaskState.TaskState_Empty_Out_Line_Executing.ToString(); } OutTask.wcstask_state = state; OutTask.wcstask_dispatcherTime = DateTime.Now; taskWCSinfoRepository.Update(OutTask, true); string str = $"出库区 出库 提升机已经内的托盘已经出去 但状态还是提升机执行中... {DateTime.Now},托盘号:{OutTask.wcstask_barcode},任务号:{OutTask.wcstask_taskNumber}"; LogRecord.WriteLog(LogEnum.Errer, str.ToString()); } } } public static void OutboundAreaDispathAction(IDt_TaskWCSinfoRepository taskWCSinfoRepository, IDt_TaskRGVinfoRepository taskRGVinfoRepository, PLCClient client) { try { //出库完成 OutboundCompleteAction(taskWCSinfoRepository, client); //压装出库口任务完成 //生成可下发的RGV任务,至缓存口 List executingTask = taskWCSinfoRepository.Find(r => r.wcstask_state != TaskState.TaskState_Assigned.ToString() && endStationNo.Contains(r.wcstask_endPoint)); // 这里要写一个逻辑,就是我获取的任务,只有对应的站台为空,我才能下发,这样改的话,空托盘入库也要改 if (executingTask.Count < 8) //小于8,则可生成去这边的任务 { //直接将任务添加到AGV任务 Dt_TaskWCSinfo outboundTask = GetOutboundTask(taskWCSinfoRepository, client); if (null != outboundTask) { //查找是否有该托盘条码任务,如果没有则进行添加RGV任务 Dt_TaskRGVinfo rgvInfo = taskRGVinfoRepository.FindFirst(r => r.rgvtask_barCode == outboundTask.wcstask_barcode && r.rgvtask_wcsTaskNumber == outboundTask.wcstask_taskNumber); if (null == rgvInfo) { string rgvTaskType = RGVTaskType.RgvTaskType_Inbound.ToString(); if (outboundTask.wcstask_type.Equals(TaskType.TaskType_Box_Pallet_Outbound.ToString()) || outboundTask.wcstask_type.Equals(TaskType.TaskType_Empty_Pallet_Outbound.ToString())) rgvTaskType = RGVTaskType.RgvTaskType_Outbound.ToString(); rgvInfo = GetRGVTask(taskRGVinfoRepository, outboundTask, rgvTaskType); taskRGVinfoRepository.Add(rgvInfo, true); outboundTask.wcstask_state = TaskState.TaskState_RGV_Received.ToString(); taskWCSinfoRepository.Update(outboundTask, x => x.wcstask_state, true); } } } //缓存口(缓存架)至输送线 OutboundCZ_PLC(taskWCSinfoRepository, taskRGVinfoRepository, client); //空托入库申请(生成了空托入库任务) InboundRequestAction(taskWCSinfoRepository, taskRGVinfoRepository, client); //空托到缓存口(缓存架) ReInboundKTP(taskWCSinfoRepository, client); //空托入库口,生成RGV任务,进行入库 RGV_InboundKTP(taskWCSinfoRepository, taskRGVinfoRepository, client); } catch (Exception ex) { WriteLog.Info("OutboundArea").Write($"{DateTime.Now}出库区域调度失败:{ex.Message}", "OutboundArea"); } } public static bool WriteRGVState(PLCClient client, bool value) { client.WriteValue(CLineInfoDBName.W_System_RGVState.ToString(), value); string rel = string.Empty; for (int i = 0; i < 50; i++) { rel = client.ReadValue(CLineInfoDBName.W_System_RGVState.ToString()).ToString(); if (value == bool.Parse(rel)) break; else { client.WriteValue(CLineInfoDBName.W_System_RGVState.ToString(), value); Thread.Sleep(88); } } rel = client.ReadValue(CLineInfoDBName.W_System_RGVState.ToString()).ToString(); if (value != bool.Parse(rel)) Console.Out.WriteLine($"{DateTime.Now}写入车存储中{value}失败."); return bool.Parse(rel); } /// /// 查询是否有可出的出库任务 /// /// /// /// public static Dt_TaskWCSinfo GetOutboundTask(IDt_TaskWCSinfoRepository taskWCSinfoRepository, PLCClient client) { Dt_TaskWCSinfo wcsInfo = null; List listTask = taskWCSinfoRepository.Find(r => r.wcstask_state == TaskState.TaskState_Assigned.ToString() && (r.wcstask_type == TaskType.TaskType_Box_Pallet_Outbound.ToString() || r.wcstask_type == TaskType.TaskType_Empty_Pallet_Outbound.ToString()) && endStationNo.Contains(r.wcstask_endPoint)).OrderBy(r => r.wcstask_createTime).ToList();// foreach (var item in listTask.GroupBy(r => r.wcstask_endPoint)) { Dt_TaskWCSinfo wcsTask = item.OrderBy(r => r.wcstask_createTime).FirstOrDefault(); if (null != wcsTask) { //判断压装台是否有货 也就是判断80101那边 bool yzflag = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_LoadSleep.ToString(), PlatformDict[wcsTask.wcstask_endPoint]).ToString()); bool yznoflag = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_NoLoadSleep.ToString(), PlatformDict[wcsTask.wcstask_endPoint]).ToString()); if (yzflag || !yznoflag) continue; //判断缓存架是否有货 bool flag = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_LoadSleep.ToString(), wcsTask.wcstask_startPoint).ToString()); bool noflag = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_NoLoadSleep.ToString(), wcsTask.wcstask_startPoint).ToString()); if (flag || !noflag) continue; //同一层有执行中的任务不添加 //Dt_TaskWCSinfo executingTask = taskWCSinfoRepository.FindFirst(r => //(r.wcstask_startPoint == wcsTask.wcstask_startPoint || r.wcstask_endPoint == wcsTask.wcstask_endPoint) //&& (r.wcstask_state == TaskState.TaskState_RGV_Received.ToString() || r.wcstask_state == TaskState.TaskState_HoisterExecuting.ToString())); //if (null != executingTask) // continue; // 肖佼 将上面修改成下面 Dt_TaskWCSinfo executingTask = taskWCSinfoRepository.FindFirst(r => (r.wcstask_startPoint == wcsTask.wcstask_startPoint) && (r.wcstask_state == TaskState.TaskState_RGV_Received.ToString())); if (null != executingTask) continue; List listTarget = taskWCSinfoRepository.Find(r => r.wcstask_endPoint == wcsTask.wcstask_endPoint && r.wcstask_state != TaskState.TaskState_Assigned.ToString()); string barcode = client.ReadValue(CLineInfoDBName.R_Line_Barcode.ToString(), wcsTask.wcstask_endPoint).ToString(); if (string.IsNullOrEmpty(barcode) || "0".Equals(barcode)) { if (null != listTarget && listTarget.Count > 1) continue; else { wcsInfo = wcsTask; break; } } else { if (null != listTarget && listTarget.Count > 0) continue; else { wcsInfo = wcsTask; break; } } } } return wcsInfo; } public static Dt_TaskRGVinfo GetRGVTask(IDt_TaskRGVinfoRepository taskRGVinfoRepository, Dt_TaskWCSinfo wcsTask, string rgvTaskType) { Dt_TaskRGVinfo rgvInfo = new Dt_TaskRGVinfo(); rgvInfo.rgvtask_taskId = GetTaskNumber.GetRgvTaskNumber(taskRGVinfoRepository).ToString(); rgvInfo.rgvtask_taskType = rgvTaskType; rgvInfo.rgvtask_taskStatus = RGVTaskState.RgvTaskState_Wait_Send.ToString(); rgvInfo.rgvtask_priorityCode = wcsTask.wcstask_grade.ToString(); rgvInfo.rgvtask_startNode = wcsTask.wcstask_startLocation; rgvInfo.rgvtask_endNode = wcsTask.wcstask_endLocation; rgvInfo.rgvtask_wcsTaskNumber = wcsTask.wcstask_taskNumber; rgvInfo.rgvtask_barCode = wcsTask.wcstask_barcode; rgvInfo.rgvtask_creator = wcsTask.wcstask_creator; rgvInfo.rgvtask_msgTime = DateTime.Now; rgvInfo.rgvtask_areaCode = "OutboundArea"; return rgvInfo; } // 只用一个字典:Key=托盘号,Value=Tuple<锁对象, 最后使用时间> // Tuple:Item1=锁对象,Item2=最后使用时间 private static readonly Dictionary> BarcodeLockDict = new Dictionary>(); private static readonly object GlobalLock = new object(); // 保护字典的全局锁 private const int ExpireMinutes = 1; // 过期时间:1分钟 /// /// 获取托盘号专属锁(单字典+1分钟过期+懒清理) /// private static object GetBarcodeLock(string barcode) { lock (GlobalLock) // 全局锁保护字典操作 { // ========== 第一步:懒清理(每次获取锁时,清理超过1分钟的闲置托盘号) ========== var expiredBarcodes = BarcodeLockDict .Where(kv => (DateTime.Now - kv.Value.Item2).TotalMinutes > ExpireMinutes) .Select(kv => kv.Key) .ToList(); foreach (var expiredBarcode in expiredBarcodes) { BarcodeLockDict.Remove(expiredBarcode); } // ========== 第二步:处理当前托盘号的锁和时间 ========== if (!BarcodeLockDict.ContainsKey(barcode)) { // 无该托盘号:新增锁对象+当前时间 BarcodeLockDict[barcode] = Tuple.Create(new object(), DateTime.Now); } // 返回当前托盘号的锁对象 return BarcodeLockDict[barcode].Item1; } } public static void InboundRequestAction(IDt_TaskWCSinfoRepository taskWCSinfoRepository, IDt_TaskRGVinfoRepository taskRGVinfoRepository, PLCClient client) { try { int R_Line_PLCDispatch = int.Parse(client.ReadValue(CLineInfoDBName.W_Line_PLCDispatch.ToString(), ReInboundRequestStationNo).ToString()); bool load = bool.Parse(client.ReadValue(CLineInfoDBName.R_Line_LoadSleep.ToString(), ReInboundRequestStationNo).ToString()); if (R_Line_PLCDispatch == 1 && load) { string barcode = client.ReadValue(CLineInfoDBName.R_Line_Barcode.ToString(), ReInboundRequestStationNo).ToString(); if (!string.IsNullOrEmpty(barcode) && !"0".Equals(barcode)) { if (int.Parse(barcode) < 800000 || int.Parse(barcode) > 890000) { return; } object barcodeLock = GetBarcodeLock(barcode); lock (barcodeLock) { Dt_TaskWCSinfo IsTask = taskWCSinfoRepository.FindFirst(x => x.wcstask_barcode == barcode && (x.wcstask_type == TaskType.TaskType_Empty_Pallet_Outbound.ToString() || x.wcstask_type == TaskType.TaskType_Box_Pallet_Outbound.ToString()) && (x.wcstask_state == TaskState.TaskState_Box_Out_Line_Executing.ToString() || x.wcstask_state == TaskState.TaskState_Empty_Out_Line_Executing.ToString()) && (endStationNo.Contains(x.wcstask_endPoint))); if (IsTask != null) { // 这里要加一个判断,如果拿回来的空托盘是800001,但是我有一条这个托盘的任务是800001还没有到指定的压装台,所以我要完成这个任务 // 在此完成该空托出库任务 WebResponseContent content = taskWCSinfoRepository.DbContextBeginTransaction(() => { //上报WMS任务完成 content = WMSApi.TellWmsTaskFinished(IsTask.wcstask_barcode,IsTask.wcstask_backUp_2); if (content.Status) { //移动任务到历史表 Dt_TaskWCSinfo_HtyRepository taskWCSinfo_HtyRepository = new Dt_TaskWCSinfo_HtyRepository(taskWCSinfoRepository.DbContext); IsTask.wcstask_state = TaskState.TaskState_Finished.ToString(); CommonFunction.AddWcsTaskToHistory(IsTask, taskWCSinfoRepository, taskWCSinfo_HtyRepository); } else throw new Exception($"{DateTime.Now}上报WMS任务完成出错,原因:【{content.Message}】"); return content; }); string str = string.Empty; if (content.Status) str = $" {DateTime.Now}上报WMS出库完成成功,【手动干扰出库没有到达指定压装台点位,手动拿回90101】【TaskState_ConveyorLineExecuting】,托盘号:{IsTask.wcstask_barcode},任务号:{IsTask.wcstask_taskNumber}"; else str = $" {DateTime.Now}上报WMS出库完成失败【手动干扰出库没有到达指定压装台点位,手动拿回90101】【TaskState_ConveyorLineExecuting】,托盘号:{IsTask.wcstask_barcode},任务号:{IsTask.wcstask_taskNumber}"; WriteLog.Info(IsTask.wcstask_endPoint).Write(str, IsTask.wcstask_endPoint); LogRecord.WriteLog(LogEnum.Errer, str.ToString()); } Dt_TaskWCSinfo wcsInfo = taskWCSinfoRepository.FindFirst(r => r.wcstask_barcode == barcode); if (null == wcsInfo) { //获取车轴出库 List executingTask = taskWCSinfoRepository.Find(r => (r.wcstask_type.Equals(TaskType.TaskType_Box_Pallet_Outbound.ToString()) || r.wcstask_type.Equals(TaskType.TaskType_Empty_Pallet_Outbound.ToString())) && endStationNo.Contains(r.wcstask_endPoint)); //获取有多少条出库的任务 int LineExecucount = executingTask.Count(x => x.wcstask_state == TaskState.TaskState_Box_Out_Line_Executing.ToString() || x.wcstask_state == TaskState.TaskState_Empty_Out_Line_Executing.ToString()); if (LineExecucount >= 8) { //判断是否有对应层的出库任务, //申请入库任务 WebResponseContent content = WMSApi.PostInboundRequstToWMS(barcode); if (content.Status) { try { WmsTaskInfo wmsTask = JsonConvert.DeserializeObject(content.Data.ToString()); wcsInfo = CommonFunction.AddWCSEmptyInboundTask(wmsTask, taskWCSinfoRepository); taskWCSinfoRepository.Add(wcsInfo, true); } catch (Exception) { string str = $"这里可能是出现了并发的情况托盘码 在添加的时候拦截的【{wcsInfo.wcstask_barcode}】" + $"任务号【{wcsInfo.wcstask_taskNumber}】,任务状态:【{wcsInfo.wcstask_state}】"; LogRecord.WriteLog(LogEnum.Errer, str.ToString()); //throw; } } else { if (content.Message.Contains("当前托盘号已存在库里")) { client.WriteValue(CLineInfoDBName.W_Line_PLCDispatch.ToString(), ReInboundRequestStationNo, 4); } throw new Exception("入库申请失败:" + content.Message); } } else { //判断有多少条在缓存架的 int RGV_Finishedcount = executingTask.Count(x => x.wcstask_state != TaskState.TaskState_Box_Out_Line_Executing.ToString() && x.wcstask_state != TaskState.TaskState_Empty_Out_Line_Executing.ToString() && x.wcstask_state != TaskState.TaskState_Assigned.ToString()); if (RGV_Finishedcount == 0) { // 核心:获取当前托盘号的专属锁,同一托盘号串行处理 //申请入库任务 WebResponseContent content = WMSApi.PostInboundRequstToWMS(barcode); if (content.Status) { try { WmsTaskInfo wmsTask = JsonConvert.DeserializeObject(content.Data.ToString()); wcsInfo = CommonFunction.AddWCSEmptyInboundTask(wmsTask, taskWCSinfoRepository); taskWCSinfoRepository.Add(wcsInfo, true); } catch (Exception) { string str = $"这里可能是出现了并发的情况托盘码,在添加的时候拦截的【{wcsInfo.wcstask_barcode}】" + $"任务号【{wcsInfo.wcstask_taskNumber}】,任务状态:【{wcsInfo.wcstask_state}】"; LogRecord.WriteLog(LogEnum.Errer, str.ToString()); //throw; } } else { if (content.Message.Contains("当前托盘号已存在库里")) { client.WriteValue(CLineInfoDBName.W_Line_PLCDispatch.ToString(), ReInboundRequestStationNo, 4); } else if (content.Message.Contains("暂无货位可分配")) { } else { throw new Exception("入库申请失败:" + content.Message); } } } } } else { string str = $"这里可能是出现了并发的情况托盘码 在查询的时候拦截的【{wcsInfo.wcstask_barcode}】" + $"任务号【{wcsInfo.wcstask_taskNumber}】,任务状态:【{wcsInfo.wcstask_state}】"; LogRecord.WriteLog(LogEnum.Errer, str.ToString()); } } } else { throw new Exception("入库申请失败:读取到的空托盘条码为空,读取内容:" + barcode); } } } catch (Exception ex) { client.WriteValue(CLineInfoDBName.W_Line_PLCDispatch.ToString(), ReInboundRequestStationNo, 3); LogRecord.WriteLog(LogEnum.Errer, DateTime.Now.ToString() + ex.Message.ToString()); Console.Out.WriteLine(DateTime.Now + ex.Message); } } } }