You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

751 lines
30 KiB
C#

using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using GalaSoft.MvvmLight;
3 months ago
using Microsoft.Extensions.DependencyInjection;
using NVelocity.Runtime.Directive;
2 months ago
using Serilog;
using SlnMesnac.Common;
3 months ago
using SlnMesnac.Config;
using SlnMesnac.Model.domain;
using SlnMesnac.Model.dto;
using SlnMesnac.Model.Enum;
using SlnMesnac.Repository;
3 months ago
using SlnMesnac.Repository.service;
using SlnMesnac.Repository.service.Impl;
3 months ago
using SlnMesnac.Rfid;
using SlnMesnac.Rfid.Enum;
3 months ago
using SlnMesnac.Serilog;
using SlnMesnac.TouchSocket;
using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
3 months ago
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Markup;
3 months ago
using System.Windows.Threading;
using System.Xml.Serialization;
using TouchSocket.Core;
3 months ago
using static Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System;
using Task = System.Threading.Tasks.Task;
namespace SlnMesnac.WPF.ViewModel.IndexPage
{
[RegisterAsSingletonAttribute]
public class ProductionLineViewModel : ViewModelBase
{
#region 参数定义
private static StringChange _StringChange;
private String SerialNo = "";
3 months ago
private SerilogHelper _logger;
//private ISqlSugarClient? sqlSugarClient;
3 months ago
//容器里面的读写器集合
public List<RfidAbsractFactory> rfidList;
private AppConfig appConfig;
private MeshttpClient meshttpClient;
public TcpServer _TcpServer;
private DispatcherTimer _timer;
private Dictionary<string, DispatcherTimer> _inventoryTimers = new Dictionary<string, DispatcherTimer>();
3 months ago
private ObservableCollection<RFIDRecord> _rfidHistoryRecords = new ObservableCollection<RFIDRecord>();
private RealReadDataImpl databaseService = RealReadDataImpl.Instance;
private System.Threading.Timer ReReadTimer;
private bool IsVerify = false;
private int WriteTime = 0;
private string LastWrite;
private string LastRFIDEPC;
private CancellationTokenSource? _verifyCts;
private CancellationTokenSource? _writeCts;
#endregion
#region 关联属性
3 months ago
public ObservableCollection<RFIDRecord> RFIDHistoryRecords
{
get { return _rfidHistoryRecords; }
set
{
_rfidHistoryRecords = value;
RaisePropertyChanged(() => RFIDHistoryRecords);
}
}
ChangeTypeViewModel ChangeTypeView;
private ObservableCollection<Real_DataInfo> _Deviceinfo = new ObservableCollection<Real_DataInfo>();
public ObservableCollection<Real_DataInfo> Deviceinfo
{
get { return _Deviceinfo; }
set
{
_Deviceinfo = value;
RaisePropertyChanged(() => Deviceinfo);
}
}
3 months ago
/// <summary>
/// 日期时间
/// </summary>
private DateTime _currentDateTime;
public DateTime CurrentDateTime
{
get => _currentDateTime;
set
{
if (_currentDateTime != value)
{
_currentDateTime = value;
RaisePropertyChanged(() => CurrentDateTime);
}
}
}
/// <summary>
/// 上一次写入状态
/// </summary>
private string _lastWriteState;
public string LastWriteState
{
get => _lastWriteState;
set
{
if (_lastWriteState != value)
{
_lastWriteState = value;
RaisePropertyChanged(() => LastWriteState);
}
}
}
/// <summary>
/// 当前状态 空闲 盘点中 写入中
/// </summary>
private string _CurrentState = "空闲";
public string CurrentState
{
get => _CurrentState;
set
{
if (_CurrentState != value)
{
_CurrentState = value;
RaisePropertyChanged(() => CurrentState);
}
}
}
#endregion
#region 构造函数
3 months ago
public ProductionLineViewModel()
{
ChangeTypeView = App.ServiceProvider.GetService<ChangeTypeViewModel>();
// 构造函数里注册
WeakReferenceMessenger.Default.Register<Real_DataInfo>(this, RefreshOrderNo);
WeakReferenceMessenger.Default.Register<string, string>(this, "Cancel", StopMessage);
_StringChange = App.ServiceProvider.GetService<StringChange>();
3 months ago
_logger = App.ServiceProvider.GetService<SerilogHelper>();
appConfig = App.ServiceProvider.GetService<AppConfig>();
//sqlSugarClient = App.ServiceProvider.GetService<ISqlSugarClient>();
3 months ago
_TcpServer = App.ServiceProvider.GetRequiredService<TcpServer>();
rfidList = App.ServiceProvider.GetRequiredService<List<RfidAbsractFactory>>();
rfidList.ForEach(rfid =>
{
3 months ago
rfid._Action += RecvIdentifyData_Instance;
//rfid._RefreshLogMessageAction += RefreshLogMessage;
});
LoadDeviceInfo();
StartCheckStatus();
//GetRFIDHistoryRecords();
_currentDateTime = DateTime.Now;
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1) // 每秒更新一次
};
_timer.Tick += (s, e) =>
{
CurrentDateTime = DateTime.Now;
};
_timer.Start();
2 months ago
Log.Information("RFID输送带系统启动");
3 months ago
}
#endregion
#region 核心写入算法
3 months ago
/// <summary>
/// 接收到连续盘点标签返回
/// </summary>
/// <param name="iCombineId"></param>
/// <param name="tagInfos"></param>
private async void RecvIdentifyData_Instance(string iCombineId, List<TagInfo> tagInfos)
{
try
{
_writeCts?.Cancel();
var rfidInfo = rfidList.FirstOrDefault(x => x.deviceid == iCombineId);
var deviceInfo = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId);
var deviceConfig = appConfig.deviceInfoConfig.FirstOrDefault(x => x.Deviceid == iCombineId);
//初次写入
if (!IsVerify)
3 months ago
{
//写入前等待标签稳定
if (!IsVerify)
{
await Task.Delay(deviceInfo.WriteDelaySet ?? 2000);
}
//验证是否已获取订单信息
if (string.IsNullOrEmpty(deviceInfo.OrderNo))
{
MessageBox.Show("请先获取MES订单号", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
//查询数据库是否已存在记录
string epcascii = Encoding.ASCII.GetString(tagInfos[0].EPC);
epcascii = epcascii.Replace("\0", "");
List<real_readdata> real_Readdatas = databaseService._helper.Query(x => x.rfidascii == epcascii);
//如果不存在则写入
if (real_Readdatas.Count <= 0)
//if (true)
{
//验证是否已获取序列号
if (string.IsNullOrEmpty(deviceInfo.SerialNo))
{
return;
}
deviceInfo.ReadTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
//拼接订单号写入标签
string WriteData = deviceInfo.OrderNo
+ deviceInfo.LineNo
+ DateTime.Now.ToString("yy")
+ deviceInfo.NextProductNo
+ deviceInfo.SerialNo;
CurrentState = "写入中";
LastRFIDEPC = tagInfos[0].EPCstring;
//写入前暂停心跳和连续盘点
StopInventoryTimer(iCombineId);
await rfidInfo.Set_HeartBeat(0);
Task.Delay(100).Wait();
//处理写入字符串,并写入
//var originBytes = tagInfos[0].EPC.Where(b => b != 0x00).ToArray();
bool writeflag = rfidInfo.Set_Write(tagInfos[0].EPC, WriteData).GetAwaiter().GetResult();
WriteTime++;
if (writeflag) //写入成功
{
LastWrite = WriteData;
IsVerify = true;
WriteTime = 0;
//开始二次验证
rfidInfo.Set_BeginIdentify().GetAwaiter().GetResult();
// 启动验证超时检测
_verifyCts?.Cancel();
_verifyCts = new CancellationTokenSource();
var token = _verifyCts.Token;
var capturedTagInfos = tagInfos;
var timeoutMs = deviceConfig.VerifyTimeoutMilliseconds ?? 20000;
_ = Task.Run(async () =>
{
try
{
await Task.Delay(timeoutMs, token);
Log.Error($"二次验证超时未读到标签,标签已离开,记为写入失败");
await DataAdd(iCombineId, capturedTagInfos, false);
}
catch (TaskCanceledException) { }
}, token);
//开启定时盘点和心跳
StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value);
await rfidInfo.Set_HeartBeat(5);
return;
}
else //写入失败
{
Log.Error($"第{WriteTime + 1}次写入失败,重试中...");
//重试前再读取一次标签
rfidInfo.Set_BeginIdentify().GetAwaiter().GetResult();
//设置下次读取超时
_writeCts?.Cancel();
_writeCts = new CancellationTokenSource();
var token = _writeCts.Token;
var capturedTagInfos = tagInfos;
var timeoutMs = deviceConfig.WriteTimeoutMilliseconds ?? 10000;
_ = Task.Run(async () =>
{
try
{
await Task.Delay(timeoutMs, token);
Log.Error($"写入前读取超时,[{tagInfos[0].EPCstring}] 记为写入失败");
await DataAdd(iCombineId, capturedTagInfos, false);
WriteTime = 0;
}
catch (TaskCanceledException) { }
}, token);
StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value);
await rfidInfo.Set_HeartBeat(5);
Task.Delay(100).Wait();
}
return;
}
else
{
await Task.Run(async () =>
{
await Task.Delay(1000);
await rfidInfo!.Set_BeginIdentify();
CurrentState = "盘点中";
});
}
3 months ago
}
else //二次验证流程
{
var judgeString = Encoding.ASCII.GetString(tagInfos[0].EPC);
judgeString = judgeString.Replace("\0", "");
//二次验证如果不通过 重新开始盘点
if (judgeString != LastWrite)
{
// 读到标签但数据不匹配,重置超时(标签仍在天线范围内)
_verifyCts?.Cancel();
_verifyCts = new CancellationTokenSource();
var token = _verifyCts.Token;
var capturedTagInfos = tagInfos;
var timeoutMs = deviceConfig?.VerifyTimeoutMilliseconds ?? 20000;
_ = Task.Run(async () =>
{
try
{
await Task.Delay(timeoutMs, token);
Log.Error($"二次验证超时写入标签超时,标签已离开,记为写入失败");
await DataAdd(iCombineId, capturedTagInfos, false);
}
catch (TaskCanceledException) { }
}, token);
IsVerify = false;
var tempEPC = LastRFIDEPC;
rfidInfo.Set_BeginIdentify().GetAwaiter().GetResult();
LastRFIDEPC = tempEPC;
CurrentState = "盘点中";
return;
}
else //二次验证通过
{
_verifyCts?.Cancel();
Log.Information($"验证成功");
//插入成功记录
await DataAdd(iCombineId, tagInfos, true);
return;
}
}
3 months ago
}
catch (Exception e)
{
Log.Error($"读结果准备写入时异常:{e.Message}");
CurrentState = "空闲";
3 months ago
}
}
/// <summary>
/// 插入记录
/// </summary>
/// <param name="iCombineId"></param>
/// <param name="tagInfos"></param>
/// <param name="isSuccess"></param>
private async Task DataAdd(string iCombineId, List<TagInfo> tagInfos, bool isSuccess)
{
var deviceInfo = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId);
var rfidInfo = rfidList.FirstOrDefault(x => x.deviceid == iCombineId);
var deviceConfig = appConfig.deviceInfoConfig.FirstOrDefault(x => x.Deviceid == iCombineId);
IsVerify = false;
deviceInfo.WriteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
deviceInfo.WriteStatus = isSuccess ? "成功" : "失败";
LastWriteState = isSuccess ? "成功" : "失败";
deviceInfo.RfidASCII = LastWrite;
deviceInfo.RfidEPC = LastRFIDEPC;
deviceInfo.SerialNo = (Convert.ToInt32(deviceInfo.SerialNo) + 1).ToString("D2");
//保存写入记录
real_readdata real_Readdata = new real_readdata()
{
objid = Guid.NewGuid().ToString(),
serialno = (Convert.ToInt32(deviceInfo.SerialNo) - 1).ToString("D2"),
orderno = deviceInfo.OrderNo,
lineno = deviceInfo.LineNo,
producttype = deviceInfo.ProductType,
rfidepc = deviceInfo.RfidEPC,
rfidascii = LastWrite,
readtime = Convert.ToDateTime(deviceInfo.ReadTime),
writetime = Convert.ToDateTime(deviceInfo.WriteTime),
writestatus = deviceInfo.WriteStatus
};
//插入到数据库历史记录中
var a = databaseService._helper.Insert(real_Readdata);
//插入记录到首页临时数据显示
AddRFIDData(iCombineId, tagInfos);
LastRFIDEPC = "";
LastWrite = "";
await rfidInfo.Set_BeginIdentify();
await Task.Delay(100);
StartInventoryTimer(iCombineId, deviceConfig.InventoryIntervalSeconds.Value);
await rfidInfo.Set_HeartBeat(5);
CurrentState = "盘点中";
return;
}
#endregion
#region 数据流相关
/// <summary>
/// 开始订单初次流程
/// </summary>
/// <param name="recipient"></param>
/// <param name="real_Data"></param>
private void RefreshOrderNo(object recipient, Real_DataInfo real_Data)
{
//查询当前订单产线最大序列号
List<real_readdata> real_Readdatas = databaseService._helper.Query(x => x.orderno == real_Data.OrderNo && x.lineno == real_Data.LineNo);
string SNo = "";
if (real_Readdatas.Count > 0)
{
SNo = real_Readdatas.Max(x => x.serialno);
SNo = (Convert.ToInt32(SNo) + 1).ToString("D2");
}
else
{
SNo = "1";
}
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).SerialNo = Convert.ToInt32(SNo).ToString("D2");
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).OrderNo = real_Data.OrderNo;
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).ProductType = real_Data.ProductType;
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).LineNo = real_Data.LineNo;
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).RfidCount = real_Data.RfidCount;
Deviceinfo.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid).NextProductNo = real_Data.NextProductNo;
rfidList.FirstOrDefault(x => x.deviceid == real_Data.Deviceid).Set_BeginIdentify().GetAwaiter().GetResult();
IsVerify = false;
CurrentState = "盘点中";
// 启动定时盘点
var deviceConfig = appConfig.deviceInfoConfig.FirstOrDefault(x => x.Deviceid == real_Data.Deviceid);
if (deviceConfig?.InventoryIntervalSeconds > 0)
3 months ago
{
StartInventoryTimer(real_Data.Deviceid, deviceConfig.InventoryIntervalSeconds.Value);
3 months ago
}
}
/// <summary>
/// 停止盘点消息
/// </summary>
private void StopMessage(object o, string e)
3 months ago
{
var rfid = rfidList.FirstOrDefault(x => x.deviceid == e);
StopInventoryTimer(e);
var res = rfid.Set_StopIdentify().GetAwaiter().GetResult();
if (res)
{
MessageBox.Show("停止盘点成功!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("停止盘点失败!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
CurrentState = "空闲";
}
/// <summary>
/// 初次加载设备信息
/// </summary>
private void LoadDeviceInfo()
{
List<DeviceInfoConfig> DeviceInfos = appConfig.deviceInfoConfig.Where(x => x.Collectid == appConfig.StationCode && x.Deleteflag == 0).ToList();
List<Real_DataInfo> real_DataInfos = new List<Real_DataInfo>();
foreach (var item in DeviceInfos)
{
Real_DataInfo real_DataInfo = new Real_DataInfo()
{
Name = item.Name,
Deviceid = item.Deviceid,
Connectstr = item.Connectstr,
LineName = item.Name,
LineNo = item.Addr,
OrderNo = "",
ProductType = "",
WriteCount = "",
RfidCount = "",
IsOnline = "未连接",
RfidASCII = "",
RfidEPC = "",
ReadTime = "",
WriteTime = "",
ProductStatus = "",
WriteStatus = "",
WriteDelaySet = item.WriteDelaySet
};
real_DataInfos.Add(real_DataInfo);
}
3 months ago
App.Current.Dispatcher.Invoke(() =>
{
Deviceinfo.Clear();
foreach (var item in real_DataInfos)
{
Real_DataInfo real_DataInfo = new Real_DataInfo()
{
Name = item.Name,
Deviceid = item.Deviceid,
Connectstr = item.Connectstr,
LineName = item.Name,
LineNo = item.LineNo,
OrderNo = item.OrderNo,
ProductType = item.ProductType,
WriteCount = item.WriteCount,
RfidCount = item.RfidCount,
IsOnline = item.IsOnline,
RfidASCII = item.RfidASCII,
ReadTime = item.ReadTime,
WriteTime = item.WriteTime,
ProductStatus = item.ProductStatus,
WriteStatus = item.WriteStatus,
RfidEPC = item.RfidEPC,
WriteDelaySet = item.WriteDelaySet
};
Deviceinfo.Add(real_DataInfo);
}
});
}
/// <summary>
/// 添加数据的方法
/// </summary>
/// <param name="iCombineId"></param>
/// <param name="tagInfos"></param>
public void AddRFIDData(string iCombineId, List<TagInfo> tagInfos)
{
Task.Run(() =>
3 months ago
{
App.Current.Dispatcher.Invoke(() =>
3 months ago
{
RFIDHistoryRecords.Insert(0, new RFIDRecord
{
OrderNumber = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).OrderNo,
LineNumber = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).LineNo,
ProductType = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).ProductType,
OriginalEPC = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).RfidEPC,
RFIDCode = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).RfidASCII,
ReadTime = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).ReadTime,
WriteTime = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).ReadTime,
WriteStatus = Deviceinfo.FirstOrDefault(x => x.Deviceid == iCombineId).WriteStatus // 模拟1条失败数据
});
3 months ago
});
});
}
#endregion
#region 设备状态检测
3 months ago
private void StartCheckStatus()
{
Task.Run(async () =>
{
while (true)
{
try
{
#region RFID状态
//RefreshLogMessage("检测设备状态");
var batches = SplitListIntoBatches(rfidList, 10);
for (int i = 0; i < batches.Count; i++)
{
var currentBatch = batches[i];
// 显示当前批次信息UI线程
Console.WriteLine($"{DateTime.Now}开始检测第{i + 1}批(共{currentBatch.Count}个设备)\r\n");
// 异步检测当前批次的设备状态不阻塞UI
await DetectDeviceBatchAsync(currentBatch);
// 批次之间的间隔(最后一批无需等待)
if (i < batches.Count - 1)
{
Console.WriteLine($"{DateTime.Now}第{i + 1}批检测完成,共{batches.Count}批,等待{1000 / 1000}秒后检测下一批\r\n");
await Task.Delay(10);
}
}
#endregion RFID状态
}
catch (Exception ex)
{
2 months ago
Log.Information($"监听设备状态异常:{ex.Message}");
3 months ago
}
await Task.Delay(1000 * 20);
3 months ago
}
});
}
private List<List<RfidAbsractFactory>> SplitListIntoBatches(List<RfidAbsractFactory> sourceList, int batchSize)
{
var batches = new List<List<RfidAbsractFactory>>();
for (int i = 0; i < sourceList.Count; i += batchSize)
{
// 截取每批的设备最后一批可能不足10个
var batch = sourceList.Skip(i).Take(batchSize).ToList();
batches.Add(batch);
}
return batches;
}
private async Task DetectDeviceBatchAsync(List<RfidAbsractFactory> batch)
{
// 并行检测当前批次的设备(可选,根据接口性能调整)
var detectionTasks = batch.Select(device => DetectSingleDeviceAsync(device));
// 等待当前批次所有设备检测完成
await Task.WhenAll(detectionTasks);
}
/// <summary>
/// 重连核心算法
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
3 months ago
private async Task DetectSingleDeviceAsync(RfidAbsractFactory device)
{
try
{
//基于心跳检测的断线重连机制
if (GloalVar.HeartBeatRecoard.TryGetValue(device.deviceid, out var value))
3 months ago
{
if (value.AddSeconds(15) < DateTime.Now)
3 months ago
{
await Reconnect(device);
3 months ago
Deviceinfo.FirstOrDefault(x => x.Deviceid == device.deviceid).IsOnline = "已连接";
}
}
else //第一次的逻辑
3 months ago
{
await Reconnect(device);
Deviceinfo.FirstOrDefault(x => x.Deviceid == device.deviceid).IsOnline = "已连接";
var res = false;
//无限获取心跳返回报文
Log.Information($"准备第一次获取心跳");
res = await device.Set_HeartBeat(5);
3 months ago
}
}
catch (Exception ex)
{
2 months ago
Log.Information($"更新RFID状态异常:{ex.Message}", ex);
3 months ago
}
}
/// <summary>
/// 无限重连
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
private async Task Reconnect(RfidAbsractFactory device)
{
var res = false;
do
{
await Task.Delay(1000);
Deviceinfo.FirstOrDefault(x => x.Deviceid == device.deviceid).IsOnline = "未连接";
Log.Information($"[{device.deviceid}]:[{device.ip}:{device.port}]连接中...");
res = await device.ConnectAsync(device.ip, device.port, device.deviceid);
}
while (!res);
}
#endregion
#region 定时盘点
/// <summary>
/// 启动定时盘点
/// </summary>
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");
}
/// <summary>
/// 停止定时盘点
/// </summary>
private void StopInventoryTimer(string deviceId)
{
if (_inventoryTimers.TryGetValue(deviceId, out var timer))
{
timer.Stop();
_inventoryTimers.Remove(deviceId);
Log.Information($"设备 {deviceId} 停止定时盘点");
}
}
/// <summary>
/// 定时盘点回调
/// </summary>
private async void OnInventoryTimerTick(string deviceId)
{
try
{
var rfid = rfidList.FirstOrDefault(x => x.deviceid == deviceId);
if (rfid != null)
{
//Log.Information($"设备 {deviceId} 执行定时盘点");
//await rfid.Set_StopIdentify();
//await Task.Delay(100);
rfid.Set_BeginIdentify().GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
Log.Error($"设备 {deviceId} 定时盘点异常: {ex.Message}");
}
}
#endregion
3 months ago
}
}