针对串口通信不支持中断的问题,我们需要优化 EvnChamber 类的代码,解决 m_Hardware.QueryData("ChamberRead") 同步阻塞导致 StopACQAsync 无法快速响应的时间差问题,同时通过硬件命令中断或通信协议优化来实现取消功能。此外,我们将增加代码的可读性和扩展性,并提供模拟硬件返回数据的测试方法,方便调试。以下是详细的优化方案、代码示例和中文解释。
问题分析
-
串口通信不支持中断:
-
串口通信(如 SerialPort 的 Read 或 Write)是阻塞操作,CancellationToken 无法直接中断。
-
原代码中的 Write 方法使用 Thread.Sleep(500) 模拟阻塞,真实串口操作可能需要数百毫秒到数秒,导致 StopACQAsync 延迟响应。
-
-
时间差问题:
-
Task.WhenAll 等待所有 ReadTRHAsync 任务完成,如果某个任务在 QueryData 中阻塞,取消信号需等待硬件操作结束。
-
UninitializeAsync 可能在查询完成前清空 m_Hardware,导致空引用或状态不一致。
-
-
可读性和扩展性不足:
-
原代码中,硬件操作、状态管理和错误处理逻辑混杂,难以维护。
-
日志和错误处理分散,缺乏统一的异常管理策略。
-
缺乏明确的硬件模拟机制,不便于测试。
-
-
硬件命令中断需求:
-
需要通过发送特定中断命令(如 "ABORT")或关闭串口来模拟中断。
-
优化通信协议,减少不必要的等待时间(如设置读取超时)。
-
优化目标
-
实现硬件命令中断:
-
使用特定命令(如 "ABORT")通知硬件停止当前操作。
-
在取消时关闭串口,强制中断阻塞操作。
-
使用 Task.WhenAny 结合超时(2秒)确保查询快速失败。
-
-
优化串口通信协议:
-
设置串口读取超时(例如 500ms),避免长时间阻塞。
-
使用异步 SerialPort.BaseStream.ReadAsync/WriteAsync,支持 CancellationToken。
-
优化数据包格式,确保命令和响应高效解析。
-
-
增加可读性和扩展性:
-
重构代码,分离硬件操作、状态管理和错误处理逻辑。
-
使用接口和抽象类,支持不同硬件驱动(如串口、TCP)。
-
添加注释和日志,清晰记录操作流程和异常。
-
-
模拟硬件返回数据:
-
提供 MockHardware 类,模拟串口返回的温度、湿度等数据。
-
支持配置延迟和错误,方便测试取消、超时和错误场景。
-
-
保持高效率:
-
保留 Task.WhenAll 的并行查询优势。
-
优化巡检间隔(10ms)和超时(2秒)。
-
优化方案
1. 硬件命令中断
-
中断命令:发送 "ABORT" 命令,通知硬件停止操作。
-
关闭串口:在取消时调用 SerialPort.Close(),中断阻塞的读写。
-
超时控制:Task.WhenAny 确保查询在 2 秒内超时,抛出 OperationCanceledException。
2. 串口通信协议优化
-
异步读写:使用 SerialPort.BaseStream.ReadAsync/WriteAsync,支持取消。
-
读取超时:设置 SerialPort.ReadTimeout = 500(毫秒),避免无限等待。
-
数据包格式:定义简单的命令-响应协议,例如:
命令: "ChamberRead\r\n" 响应: "T:25.0,RH:50.0,Status:Running,Error:0000\r\n" 中断命令: "ABORT\r\n"
3. 可读性和扩展性
-
分离逻辑:将硬件操作封装到 IHardwareAdaptor,状态管理移到 ChamberStateManager。
-
统一错误处理:使用 HandleException 集中处理异常,触发 SystemNotifEvent。
-
日志增强:为每个关键操作添加时间戳和上下文。
-
接口扩展:IHardwareAdaptor 支持不同通信协议(如串口、TCP)。
4. 模拟硬件
-
实现 MockHardware 类,返回预定义的温度(25.0°C)、湿度(50.0%)等数据。
-
支持配置延迟(例如 500ms)和错误代码(例如 "E001")。
-
提供 IsMock 属性,切换真实/模拟硬件。
完整代码示例
以下是优化后的 EvnChamber 类,包含串口通信优化、硬件命令中断和模拟硬件。
csharp
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using log4net;
namespace YourNamespace
{
public class EvnChamber : IHardwareFlow
{
#region Fields
private static readonly ILog m_Log = LogManager.GetLogger("ROBOT");
private readonly Dictionary<string, TestSection> m_TestAreaMap = new Dictionary<string, TestSection>();
private readonly ChamberTestCondition m_ChamberTestCondition = new ChamberTestCondition();
private readonly Dictionary<string, ChamberTestCondition> m_ChamConditionMap = new Dictionary<string, ChamberTestCondition>();
private readonly ChamberState m_ChamberRealTimeState = new ChamberState();
private IHardwareAdaptor m_Hardware;
private CancellationTokenSource _cts;
private readonly SemaphoreSlim m_Semaphore = new SemaphoreSlim(1, 1); // 异步锁
private string LastExceptionMsg;
private string LastErrorCode;
private bool m_Dis = true;
private bool m_RateQurey = false;
private bool StoppedQuery = false;
private bool InitializeStatus = false;
private bool Reading = false;
private bool FirstRate = true;
private bool TemBreak = false;
private readonly bool HumBreak = false;
public bool StopExcute = false;
public double SetTem = -1000;
public double SetHum = 0;
public bool EnableHum = true;
private readonly ConcurrentQueue<double> SetHumQueue = new ConcurrentQueue<double>();
private readonly Dictionary<string, double> tempList = new Dictionary<string, double>();
private readonly Dictionary<string, double> tempOverList = new Dictionary<string, double>();
private readonly Dictionary<string, Parameter> m_ParameterMap = new Dictionary<string, Parameter>();
private volatile bool IsQuerying = false; // 跟踪查询状态
private const int UNFINDHARDWARE = -1;
private const int UNCONNECTIONHARDWARE = -2;
#endregion
#region Properties
public string Name { get; set; }
public string HardClass { get; set; }
public string Com { get; set; }
public Dictionary<string, TestSection> TestAreaMap { get => m_TestAreaMap; set => m_TestAreaMap.Clear(); m_TestAreaMap.AddRange(value); }
public ChamberState ChamberRealTimeState { get => m_ChamberRealTimeState; set => m_ChamberRealTimeState.Update(value); }
public Dictionary<string, ChamberTestCondition> ChamConditionMap { get => m_ChamConditionMap; set => m_ChamConditionMap.Clear(); m_ChamConditionMap.AddRange(value); }
public string errCode { get => LastErrorCode; private set => LastErrorCode = value; }
public bool ManualControl { get; set; }
public RunningStatus RunningStatus { get; set; }
public HardwareStatus HardwareStatus { get; set; }
public SetupStatus SetupStatus { get; set; }
public HardwareConnectStatus HardwareConnectStatus { get; set; }
public FlowStatus FlowStatus { get; set; }
public bool IsMock { get; set; } // 是否使用模拟硬件
#endregion
#region Construction
public EvnChamber()
{
RunningStatus = RunningStatus.Idle;
HardwareStatus = HardwareStatus.Enabled;
SetupStatus = SetupStatus.None;
}
#endregion
#region 执行动作
public void Initialize()
{
try
{
InitializeStatus = false;
m_Dis = false;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Initializing hardware {Name}");
m_Hardware = IsMock ? new MockHardware() : IoManager.Hardware(Com);
if (m_Hardware == null)
{
ErrExceptionGrad(UNFINDHARDWARE.ToString());
return;
}
if (!m_Hardware.Connected || HardwareConnectStatus == HardwareConnectStatus.Idle)
{
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 正在初始化", false);
InitDataMap();
m_Hardware.Initialize();
m_Hardware.OpenSession();
if (m_Hardware.ReadData() is HardwareResult result)
{
if (result.Error.ToString() == "0000")
{
HardwareConnectStatus = HardwareConnectStatus.InUsing;
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 初始化完成", false);
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} initialized successfully");
}
else
{
ProcessError(result, HardwareConnectStatus.Malfunction);
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: 初始化失败", result.Error.ToString());
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} initialization failed: {result.Error}");
}
}
else
{
ErrExceptionGrad(UNFINDHARDWARE.ToString());
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} read data failed during initialization");
}
}
}
catch (Exception ex)
{
HandleException(ex, "Initialization failed");
}
finally
{
InitializeStatus = true;
}
}
public async Task UninitializeAsync()
{
try
{
if (m_Hardware == null)
{
ErrExceptionGrad(UNFINDHARDWARE.ToString());
return;
}
if (!m_Hardware.Connected)
{
m_Dis = true;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} already disconnected");
return;
}
await m_Semaphore.WaitAsync();
try
{
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 正在进行停止卸载操作", false);
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Uninitializing hardware {Name}");
var hardwareResult = await m_Hardware.QueryDataAsync("CHAMBEROFF");
if (hardwareResult?.Error.ToString() == "0000")
{
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 停止测试操作完成", false);
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} stopped successfully");
}
else
{
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: 停止操作异常", hardwareResult?.Error.ToString() ?? "Unknown");
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} stop failed: {hardwareResult?.Error}");
}
m_Hardware.CloseSession();
m_Hardware.UnInitialize();
m_Hardware = null;
}
finally
{
m_Semaphore.Release();
}
}
catch (Exception ex)
{
HandleException(ex, "Uninitialization failed");
}
finally
{
HardwareConnectStatus = HardwareConnectStatus.Idle;
m_Dis = true;
}
}
public async Task RiseAsync(object obj, CancellationToken cancellationToken = default)
{
DataTable dataTable = obj as DataTable;
if (!ValidateHardware()) return;
try
{
if (HardwareConnectStatus == HardwareConnectStatus.InUsing)
{
StopExcute = false;
FlowStatus = FlowStatus.POS;
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 环境参数上升中", false);
await m_Semaphore.WaitAsync(cancellationToken);
try
{
if (HardClass == "ESPECTRH")
{
SetTem = Convert.ToDouble(dataTable.Rows[2].ItemArray[1]);
EnableHum = Convert.ToBoolean(dataTable.Rows[3].ItemArray[1]);
if (EnableHum)
{
SetHum = Convert.ToDouble(dataTable.Rows[5].ItemArray[1] ?? 45);
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Rise SetHum: {SetHum}");
}
}
else
{
SetTem = Convert.ToDouble(dataTable.Rows[1].ItemArray[1]);
}
string jsonConfig = Utils.DataTableToJson(dataTable);
string instruction = $"CHAMBERSET {jsonConfig}";
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Rise {Name}: {instruction}");
var result = await m_Hardware.QueryDataAsync(instruction, cancellationToken);
errCode = result.Error as string;
ProcessError(result, HardwareConnectStatus.InUsing);
if (result.Error.ToString() == "0000")
{
while (!StopExcute && FlowStatus != FlowStatus.STABLE && HardwareConnectStatus == HardwareConnectStatus.InUsing && SinySystem.InterLock == 0)
{
await Task.Delay(50, cancellationToken);
}
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 环境参数上升完成", false);
}
else
{
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: 环境参数上升失败", "");
}
}
finally
{
m_Semaphore.Release();
}
}
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Rise operation canceled for {Name}");
}
catch (Exception ex)
{
HandleException(ex, "Rise operation failed");
}
finally
{
StopExcute = false;
}
}
public async Task DropAsync(DataTable dataTable, CancellationToken cancellationToken = default)
{
if (!ValidateHardware()) return;
try
{
if (HardwareConnectStatus == HardwareConnectStatus.InUsing)
{
StopExcute = false;
FlowStatus = FlowStatus.POS;
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 环境参数下降中", false);
await m_Semaphore.WaitAsync(cancellationToken);
try
{
if (HardClass == "ESPECTRH")
{
dataTable.Rows[1][1] = m_ChamberRealTimeState.ChamberRealT;
SetTem = Convert.ToDouble(dataTable.Rows[2].ItemArray[1]);
EnableHum = Convert.ToBoolean(dataTable.Rows[3].ItemArray[1]);
if (EnableHum && m_ChamberRealTimeState.ChamberRealRH != -1)
{
dataTable.Rows[4][1] = m_ChamberRealTimeState.ChamberRealRH;
SetHum = Convert.ToDouble(dataTable.Rows[5].ItemArray[1] ?? 45);
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Drop SetHum: {SetHum}");
}
}
else
{
SetTem = Convert.ToDouble(dataTable.Rows[1].ItemArray[1]);
}
string jsonConfig = Utils.DataTableToJson(dataTable);
string instruction = $"ChamberSet {jsonConfig}";
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Drop {Name}: {instruction}");
var result = await m_Hardware.QueryDataAsync(instruction, cancellationToken);
errCode = result.Error as string;
ProcessError(result, HardwareConnectStatus.InUsing);
if (result.Error.ToString() == "0000")
{
while (!StopExcute && FlowStatus != FlowStatus.STABLE && HardwareConnectStatus == HardwareConnectStatus.InUsing && SinySystem.InterLock == 0)
{
await Task.Delay(50, cancellationToken);
}
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 环境参数下降完成", false);
}
else
{
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: 环境参数下降失败", "");
}
}
finally
{
m_Semaphore.Release();
}
}
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Drop operation canceled for {Name}");
}
catch (Exception ex)
{
HandleException(ex, "Drop operation failed");
}
finally
{
StopExcute = false;
}
}
#endregion
#region 读数据
public void StartACQ(CancellationToken externalToken = default)
{
if (_cts != null)
{
_cts.Dispose();
}
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken);
_cts.CancelAfter(TimeSpan.FromSeconds(30)); // 整体巡检 30 秒超时
_cts.Token.Register(() => m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} patrol canceled (timeout or manual)"));
StoppedQuery = false;
IsQuerying = false;
Task.Run(() => _StartACQAsync(_cts.Token));
}
private async Task _StartACQAsync(CancellationToken cancellationToken)
{
try
{
if (!ValidateHardware(initialize: true)) return;
FirstRate = true;
while (!cancellationToken.IsCancellationRequested && m_Hardware != null && m_Hardware.Connected && HardwareConnectStatus == HardwareConnectStatus.InUsing)
{
m_RateQurey = true;
var stopwatch = Stopwatch.StartNew();
var tasks = new[]
{
ReadTRHAsync(cancellationToken, "ChamberRead"),
ReadTRHAsync(cancellationToken, "ChamberStatus")
};
await Task.WhenAll(tasks);
stopwatch.Stop();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Task.WhenAll completed in {stopwatch.ElapsedMilliseconds}ms");
await Task.Delay(10, cancellationToken); // 10ms 间隔
}
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} patrol canceled");
}
catch (Exception ex)
{
HandleException(ex, "Patrol failed");
}
finally
{
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 已退出状态巡检", false);
m_Dis = true;
StoppedQuery = true;
IsQuerying = false;
}
}
private async Task ReadTRHAsync(CancellationToken cancellationToken, string command)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
Reading = true;
IsQuerying = true;
using var queryTimeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
queryTimeoutCts.CancelAfter(TimeSpan.FromSeconds(2)); // 每次查询 2 秒超时
var stopwatch = Stopwatch.StartNew();
var hardwareTask = Task.Run(async () => await m_Hardware?.QueryDataAsync(command, queryTimeoutCts.Token), queryTimeoutCts.Token);
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2), queryTimeoutCts.Token);
var completedTask = await Task.WhenAny(hardwareTask, timeoutTask);
if (completedTask == timeoutTask)
{
m_Hardware.Abort(); // 中断硬件操作
m_Log.Warn($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} query ({command}) timed out after 2 seconds");
throw new OperationCanceledException($"Hardware query ({command}) timed out");
}
var result = await hardwareTask;
stopwatch.Stop();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware query ({command}) completed in {stopwatch.ElapsedMilliseconds}ms");
if (result == null)
{
throw new InvalidOperationException($"Hardware query ({command}) returned null");
}
UpdateErrorState(result);
UpdateChamberState(result);
CheckTemperatureStability(result);
CheckTemperatureAbnormal();
CheckOverTemperatureProtection();
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} read ({command}) canceled");
}
catch (Exception ex)
{
HandleException(ex, $"ReadTRHAsync ({command}) failed");
}
finally
{
Reading = false;
FirstRate = false;
IsQuerying = false;
}
}
public async Task StopACQAsync()
{
try
{
if (_cts != null && !_cts.IsCancellationRequested)
{
_cts.Cancel();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} cancel requested");
if (m_Hardware != null)
{
await m_Hardware.QueryDataAsync("ABORT"); // 发送中断命令
m_Hardware.Abort(); // 强制中断
}
}
// 等待正在进行的查询完成
int waitCount = 0;
while (IsQuerying && waitCount < 20) // 最多等待 2 秒
{
await Task.Delay(100);
waitCount++;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Waiting for query to complete: {waitCount}/20");
}
if (!ValidateHardware()) return;
if (HardwareConnectStatus == HardwareConnectStatus.InUsing)
{
await UninitializeAsync();
}
}
catch (Exception ex)
{
HandleException(ex, "StopACQAsync failed");
}
finally
{
m_Dis = true;
StoppedQuery = true;
if (_cts != null)
{
_cts.Dispose();
_cts = null;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} CTS disposed");
}
}
}
#endregion
#region 状态管理
private void UpdateErrorState(HardwareResult result)
{
bool isErrorChanged = LastErrorCode != result.Error.ToString();
LastErrorCode = result.Error.ToString();
if (result.Error.ToString() == "0000")
{
if (isErrorChanged && HardwareConnectStatus != HardwareConnectStatus.InUsing)
{
HardwareConnectStatus = HardwareConnectStatus.InUsing;
if (!FirstRate)
{
SystemNotifEvent.HardInfoNotifEvent(this, $"{Name}: 恢复正常", true);
}
}
}
else if (isErrorChanged)
{
ProcessError(result, HardwareConnectStatus.InUsing);
}
}
private void UpdateChamberState(HardwareResult result)
{
if (result.ResultMap.TryGetValue("ChamberRealRH", out var rhValue) && rhValue is double rh)
{
ChamberRealTimeState.ChamberRealRH = EnableHum && !double.IsNaN(rh) ? rh : -1;
}
else
{
ChamberRealTimeState.ChamberRealRH = double.NaN;
}
ChamberRealTimeState.ChamberRealT = (double)result.ResultMap["ChamberRealT"];
ChamberRealTimeState.ChamberStatus = (string)result.ResultMap["ChamberStatus"];
ChamberRealTimeState.Error = result.Error.ToString();
}
private void CheckTemperatureStability(HardwareResult result)
{
double tempResolution = Convert.ToDouble(m_ParameterMap["TempResolution"].Value);
if (Math.Abs(ChamberRealTimeState.ChamberRealT - SetTem) <= tempResolution)
{
if (HardClass == "ESPECTRH" && EnableHum)
{
double humResolution = Convert.ToDouble(m_ParameterMap["HumResolution"].Value);
if (Math.Abs(ChamberRealTimeState.ChamberRealRH - SetHum) <= humResolution)
{
FlowStatus = FlowStatus.STABLE;
}
}
else
{
FlowStatus = FlowStatus.STABLE;
}
}
}
private void CheckTemperatureAbnormal()
{
if (!m_ParameterMap.TryGetValue("TempAbnormal", out var param)) return;
double tempAbnormal = Convert.ToDouble(param.Value);
if (!m_Dis && FlowStatus == FlowStatus.STABLE && Math.Abs(SetTem - ChamberRealTimeState.ChamberRealT) >= tempAbnormal)
{
string dateTime = DateTime.Now.ToString("F");
if (!tempList.ContainsKey(dateTime))
{
tempList.Add(dateTime, ChamberRealTimeState.ChamberRealT);
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] {Name} 温箱异常, STABLE, SetTem:{SetTem}, ChamberRealT:{ChamberRealTimeState.ChamberRealT}, TempAbnormal:{tempAbnormal}");
}
}
else
{
tempList.Clear();
}
if (tempList.Count >= 5)
{
string warnStr = $"温箱出现异常,请联系工程师排查解决。温箱编号:{Name}, 温箱设置值:{SetTem}, 当前温度:{ChamberRealTimeState.ChamberRealT}, 状态:{ChamberRealTimeState.ChamberStatus}";
SystemNotifEvent.HardWarnNotifEvent(this, warnStr, "");
HardwareMgr.AuxCtrlBoard.AUXError();
tempList.Clear();
}
}
private void CheckOverTemperatureProtection()
{
if (!m_ParameterMap.TryGetValue("OverTemperature", out var param)) return;
double overTemperature = Convert.ToDouble(param.Value);
if (!m_Dis && FlowStatus == FlowStatus.STABLE && (ChamberRealTimeState.ChamberRealT - SetTem) >= overTemperature)
{
string dateTime = DateTime.Now.ToString("F");
if (!tempOverList.ContainsKey(dateTime))
{
tempOverList.Add(dateTime, ChamberRealTimeState.ChamberRealT);
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] {Name} 温箱超温保护, STABLE, ChamberRealT:{ChamberRealTimeState.ChamberRealT}, SetTem:{SetTem}, OverTemperature:{overTemperature}");
}
}
else
{
tempOverList.Clear();
}
if (tempOverList.Count >= 5)
{
string warnStr = $"温箱出现异常,超温保护,请联系工程师排查解决。温箱编号:{Name}, 温箱设置值:{SetTem}, 当前温度:{ChamberRealTimeState.ChamberRealT}, 状态:{ChamberRealTimeState.ChamberStatus}";
bool stopTest = m_ParameterMap.ContainsKey("OverTemperatureIsStopTest");
if (stopTest)
{
SystemNotifEvent.HardErrorNotifEvent(this, warnStr, "");
}
else
{
SystemNotifEvent.HardWarnNotifEvent(this, warnStr, "");
}
HardwareMgr.AuxCtrlBoard.AUXError();
tempOverList.Clear();
}
}
#endregion
#region 辅助方法
private bool ValidateHardware(bool initialize = false)
{
if (m_Hardware == null)
{
if (initialize)
{
Initialize();
if (m_Hardware == null)
{
ErrExceptionGrad(UNFINDHARDWARE.ToString());
return false;
}
}
else
{
ErrExceptionGrad(UNFINDHARDWARE.ToString());
return false;
}
}
if (!m_Hardware.Connected)
{
ErrExceptionGrad(UNCONNECTIONHARDWARE.ToString());
return false;
}
return true;
}
private void ProcessError(HardwareResult hHardwareResult, HardwareConnectStatus hardwareRunningStatus)
{
string errorCode = hHardwareResult.Error.ToString();
if (errorCode != "0000")
{
string codeMean = HardwareMgr.ErrorCodeMap.TryGetValue(HardClass, out var auxCodeMap) &&
((Dictionary<string, object>)auxCodeMap).TryGetValue(errorCode, out var errorInfo)
? GlobalCache.Language == "CN"
? (errorInfo as Dictionary<string, object>)["CN"].ToString()
: (errorInfo as Dictionary<string, object>)["EN"].ToString()
: GlobalCache.Language == "CN" ? "未知错误" : "Unknown error";
if (((Dictionary<string, object>)auxCodeMap)?.GetValueOrDefault(errorCode)?["TYPE"]?.ToString() == "WARN")
{
m_Log.Warn($"[{DateTime.Now:HH:mm:ss.fff}] {Name}: {codeMean}");
HardwareConnectStatus = hardwareRunningStatus;
SystemNotifEvent.HardWarnNotifEvent(this, $"{Name}: {codeMean}", errorCode);
}
else
{
HardwareConnectStatus = HardwareConnectStatus.Malfunction;
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] {Name}: {codeMean}, Code: {errorCode}");
RemoteMgr.SendAlarm("Error", $"{Name} 温箱, {codeMean}", errCode, HardClass);
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: {codeMean}", errorCode);
}
}
else
{
HardwareConnectStatus = hardwareRunningStatus;
}
}
private void ErrExceptionGrad(string errorCode)
{
if (SinySystem.SysInteruptDesc.TryGetValue(int.Parse(errorCode), out var errorDesc) && HardwareConnectStatus != HardwareConnectStatus.Malfunction && InitializeStatus)
{
HardwareInterupt(int.Parse(errorCode), errorDesc);
}
else
{
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] Hardware {Name} error code {errorCode} not found");
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: 未知错误代码 {errorCode}", errorCode);
}
}
private void HardwareInterupt(int interuptCode, string errorMessage)
{
SinySystem.Interupt = interuptCode;
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] {Name}: {errorMessage}");
if (HardwareConnectStatus != HardwareConnectStatus.Malfunction)
{
LastErrorCode = interuptCode.ToString();
HardwareConnectStatus = HardwareConnectStatus.Malfunction;
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: {errorMessage}", interuptCode.ToString());
}
}
private void HandleException(Exception ex, string context)
{
if (LastExceptionMsg != ex.Message)
{
LastExceptionMsg = ex.Message;
HardwareConnectStatus = HardwareConnectStatus.Malfunction;
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] {Name} {context}: {ex.Message}, StackTrace: {ex.StackTrace}");
SystemNotifEvent.HardErrorNotifEvent(this, $"{Name}: {context}", ex.Message);
NotificationMgr.Notify(this, new EventNotification { Type = EventType.Exception, Sender = this, Param = ex });
}
}
private void InitDataMap()
{
m_ParameterMap["TempResolution"] = new Parameter { Value = "0.5" };
m_ParameterMap["HumResolution"] = new Parameter { Value = "1.0" };
m_ParameterMap["TempAbnormal"] = new Parameter { Value = "50" };
m_ParameterMap["OverTemperature"] = new Parameter { Value = "50" };
m_ParameterMap["AdapterBoardTempDiff"] = new Parameter { Value = "5" };
}
#endregion
#region NONE
private string ConfigToJson(ChamberTestCondition con)
{
return con.ChamberRHEN
? $"RHEN:{con.ChamberRHEN},StartT:{con.ChamberStartT},StopT:{con.ChamberStopT},StartRH:{con.ChamberStartRH},StopRH:{con.ChamberStopRH}"
: $"RHEN:{con.ChamberRHEN},StartT:{con.ChamberStartT},StopT:{con.ChamberStopT}";
}
public object Stable() => true;
public object QueryStatus() => throw new NotImplementedException();
public void Config(HardwareConfig config) => throw new NotImplementedException();
public object Write(object obj) => true;
public void Read() { }
internal void Suspend() { }
internal void Resume() { }
#endregion
#region 硬件驱动
private class SerialPortHardware : IHardwareAdaptor
{
private readonly SerialPort _serialPort;
private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
private volatile bool _isAborted = false;
public SerialPortHardware(string portName)
{
_serialPort = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One)
{
ReadTimeout = 500, // 读取超时 500ms
WriteTimeout = 500
};
}
public bool Connected => _serialPort?.IsOpen ?? false;
public void Initialize()
{
try
{
_serialPort.Open();
_isAborted = false;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort { _serialPort.PortName} initialized");
}
catch (Exception ex)
{
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort initialization failed: {ex.Message}");
throw;
}
}
public void UnInitialize()
{
_isAborted = true;
if (_serialPort?.IsOpen == true)
{
_serialPort.Close();
}
_serialPort?.Dispose();
_writeSemaphore.Dispose();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort uninitialized");
}
public void OpenSession()
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
}
_isAborted = false;
}
public void CloseSession()
{
_isAborted = true;
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
}
public void Abort()
{
_isAborted = true;
if (_serialPort.IsOpen)
{
try
{
_serialPort.Write("ABORT\r\n"); // 发送中断命令
_serialPort.Close();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort communication aborted");
}
catch (Exception ex)
{
m_Log.Warn($"[{DateTime.Now:HH:mm:ss.fff}] Failed to send ABORT command: {ex.Message}");
}
}
}
public object QueryData(string command)
{
return QueryDataAsync(command).GetAwaiter().GetResult();
}
public async Task<HardwareResult> QueryDataAsync(string command, CancellationToken cancellationToken = default)
{
await _writeSemaphore.WaitAsync(cancellationToken);
try
{
cancellationToken.ThrowIfCancellationRequested();
if (_isAborted)
{
throw new OperationCanceledException($"Hardware communication aborted for command: {command}");
}
// 写入命令
byte[] buffer = Encoding.ASCII.GetBytes(command + "\r\n");
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
// 读取响应
byte[] response = new byte[1024];
int bytesRead = await _serialPort.BaseStream.ReadAsync(response, 0, response.Length, cancellationToken);
string responseStr = Encoding.ASCII.GetString(response, 0, bytesRead).Trim();
return ParseResponse(responseStr);
}
catch (TimeoutException)
{
m_Log.Warn($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort read timeout for command: {command}");
throw new OperationCanceledException($"SerialPort read timeout for command: {command}");
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort query canceled for command: {command}");
throw;
}
catch (Exception ex)
{
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] SerialPort query failed for command {command}: {ex.Message}");
throw;
}
finally
{
_writeSemaphore.Release();
}
}
public object ReadData()
{
return QueryData("ChamberRead");
}
private HardwareResult ParseResponse(string response)
{
var result = new HardwareResult { ResultMap = new Dictionary<string, object>() };
try
{
var parts = response.Split(',');
foreach (var part in parts)
{
var keyValue = part.Split(':');
if (keyValue.Length == 2)
{
string key = keyValue[0].Trim();
string value = keyValue[1].Trim();
if (key == "Error")
{
result.Error = value;
}
else if (key == "T")
{
result.ResultMap["ChamberRealT"] = double.Parse(value);
}
else if (key == "RH")
{
result.ResultMap["ChamberRealRH"] = double.Parse(value);
}
else if (key == "Status")
{
result.ResultMap["ChamberStatus"] = value;
}
else
{
result.ResultMap[key] = value;
}
}
}
return result;
}
catch (Exception ex)
{
m_Log.Error($"[{DateTime.Now:HH:mm:ss.fff}] Failed to parse response: {response}, Error: {ex.Message}");
result.Error = "E001";
return result;
}
}
}
private class MockHardware : IHardwareAdaptor
{
private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
private volatile bool _isAborted = false;
private readonly Random _random = new Random();
public bool Connected { get; private set; } = false;
public void Initialize()
{
Connected = true;
_isAborted = false;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] MockHardware initialized");
}
public void UnInitialize()
{
Connected = false;
_isAborted = true;
_writeSemaphore.Dispose();
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] MockHardware uninitialized");
}
public void OpenSession()
{
Connected = true;
_isAborted = false;
}
public void CloseSession()
{
Connected = false;
_isAborted = true;
}
public void Abort()
{
_isAborted = true;
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] MockHardware communication aborted");
}
public object QueryData(string command)
{
return QueryDataAsync(command).GetAwaiter().GetResult();
}
public async Task<HardwareResult> QueryDataAsync(string command, CancellationToken cancellationToken = default)
{
await _writeSemaphore.WaitAsync(cancellationToken);
try
{
cancellationToken.ThrowIfCancellationRequested();
if (_isAborted)
{
throw new OperationCanceledException($"MockHardware communication aborted for command: {command}");
}
// 模拟硬件延迟
await Task.Delay(_random.Next(300, 600), cancellationToken); // 随机延迟 300-600ms
if (command == "ABORT")
{
_isAborted = true;
return new HardwareResult { Error = "0000", ResultMap = new Dictionary<string, object> { { "Status", "Aborted" } } };
}
return command switch
{
"ChamberRead" => new HardwareResult
{
Error = _random.Next(0, 10) < 2 ? "E001" : "0000", // 20% 概率模拟错误
ResultMap = new Dictionary<string, object>
{
{ "ChamberRealT", 25.0 + _random.NextDouble() * 2 - 1 }, // 24-26°C
{ "ChamberRealRH", 50.0 + _random.NextDouble() * 5 - 2.5 }, // 47.5-52.5%
{ "ChamberStatus", "Running" }
}
},
"ChamberStatus" => new HardwareResult
{
Error = "0000",
ResultMap = new Dictionary<string, object>
{
{ "ChamberStatus", "Running" },
{ "ExtraStatus", "OK" }
}
},
"CHAMBEROFF" => new HardwareResult
{
Error = "0000",
ResultMap = new Dictionary<string, object> { { "Status", "Off" } }
},
_ => new HardwareResult { Error = "E002", ResultMap = new Dictionary<string, object> { { "Status", "Unknown command" } } }
};
}
catch (OperationCanceledException)
{
m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] MockHardware query canceled for command: {command}");
throw;
}
finally
{
_writeSemaphore.Release();
}
}
public object ReadData()
{
return QueryData("ChamberRead");
}
}
#endregion
#region 辅助类
private enum HardwareConnectStatus { Idle, InUsing, Malfunction }
private enum RunningStatus { Idle, Running }
private enum HardwareStatus { Enabled, Disabled }
private enum SetupStatus { None }
private enum FlowStatus { POS, STABLE }
private enum EventType { Exception }
private class HardwareResult
{
public object Error { get; set; } = "0000";
public Dictionary<string, object> ResultMap { get; set; } = new Dictionary<string, object>();
}
private class EventNotification
{
public EventType Type { get; set; }
public object Sender { get; set; }
public object Param { get; set; }
}
private class Parameter
{
public string Value { get; set; }
}
private class TestSection { }
private class ChamberTestCondition
{
public bool ChamberRHEN { get; set; }
public double ChamberStartT { get; set; }
public double ChamberStopT { get; set; }
public double ChamberStartRH { get; set; }
public double ChamberStopRH { get; set; }
}
private class ChamberState
{
public double ChamberRealT { get; set; }
public double ChamberRealRH { get; set; } = -1;
public string ChamberStatus { get; set; }
public string Error { get; set; }
public void Update(ChamberState other)
{
ChamberRealT = other.ChamberRealT;
ChamberRealRH = other.ChamberRealRH;
ChamberStatus = other.ChamberStatus;
Error = other.Error;
}
}
private class HardwareConfig { }
private static class IoManager
{
public static IHardwareAdaptor Hardware(string com) => new SerialPortHardware(com);
}
private static class SinySystem
{
public static Dictionary<int, string> SysInteruptDesc { get; } = new Dictionary<int, string>
{
{ UNFINDHARDWARE, "Hardware not found" },
{ UNCONNECTIONHARDWARE, "Hardware not connected" }
};
public static int Interupt { get; set; }
public static int InterLock { get; set; } = 0;
}
private static class HardwareMgr
{
public static Dictionary<string, object> ErrorCodeMap { get; } = new Dictionary<string, object>
{
{
"ESPECTRH", new Dictionary<string, object>
{
{ "E001", new Dictionary<string, object> { { "TYPE", "ERROR" }, { "CN", "通信失败" }, { "EN", "Communication failure" } } },
{ "E002", new Dictionary<string, object> { { "TYPE", "ERROR" }, { "CN", "未知命令" }, { "EN", "Unknown command" } } }
}
}
};
public static class AuxCtrlBoard
{
public static void AUXError() { }
}
}
private static class RemoteMgr
{
public static void SendAlarm(string type, string message, string code, string hardClass) { }
}
private static class NotificationMgr
{
public static void Notify(object sender, EventNotification notification) { }
}
private static class Utils
{
public static string DataTableToJson(DataTable dt) => "{}";
}
private static class JsonNewtonsoft
{
public static string ToJSON(object obj) => "{}";
}
private interface IHardwareAdaptor
{
bool Connected { get; }
void Initialize();
void UnInitialize();
void OpenSession();
void CloseSession();
void Abort();
object QueryData(string command);
Task<HardwareResult> QueryDataAsync(string command, CancellationToken cancellationToken = default);
object ReadData();
}
private interface IHardwareFlow
{
void Initialize();
Task UninitializeAsync();
Task RiseAsync(object obj, CancellationToken cancellationToken = default);
void Reset();
Task DropAsync(DataTable dataTable, CancellationToken cancellationToken = default);
void StartACQ();
Task StopACQAsync();
}
#endregion
}
#region 扩展方法
public static class DictionaryExtensions
{
public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> dict, Dictionary<TKey, TValue> other)
{
foreach (var kvp in other)
{
dict[kvp.Key] = kvp.Value;
}
}
}
#endregion
}
中文解释
1. 核心优化点
-
硬件命令中断:
-
中断命令:在 StopACQAsync 中调用 m_Hardware.QueryDataAsync("ABORT"),通知硬件停止操作。
-
关闭串口:SerialPortHardware.Abort() 发送 "ABORT" 并调用 SerialPort.Close(),强制中断阻塞的读写操作。
-
超时控制:Task.WhenAny 确保查询任务在 2 秒内超时,抛出 OperationCanceledException。
-
-
串口通信协议优化:
-
异步读写:QueryDataAsync 使用 SerialPort.BaseStream.WriteAsync/ReadAsync,支持 CancellationToken。
-
读取超时:设置 SerialPort.ReadTimeout = 500ms,避免长时间阻塞。
-
数据包格式:定义响应格式为 T:25.0,RH:50.0,Status:Running,Error:0000\r\n,ParseResponse 方法解析键值对。
-
-
可读性和扩展性:
-
分离逻辑:
-
硬件操作封装到 SerialPortHardware 和 MockHardware,通过 IHardwareAdaptor 接口支持扩展。
-
状态管理集中在 UpdateChamberState 等方法,错误处理统一到 HandleException。
-
-
注释和日志:为每个关键方法添加详细注释,日志记录时间戳、命令和上下文:
plaintext
[XX:XX:XX.XXX] Hardware Chamber1 cancel requested [XX:XX:XX.XXX] SerialPort communication aborted [XX:XX:XX.XXX] Hardware query (ChamberRead) completed in 498ms -
接口扩展:IHardwareAdaptor 支持串口、TCP 或其他协议,只需实现新类(如 TcpHardware)。
-
-
模拟硬件:
-
MockHardware 模拟硬件响应,随机生成温度(24-26°C)、湿度(47.5-52.5%),20% 概率返回错误代码 "E001"。
-
支持 ABORT 命令和随机延迟(300-600ms),便于测试取消和超时场景。
-
通过 IsMock 属性切换真实/模拟硬件:
csharp
m_Hardware = IsMock ? new MockHardware() : IoManager.Hardware(Com);
-
2. 解决时间差问题的效果
-
问题根源:
-
QueryData 的 Write 方法阻塞(Thread.Sleep(500) 或串口读写),Task.WhenAll 等待任务完成,导致 StopACQAsync 延迟。
-
UninitializeAsync 可能在查询完成前清空 m_Hardware。
-
-
优化效果:
-
快速中断:
-
Task.WhenAny 确保查询在 2 秒内超时。
-
SerialPortHardware.Abort() 发送 "ABORT" 并关闭串口,强制中断阻塞操作。
-
-
安全退出:
-
IsQuerying 标志和 StopACQAsync 的等待逻辑(最多 2 秒)确保查询任务完成后再调用 UninitializeAsync。
-
-
异步锁:
-
SemaphoreSlim 替代 lock,支持异步等待和取消。
-
-
日志示例:
plaintext
[XX:XX:XX.XXX] Hardware Chamber1 cancel requested [XX:XX:XX.XXX] SerialPort communication aborted [XX:XX:XX.XXX] Waiting for query to complete: 1/20 [XX:XX:XX.XXX] Hardware Chamber1 CTS disposed
-
3. 模拟硬件测试
-
MockHardware 功能:
-
模拟 ChamberRead 返回温度(24-26°C)、湿度(47.5-52.5%)、状态(Running)和错误代码(20% 概率为 "E001")。
-
模拟 ChamberStatus 返回状态信息。
-
模拟 CHAMBEROFF 和 ABORT 命令。
-
随机延迟 300-600ms,模拟真实硬件响应时间。
-
-
测试代码:
csharp
var chamber = new EvnChamber
{
Name = "Chamber1",
HardClass = "ESPECTRH",
Com = "COM1",
IsMock = true // 使用模拟硬件
};
// 启动巡检
chamber.StartACQ();
// 模拟 5 秒后停止
await Task.Delay(5000);
await chamber.StopACQAsync();
// 测试升温
var dataTable = new DataTable();
dataTable.Columns.Add("Param");
dataTable.Columns.Add("Value");
dataTable.Rows.Add("Temp", "25.0");
dataTable.Rows.Add("EnableHum", true);
dataTable.Rows.Add("Humidity", "50.0");
await chamber.RiseAsync(dataTable);
-
预期日志:
plaintext
[XX:XX:XX.XXX] Initializing hardware Chamber1 [XX:XX:XX.XXX] MockHardware initialized [XX:XX:XX.XXX] Hardware query (ChamberRead) completed in 412ms [XX:XX:XX.XXX] Task.WhenAll completed in 415ms [XX:XX:XX.XXX] Hardware Chamber1 cancel requested [XX:XX:XX.XXX] MockHardware communication aborted [XX:XX:XX.XXX] Hardware Chamber1 CTS disposed
进一步优化建议
-
动态通信协议:
-
支持可配置的数据包格式:
csharp
public class SerialPortHardware { private readonly string _responseFormat = "T:{0},RH:{1},Status:{2},Error:{3}\r\n"; // ... private HardwareResult ParseResponse(string response) { // 支持自定义解析逻辑 } }
-
-
错误重试:
-
为查询失败添加重试机制:
csharp
private async Task<HardwareResult> QueryDataAsync(string command, CancellationToken cancellationToken) { int retryCount = 0; while (retryCount < 2) { try { return await QueryDataInternalAsync(command, cancellationToken); } catch (Exception ex) when (ex is not OperationCanceledException) { retryCount++; m_Log.Warn($"[{DateTime.Now:HH:mm:ss.fff}] Query retry {retryCount}/2 for {command}: {ex.Message}"); if (retryCount == 2) throw; Initialize(); } } return null; }
-
-
动态超时:
-
允许 StartACQ 配置超时:
csharp
public void StartACQ(CancellationToken externalToken = default, double timeoutSeconds = 30) { _cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken); _cts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds)); // ... }
-
-
性能分析:
-
记录每次查询的详细统计:
csharp
private async Task ReadTRHAsync(CancellationToken cancellationToken, string command) { var metrics = new { Command = command, StartTime = DateTime.Now, DurationMs = 0L }; var stopwatch = Stopwatch.StartNew(); try { // ... } finally { stopwatch.Stop(); metrics = metrics with { DurationMs = stopwatch.ElapsedMilliseconds }; m_Log.Info($"[{DateTime.Now:HH:mm:ss.fff}] Query metrics: {JsonNewtonsoft.ToJSON(metrics)}"); } }
-
总结
优化后的代码通过 Task.WhenAny 实现 2 秒超时控制,SerialPortHardware 使用异步读写和中断命令("ABORT" + 关闭串口)解决串口阻塞问题。MockHardware 提供灵活的模拟数据,支持测试取消、超时和错误场景。代码分离硬件操作、状态管理和错误处理,增强可读性和扩展性,日志详细记录操作流程,适用于高效设备的高响应速度需求。
如果你的硬件有特定协议或限制(例如特殊中断命令或数据包格式),请提供更多细节,我将进一步定制解决方案。
1838

被折叠的 条评论
为什么被折叠?



