From f2234fcabef24a1e8e5b3173572464f7140a153e Mon Sep 17 00:00:00 2001 From: SoulStar Date: Mon, 6 Jul 2026 16:57:53 +0800 Subject: [PATCH] =?UTF-8?q?feat=20-=20=E4=BC=98=E5=8C=96=E5=86=99=E5=85=A5?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=86=99=E5=85=A5=E9=94=81=E5=AE=9A?= =?UTF-8?q?=E7=9A=84=E5=93=88=E5=B8=8C=E8=A1=A8=20-=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E8=87=AA=E5=8A=A8=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IndexPage/ChangeTypeViewModel.cs | 33 +++++--- .../IndexPage/ProductionLineViewModel.cs | 83 +++++++++++++++---- SlnMesnac.WPF/appsettings.json | 9 +- 3 files changed, 92 insertions(+), 33 deletions(-) diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs index b388e6e..ef3e17e 100644 --- a/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs +++ b/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs @@ -540,32 +540,43 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage return; } - //查找新订单(数据库中不存在的) - var newOrders = new List(); + //查找新订单或变更订单(全新订单 OR ProductOrder变更) + var hasNewOrChanged = false; foreach (var item in responsejson.Data) { - var existing = databaseService._helper.Query(x => x.OrderNo == item.OrderNo && x.ProductCode == item.ProductCode); + var existing = databaseService._helper.Query(x => + x.LineName == lineName && x.OrderNo == item.OrderNo && x.ProductCode == item.ProductCode); if (existing == null || existing.Count == 0) { - newOrders.Add(item); + //全新订单(OrderNo + ProductCode 不存在) + hasNewOrChanged = true; + Log.Information($"自动刷新发现新订单:{item.OrderNo},物料:{item.ProductCode},ProductOrder:{item.ProductOrder}"); + break; + } + else if (existing.Any(x => x.ProductOrder != item.ProductOrder)) + { + //ProductOrder变更(同OrderNo+ProductCode但ProductOrder不同) + hasNewOrChanged = true; + Log.Information($"自动刷新发现订单ProductOrder变更:{item.OrderNo},物料:{item.ProductCode}," + + $"旧ProductOrder:{existing[0].ProductOrder},新ProductOrder:{item.ProductOrder}"); + break; } } - if (newOrders.Count == 0) + if (!hasNewOrChanged) return; - //先删除该机台的订单数据 + //先删除该机台的订单数据,再全量插入MES返回的所有订单 databaseService._helper.Delete(x => x.LineName == lineName); - //插入新订单到数据库并刷新界面 - foreach (var item in newOrders) + foreach (var item in responsejson.Data) { item.ID = Guid.NewGuid().ToString(); item.InsertDate = DateTime.Now; item.LineName = lineName; databaseService._helper.Insert(item); - Log.Information($"自动刷新发现新订单:{item.OrderNo},物料:{item.ProductCode},自动执行"); } + Log.Information($"自动刷新已更新产线{lineName}的订单数据,共{responsejson.Data.Count}条"); //刷新界面显示 var items = databaseService._helper.QueryAll(); @@ -577,8 +588,8 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage } RefreshFilteredPanel(); - //自动执行第一个新订单 - var firstNewOrder = newOrders[0]; + //自动执行第一个订单(responsejson.Data[0]已在上面循环中赋好ID) + var firstNewOrder = responsejson.Data[0]; var deviceInfo = appConfig.deviceInfoConfig .Where(x => x.Collectid == appConfig.StationCode && x.Name == lineName && x.Deleteflag == 0) .ToList(); diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/ProductionLineViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/ProductionLineViewModel.cs index 31eee82..8143c0d 100644 --- a/SlnMesnac.WPF/ViewModel/IndexPage/ProductionLineViewModel.cs +++ b/SlnMesnac.WPF/ViewModel/IndexPage/ProductionLineViewModel.cs @@ -57,6 +57,8 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage public TcpServer _TcpServer; private DispatcherTimer _timer; private Dictionary _inventoryTimers = new Dictionary(); + private HashSet _tickingDevices = new HashSet(); + private HashSet _writingDevices = new HashSet(); private ObservableCollection _rfidHistoryRecords = new ObservableCollection(); private RealReadDataImpl databaseService = RealReadDataImpl.Instance; private System.Threading.Timer ReReadTimer; @@ -214,6 +216,9 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage /// private async void RecvIdentifyData_Instance(string iCombineId, List tagInfos) { + // 防重入:同一设备正在写入中,跳过本次标签读取 + if (!_writingDevices.Add(iCombineId)) + return; var rfidInfo = rfidList.FirstOrDefault(x => x.deviceid == iCombineId); var deviceInfo = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId); @@ -274,14 +279,13 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage //暂停订单自动刷新,防止写入过程中订单切换 WeakReferenceMessenger.Default.Send(string.Empty, "PauseOrderRefresh"); - //处理写入字符串,并写入 + /////////////////////////////////处理写入字符串,并写入//////////////////////////////////////// //var originBytes = tagInfos[0].EPC.Where(b => b != 0x00).ToArray(); bool writeflag = await rfidInfo.Set_Write(tagInfos[0].EPC, WriteData); + /////////////////////////////////处理写入字符串,并写入//////////////////////////////////////// state.WriteTime++; - //开启定时盘点和心跳 - StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value); - await rfidInfo.Set_HeartBeat(5); + if (writeflag) //写入成功 { @@ -289,6 +293,9 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage state.IsVerify = true; state.WriteTime = 0; + //开启定时盘点和心跳 + StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value); + await rfidInfo.Set_HeartBeat(5); //开始二次验证 await rfidInfo.Set_BeginIdentify(); @@ -314,7 +321,10 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage else //写入失败 { Log.Error($"第{state.WriteTime + 1}次写入失败,重试中..."); - //重试前再读取一次标签 + + //开启定时盘点和心跳 + StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value); + await rfidInfo.Set_HeartBeat(5); await rfidInfo.Set_BeginIdentify(); //设置下次读取超时 @@ -344,6 +354,7 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage await Task.Run(async () => { await Task.Delay(1000); + //StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value); await rfidInfo!.Set_BeginIdentify(); deviceInfo.CurrentState = "盘点中"; }); @@ -400,6 +411,10 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value); await rfidInfo.Set_HeartBeat(5); } + finally + { + _writingDevices.Remove(iCombineId); + } } /// @@ -812,20 +827,44 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage #region 定时盘点 /// - /// 启动定时盘点 + /// 启动定时盘点(旧版 - 存在线程问题,已废弃) /// - private void StartInventoryTimer(string deviceId, int intervalSeconds) + //private void StartInventoryTimer(string deviceId, int intervalSeconds) + //{ + // StopInventoryTimer(deviceId); + // var timer = new DispatcherTimer + // { + // Interval = TimeSpan.FromMilliseconds(intervalSeconds) + // }; + // timer.Tick += (s, e) => OnInventoryTimerTick(deviceId); + // timer.Start(); + // //OnInventoryTimerTick(deviceId); + // _inventoryTimers[deviceId] = timer; + // Log.Information($"设备 {deviceId} 启动定时盘点,间隔 {intervalSeconds} ms"); + //} + + /// + /// 启动定时盘点(修复版) + /// 修复内容: + /// 1. DispatcherTimer 强制关联 UI 线程 Dispatcher,避免在后台线程创建导致 Tick 不触发 + /// 2. 参数更名为 intervalMilliseconds,修正参数名与实际单位的误导 + /// 3. StopInventoryTimer 也同步到 UI 线程执行,保证字典操作线程安全 + /// + private void StartInventoryTimer(string deviceId, int intervalMilliseconds) { - StopInventoryTimer(deviceId); - var timer = new DispatcherTimer + var uiDispatcher = System.Windows.Application.Current.Dispatcher; + uiDispatcher.Invoke(() => { - Interval = TimeSpan.FromMilliseconds(intervalSeconds) - }; - timer.Tick += (s, e) => OnInventoryTimerTick(deviceId); - timer.Start(); - //OnInventoryTimerTick(deviceId); - _inventoryTimers[deviceId] = timer; - Log.Information($"设备 {deviceId} 启动定时盘点,间隔 {intervalSeconds} ms"); + StopInventoryTimer(deviceId); + var timer = new DispatcherTimer(DispatcherPriority.Normal, uiDispatcher) + { + Interval = TimeSpan.FromMilliseconds(intervalMilliseconds) + }; + timer.Tick += (s, e) => OnInventoryTimerTick(deviceId); + timer.Start(); + _inventoryTimers[deviceId] = timer; + Log.Information($"设备 {deviceId} 启动定时盘点,间隔 {intervalMilliseconds} ms"); + }); } /// @@ -842,16 +881,20 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage } /// - /// 定时盘点回调 + /// 定时盘点回调(加防重入保护) /// private async void OnInventoryTimerTick(string deviceId) { + // 防重入:上一次 Set_BeginIdentify 未完成时跳过本次 Tick + if (!_tickingDevices.Add(deviceId)) + return; + try { var rfid = rfidList.FirstOrDefault(x => x.deviceid == deviceId); if (rfid != null) { - //Log.Information($"设备 {deviceId} 执行定时盘点"); + Log.Information($"设备 {deviceId} 执行定时盘点"); //await rfid.Set_StopIdentify(); //await Task.Delay(100); await rfid.Set_BeginIdentify(); @@ -861,6 +904,10 @@ namespace SlnMesnac.WPF.ViewModel.IndexPage { Log.Error($"设备 {deviceId} 定时盘点异常: {ex.Message}"); } + finally + { + _tickingDevices.Remove(deviceId); + } } #endregion diff --git a/SlnMesnac.WPF/appsettings.json b/SlnMesnac.WPF/appsettings.json index ceb0b8e..ffb91d3 100644 --- a/SlnMesnac.WPF/appsettings.json +++ b/SlnMesnac.WPF/appsettings.json @@ -81,7 +81,7 @@ //设备名称,显示用 "Name": "Line1", //连接字符串,格式为IP:Port - "ConnectStr": "192.168.0.7:20107", + "ConnectStr": "192.168.0.62:20108", //固定不变 "Connectmode": "1", //固定不变 设备类型,RFly_I160表示RFly_I160读写器 @@ -111,7 +111,7 @@ "Devicetype": "RFly_I160", "Collectid": "102", "Addr": "VM006-2", - "Deleteflag": "0", + "Deleteflag": "1", "WriteDelaySet": 1000, //"WriteCount": 3, "InventoryIntervalSeconds": 5000, @@ -126,12 +126,13 @@ "StationCode": "102", - "MESConfig": "10.20.8.61:8001", + //"MESConfig": "10.20.8.61:8001", + "MESConfig": "127.0.0.1:5000", //是否启用订单自动刷新 "AutoOrderRefresh": true, //订单自动刷新间隔(秒) - "OrderRefreshInterval": 300, + "OrderRefreshInterval": 30, //模拟MES返回数据(调试用,为空则走真实MES接口) "MockMesResponse": "" },