添加PLC断线重连机制

This commit is contained in:
2025-09-11 16:22:12 +08:00
parent 07880805a3
commit f75eae1551
2 changed files with 139 additions and 24 deletions

View File

@@ -27,20 +27,21 @@ namespace DOAN.ServiceCore
notificationHubContext = hubContext; notificationHubContext = hubContext;
} }
public Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
Console.WriteLine($"PCL定时任务开启"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PCL定时任务开启");
pLCTool = new PLCTool(); pLCTool = new PLCTool();
pLCTool.ConnectPLC(); bool isConnected = pLCTool.ConnectPLC();
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC初始连接状态: {(isConnected ? "" : "")}");
// 初始化料架层和点位表数据 // 初始化料架层和点位表数据
RefreshData(null); await RefreshData(null);
// 启动后台任务 // 启动后台任务
_executingTask = ExecuteAsync(_cancellationTokenSource.Token); _executingTask = ExecuteAsync(_cancellationTokenSource.Token);
// 设置定时器每10分钟刷新一次料架层和点位表 .FromMinutes(1) // 设置定时器每10分钟刷新一次料架层和点位表 .FromMinutes(1)
refreshTimer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(10)); refreshTimer = new Timer(RefreshDataTimerCallback, null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; await (_executingTask.IsCompleted ? _executingTask : Task.CompletedTask);
} }
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)
@@ -78,21 +79,38 @@ namespace DOAN.ServiceCore
/// <returns></returns> /// <returns></returns>
private async Task ExecuteAsync(CancellationToken stoppingToken) private async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
Console.WriteLine($"PCL定时任务ExecuteAsync"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PCL定时任务ExecuteAsync");
try try
{ {
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
// 读取PLC I/O状态12个字节 // 检查PLC连接状态,如果断开则尝试重连
if (!pLCTool.IsConnected())
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 检测到PLC连接断开尝试重连...");
// 由于PLCTool中的ReconnectPLC是无限重连的这里不需要额外的延迟
if (!pLCTool.ReconnectPLC())
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败跳过本次循环");
continue;
}
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功");
}
// 读取PLC I/O状态13个字节
byte[] plcSensorValues; byte[] plcSensorValues;
try try
{ {
// 读取1开头100,101。。。等全部地址数据 // 读取1开头100,101。。。等全部地址数据
plcSensorValues = pLCTool.ReadAllValue("VB100", 13); plcSensorValues = pLCTool.ReadAllValue("VB100", 13);
if (plcSensorValues != null)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 成功读取PLC数据字节数: {plcSensorValues.Length}");
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
continue; continue;
} }
@@ -334,12 +352,12 @@ namespace DOAN.ServiceCore
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
Console.WriteLine("任务已取消"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 任务已取消");
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine( Console.WriteLine(
$"DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}" $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"
); );
} }
} }
@@ -348,22 +366,53 @@ namespace DOAN.ServiceCore
/// 刷新料架层和点位表数据 /// 刷新料架层和点位表数据
/// </summary> /// </summary>
/// <param name="state"></param> /// <param name="state"></param>
private async void RefreshData(object state) private async Task RefreshData(object state)
{ {
try try
{ {
Console.WriteLine($"刷新料架点位和按钮"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 开始刷新料架点位和按钮数据");
storagelocationList = await Context.Queryable<Storagelocation>().ToListAsync(); // 检查PLC连接状态如果断开则尝试重连
pointPositionList = await Context.Queryable<PlcAddressTable>().ToListAsync(); if (!pLCTool.IsConnected())
buttonPositionList = await Context.Queryable<PlcButton>().ToListAsync(); {
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 检测到PLC连接断开尝试重连...");
// 由于PLCTool中的ReconnectPLC是无限重连的这里不需要额外的处理
if (!pLCTool.ReconnectPLC())
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败但继续执行刷新操作");
}
else
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功");
}
}
var newStoragelocationList = await Context.Queryable<Storagelocation>().ToListAsync();
var newPointPositionList = await Context.Queryable<PlcAddressTable>().ToListAsync();
var newButtonPositionList = await Context.Queryable<PlcButton>().ToListAsync();
// 原子性地更新数据
storagelocationList = newStoragelocationList;
pointPositionList = newPointPositionList;
buttonPositionList = newButtonPositionList;
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 刷新料架点位和按钮完成: 料架{storagelocationList.Count}个, 点位{pointPositionList.Count}个, 按钮{buttonPositionList.Count}个");
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}");
// 如果刷新失败,保持现有数据不变 // 如果刷新失败,保持现有数据不变
} }
} }
/// <summary>
/// 用于Timer回调的适配器方法
/// </summary>
/// <param name="state"></param>
private void RefreshDataTimerCallback(object state)
{
_ = RefreshData(state); // 不等待异步操作完成避免阻塞Timer
}
/// <summary> /// <summary>
/// 更新料架位置的箱子数量并记录库存日志 /// 更新料架位置的箱子数量并记录库存日志
/// </summary> /// </summary>
@@ -404,16 +453,24 @@ namespace DOAN.ServiceCore
{ {
try try
{ {
// 取消令牌源
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
_executingTask?.GetAwaiter().GetResult(); // 确保任务完成
// 等待任务完成,但设置超时以避免死锁
if (_executingTask != null)
{
Task.WaitAny(_executingTask, Task.Delay(5000)); // 最多等待5秒
}
// 释放资源
_cancellationTokenSource.Dispose(); _cancellationTokenSource.Dispose();
refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite);
refreshTimer?.Dispose(); refreshTimer?.Dispose();
pLCTool.ConnectClose(); pLCTool?.ConnectClose();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("DoanBackGround线程Dispose异常:" + ex.Message); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] DoanBackGround线程Dispose异常: {ex.Message}");
} }
} }
} }

View File

@@ -14,6 +14,8 @@ namespace DOAN.Infrastructure.PLC
// 私有连接对象 // 私有连接对象
private SiemensS7Net siemensTcpNet = null; private SiemensS7Net siemensTcpNet = null;
private readonly string plcAddress; private readonly string plcAddress;
private const int ReconnectDelayMs = 5000;
public PLCTool() public PLCTool()
{ {
plcAddress = AppSettings.GetConfig("PLCConfig:Address"); plcAddress = AppSettings.GetConfig("PLCConfig:Address");
@@ -34,21 +36,77 @@ namespace DOAN.Infrastructure.PLC
var connect = siemensTcpNet.ConnectServer(); var connect = siemensTcpNet.ConnectServer();
if (connect.IsSuccess) if (connect.IsSuccess)
{ {
Console.WriteLine($"PLC连接成功,地址{plcAddress}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接成功,地址{plcAddress}");
return true; return true;
} }
else else
{ {
Console.WriteLine($"PLC连接失败,地址{plcAddress}: {connect.Message}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接失败,地址{plcAddress}: {connect.Message}");
return false; return false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine($"PLC连接失败,地址{plcAddress}: {e.Message}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接失败,地址{plcAddress}: {e.Message}");
return false; return false;
} }
} }
/// <summary>
/// 检查PLC连接状态
/// </summary>
/// <returns>连接正常返回true否则返回false</returns>
public bool IsConnected()
{
try
{
// 尝试读取一个字节来检查连接状态
var result = siemensTcpNet.Read("VB100", 1);
if (!result.IsSuccess)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接状态检查失败: {result.Message}");
}
return result.IsSuccess;
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接状态检查异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 重连PLC
/// </summary>
/// <returns>重连成功返回true失败返回false</returns>
public bool ReconnectPLC()
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 尝试重新连接PLC...");
// 先关闭现有连接
ConnectClose();
// 重新创建PLC对象
this.siemensTcpNet = new SiemensS7Net(SiemensPLCS.S200Smart, plcAddress)
{
ConnectTimeOut = 5000
};
// 持续尝试重新连接,直到成功
int attempt = 0;
while (true)
{
attempt++;
if (ConnectPLC())
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功尝试次数: {attempt}");
return true;
}
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败尝试次数: {attempt}");
System.Threading.Thread.Sleep(ReconnectDelayMs);
}
}
/// <summary> /// <summary>
/// 向PLC写入单个bit值 /// 向PLC写入单个bit值
@@ -87,7 +145,7 @@ namespace DOAN.Infrastructure.PLC
} }
else else
{ {
Console.WriteLine($"PLC IO 取值失败,PLC地址{plcAddress},访问地址为{addr},地址个数为{length}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC IO 取值失败,PLC地址{plcAddress},访问地址为{addr},地址个数为{length}");
return null; return null;
} }
} }