5648 lines
253 KiB
C#
5648 lines
253 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Net;
|
||
using System.Net.Sockets;
|
||
using System.IO;
|
||
using System.Threading;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using System.Windows.Forms;
|
||
using System.Text.RegularExpressions;
|
||
using System.Diagnostics;
|
||
using System.Net.NetworkInformation;
|
||
|
||
namespace JoyD.Windows.CS.Toprie
|
||
{
|
||
/// <summary>
|
||
/// 设备连接状态枚举
|
||
/// </summary>
|
||
public enum ConnectionStatus
|
||
{
|
||
Disconnected,
|
||
Connecting,
|
||
Connected,
|
||
Reconnecting
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 图像模式枚举
|
||
/// </summary>
|
||
public enum ImageMode
|
||
{
|
||
Infrared, // 红外模式
|
||
Natural // 自然模式
|
||
}
|
||
|
||
/// <summary>
|
||
/// 色彩模式枚举
|
||
/// 对应SDK文档中的8种色板模式:白热、黑热、铁红、熔岩、彩虹、铁灰、红热、彩虹2
|
||
/// </summary>
|
||
public enum PaletteType
|
||
{
|
||
WhiteHot, // 白热,对应SDK参数0
|
||
BlackHot, // 黑热,对应SDK参数1
|
||
IronRed, // 铁红,对应SDK参数2
|
||
Lava, // 熔岩,对应SDK参数3
|
||
Rainbow, // 彩虹,对应SDK参数4
|
||
IronGray, // 铁灰,对应SDK参数5
|
||
RedHot, // 红热,对应SDK参数6
|
||
Rainbow2 // 彩虹2,对应SDK参数7
|
||
}
|
||
|
||
/// <summary>
|
||
/// 视频模式枚举
|
||
/// 对应SDK文档中的7种视频模式:红外、可见光、融合1~5
|
||
/// </summary>
|
||
public enum VideoMode
|
||
{
|
||
Infrared, // 红外,对应SDK参数0
|
||
VisibleLight, // 可见光,对应SDK参数1
|
||
Fusion1, // 融合1,对应SDK参数2
|
||
Fusion2, // 融合2,对应SDK参数3
|
||
Fusion3, // 融合3,对应SDK参数4
|
||
Fusion4, // 融合4,对应SDK参数5
|
||
Fusion5 // 融合5,对应SDK参数6
|
||
}
|
||
|
||
/// <summary>
|
||
/// 温度模式枚举
|
||
/// </summary>
|
||
public enum TemperatureMode
|
||
{
|
||
/// <summary>
|
||
/// 摄氏度
|
||
/// </summary>
|
||
Celsius,
|
||
/// <summary>
|
||
/// 华氏度
|
||
/// </summary>
|
||
Fahrenheit,
|
||
/// <summary>
|
||
/// 开尔文
|
||
/// </summary>
|
||
Kelvin
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接状态改变事件参数
|
||
/// </summary>
|
||
public class ConnectionStatusChangedEventArgs : EventArgs
|
||
{
|
||
public ConnectionStatus Status { get; set; }
|
||
public string DeviceInfo { get; set; }
|
||
|
||
public ConnectionStatusChangedEventArgs(ConnectionStatus status, string deviceInfo = null)
|
||
{
|
||
Status = status;
|
||
DeviceInfo = deviceInfo;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图像接收事件参数
|
||
/// </summary>
|
||
public class ImageReceivedEventArgs : EventArgs
|
||
{
|
||
public byte[] ImageData { get; set; }
|
||
public ImageMode Mode { get; set; }
|
||
|
||
public ImageReceivedEventArgs(byte[] imageData, ImageMode mode)
|
||
{
|
||
ImageData = imageData;
|
||
Mode = mode;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接异常事件参数
|
||
/// </summary>
|
||
public class ConnectionExceptionEventArgs : EventArgs
|
||
{
|
||
public Exception Exception { get; set; }
|
||
public string Message { get; set; }
|
||
|
||
public ConnectionExceptionEventArgs(Exception ex, string message = null)
|
||
{
|
||
Exception = ex;
|
||
Message = message ?? ex?.Message;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设备管理器类
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 设备信息类,用于存储设备的ID、IP等信息
|
||
/// </summary>
|
||
public class DeviceInfo
|
||
{
|
||
/// <summary>
|
||
/// 设备ID
|
||
/// </summary>
|
||
public int DeviceID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设备IP地址
|
||
/// </summary>
|
||
public string IPAddress { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设备型号
|
||
/// </summary>
|
||
public string Model { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设备序列号
|
||
/// </summary>
|
||
public string SerialNumber { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"设备 {DeviceID} ({IPAddress})";
|
||
}
|
||
}
|
||
|
||
public class DeviceManager : IDisposable
|
||
{
|
||
// 设计模式标志,用于在设计模式下跳过实际的设备连接和初始化
|
||
public static bool IsDesignMode { get; set; } = true;
|
||
|
||
// 暂停检测标志,用于控制是否进行连接检测和重连操作
|
||
public bool IsDetectionPaused { get; set; } = false;
|
||
|
||
// 项目路径,用于数据文件的存取
|
||
private string _projectPath = "";
|
||
|
||
// A8SDK实例
|
||
private A8SDK _a8Sdk;
|
||
// 设备ID列表
|
||
private List<string> _deviceIds = new List<string>();
|
||
|
||
// 设备信息列表
|
||
private List<DeviceInfo> _deviceList = new List<DeviceInfo>();
|
||
|
||
// 目标设备ID,用于指定ID连接
|
||
private readonly int _targetDeviceId = -1;
|
||
// 当前设备ID
|
||
private int _currentDeviceId = -1;
|
||
// 默认设备IP
|
||
private string _deviceIp = "192.168.100.2";
|
||
// 设备端口
|
||
private int _devicePort = 8080;
|
||
// 设备连接状态
|
||
private ConnectionStatus _connectionStatus = ConnectionStatus.Disconnected;
|
||
// 是否已初始化
|
||
private bool _isInitialized = false;
|
||
// 是否已释放
|
||
private readonly bool _isDisposed = false;
|
||
// 图像模式
|
||
private ImageMode _currentImageMode = ImageMode.Infrared;
|
||
// 当前色彩模式
|
||
private PaletteType _currentPaletteType = PaletteType.WhiteHot;
|
||
// 当前视频模式
|
||
private VideoMode _currentVideoMode = VideoMode.Infrared; // 默认红外模式
|
||
// 自动重连是否启用
|
||
// 自动重连定时器
|
||
private System.Threading.Timer _reconnectTimer;
|
||
// 重连间隔(毫秒)
|
||
private int _reconnectInterval = 2000;
|
||
// 重连尝试次数
|
||
|
||
// 最大重连尝试次数
|
||
public static int MaxReconnectAttempts = 5;
|
||
// 连接检查定时器
|
||
private System.Threading.Timer _connectionCheckTimer;
|
||
// 心跳定时器
|
||
private System.Threading.Timer _heartbeatTimer;
|
||
// 连接超时时间(毫秒)
|
||
private const int ConnectionTimeout = 5000;
|
||
// 连接检查间隔(毫秒)
|
||
private const int ConnectionCheckInterval = 5000;
|
||
// 心跳间隔(毫秒)
|
||
private int _heartbeatInterval = 5000;
|
||
// 最后接收数据时间戳
|
||
private DateTime _lastDataReceivedTime = DateTime.MinValue;
|
||
// 数据接收超时时间(毫秒)
|
||
private const int DataReceivedTimeout = 15000; // 15秒内未收到数据则认为连接可能断开
|
||
// TCP客户端
|
||
// 该变量已在文件上方定义,删除重复实现
|
||
|
||
// 停止请求事件
|
||
private ManualResetEvent _stopRequested = new ManualResetEvent(false);
|
||
// 缓冲区
|
||
private readonly byte[] _imageBuffer = new byte[4096];
|
||
// 图像数据累积缓冲区
|
||
private readonly byte[] _imageDataAccumulator = new byte[0];
|
||
// 多部分请求边界
|
||
private readonly string _multipartBoundary = string.Empty;
|
||
// 锁对象
|
||
private readonly object _lockObject = new object();
|
||
// 是否启用自动重连
|
||
private bool _isAutoReconnectEnabled = true;
|
||
// 最大重连次数
|
||
private int _maxReconnectAttempts = 5;
|
||
// 是否已连接
|
||
private bool _isConnected = false;
|
||
// 连接超时设置
|
||
private readonly int _connectTimeout = 5000;
|
||
// 当前重连尝试次数
|
||
private int _currentReconnectAttempt = 0;
|
||
// 连接取消令牌源
|
||
private CancellationTokenSource _connectCancellationTokenSource;
|
||
// 图像接收相关变量
|
||
private ManualResetEvent _stopImageEvent;
|
||
private bool _isReceivingImages = false;
|
||
private Thread _imageReceiveThread;
|
||
private Thread _imageReconnectThread;
|
||
private Stream _imageStream;
|
||
|
||
// 温度数据接收相关字段
|
||
private Thread _temperatureReceiveThread;
|
||
private TcpClient _temperatureTcpClient;
|
||
private NetworkStream _temperatureStream;
|
||
private bool _isReceivingTemperatureData = false;
|
||
private bool _isTemperatureReceivingPaused = false; // 暂停标志,用于控制是否处理接收到的温度数据
|
||
private ManualResetEvent _stopTemperatureEvent;
|
||
private const int TEMPERATURE_TCP_PORT = 8081; // 温度数据TCP端口 - 修正为热像仪SDK文档中指定的端口
|
||
|
||
/// <summary>
|
||
/// 项目路径属性
|
||
/// 用于设置控件存取数据文件的目录
|
||
/// </summary>
|
||
public string ProjectPath
|
||
{
|
||
get { return _projectPath; }
|
||
set { _projectPath = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查网络是否可用
|
||
/// </summary>
|
||
/// <returns>网络是否可用</returns>
|
||
private bool IsNetworkAvailable()
|
||
{
|
||
try
|
||
{
|
||
bool isAvailable = System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();
|
||
Log($"网络可用性检查: {(isAvailable ? "可用" : "不可用")}");
|
||
return isAvailable;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] IsNetworkAvailable() - 捕获异常: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
private TcpClient _imageTcpClient;
|
||
// 使用CurrentImageMode代替_isInfraredMode
|
||
private static readonly object _logLock = new object();
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 记录日志到控制台和文件
|
||
/// </summary>
|
||
private void Log(string message)
|
||
{
|
||
// 输出到控制台
|
||
Console.WriteLine(message);
|
||
|
||
// 输出到日志文件
|
||
try
|
||
{
|
||
lock (_logLock)
|
||
{
|
||
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log.txt");
|
||
// 确保目录存在
|
||
Directory.CreateDirectory(Path.GetDirectoryName(logPath));
|
||
// 写入日志,包含时间戳
|
||
File.AppendAllText(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}\r\n");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 如果写入日志文件失败,只输出到控制台
|
||
Console.WriteLine($"日志写入失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从设备字符串中解析设备ID
|
||
/// </summary>
|
||
/// <param name="deviceString">设备字符串</param>
|
||
/// <returns>设备ID</returns>
|
||
private int ParseDeviceId(string deviceString)
|
||
{
|
||
try
|
||
{
|
||
// 简单的ID解析逻辑,实际应该根据设备返回的数据格式来解析
|
||
if (int.TryParse(deviceString.Trim(), out int id))
|
||
{
|
||
return id;
|
||
}
|
||
return -1;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新连接状态
|
||
/// </summary>
|
||
/// <param name="newStatus">新的连接状态</param>
|
||
/// <param name="message">状态变更消息</param>
|
||
/// <param name="exception">相关异常(如果有)</param>
|
||
private void UpdateConnectionStatus(ConnectionStatus newStatus, string message = null, Exception exception = null)
|
||
{
|
||
// 使用线程安全的方式记录方法进入
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 开始执行");
|
||
|
||
lock (_lockObject) // 添加线程同步锁
|
||
{
|
||
// 检查对象是否已被释放
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 对象已释放,忽略状态更新");
|
||
return;
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 尝试更新状态: {_connectionStatus} -> {newStatus}, 消息: {message}");
|
||
|
||
bool statusChanged = (_connectionStatus != newStatus);
|
||
ConnectionStatus oldStatus = _connectionStatus;
|
||
|
||
if (statusChanged)
|
||
{
|
||
_connectionStatus = newStatus;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 状态已更新: {oldStatus} -> {newStatus}");
|
||
|
||
// 触发连接状态变更事件前再次检查对象是否已被释放
|
||
if (!_isDisposed)
|
||
{
|
||
try
|
||
{
|
||
OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(newStatus, message));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 触发连接状态变更事件异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
Log($"连接状态变更: {oldStatus} -> {newStatus}");
|
||
if (!string.IsNullOrEmpty(message))
|
||
{
|
||
Log($"状态消息: {message}");
|
||
}
|
||
if (exception != null)
|
||
{
|
||
Log($"异常信息: {exception.Message}");
|
||
}
|
||
|
||
// 保存状态变更相关信息供后续处理
|
||
ConnectionStatus finalNewStatus = newStatus;
|
||
bool shouldReconnect = (newStatus == ConnectionStatus.Disconnected && _isAutoReconnectEnabled && oldStatus != ConnectionStatus.Connecting);
|
||
bool shouldReset = (newStatus == ConnectionStatus.Connected);
|
||
|
||
// 添加状态转换验证,避免不合理的状态切换
|
||
bool isValidTransition = ValidateStatusTransition(oldStatus, newStatus);
|
||
if (!isValidTransition)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 无效的状态转换: {oldStatus} -> {newStatus},已忽略");
|
||
return;
|
||
}
|
||
|
||
// 添加额外的保护:防止短时间内状态频繁变化导致的闪烁问题
|
||
// 1. 当从Connected转换到Reconnecting时,检查是否真的需要重连
|
||
if (oldStatus == ConnectionStatus.Connected && newStatus == ConnectionStatus.Reconnecting)
|
||
{
|
||
// 检查当前是否有其他重连操作正在进行
|
||
if (Interlocked.CompareExchange(ref _isReconnecting, 1, 1) == 1)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 检测到重连操作正在进行,推迟状态转换: {oldStatus} -> {newStatus}");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 2. 当从Reconnecting快速切换回Connected时,确保消息不会引起混淆
|
||
if (oldStatus == ConnectionStatus.Reconnecting && newStatus == ConnectionStatus.Connected && message.Contains("正在进行自动重连"))
|
||
{
|
||
// 修改消息内容,使其更准确
|
||
message = "重连成功,设备已恢复连接";
|
||
}
|
||
|
||
// 在锁外执行可能耗时的操作
|
||
ThreadPool.QueueUserWorkItem((state) =>
|
||
{
|
||
try
|
||
{
|
||
if (shouldReconnect)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 在锁外启动自动重连");
|
||
StartAutoReconnect();
|
||
}
|
||
else if (shouldReset)
|
||
{
|
||
lock (_lockObject) // 重置时需要加锁
|
||
{
|
||
if (!_isDisposed)
|
||
{
|
||
_currentReconnectAttempt = 0;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 连接成功,已重置重连计数");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 后续操作异常: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
else
|
||
{
|
||
// 优化:只有在调试模式且有异常时才记录调用堆栈,减少日志量
|
||
if (System.Diagnostics.Debugger.IsAttached || exception != null || (!string.IsNullOrEmpty(message) && !message.Contains("正在进行自动重连")))
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 状态未变化({_connectionStatus}),消息: {message}");
|
||
|
||
// 仅在调试模式且有异常时才记录调用堆栈,避免频繁的堆栈记录
|
||
#if DEBUG
|
||
if (exception != null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 调用堆栈: {Environment.StackTrace}");
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] UpdateConnectionStatus() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证状态转换的有效性
|
||
/// </summary>
|
||
/// <param name="fromStatus">当前状态</param>
|
||
/// <param name="toStatus">目标状态</param>
|
||
/// <returns>是否为有效的状态转换</returns>
|
||
private bool ValidateStatusTransition(ConnectionStatus fromStatus, ConnectionStatus toStatus)
|
||
{
|
||
// 如果状态相同,认为是有效的(但会在主方法中跳过实际变更)
|
||
if (fromStatus == toStatus)
|
||
return true;
|
||
|
||
// 定义有效的状态转换规则
|
||
switch (fromStatus)
|
||
{
|
||
case ConnectionStatus.Disconnected:
|
||
// 断开状态可以转换为连接中或重连中
|
||
return toStatus == ConnectionStatus.Connecting || toStatus == ConnectionStatus.Reconnecting;
|
||
|
||
case ConnectionStatus.Connecting:
|
||
// 连接中状态可以转换为已连接或断开
|
||
return toStatus == ConnectionStatus.Connected || toStatus == ConnectionStatus.Disconnected;
|
||
|
||
case ConnectionStatus.Connected:
|
||
// 已连接状态可以转换为断开或重连中
|
||
// 允许直接从Connected转换到Reconnecting,以支持更灵活的重连机制
|
||
return toStatus == ConnectionStatus.Disconnected || toStatus == ConnectionStatus.Reconnecting;
|
||
|
||
case ConnectionStatus.Reconnecting:
|
||
// 重连中状态可以转换为已连接或断开
|
||
return toStatus == ConnectionStatus.Connected || toStatus == ConnectionStatus.Disconnected;
|
||
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化设备管理器
|
||
/// </summary>
|
||
/// <returns>是否初始化成功</returns>
|
||
private bool Initialize()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 开始执行初始化");
|
||
|
||
// 在设计模式下,跳过实际初始化,不设置连接成功状态
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际初始化");
|
||
_isInitialized = true; // 仍然标记为已初始化,避免重复初始化
|
||
_connectionStatus = ConnectionStatus.Disconnected; // 设置为断开状态,避免触发图像接收
|
||
return false; // 返回false,避免Camera认为初始化成功
|
||
}
|
||
|
||
// 双重检查锁定模式,确保线程安全的初始化
|
||
if (_isInitialized)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 已经初始化,直接返回成功");
|
||
return true;
|
||
}
|
||
|
||
// 加锁进行线程安全的初始化
|
||
lock (_lockObject)
|
||
{
|
||
// 再次检查,因为可能在获取锁的过程中已被其他线程初始化
|
||
if (_isInitialized)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 已被其他线程初始化,直接返回成功");
|
||
return true;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 清理旧资源(如果有)
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 清理旧资源");
|
||
CleanupResources();
|
||
|
||
// 初始化设备ID列表和相关变量
|
||
_deviceIds = new List<string>();
|
||
_currentDeviceId = -1;
|
||
// 保留默认设备IP,不要重置为空字符串
|
||
// _deviceIp = string.Empty; // 注释掉这行,避免IP地址被清空
|
||
_connectionStatus = ConnectionStatus.Disconnected;
|
||
_currentReconnectAttempt = 0;
|
||
|
||
Log("开始SDK初始化...");
|
||
|
||
// 首先检查网络可用性
|
||
if (!IsNetworkAvailable())
|
||
{
|
||
Log("网络不可用,初始化暂缓");
|
||
return false;
|
||
}
|
||
|
||
// 更新状态为连接中
|
||
UpdateConnectionStatus(ConnectionStatus.Connecting, "正在初始化设备连接");
|
||
|
||
// 首先调用SDK静态初始化方法 - 这是连接成功的关键步骤!
|
||
// 添加重试机制,增强初始化可靠性
|
||
int initResult = -1;
|
||
int maxInitRetries = 2;
|
||
|
||
for (int retry = 0; retry <= maxInitRetries; retry++)
|
||
{
|
||
try
|
||
{
|
||
if (retry > 0)
|
||
{
|
||
Log($"SDK初始化重试 {retry}/{maxInitRetries}");
|
||
Thread.Sleep(300); // 重试前等待一小段时间
|
||
}
|
||
|
||
initResult = A8SDK.SDK_initialize();
|
||
if (initResult == 0)
|
||
{
|
||
break; // 成功,跳出循环
|
||
}
|
||
|
||
Log($"SDK静态初始化失败,返回值: {initResult}");
|
||
}
|
||
catch (Exception initEx)
|
||
{
|
||
Log($"SDK初始化异常: {initEx.Message},堆栈: {initEx.StackTrace}");
|
||
}
|
||
}
|
||
|
||
if (initResult != 0)
|
||
{
|
||
Log($"SDK静态初始化失败,所有重试均未成功,返回值: {initResult}");
|
||
_isInitialized = false;
|
||
return false;
|
||
}
|
||
|
||
Log("SDK静态初始化成功");
|
||
|
||
// 设置默认配置参数
|
||
_maxReconnectAttempts = 15; // 增加最大重连次数
|
||
_isAutoReconnectEnabled = true;
|
||
|
||
_isInitialized = true;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 初始化成功,_isInitialized设为true");
|
||
|
||
// 更新状态为已连接
|
||
UpdateConnectionStatus(ConnectionStatus.Connected, "SDK初始化成功");
|
||
|
||
// 连接成功后,停止自动重连(如果正在运行)
|
||
StopAutoReconnect();
|
||
|
||
// 启动图像接收
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 连接成功,启动图像接收");
|
||
StartImageReceiving();
|
||
|
||
// 启动温度数据接收
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 启动温度数据接收");
|
||
StartTemperatureDataReceiving();
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"SDK初始化失败: {ex.Message},堆栈: {ex.StackTrace}");
|
||
_isInitialized = false;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Initialize() - 初始化异常,_isInitialized保持false: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "初始化设备管理器失败"));
|
||
// 更新状态为断开
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "初始化失败", ex);
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
// 确保在异常情况下也能清理资源
|
||
if (!_isInitialized && _connectionStatus == ConnectionStatus.Connecting)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "初始化未完成");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 清理所有资源,确保安全释放
|
||
/// </summary>
|
||
private void CleanupResources()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CleanupResources() - 开始清理资源");
|
||
try
|
||
{
|
||
// 停止所有定时器
|
||
StopAutoReconnect();
|
||
StopHeartbeat();
|
||
StopConnectionCheck();
|
||
// 停止温度数据接收
|
||
StopTemperatureDataReceiving();
|
||
|
||
// 安全释放SDK实例
|
||
A8SDK oldSdk = Interlocked.Exchange(ref _a8Sdk, null);
|
||
if (oldSdk != null)
|
||
{
|
||
try
|
||
{
|
||
Log("释放旧SDK实例...");
|
||
// 注意:如果SDK提供了destroy方法,应该在这里调用
|
||
// A8SDK.SDK_destroy();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"释放SDK实例异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 重置状态变量
|
||
_deviceIds = null;
|
||
_connectionStatus = ConnectionStatus.Disconnected;
|
||
_currentDeviceId = -1;
|
||
// _deviceIp = string.Empty; // 注释掉这行,避免IP地址在资源清理时被清空
|
||
_currentReconnectAttempt = 0;
|
||
_isInitialized = false;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CleanupResources() - _isInitialized已设为false");
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CleanupResources() - 资源清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"清理资源异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试ping设备IP地址
|
||
/// </summary>
|
||
/// <param name="ipAddress">设备IP地址</param>
|
||
/// <returns>是否ping通</returns>
|
||
private bool PingDevice(string ipAddress)
|
||
{
|
||
try
|
||
{
|
||
// 尝试真正的ICMP ping,提供更准确的设备可达性检查
|
||
using (var ping = new System.Net.NetworkInformation.Ping())
|
||
{
|
||
try
|
||
{
|
||
Console.WriteLine($"正在ping设备IP: {ipAddress},检查网络可达性...");
|
||
var reply = ping.Send(ipAddress, 2000); // 增加超时时间到2秒
|
||
bool isReachable = reply != null && reply.Status == System.Net.NetworkInformation.IPStatus.Success;
|
||
Console.WriteLine($"Ping结果: {(isReachable ? "成功" : "失败")}");
|
||
return isReachable;
|
||
}
|
||
catch (Exception pingEx)
|
||
{
|
||
Console.WriteLine($"Ping异常: {pingEx.Message}");
|
||
// 如果ICMP ping失败,退回到原有的网络接口检查逻辑
|
||
Console.WriteLine("尝试网络接口检查作为备选方案...");
|
||
}
|
||
}
|
||
|
||
// 改进的网络接口检查逻辑
|
||
System.Net.NetworkInformation.NetworkInterface[] interfaces =
|
||
System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
|
||
|
||
// 检查是否有活动的以太网或WiFi连接
|
||
foreach (var ni in interfaces)
|
||
{
|
||
// 仅检查活动的网络接口,移除过于严格的过滤条件
|
||
if (ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up &&
|
||
(ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Ethernet ||
|
||
ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Wireless80211 ||
|
||
ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.GigabitEthernet))
|
||
{
|
||
// 获取该网络接口的IP信息,检查是否有有效的IP地址
|
||
var properties = ni.GetIPProperties();
|
||
foreach (var addr in properties.UnicastAddresses)
|
||
{
|
||
// 检查是否有IPv4地址
|
||
if (addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||
{
|
||
// 如果设备IP不为空,可以尝试检查设备IP是否在该接口的子网内
|
||
if (!string.IsNullOrEmpty(_deviceIp))
|
||
{
|
||
try
|
||
{
|
||
// 使用更准确的子网判断方法
|
||
var deviceAddress = System.Net.IPAddress.Parse(_deviceIp);
|
||
var localAddress = addr.Address;
|
||
|
||
// 获取子网掩码
|
||
byte[] subnetMaskBytes = addr.IPv4Mask.GetAddressBytes();
|
||
byte[] deviceAddressBytes = deviceAddress.GetAddressBytes();
|
||
byte[] localAddressBytes = localAddress.GetAddressBytes();
|
||
|
||
// 检查是否在同一子网(使用子网掩码进行计算)
|
||
bool isInSameSubnet = true;
|
||
for (int i = 0; i < subnetMaskBytes.Length; i++)
|
||
{
|
||
if ((deviceAddressBytes[i] & subnetMaskBytes[i]) !=
|
||
(localAddressBytes[i] & subnetMaskBytes[i]))
|
||
{
|
||
isInSameSubnet = false;
|
||
break;
|
||
}
|
||
}
|
||
// 添加心跳检测异常处理,但位置应该在CheckConnectionValidity方法中
|
||
// 这里保持PingDevice方法的纯净性
|
||
|
||
if (isInSameSubnet)
|
||
{
|
||
Console.WriteLine($"设备IP与本地网络接口在同一网段: {localAddress}");
|
||
return true;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"子网判断异常: {ex.Message}");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Console.WriteLine("未找到有效的网络连接或设备IP不在同一网段");
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"检查网络可用性异常: {ex.Message}");
|
||
// 发生异常时默认为网络不可用,避免误判
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止所有图像接收线程
|
||
/// </summary>
|
||
private void StopAllImageReceivers()
|
||
{
|
||
_stopRequested.Set();
|
||
// 给线程一点时间来结束
|
||
Thread.Sleep(100);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置停止标志
|
||
/// </summary>
|
||
private void ResetStopFlag()
|
||
{
|
||
_stopRequested.Reset();
|
||
}
|
||
|
||
#endregion 私有方法
|
||
|
||
#region 温度数据处理方法
|
||
|
||
/// <summary>
|
||
/// 获取温度补偿值
|
||
/// </summary>
|
||
/// <returns>温度补偿值</returns>
|
||
private float GetTemperatureCompensationValue()
|
||
{
|
||
try
|
||
{
|
||
if (_connectionStatus == ConnectionStatus.Connected && _a8Sdk != null)
|
||
{
|
||
float compensationValue = _a8Sdk.Comp_temp;
|
||
Log($"获取温度补偿值: {compensationValue}");
|
||
return compensationValue;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"获取温度补偿值异常: {ex.Message}");
|
||
}
|
||
return 0.0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始接收温度数据
|
||
/// </summary>
|
||
public void StartTemperatureDataReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 开始执行");
|
||
|
||
// 在设计模式下,跳过实际的温度数据接收
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的温度数据接收");
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 检查对象是否已被释放
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 对象已释放,忽略操作");
|
||
return;
|
||
}
|
||
|
||
// 检查连接状态
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 连接未建立,无法开始温度数据接收");
|
||
return;
|
||
}
|
||
|
||
// 添加额外的状态检查,避免不必要的重复启动
|
||
if (_isReceivingTemperatureData && _temperatureReceiveThread != null && _temperatureReceiveThread.IsAlive)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 温度数据接收已经在进行中,避免重复启动");
|
||
return;
|
||
}
|
||
}
|
||
|
||
Log("开始使用TCP方式接收温度数据");
|
||
try
|
||
{
|
||
// 确保之前的连接已关闭
|
||
StopTemperatureDataReceiving();
|
||
// 在锁内执行所有关键状态更新,确保原子性
|
||
lock (_lockObject)
|
||
{
|
||
// 重置停止事件
|
||
_stopTemperatureEvent?.Dispose();
|
||
_stopTemperatureEvent = new ManualResetEvent(false);
|
||
_isReceivingTemperatureData = true;
|
||
}
|
||
Thread.Sleep(500);
|
||
|
||
// 创建并启动温度数据接收线程
|
||
Thread newThread = new Thread(ReceiveTemperatureDataWithTcp)
|
||
{
|
||
IsBackground = true,
|
||
Name = "TemperatureReceiveThread"
|
||
};
|
||
|
||
// 在锁内更新线程引用
|
||
lock (_lockObject)
|
||
{
|
||
_temperatureReceiveThread = newThread;
|
||
}
|
||
|
||
// 启动线程
|
||
newThread.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录异常
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 异常: {ex.Message}");
|
||
|
||
// 在异常情况下确保状态正确重置
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingTemperatureData = false;
|
||
}
|
||
|
||
// 如果连接状态异常,触发异常事件
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "启动温度数据接收失败"));
|
||
}
|
||
finally
|
||
{
|
||
// 确保方法执行完成时记录日志
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止接收温度数据
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 暂停温度数据接收处理(保持TCP连接,但不处理数据)
|
||
/// </summary>
|
||
public void PauseTemperatureDataReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] PauseTemperatureDataReceiving() - 开始执行");
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] PauseTemperatureDataReceiving() - 对象已释放,跳过操作");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
_isTemperatureReceivingPaused = true;
|
||
}
|
||
Log("温度数据接收已暂停,保持TCP连接但不处理数据");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] PauseTemperatureDataReceiving() - 异常: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] PauseTemperatureDataReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复温度数据接收处理
|
||
/// </summary>
|
||
public void ResumeTemperatureDataReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ResumeTemperatureDataReceiving() - 开始执行");
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ResumeTemperatureDataReceiving() - 对象已释放,跳过操作");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
_isTemperatureReceivingPaused = false;
|
||
}
|
||
Log("温度数据接收已恢复,开始处理数据");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ResumeTemperatureDataReceiving() - 异常: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ResumeTemperatureDataReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止接收温度数据(断开TCP连接)
|
||
/// </summary>
|
||
public void StopTemperatureDataReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 开始执行");
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 对象已释放,跳过操作");
|
||
return;
|
||
}
|
||
|
||
// 使用变量保存需要释放的资源,减少锁的持有时间
|
||
ManualResetEvent stopEventToDispose = null;
|
||
Thread threadToJoin = null;
|
||
NetworkStream streamToClose = null;
|
||
TcpClient clientToClose = null;
|
||
|
||
try
|
||
{
|
||
// 在一个锁内收集所有需要清理的资源和更新状态
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingTemperatureData = false;
|
||
_isTemperatureReceivingPaused = false; // 同时重置暂停状态
|
||
stopEventToDispose = _stopTemperatureEvent;
|
||
threadToJoin = _temperatureReceiveThread;
|
||
streamToClose = _temperatureStream;
|
||
clientToClose = _temperatureTcpClient;
|
||
|
||
// 立即重置引用,避免资源被重新使用
|
||
_temperatureStream = null;
|
||
_temperatureTcpClient = null;
|
||
_temperatureReceiveThread = null;
|
||
_stopTemperatureEvent = null;
|
||
}
|
||
|
||
// 在锁外通知线程停止,避免死锁
|
||
if (stopEventToDispose != null)
|
||
{
|
||
try
|
||
{
|
||
stopEventToDispose.Set();
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
// 忽略已释放的事件
|
||
}
|
||
}
|
||
|
||
// 在锁外等待线程结束,避免死锁
|
||
if (threadToJoin != null && threadToJoin.IsAlive)
|
||
{
|
||
Log("等待温度接收线程结束...");
|
||
// 增加等待时间,确保线程有足够时间完成清理
|
||
if (threadToJoin.Join(2000)) // 等待最多2秒
|
||
{
|
||
Log("温度接收线程已正常停止");
|
||
}
|
||
else
|
||
{
|
||
Log("警告:温度接收线程可能未正常停止,已超时");
|
||
}
|
||
}
|
||
|
||
// 在锁外关闭TCP客户端和流,避免死锁
|
||
if (streamToClose != null)
|
||
{
|
||
try
|
||
{
|
||
streamToClose.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"关闭NetworkStream时出现异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
if (clientToClose != null)
|
||
{
|
||
try
|
||
{
|
||
clientToClose.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"关闭TcpClient时出现异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 释放停止事件
|
||
if (stopEventToDispose != null)
|
||
{
|
||
try
|
||
{
|
||
stopEventToDispose.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"释放ManualResetEvent时出现异常: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 异常: {ex.Message}");
|
||
|
||
// 在异常情况下,再次尝试重置状态
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingTemperatureData = false;
|
||
_temperatureStream = null;
|
||
_temperatureTcpClient = null;
|
||
_temperatureReceiveThread = null;
|
||
}
|
||
}
|
||
catch {}
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用TCP接收温度数据
|
||
/// </summary>
|
||
private void ReceiveTemperatureDataWithTcp()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 开始执行");
|
||
|
||
// 使用局部变量存储资源,避免在finally中访问可能已被释放的字段
|
||
TcpClient localTcpClient = null;
|
||
NetworkStream localStream = null;
|
||
List<byte> temperatureDataAccumulator = new List<byte>();
|
||
// 根据分析,温度数据帧大小为98313字节(9字节头部+256×192×2字节数据),增大缓冲区提高接收效率
|
||
byte[] buffer = new byte[65536]; // 增大温度数据缓冲区,减少Read操作次数
|
||
|
||
try
|
||
{
|
||
// 创建TCP客户端并连接到设备的温度数据端口
|
||
localTcpClient = new TcpClient
|
||
{
|
||
ReceiveTimeout = 5000,
|
||
SendTimeout = 5000
|
||
};
|
||
|
||
Log($"正在连接到温度数据端口 {TEMPERATURE_TCP_PORT}...");
|
||
IAsyncResult result = localTcpClient.BeginConnect(_deviceIp, TEMPERATURE_TCP_PORT, null, null);
|
||
bool connected = result.AsyncWaitHandle.WaitOne(3000, true);
|
||
|
||
if (!connected || !localTcpClient.Connected)
|
||
{
|
||
Log("温度数据TCP连接失败,超时");
|
||
localTcpClient.Close();
|
||
return;
|
||
}
|
||
|
||
localTcpClient.EndConnect(result);
|
||
Log("温度数据TCP连接成功");
|
||
|
||
// 获取网络流
|
||
localStream = localTcpClient.GetStream();
|
||
localStream.ReadTimeout = 5000;
|
||
|
||
// 更新类成员变量,在锁内进行
|
||
lock (_lockObject)
|
||
{
|
||
_temperatureTcpClient = localTcpClient;
|
||
_temperatureStream = localStream;
|
||
// 确保接收状态为true
|
||
_isReceivingTemperatureData = true;
|
||
}
|
||
|
||
// 发送开始温度数据传输的命令
|
||
byte[] startCommand = Encoding.ASCII.GetBytes("start_temp_transfer\r\n");
|
||
localStream.Write(startCommand, 0, startCommand.Length);
|
||
localStream.Flush();
|
||
|
||
Log("已发送开始温度数据传输命令,开始接收温度数据");
|
||
|
||
// 循环读取数据,使用更安全的退出机制
|
||
bool shouldContinue = true;
|
||
while (shouldContinue)
|
||
{
|
||
// 检查停止信号和接收状态
|
||
bool stopRequested = false;
|
||
bool isReceiving = true;
|
||
|
||
lock (_lockObject)
|
||
{
|
||
if (_stopTemperatureEvent != null && _stopTemperatureEvent.WaitOne(0))
|
||
{
|
||
stopRequested = true;
|
||
}
|
||
isReceiving = _isReceivingTemperatureData;
|
||
}
|
||
|
||
if (stopRequested)
|
||
{
|
||
Log("接收到停止信号,准备退出温度数据接收循环");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
|
||
if (!isReceiving)
|
||
{
|
||
Log("接收状态已更改(isReceiving=false),准备退出温度数据接收循环");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 检查连接状态
|
||
if (localTcpClient == null || !localTcpClient.Connected)
|
||
{
|
||
Log("温度数据TCP连接已断开");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
|
||
// 检查流是否可读
|
||
if (localStream == null || !localStream.CanRead)
|
||
{
|
||
Log("网络流不可读,停止接收");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
|
||
// 检查是否有数据可读,避免阻塞
|
||
if (localStream.DataAvailable)
|
||
{
|
||
// 读取数据
|
||
int bytesRead = localStream.Read(buffer, 0, buffer.Length);
|
||
if (bytesRead > 0)
|
||
{
|
||
// 将读取的数据添加到累积器
|
||
byte[] receivedBytes = new byte[bytesRead];
|
||
Array.Copy(buffer, receivedBytes, bytesRead);
|
||
|
||
Log($"接收到温度数据字节数: {bytesRead}");
|
||
|
||
// 检查是否处于暂停状态
|
||
bool isPaused = false;
|
||
lock (_lockObject)
|
||
{
|
||
isPaused = _isTemperatureReceivingPaused;
|
||
}
|
||
|
||
if (isPaused)
|
||
{
|
||
Log("温度数据接收处于暂停状态,数据已接收但不处理");
|
||
// 数据已接收但不处理,直接丢弃
|
||
}
|
||
else
|
||
{
|
||
// 线程安全地更新累积器或直接处理
|
||
lock (temperatureDataAccumulator)
|
||
{
|
||
temperatureDataAccumulator.AddRange(receivedBytes);
|
||
ProcessReceivedTemperatureData(temperatureDataAccumulator);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 读取到0字节表示连接已关闭
|
||
Log("远程主机关闭了连接");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 如果没有数据可读,短暂休眠避免CPU占用过高
|
||
Thread.Sleep(10);
|
||
}
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
// 超时异常,继续尝试读取
|
||
Log("温度数据接收超时,继续尝试");
|
||
// 短暂休眠后重试,避免CPU占用过高
|
||
Thread.Sleep(50);
|
||
}
|
||
catch (IOException ex)
|
||
{
|
||
Log($"温度数据接收IO异常: {ex.Message}");
|
||
// 对于网络中断异常,尝试重新连接而不是直接退出
|
||
// 在.NET Framework 4.0中,HResult是受保护的,我们通过消息内容来判断
|
||
string exceptionMessage = ex.Message.ToLower();
|
||
if (exceptionMessage.Contains("远程主机") ||
|
||
exceptionMessage.Contains("强制关闭") ||
|
||
exceptionMessage.Contains("connection") ||
|
||
exceptionMessage.Contains("closed"))
|
||
{
|
||
Log("检测到网络连接异常,准备重新连接");
|
||
// 短暂休眠后允许循环退出,让外层逻辑处理重连
|
||
Thread.Sleep(100);
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
Log("非致命IO异常,继续尝试接收");
|
||
// 短暂休眠后继续尝试
|
||
Thread.Sleep(50);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"温度数据接收异常: {ex.Message}");
|
||
Log($"异常详情: {ex.StackTrace}");
|
||
// 检查是否为严重异常
|
||
if (ex is System.Security.SecurityException ||
|
||
ex is UnauthorizedAccessException ||
|
||
ex is System.Threading.ThreadAbortException)
|
||
{
|
||
Log("发生严重异常,停止温度数据接收");
|
||
shouldContinue = false;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
Log("非致命异常,继续尝试接收温度数据");
|
||
// 非致命异常,短暂休眠后继续尝试
|
||
Thread.Sleep(100);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 严重异常: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 执行完成");
|
||
|
||
// 确保状态正确更新
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingTemperatureData = false;
|
||
// 不在这里调用StopTemperatureDataReceiving,避免递归调用和潜在死锁
|
||
// 由调用者负责调用StopTemperatureDataReceiving
|
||
|
||
// 记录线程结束信息
|
||
Log($"温度数据接收线程已结束,重置相关状态");
|
||
}
|
||
|
||
// 清理局部资源
|
||
try
|
||
{
|
||
if (localStream != null)
|
||
{
|
||
localStream.Close();
|
||
}
|
||
}
|
||
catch {}
|
||
|
||
try
|
||
{
|
||
if (localTcpClient != null)
|
||
{
|
||
localTcpClient.Close();
|
||
}
|
||
}
|
||
catch {}
|
||
|
||
// 重置类成员变量中的流和客户端引用
|
||
lock (_lockObject)
|
||
{
|
||
_temperatureStream = null;
|
||
_temperatureTcpClient = null;
|
||
}
|
||
|
||
// 检查是否需要自动重新启动温度数据接收
|
||
// 只有当设备仍在连接状态且用户没有明确停止时才尝试重连
|
||
bool shouldAutoReconnect = false;
|
||
lock (_lockObject)
|
||
{
|
||
// 检查是否有显式的停止信号
|
||
bool hasStopSignal = _stopTemperatureEvent != null && _stopTemperatureEvent.WaitOne(0);
|
||
// 如果没有停止信号且设备应该处于连接状态,则尝试重连
|
||
shouldAutoReconnect = !hasStopSignal && _connectionStatus == ConnectionStatus.Connected;
|
||
}
|
||
|
||
if (shouldAutoReconnect)
|
||
{
|
||
Log("检测到温度数据接收线程异常退出,准备自动重连");
|
||
try
|
||
{
|
||
// 短暂延迟后尝试重新启动温度数据接收
|
||
Thread.Sleep(2000);
|
||
// 确保在新线程中启动,避免递归调用
|
||
ThreadPool.QueueUserWorkItem(state =>
|
||
{
|
||
try
|
||
{
|
||
Log("开始自动重新启动温度数据接收...");
|
||
StartTemperatureDataReceiving();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"自动重连温度数据接收失败: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"调度温度数据自动重连失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理接收到的温度数据
|
||
/// </summary>
|
||
/// <param name="dataAccumulator">累积的温度数据</param>
|
||
private void ProcessReceivedTemperatureData(List<byte> dataAccumulator)
|
||
{
|
||
// 根据TemperatureData类的要求,每个温度帧包含9字节头部 + 温度数据
|
||
// 根据注释,设备实际提供的数据分辨率应为256x192,最终映射到512x384显示
|
||
const int HEADER_SIZE = 9; // 9字节头部("+TEMP"+数据长度)
|
||
const int WIDTH = 256;
|
||
const int HEIGHT = 192;
|
||
const int TEMPERATURE_DATA_FRAME_SIZE = HEADER_SIZE + WIDTH * HEIGHT * 2; // 9字节头部 + 每个温度值2字节
|
||
|
||
try
|
||
{
|
||
Log($"开始处理温度数据,当前累积数据量: {dataAccumulator.Count} 字节,所需帧大小: {TEMPERATURE_DATA_FRAME_SIZE} 字节");
|
||
|
||
// 检查是否有足够的数据构成完整的温度数据帧
|
||
while (dataAccumulator.Count >= TEMPERATURE_DATA_FRAME_SIZE)
|
||
{
|
||
Log($"找到完整的温度数据帧,开始处理");
|
||
|
||
// 提取一帧温度数据
|
||
byte[] temperatureFrame = dataAccumulator.GetRange(0, TEMPERATURE_DATA_FRAME_SIZE).ToArray();
|
||
dataAccumulator.RemoveRange(0, TEMPERATURE_DATA_FRAME_SIZE);
|
||
|
||
Log($"提取温度数据帧完成,剩余累积数据量: {dataAccumulator.Count} 字节");
|
||
|
||
// 获取温度补偿值
|
||
float compensationValue = GetTemperatureCompensationValue();
|
||
Log($"获取到温度补偿值: {compensationValue}");
|
||
|
||
// 创建温度数据对象,使用正确的分辨率参数
|
||
TemperatureData temperatureData = new TemperatureData(temperatureFrame, WIDTH, HEIGHT, compensationValue);
|
||
Log($"温度数据对象创建成功,分辨率: {WIDTH}x{HEIGHT}");
|
||
|
||
// 触发温度数据接收事件
|
||
OnTemperatureReceived(new TemperatureReceivedEventArgs(temperatureData, temperatureFrame, compensationValue));
|
||
Log($"温度数据接收事件触发完成");
|
||
}
|
||
|
||
Log($"温度数据处理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ProcessReceivedTemperatureData() - 异常: {ex.Message}");
|
||
// 清空累积器,重新开始
|
||
Log("清空温度数据累积器,准备重新接收");
|
||
dataAccumulator.Clear();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发温度数据接收事件
|
||
/// </summary>
|
||
/// <param name="e">事件参数</param>
|
||
protected virtual void OnTemperatureReceived(TemperatureReceivedEventArgs e)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnTemperatureReceived() - 开始执行");
|
||
|
||
// 检查参数有效性
|
||
if (e == null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnTemperatureReceived() - 参数为空,跳过事件触发");
|
||
return;
|
||
}
|
||
|
||
// 获取事件处理程序的快照,避免在多线程环境中出现竞态条件
|
||
EventHandler<TemperatureReceivedEventArgs> handler = TemperatureReceived;
|
||
|
||
// 检查是否有订阅者
|
||
if (handler != null)
|
||
{
|
||
// 获取所有订阅的委托,单独处理每个处理器
|
||
Delegate[] invocationList = handler.GetInvocationList();
|
||
|
||
foreach (Delegate d in invocationList)
|
||
{
|
||
try
|
||
{
|
||
// 安全地转换并调用每个处理器
|
||
EventHandler<TemperatureReceivedEventArgs> invoker = (EventHandler<TemperatureReceivedEventArgs>)d;
|
||
invoker(this, e);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnTemperatureReceived() - 成功触发一个事件处理器: {d.Method.Name}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获单个事件处理器的异常,确保其他处理器仍能被调用
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnTemperatureReceived() - 事件处理器异常: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnTemperatureReceived() - 执行完成");
|
||
}
|
||
|
||
#endregion 温度数据处理方法
|
||
|
||
#region 公共事件
|
||
|
||
/// <summary>
|
||
/// 连接状态变更事件
|
||
/// </summary>
|
||
public event EventHandler<ConnectionStatusChangedEventArgs> ConnectionStatusChanged;
|
||
|
||
/// <summary>
|
||
/// 连接异常事件
|
||
/// </summary>
|
||
public event EventHandler<ConnectionExceptionEventArgs> ConnectionException;
|
||
|
||
/// <summary>
|
||
/// 图像数据接收事件
|
||
/// </summary>
|
||
public event EventHandler<ImageReceivedEventArgs> ImageReceived;
|
||
|
||
/// <summary>
|
||
/// 温度数据接收事件
|
||
/// </summary>
|
||
public event EventHandler<TemperatureReceivedEventArgs> TemperatureReceived;
|
||
|
||
#endregion 公共事件
|
||
|
||
/// <summary>
|
||
/// 触发连接状态变更事件
|
||
/// </summary>
|
||
/// <param name="e">事件参数</param>
|
||
protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs e)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionStatusChanged() - 开始执行,状态: {e.Status}");
|
||
|
||
// 检查参数有效性
|
||
if (e == null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionStatusChanged() - 参数为空,跳过事件触发");
|
||
return;
|
||
}
|
||
|
||
// 获取事件处理程序的快照,避免在多线程环境中出现竞态条件
|
||
EventHandler<ConnectionStatusChangedEventArgs> handler = ConnectionStatusChanged;
|
||
|
||
// 检查是否有订阅者
|
||
if (handler != null)
|
||
{
|
||
// 获取所有订阅的委托,单独处理每个处理器
|
||
Delegate[] invocationList = handler.GetInvocationList();
|
||
|
||
foreach (Delegate d in invocationList)
|
||
{
|
||
try
|
||
{
|
||
// 安全地转换并调用每个处理器
|
||
EventHandler<ConnectionStatusChangedEventArgs> invoker = (EventHandler<ConnectionStatusChangedEventArgs>)d;
|
||
invoker(this, e);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionStatusChanged() - 成功触发一个事件处理器: {d.Method.Name}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获单个事件处理器的异常,确保其他处理器仍能被调用
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionStatusChanged() - 事件处理器异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionStatusChanged() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动连接状态检查
|
||
/// </summary>
|
||
public void StartConnectionCheck()
|
||
{
|
||
lock (_lockObject) // 添加线程同步锁
|
||
{
|
||
try
|
||
{
|
||
// 在设计模式下,跳过实际的连接检查
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的连接检查");
|
||
return;
|
||
}
|
||
|
||
// 在暂停检测模式下,跳过连接检查
|
||
if (IsDetectionPaused)
|
||
{
|
||
Log("暂停检测模式下跳过连接检查");
|
||
return;
|
||
}
|
||
|
||
// 首先停止现有的连接检查,确保资源释放
|
||
StopConnectionCheck();
|
||
|
||
Log("启动连接状态检查,每5秒检查一次"); // 统一使用Log方法
|
||
|
||
// 使用try-catch包装定时器创建,避免异常
|
||
try
|
||
{
|
||
// 使用非重复模式的定时器,避免前一个检查未完成就开始新的检查
|
||
_connectionCheckTimer = new System.Threading.Timer(state =>
|
||
{
|
||
// 确保定时器状态检查在工作线程中安全执行
|
||
try
|
||
{
|
||
// 避免在定时器回调中可能的资源访问冲突
|
||
if (_connectionCheckTimer == null)
|
||
return;
|
||
|
||
lock (_lockObject) // 添加线程同步锁保护状态访问
|
||
{
|
||
if (_isDisposed)
|
||
return;
|
||
|
||
// 关键修改:即使_isInitialized为false,也需要检查连接状态
|
||
// 当显示为已连接但初始化状态为false时,必须检查连接
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
// 如果初始化失败但显示已连接,尝试重新初始化
|
||
if (!_isInitialized)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 警告: 显示已连接但初始化状态为false,尝试重新初始化...");
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 当前状态: _connectionStatus={_connectionStatus}, _isInitialized={_isInitialized}, _a8Sdk={(_a8Sdk == null ? "null" : "已初始化")}, _currentDeviceId={_currentDeviceId}");
|
||
|
||
// 避免在定时器回调中直接调用可能阻塞的方法
|
||
// 使用线程池执行初始化
|
||
ThreadPool.QueueUserWorkItem((obj) =>
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (!_isDisposed && _connectionStatus == ConnectionStatus.Connected && !_isInitialized)
|
||
{
|
||
if (!Initialize())
|
||
{
|
||
Log("重新初始化失败,确认连接已断开");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备未初始化,连接已断开");
|
||
// 启动自动重连
|
||
if (_isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 无论初始化状态如何,只要显示为已连接就进行检查
|
||
// 复制状态变量到局部变量以避免在检查过程中状态被修改
|
||
bool isConnected = _connectionStatus == ConnectionStatus.Connected;
|
||
bool isInitialized = _isInitialized;
|
||
A8SDK sdkInstance = _a8Sdk;
|
||
int deviceId = _currentDeviceId;
|
||
|
||
// 在锁外执行检查以避免阻塞
|
||
ThreadPool.QueueUserWorkItem((obj) =>
|
||
{
|
||
CheckConnectionWrapper();
|
||
});
|
||
}
|
||
// 正常情况下的连接检查
|
||
else if (_isInitialized && _a8Sdk != null && _currentDeviceId != -1)
|
||
{
|
||
// 复制状态变量到局部变量以避免在检查过程中状态被修改
|
||
bool isInitialized = _isInitialized;
|
||
A8SDK sdkInstance = _a8Sdk;
|
||
int deviceId = _currentDeviceId;
|
||
|
||
// 在锁外执行检查以避免阻塞
|
||
ThreadPool.QueueUserWorkItem((obj) =>
|
||
{
|
||
CheckConnectionWrapper();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"定时器回调异常: {ex.Message}");
|
||
// 异常时如果是已连接状态,将其设为断开
|
||
lock (_lockObject)
|
||
{
|
||
if (!_isDisposed && _connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接检查异常", ex);
|
||
_isInitialized = false;
|
||
// 启动自动重连,只有在未暂停检测时才执行
|
||
if (_isAutoReconnectEnabled && !IsDetectionPaused)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
}
|
||
// 发生异常时停止定时器,避免持续报错
|
||
StopConnectionCheck();
|
||
}
|
||
finally
|
||
{
|
||
// 检查完成后,如果定时器仍存在且未释放,重新启动定时器
|
||
// 这样可以确保一个检查完成后才开始下一个检查
|
||
lock (_lockObject)
|
||
{
|
||
if (!_isDisposed && _connectionCheckTimer != null)
|
||
{
|
||
try
|
||
{
|
||
_connectionCheckTimer.Change(5000, Timeout.Infinite);
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
// 忽略已释放对象的异常
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}, null, 5000, Timeout.Infinite);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"创建连接检查定时器失败: {ex.Message}");
|
||
_connectionCheckTimer = null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"启动连接检查异常: {ex.Message}");
|
||
StopConnectionCheck();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 连接检查的安全包装方法
|
||
private void CheckConnectionWrapper()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 开始连接检查");
|
||
|
||
// 先检查对象状态,避免不必要的操作
|
||
lock (_lockObject)
|
||
{
|
||
if (_isDisposed || _connectionStatus != ConnectionStatus.Connected || IsDetectionPaused)
|
||
{
|
||
string reason = _isDisposed ? "对象已释放" :
|
||
(_connectionStatus != ConnectionStatus.Connected ? "连接状态非Connected" : "检测已暂停");
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - {reason},跳过检查");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 在锁外执行连接有效性检查,避免长时间阻塞
|
||
bool isStillConnected;
|
||
try
|
||
{
|
||
isStillConnected = CheckConnectionValidity();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - CheckConnectionValidity返回: {isStillConnected}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 检查连接状态异常: {ex.Message}");
|
||
isStillConnected = false;
|
||
}
|
||
|
||
// 如果连接无效,需要在锁内更新状态
|
||
if (!isStillConnected)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (!_isDisposed && _connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 检测到连接已断开: _connectionStatus={_connectionStatus}, isStillConnected={isStillConnected}, _isInitialized={_isInitialized}");
|
||
// 断开连接时重置初始化状态和SDK实例
|
||
_isInitialized = false;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 重置初始化状态为false");
|
||
|
||
// 安全释放SDK实例
|
||
A8SDK oldSdk = _a8Sdk;
|
||
_a8Sdk = null;
|
||
|
||
// 在锁外释放资源
|
||
if (oldSdk != null)
|
||
{
|
||
ThreadPool.QueueUserWorkItem((obj) =>
|
||
{
|
||
try
|
||
{
|
||
// 注意:A8SDK类没有Dispose方法,这里只记录日志
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - SDK实例引用已处理");
|
||
}
|
||
catch (Exception disposeEx)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] SDK资源释放异常: {disposeEx.Message}");
|
||
}
|
||
});
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - SDK实例已为空,无需释放");
|
||
}
|
||
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接已断开:设备离线");
|
||
|
||
// 断开连接后自动启动重连,但在锁外执行,只有在未暂停检测时才执行
|
||
if (_isAutoReconnectEnabled && !IsDetectionPaused)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 连接断开,将启动自动重连");
|
||
// 在锁外启动自动重连,避免潜在的死锁
|
||
ThreadPool.QueueUserWorkItem((obj) =>
|
||
{
|
||
StartAutoReconnect();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 连接检查完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查连接有效性的内部方法
|
||
/// </summary>
|
||
/// <returns>连接是否有效</returns>
|
||
private bool CheckConnectionValidity()
|
||
{
|
||
// 在设计模式下,跳过实际的连接有效性检查,直接返回连接有效
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的连接有效性检查,模拟连接有效");
|
||
return true;
|
||
}
|
||
|
||
// 在暂停检测模式下,跳过连接有效性检查,直接返回连接有效
|
||
if (IsDetectionPaused)
|
||
{
|
||
Log("暂停检测模式下跳过连接有效性检查,模拟连接有效");
|
||
return true;
|
||
}
|
||
|
||
// 注意:此方法被CheckConnectionWrapper调用,已经在线程安全的上下文中
|
||
// 不需要额外加锁,但需要确保SDK实例的访问是线程安全的
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 开始检查连接有效性");
|
||
try
|
||
{
|
||
// 记录当前状态信息
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 当前状态: _currentDeviceId={_currentDeviceId}, _deviceIp={_deviceIp}, _a8Sdk={(null == _a8Sdk ? "null" : "存在")}");
|
||
|
||
// 重要修改:先检查设备ID有效性,避免后续无效检查
|
||
if (_currentDeviceId == -1)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 当前设备ID无效,返回false");
|
||
return false;
|
||
}
|
||
|
||
// 优化1: 优先尝试ping设备IP地址,这是物理连接断开的最直接检测
|
||
bool pingResult = PingDevice(_deviceIp);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - ping检测结果: {pingResult}");
|
||
if (!pingResult)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 设备IP {_deviceIp} 不可达,物理连接可能已断开,返回false");
|
||
return false;
|
||
}
|
||
|
||
// 优化2: 系统网络接口检测作为辅助检查,不再作为首要条件
|
||
bool networkAvailable = IsNetworkAvailable();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 网络可用性检测结果: {networkAvailable}");
|
||
if (!networkAvailable)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 警告: 系统网络连接不可用,但ping检测通过");
|
||
}
|
||
|
||
// 优化3: 加强SDK连接状态验证,参考热像仪示例的实现方式
|
||
try
|
||
{
|
||
// 使用线程安全的SDK实例创建
|
||
if (_a8Sdk == null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK实例不存在,重新初始化...");
|
||
// 先创建新实例,再原子赋值,避免中间状态
|
||
A8SDK newSdk = new A8SDK(_deviceIp);
|
||
_a8Sdk = newSdk;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK实例已创建");
|
||
}
|
||
|
||
// 增强心跳检测重试机制,提高连接稳定性
|
||
int maxHeartbeatRetries = 3; // 增加到3次重试
|
||
int heartbeatResult = -1;
|
||
|
||
for (int retry = 0; retry <= maxHeartbeatRetries; retry++)
|
||
{
|
||
try
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK心跳检测...(尝试 {retry + 1}/{maxHeartbeatRetries + 1})");
|
||
heartbeatResult = _a8Sdk.Heartbeat();
|
||
|
||
// 严格检查返回值,确保连接有效
|
||
if (heartbeatResult == 1) // V8.Heartbeat()方法返回1表示成功
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK心跳检测成功");
|
||
break;
|
||
}
|
||
else if (retry < maxHeartbeatRetries)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK心跳检测失败,返回代码: {heartbeatResult},等待500ms后重试...");
|
||
Thread.Sleep(500); // 增加重试间隔到500ms
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK心跳检测多次失败,尝试重建SDK连接...");
|
||
// 安全释放旧的SDK实例
|
||
A8SDK oldSdk = Interlocked.Exchange(ref _a8Sdk, null);
|
||
// 尝试重建SDK连接
|
||
Thread.Sleep(500);
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 已重建SDK连接,进行最后一次心跳检测");
|
||
Thread.Sleep(500);
|
||
heartbeatResult = _a8Sdk.Heartbeat();
|
||
|
||
if (heartbeatResult == 1)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK重新连接成功");
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK重新连接失败,返回代码: {heartbeatResult}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 心跳检测异常: {ex.Message}");
|
||
if (retry < maxHeartbeatRetries)
|
||
{
|
||
Thread.Sleep(500); // 增加重试间隔到500ms
|
||
}
|
||
}
|
||
}
|
||
|
||
if (heartbeatResult != 1)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 心跳检测最终失败,返回false");
|
||
// 心跳失败时,安全释放SDK实例
|
||
if (_a8Sdk != null)
|
||
{
|
||
_a8Sdk = null;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 添加额外的连接验证步骤
|
||
try
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 进行额外连接验证...");
|
||
// 再次发送心跳包确保连接稳定
|
||
int finalResult = _a8Sdk.Heartbeat();
|
||
if (finalResult != 1)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 最终验证失败,返回代码: {finalResult},返回false");
|
||
_a8Sdk = null;
|
||
return false;
|
||
}
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 最终验证成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 连接验证异常: {ex.Message},返回false");
|
||
_a8Sdk = null;
|
||
return false;
|
||
}
|
||
|
||
// 设备名称获取使用try-catch,避免在心跳成功但设备响应异常时崩溃
|
||
try
|
||
{
|
||
// 如果SDK支持获取设备名称,可以尝试获取
|
||
// string deviceName = _a8Sdk.Get_device_name();
|
||
// if (string.IsNullOrEmpty(deviceName))
|
||
// {
|
||
// Console.WriteLine("设备名称获取失败,可能连接不稳定");
|
||
// }
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 获取设备名称异常,但心跳检测成功: {ex.Message}");
|
||
// 设备名称获取失败不应导致整体连接失效
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - SDK心跳检测失败: {ex.Message},返回false");
|
||
// 确保异常时SDK实例被释放
|
||
_a8Sdk = null;
|
||
return false;
|
||
}
|
||
|
||
// 所有检查通过,连接有效
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 所有检查通过,连接有效,返回true");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionValidity() - 连接有效性检查异常: {ex.Message},返回false");
|
||
// 确保异常时SDK实例被释放
|
||
_a8Sdk = null;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止连接状态检查
|
||
/// </summary>
|
||
private void StopConnectionCheck()
|
||
{
|
||
try
|
||
{
|
||
// 使用局部变量暂存,避免多线程访问时的空引用问题
|
||
System.Threading.Timer timerToDispose = Interlocked.Exchange(ref _connectionCheckTimer, null);
|
||
if (timerToDispose != null)
|
||
{
|
||
// 立即停止定时器,避免回调执行
|
||
timerToDispose.Change(Timeout.Infinite, Timeout.Infinite);
|
||
// 安全释放定时器资源
|
||
timerToDispose.Dispose();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopConnectionCheck() - 连接检查已停止");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopConnectionCheck() - 停止连接检查异常: {ex.Message}");
|
||
// 确保即使异常,引用也被清空
|
||
_connectionCheckTimer = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发连接异常事件
|
||
/// </summary>
|
||
/// <param name="e">事件参数</param>
|
||
protected virtual void OnConnectionException(ConnectionExceptionEventArgs e)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionException() - 开始执行");
|
||
|
||
// 检查参数有效性
|
||
if (e == null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionException() - 参数为空,跳过事件触发");
|
||
return;
|
||
}
|
||
|
||
// 获取事件处理程序的快照,避免在多线程环境中出现竞态条件
|
||
EventHandler<ConnectionExceptionEventArgs> handler = ConnectionException;
|
||
|
||
// 检查是否有订阅者
|
||
if (handler != null)
|
||
{
|
||
// 获取所有订阅的委托,单独处理每个处理器
|
||
Delegate[] invocationList = handler.GetInvocationList();
|
||
|
||
foreach (Delegate d in invocationList)
|
||
{
|
||
try
|
||
{
|
||
// 安全地转换并调用每个处理器
|
||
EventHandler<ConnectionExceptionEventArgs> invoker = (EventHandler<ConnectionExceptionEventArgs>)d;
|
||
invoker(this, e);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionException() - 成功触发一个事件处理器: {d.Method.Name}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获单个事件处理器的异常,确保其他处理器仍能被调用
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionException() - 事件处理器异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnConnectionException() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发图像接收事件
|
||
/// </summary>
|
||
/// <param name="e">事件参数</param>
|
||
protected virtual void OnImageReceived(ImageReceivedEventArgs e)
|
||
{
|
||
// 图像事件可能高频触发,只记录重要日志以避免性能影响
|
||
bool isDebugMode = false; // 可配置是否启用详细日志
|
||
|
||
if (isDebugMode)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnImageReceived() - 开始执行");
|
||
}
|
||
|
||
// 检查参数有效性
|
||
if (e == null)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnImageReceived() - 警告: 参数为空,跳过事件触发");
|
||
return;
|
||
}
|
||
|
||
// 获取事件处理程序的快照,避免在多线程环境中出现竞态条件
|
||
EventHandler<ImageReceivedEventArgs> handler = ImageReceived;
|
||
|
||
// 检查是否有订阅者
|
||
if (handler != null)
|
||
{
|
||
// 获取所有订阅的委托,单独处理每个处理器
|
||
Delegate[] invocationList = handler.GetInvocationList();
|
||
|
||
foreach (Delegate d in invocationList)
|
||
{
|
||
try
|
||
{
|
||
// 安全地转换并调用每个处理器
|
||
EventHandler<ImageReceivedEventArgs> invoker = (EventHandler<ImageReceivedEventArgs>)d;
|
||
invoker(this, e);
|
||
|
||
if (isDebugMode)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnImageReceived() - 成功触发处理器: {d.Method.Name}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获单个事件处理器的异常,确保其他处理器仍能被调用
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnImageReceived() - 处理器异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (isDebugMode)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] OnImageReceived() - 执行完成");
|
||
}
|
||
}
|
||
|
||
#region 公共属性
|
||
|
||
/// <summary>
|
||
/// 获取或设置设备IP地址
|
||
/// </summary>
|
||
public string IPAddress
|
||
{
|
||
get { return _deviceIp; }
|
||
set
|
||
{
|
||
if (!string.IsNullOrEmpty(value))
|
||
{
|
||
_deviceIp = value;
|
||
Console.WriteLine($"设备IP地址已设置为: {_deviceIp}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前图像模式
|
||
/// </summary>
|
||
public ImageMode CurrentImageMode
|
||
{
|
||
get { return _currentImageMode; }
|
||
set
|
||
{
|
||
if (_currentImageMode != value)
|
||
{
|
||
_currentImageMode = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接状态
|
||
/// </summary>
|
||
public ConnectionStatus ConnectionStatus
|
||
{
|
||
get { return _connectionStatus; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否启用自动重连
|
||
/// </summary>
|
||
public bool AutoReconnectEnabled
|
||
{
|
||
get { return _isAutoReconnectEnabled; }
|
||
set
|
||
{
|
||
_isAutoReconnectEnabled = value;
|
||
if (value && _connectionStatus == ConnectionStatus.Disconnected)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
else if (!value)
|
||
{
|
||
StopAutoReconnect();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重连间隔(毫秒)
|
||
/// </summary>
|
||
public int ReconnectInterval
|
||
{
|
||
get { return _reconnectInterval; }
|
||
set
|
||
{
|
||
if (value > 0)
|
||
{
|
||
_reconnectInterval = value;
|
||
// 如果已经在自动重连中,更新定时器
|
||
if (_isAutoReconnectEnabled && _reconnectTimer != null)
|
||
{
|
||
_reconnectTimer.Change(0, _reconnectInterval);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 该属性已在文件上方定义,删除重复实现
|
||
|
||
/// <summary>
|
||
/// 设备IP地址
|
||
/// </summary>
|
||
public string DeviceIp
|
||
{
|
||
get { return _deviceIp; }
|
||
set
|
||
{
|
||
if (_deviceIp != value)
|
||
{
|
||
_deviceIp = value;
|
||
// 如果已连接,重新连接
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
Disconnect();
|
||
ConnectDevice();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设备端口
|
||
/// </summary>
|
||
public int DevicePort
|
||
{
|
||
get { return _devicePort; }
|
||
set
|
||
{
|
||
if (_devicePort != value && value > 0 && value <= 65535)
|
||
{
|
||
_devicePort = value;
|
||
// 如果已连接,重新连接
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
Disconnect();
|
||
ConnectDevice();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 心跳间隔(毫秒)
|
||
/// </summary>
|
||
public int HeartbeatInterval
|
||
{
|
||
get { return _heartbeatInterval; }
|
||
set
|
||
{
|
||
if (value > 0)
|
||
{
|
||
_heartbeatInterval = value;
|
||
// 如果已经启用心跳检测,更新定时器
|
||
if (_heartbeatTimer != null)
|
||
{
|
||
_heartbeatTimer.Change(0, _heartbeatInterval);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion 公共属性
|
||
|
||
#region 图像接收方法
|
||
|
||
/// <summary>
|
||
/// 开始接收图像数据
|
||
/// </summary>
|
||
public void StartImageReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartImageReceiving() - 开始执行");
|
||
|
||
// 在设计模式下,跳过实际的图像接收
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的图像接收");
|
||
return;
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
// 检查对象是否已被释放
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartImageReceiving() - 对象已释放,忽略操作");
|
||
return;
|
||
}
|
||
|
||
// 检查连接状态
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartImageReceiving() - 连接未建立,无法开始图像接收");
|
||
return;
|
||
}
|
||
}
|
||
|
||
Log("开始使用HTTP方式接收图像数据");
|
||
try
|
||
{
|
||
// 确保之前的连接已关闭
|
||
StopImageReceiving();
|
||
|
||
// 重置停止事件
|
||
_stopImageEvent = new ManualResetEvent(false);
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingImages = true;
|
||
}
|
||
|
||
// 创建并启动图像接收线程
|
||
_imageReceiveThread = new Thread(ReceiveImageDataWithHttpWebRequest)
|
||
{
|
||
IsBackground = true,
|
||
Name = "ImageReceiveThread"
|
||
};
|
||
_imageReceiveThread.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录异常
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartImageReceiving() - 异常: {ex.Message}");
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingImages = false;
|
||
}
|
||
|
||
// 如果连接状态异常,触发异常事件
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "启动图像接收失败"));
|
||
}
|
||
finally
|
||
{
|
||
// 确保方法执行完成时记录日志
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartImageReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止接收图像数据
|
||
/// </summary>
|
||
public void StopImageReceiving()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopImageReceiving() - 开始执行");
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopImageReceiving() - 对象已释放,跳过操作");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 线程安全地更新状态
|
||
lock (_lockObject)
|
||
{
|
||
_isReceivingImages = false;
|
||
}
|
||
|
||
// 同步停止温度数据接收
|
||
StopTemperatureDataReceiving();
|
||
|
||
// 通知线程停止
|
||
ManualResetEvent stopEvent = null;
|
||
lock (_lockObject)
|
||
{
|
||
stopEvent = _stopImageEvent;
|
||
}
|
||
|
||
if (stopEvent != null)
|
||
{
|
||
try
|
||
{
|
||
stopEvent.Set();
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
// 忽略已释放的事件
|
||
}
|
||
}
|
||
|
||
// 等待线程结束
|
||
Thread imageThread = null;
|
||
lock (_lockObject)
|
||
{
|
||
imageThread = _imageReceiveThread;
|
||
}
|
||
|
||
if (imageThread != null && imageThread.IsAlive)
|
||
{
|
||
try
|
||
{
|
||
imageThread.Join(3000); // 最多等待3秒
|
||
lock (_lockObject)
|
||
{
|
||
if (_imageReceiveThread == imageThread) // 确保没有被其他线程修改
|
||
{
|
||
_imageReceiveThread = null;
|
||
}
|
||
}
|
||
}
|
||
catch (ThreadStateException)
|
||
{
|
||
// 忽略线程状态异常
|
||
}
|
||
}
|
||
|
||
// 停止重连线程
|
||
Thread reconnectThread = null;
|
||
lock (_lockObject)
|
||
{
|
||
reconnectThread = _imageReconnectThread;
|
||
}
|
||
|
||
if (reconnectThread != null && reconnectThread.IsAlive)
|
||
{
|
||
try
|
||
{
|
||
reconnectThread.Join(1000);
|
||
lock (_lockObject)
|
||
{
|
||
if (_imageReconnectThread == reconnectThread) // 确保没有被其他线程修改
|
||
{
|
||
_imageReconnectThread = null;
|
||
}
|
||
}
|
||
}
|
||
catch (ThreadStateException)
|
||
{
|
||
// 忽略线程状态异常
|
||
}
|
||
}
|
||
|
||
// 释放资源
|
||
Stream stream = null;
|
||
lock (_lockObject)
|
||
{
|
||
stream = _imageStream;
|
||
_imageStream = null;
|
||
}
|
||
|
||
if (stream != null)
|
||
{
|
||
try
|
||
{
|
||
stream.Close();
|
||
stream.Dispose();
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// 忽略释放资源时的异常
|
||
}
|
||
}
|
||
|
||
TcpClient tcpClient = null;
|
||
lock (_lockObject)
|
||
{
|
||
tcpClient = _imageTcpClient;
|
||
_imageTcpClient = null;
|
||
}
|
||
|
||
if (tcpClient != null)
|
||
{
|
||
try
|
||
{
|
||
tcpClient.Close();
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// 忽略关闭连接时的异常
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopImageReceiving() - 异常: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopImageReceiving() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用HttpWebRequest接收图像数据
|
||
/// </summary>
|
||
private void ReceiveImageDataWithHttpWebRequest()
|
||
{
|
||
// 在设计模式下,跳过实际的图像接收
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的图像接收");
|
||
// 设置为不接收状态,避免持续运行
|
||
_isReceivingImages = false;
|
||
return;
|
||
}
|
||
|
||
Console.WriteLine($"图像接收线程启动: 使用HTTP请求获取图像数据");
|
||
try
|
||
{
|
||
while (_isReceivingImages)
|
||
{
|
||
// 确保连接状态正常
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
Console.WriteLine("连接状态异常,暂停图像接收");
|
||
Thread.Sleep(500);
|
||
continue;
|
||
}
|
||
// 根据当前图像模式构建URL,确保模式更改能实时生效
|
||
string url = string.Format("http://{0}:8080{1}", DeviceIp, CurrentImageMode == ImageMode.Infrared ? "/video/infrared.jpg" : "/video/optical.jpg");
|
||
// 添加时间戳避免缓存
|
||
string timestampUrl = url + "?t=" + DateTime.Now.Ticks;
|
||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(timestampUrl);
|
||
request.Method = "GET";
|
||
request.Timeout = 3000; // 3秒超时
|
||
request.KeepAlive = true;
|
||
request.UserAgent = "Mozilla/5.0"; // 模拟浏览器
|
||
request.ServicePoint.Expect100Continue = false; // 优化HTTP请求性能
|
||
|
||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||
{
|
||
if (response.StatusCode == HttpStatusCode.OK)
|
||
{
|
||
using (Stream stream = response.GetResponseStream())
|
||
{
|
||
// 读取图像数据
|
||
byte[] buffer = new byte[8192]; // 增加缓冲区大小到8KB
|
||
using (MemoryStream ms = new MemoryStream())
|
||
{
|
||
int bytesRead;
|
||
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
|
||
{
|
||
ms.Write(buffer, 0, bytesRead);
|
||
}
|
||
byte[] imageData = ms.ToArray();
|
||
|
||
// 处理接收到的数据
|
||
ProcessReceivedImageData(imageData);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查是否需要停止
|
||
if (_stopImageEvent.WaitOne(100)) // 100ms检查一次
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录异常并尝试重连
|
||
if (_isReceivingImages)
|
||
{
|
||
Console.WriteLine($"ReceiveImageDataWithHttpWebRequest error: {ex.Message}");
|
||
// 避免频繁重连
|
||
Thread.Sleep(100);
|
||
|
||
// 只有在连接状态为Connected时才尝试图像重连
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
StartImageReconnect();
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("连接已断开,不启动图像重连");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理接收到的图像数据
|
||
/// </summary>
|
||
/// <param name="data">原始数据</param>
|
||
private void ProcessReceivedImageData(byte[] data)
|
||
{
|
||
if (data == null || data.Length == 0)
|
||
return;
|
||
|
||
// 记录数据接收时间
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [图像接收] 开始处理接收到的数据,大小: {data.Length}字节");
|
||
|
||
// 更新最后接收数据时间戳
|
||
_lastDataReceivedTime = DateTime.Now;
|
||
|
||
// 检查是否为HTTP响应
|
||
if (IsHttpResponse(data))
|
||
{
|
||
// 查找HTTP响应中的图像数据
|
||
int imageStartPos = FindImageStartPosition(data);
|
||
if (imageStartPos > 0)
|
||
{
|
||
// 提取图像数据部分
|
||
int imageLength = data.Length - imageStartPos;
|
||
byte[] imageData = new byte[imageLength];
|
||
Array.Copy(data, imageStartPos, imageData, 0, imageLength);
|
||
|
||
// 验证并处理提取的图像数据
|
||
ValidateAndProcessImage(imageData);
|
||
}
|
||
else
|
||
{
|
||
// 尝试直接验证整个HTTP响应
|
||
ValidateAndProcessImage(data);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 直接验证和处理普通图像数据
|
||
ValidateAndProcessImage(data);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证并处理图像数据
|
||
/// </summary>
|
||
/// <param name="imageData">图像数据</param>
|
||
private void ValidateAndProcessImage(byte[] imageData)
|
||
{
|
||
// 首先检查文件头
|
||
if (IsValidImageData(imageData))
|
||
{
|
||
try
|
||
{
|
||
// 尝试从流中创建图像对象进行验证
|
||
using (MemoryStream ms = new MemoryStream(imageData))
|
||
{
|
||
// 检查是否包含完整的JPEG文件
|
||
if (imageData.Length > 4 && imageData[0] == 0xFF && imageData[1] == 0xD8)
|
||
{
|
||
int endPos = FindImageEndPosition(imageData, 0);
|
||
if (endPos > 0 && endPos + 1 < imageData.Length)
|
||
{
|
||
// 确保图像有完整的EOF标记
|
||
if (imageData[endPos + 1] == 0xD9)
|
||
{
|
||
// 创建并触发事件
|
||
Image jpegImage = Image.FromStream(ms);
|
||
// 记录图像接收时间
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [图像接收] 成功接收到JPEG图像,大小: {imageData.Length}字节");
|
||
OnImageReceived(new ImageReceivedEventArgs(imageData, _currentImageMode));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 对于其他格式或无法确认完整度的情况,直接尝试创建
|
||
Image generalImage = Image.FromStream(ms);
|
||
// 记录图像接收时间
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [图像接收] 成功接收到图像,大小: {imageData.Length}字节");
|
||
OnImageReceived(new ImageReceivedEventArgs(imageData, _currentImageMode));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录异常日志
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [图像接收] 处理图像数据异常: {ex.Message}");
|
||
Console.WriteLine("处理图像数据异常: " + ex.Message);
|
||
// 尝试查找有效的图像起始位置
|
||
int validStartPos = FindImageStartPosition(imageData);
|
||
if (validStartPos > 0)
|
||
{
|
||
try
|
||
{
|
||
// 尝试从有效位置提取图像
|
||
int newLength = imageData.Length - validStartPos;
|
||
byte[] validImageData = new byte[newLength];
|
||
Array.Copy(imageData, validStartPos, validImageData, 0, newLength);
|
||
|
||
using (MemoryStream ms = new MemoryStream(validImageData))
|
||
{
|
||
Image validImage = Image.FromStream(ms);
|
||
// 记录图像接收时间
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [图像接收] 成功接收到有效图像(二次尝试),大小: {validImageData.Length}字节");
|
||
OnImageReceived(new ImageReceivedEventArgs(validImageData, _currentImageMode));
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 二次尝试也失败,放弃处理
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动图像重连
|
||
/// </summary>
|
||
private void StartImageReconnect()
|
||
{
|
||
// 在设计模式下,跳过实际的图像重连
|
||
if (IsDesignMode)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 避免重复创建重连线程
|
||
if (_imageReconnectThread != null && _imageReconnectThread.IsAlive)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_imageReconnectThread = new Thread(() =>
|
||
{
|
||
try
|
||
{
|
||
int reconnectCount = 0;
|
||
while (_isReceivingImages && reconnectCount < 3) // 最多重连3次
|
||
{
|
||
reconnectCount++;
|
||
Thread.Sleep(2000); // 2秒后重连
|
||
if (_isReceivingImages)
|
||
{
|
||
ReceiveImageDataWithHttpWebRequest();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("Image reconnect error: " + ex.Message);
|
||
}
|
||
})
|
||
{
|
||
IsBackground = true,
|
||
Name = "ImageReconnectThread"
|
||
};
|
||
_imageReconnectThread.Start();
|
||
}
|
||
|
||
#endregion 图像接收方法
|
||
|
||
#region 图像处理辅助方法
|
||
|
||
/// <summary>
|
||
/// 查找图像数据的结束位置(特别是JPEG的EOF标记)
|
||
/// </summary>
|
||
/// <param name="data">数据缓冲区</param>
|
||
/// <param name="startIndex">开始查找的位置</param>
|
||
/// <returns>图像结束位置索引,-1表示未找到</returns>
|
||
private int FindImageEndPosition(byte[] data, int startIndex)
|
||
{
|
||
// 对于JPEG,查找EOF标记 (FF D9)
|
||
if (data.Length >= startIndex + 2 && data[startIndex] == 0xFF && data[startIndex + 1] == 0xD8)
|
||
{
|
||
for (int i = startIndex + 2; i < data.Length - 1; i++)
|
||
{
|
||
if (data[i] == 0xFF && data[i + 1] == 0xD9)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查数据是否为HTTP响应
|
||
/// </summary>
|
||
/// <param name="data">待检查的数据</param>
|
||
/// <returns>是否为HTTP响应</returns>
|
||
private bool IsHttpResponse(byte[] data)
|
||
{
|
||
if (data == null || data.Length < 4)
|
||
return false;
|
||
|
||
// 检查HTTP响应状态行的特征
|
||
// HTTP/1.x 状态码 描述
|
||
string responseStart = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(10, data.Length));
|
||
return responseStart.StartsWith("HTTP/");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找图像数据的开始位置,跳过HTTP头部
|
||
/// </summary>
|
||
/// <param name="data">数据缓冲区</param>
|
||
/// <returns>图像数据开始位置索引,-1表示未找到</returns>
|
||
private int FindImageStartPosition(byte[] data)
|
||
{
|
||
if (data == null || data.Length < 8)
|
||
return -1;
|
||
|
||
// 首先检查是否以图像头开始
|
||
if (IsValidImageData(data))
|
||
return 0;
|
||
|
||
// 否则尝试查找HTTP响应中的图像数据
|
||
// 查找空行后的位置(HTTP头部结束)
|
||
for (int i = 0; i < data.Length - 3; i++)
|
||
{
|
||
if (data[i] == 0x0D && data[i + 1] == 0x0A && data[i + 2] == 0x0D && data[i + 3] == 0x0A)
|
||
{
|
||
// 找到HTTP头部结束位置,检查后续数据是否为图像
|
||
int startPos = i + 4;
|
||
if (startPos < data.Length - 7)
|
||
{
|
||
// 检查是否为JPEG
|
||
if (data[startPos] == 0xFF && data[startPos + 1] == 0xD8)
|
||
return startPos;
|
||
|
||
// 检查是否为PNG
|
||
if (data[startPos] == 0x89 && data[startPos + 1] == 0x50 && data[startPos + 2] == 0x4E && data[startPos + 3] == 0x47)
|
||
return startPos;
|
||
}
|
||
}
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从HTTP响应中提取Content-Type
|
||
/// </summary>
|
||
/// <param name="data">HTTP响应数据</param>
|
||
/// <returns>Content-Type字符串</returns>
|
||
private string ExtractContentType(byte[] data)
|
||
{
|
||
string headerText = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(1024, data.Length));
|
||
int contentTypeIndex = headerText.IndexOf("Content-Type:", StringComparison.OrdinalIgnoreCase);
|
||
if (contentTypeIndex >= 0)
|
||
{
|
||
contentTypeIndex += "Content-Type:".Length;
|
||
int endIndex = headerText.IndexOf("\r\n", contentTypeIndex);
|
||
if (endIndex >= 0)
|
||
{
|
||
return headerText.Substring(contentTypeIndex, endIndex - contentTypeIndex).Trim();
|
||
}
|
||
}
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从Content-Type中提取multipart boundary
|
||
/// </summary>
|
||
/// <param name="contentType">Content-Type字符串</param>
|
||
/// <returns>boundary字符串</returns>
|
||
private string ExtractBoundary(string contentType)
|
||
{
|
||
int boundaryIndex = contentType.IndexOf("boundary=", StringComparison.OrdinalIgnoreCase);
|
||
if (boundaryIndex >= 0)
|
||
{
|
||
boundaryIndex += "boundary=".Length;
|
||
// 处理可能包含引号的情况
|
||
char quoteChar = contentType[boundaryIndex];
|
||
int startIndex = (quoteChar == '"' || quoteChar == '\'') ? boundaryIndex + 1 : boundaryIndex;
|
||
int endIndex = -1;
|
||
|
||
if (startIndex > boundaryIndex)
|
||
{
|
||
// 寻找匹配的引号
|
||
endIndex = contentType.IndexOf(quoteChar, startIndex);
|
||
}
|
||
else
|
||
{
|
||
// 寻找分号或行尾
|
||
endIndex = contentType.IndexOf(';', startIndex);
|
||
if (endIndex < 0)
|
||
{
|
||
endIndex = contentType.Length;
|
||
}
|
||
}
|
||
|
||
if (endIndex >= 0)
|
||
{
|
||
return contentType.Substring(startIndex, endIndex - startIndex).Trim();
|
||
}
|
||
}
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理multipart格式的图像数据
|
||
/// </summary>
|
||
/// <param name="buffer">数据缓冲区</param>
|
||
/// <param name="boundary">multipart boundary</param>
|
||
/// <param name="imageDataBuffer">原始图像数据缓冲区</param>
|
||
/// <returns>处理到的位置索引,-1表示未找到完整的图像块</returns>
|
||
private int ProcessMultipartImageData(byte[] buffer, string boundary, MemoryStream imageDataBuffer)
|
||
{
|
||
byte[] boundaryBytes = System.Text.Encoding.ASCII.GetBytes(boundary);
|
||
int startPos = 0;
|
||
|
||
// 查找第一个boundary
|
||
while (startPos < buffer.Length - boundaryBytes.Length)
|
||
{
|
||
bool found = true;
|
||
for (int i = 0; i < boundaryBytes.Length; i++)
|
||
{
|
||
if (buffer[startPos + i] != boundaryBytes[i])
|
||
{
|
||
found = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
{
|
||
// 找到第一个boundary,继续查找下一个
|
||
int nextBoundaryPos = startPos + boundaryBytes.Length;
|
||
while (nextBoundaryPos < buffer.Length - boundaryBytes.Length)
|
||
{
|
||
bool nextFound = true;
|
||
for (int i = 0; i < boundaryBytes.Length; i++)
|
||
{
|
||
if (buffer[nextBoundaryPos + i] != boundaryBytes[i])
|
||
{
|
||
nextFound = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (nextFound)
|
||
{
|
||
// 找到两个boundary之间的数据,提取HTTP头和图像数据
|
||
int headerEndPos = -1;
|
||
for (int i = startPos + boundaryBytes.Length; i < nextBoundaryPos - 3; i++)
|
||
{
|
||
if (buffer[i] == 0x0D && buffer[i+1] == 0x0A && buffer[i+2] == 0x0D && buffer[i+3] == 0x0A)
|
||
{
|
||
headerEndPos = i + 4;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (headerEndPos > 0)
|
||
{
|
||
// 提取图像数据
|
||
int imageLength = nextBoundaryPos - headerEndPos;
|
||
byte[] imageBytes = new byte[imageLength];
|
||
Array.Copy(buffer, headerEndPos, imageBytes, 0, imageLength);
|
||
|
||
// 验证并处理图像数据
|
||
if (IsValidImageData(imageBytes))
|
||
{
|
||
try
|
||
{
|
||
using (MemoryStream ms = new MemoryStream(imageBytes))
|
||
{
|
||
ms.Write(new byte[0], 0, 0);
|
||
Image boundaryImage = Image.FromStream(ms);
|
||
using (MemoryStream imageMs = new MemoryStream())
|
||
{
|
||
boundaryImage.Save(imageMs, boundaryImage.RawFormat);
|
||
OnImageReceived(new ImageReceivedEventArgs(imageMs.ToArray(), _currentImageMode));
|
||
}
|
||
}
|
||
return nextBoundaryPos;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"处理multipart图像失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
return nextBoundaryPos;
|
||
}
|
||
nextBoundaryPos++;
|
||
}
|
||
break;
|
||
}
|
||
startPos++;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
#endregion 图像处理辅助方法
|
||
|
||
#region 视频流处理方法
|
||
|
||
/// <summary>
|
||
/// 设置视频模式
|
||
/// </summary>
|
||
/// <param name="mode">图像模式</param>
|
||
public void SetImageMode(ImageMode mode)
|
||
{
|
||
CurrentImageMode = mode;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置视频模式
|
||
/// </summary>
|
||
/// <param name="videoMode">视频模式枚举</param>
|
||
public void SetVideoMode(VideoMode videoMode)
|
||
{
|
||
// 调用SDK设置视频模式
|
||
SendVideoModeCommand(videoMode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前视频模式
|
||
/// </summary>
|
||
/// <returns>当前视频模式枚举</returns>
|
||
public VideoMode GetCurrentVideoMode()
|
||
{
|
||
lock (_sdkOperationLock)
|
||
{
|
||
// 验证连接状态
|
||
if (_connectionStatus != ConnectionStatus.Connected || _a8Sdk == null)
|
||
{
|
||
throw new InvalidOperationException("设备未连接");
|
||
}
|
||
|
||
// 从设备同步视频模式到内部状态
|
||
SyncVideoModeFromDevice();
|
||
|
||
// 返回内部状态中的视频模式值
|
||
return _currentVideoMode;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送视频模式变更命令到设备
|
||
/// </summary>
|
||
/// <param name="videoMode">视频模式枚举值</param>
|
||
private void SendVideoModeCommand(VideoMode videoMode)
|
||
{
|
||
lock (_sdkOperationLock)
|
||
{
|
||
const int maxRetries = 3;
|
||
const int retryDelayMs = 100;
|
||
|
||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||
{
|
||
try
|
||
{
|
||
// 检查对象状态和连接状态
|
||
if (_a8Sdk == null || _connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
Log($"视频模式切换失败: {(attempt > 0 ? "重试中" : "")}SDK实例为空或设备未连接");
|
||
Thread.Sleep(retryDelayMs);
|
||
continue;
|
||
}
|
||
|
||
// 调用SDK设置视频模式(转换为int)
|
||
_a8Sdk.SetVideoMode((int)videoMode);
|
||
|
||
// 短暂延迟,确保设置生效
|
||
Thread.Sleep(50);
|
||
|
||
// 简单验证:尝试读取视频模式确认设置成功
|
||
int currentModeInt = _a8Sdk.GetVideoMode();
|
||
if (Enum.IsDefined(typeof(VideoMode), currentModeInt))
|
||
{
|
||
VideoMode currentMode = (VideoMode)currentModeInt;
|
||
if (currentMode == videoMode)
|
||
{
|
||
// 更新内部状态
|
||
_currentVideoMode = videoMode;
|
||
Log($"视频模式切换成功,当前模式: {videoMode}");
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
Log($"视频模式验证失败,期望: {videoMode},实际: {currentMode}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"视频模式验证失败,获取到无效的视频模式值: {currentModeInt}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"视频模式切换异常 (尝试 {attempt + 1}/{maxRetries}): {ex.Message}");
|
||
}
|
||
|
||
// 重试前短暂延迟
|
||
if (attempt < maxRetries - 1)
|
||
{
|
||
Thread.Sleep(retryDelayMs);
|
||
}
|
||
}
|
||
|
||
throw new Exception($"视频模式切换失败,已重试 {maxRetries} 次");
|
||
}
|
||
}
|
||
|
||
// 用于保护SDK操作的线程锁
|
||
private readonly object _sdkOperationLock = new object();
|
||
|
||
// 连续心跳失败计数,用于实现容错机制
|
||
private int _consecutiveHeartbeatFailures = 0;
|
||
// 连续心跳失败阈值,超过此值才认为连接真正断开
|
||
private const int HEARTBEAT_FAILURE_THRESHOLD = 3;
|
||
|
||
/// <summary>
|
||
/// 获取或设置当前色彩模式
|
||
/// </summary>
|
||
public PaletteType CurrentPaletteType
|
||
{
|
||
get { return _currentPaletteType; }
|
||
set { _currentPaletteType = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前温度模式
|
||
/// </summary>
|
||
public TemperatureMode CurrentTemperatureMode { get; set; } = TemperatureMode.Celsius;
|
||
|
||
/// <summary>
|
||
/// 是否正在接收温度数据
|
||
/// </summary>
|
||
public bool IsReceivingTemperatureData
|
||
{
|
||
get
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
return _isReceivingTemperatureData;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取或设置当前视频模式
|
||
/// </summary>
|
||
public VideoMode CurrentVideoMode
|
||
{
|
||
get { return _currentVideoMode; }
|
||
set { _currentVideoMode = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从设备同步视频模式到内部状态
|
||
/// </summary>
|
||
public void SyncVideoModeFromDevice()
|
||
{
|
||
try
|
||
{
|
||
// 确保设备已连接且SDK实例有效
|
||
if (_connectionStatus == ConnectionStatus.Connected && _a8Sdk != null)
|
||
{
|
||
// 获取当前设备的视频模式值
|
||
int currentValue = _a8Sdk.GetVideoMode();
|
||
Log($"从设备读取的视频模式值: {currentValue}");
|
||
|
||
// 验证视频模式值是否在有效范围内并转换为枚举
|
||
if (Enum.IsDefined(typeof(VideoMode), currentValue))
|
||
{
|
||
VideoMode actualMode = (VideoMode)currentValue;
|
||
_currentVideoMode = actualMode;
|
||
Log($"已更新内部状态为设备实际值: {actualMode} (值: {currentValue})");
|
||
}
|
||
else
|
||
{
|
||
Log($"警告:设备返回的视频模式值 {currentValue} 不在有效范围内(0-6),使用默认值");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"同步视频模式失败:设备未连接或SDK未初始化");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"同步视频模式时发生异常: {ex.Message}");
|
||
throw; // 重新抛出异常,让调用方知道发生了错误
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从设备同步色彩模式到内部状态
|
||
/// </summary>
|
||
public void SyncPaletteTypeFromDevice()
|
||
{
|
||
try
|
||
{
|
||
// 使用SDK操作锁保护对_a8Sdk的访问
|
||
lock (_sdkOperationLock)
|
||
{
|
||
// 确保设备已连接且SDK实例有效
|
||
if (_connectionStatus == ConnectionStatus.Connected && _a8Sdk != null)
|
||
{
|
||
try
|
||
{
|
||
// 获取当前设备的色彩模式值
|
||
int currentValue = _a8Sdk.GetColorPlate();
|
||
Log($"从设备读取的色彩模式值: {currentValue}");
|
||
|
||
// 尝试将读取到的值转换为PaletteType枚举并更新内部状态
|
||
if (Enum.IsDefined(typeof(PaletteType), currentValue))
|
||
{
|
||
PaletteType actualPalette = (PaletteType)currentValue;
|
||
_currentPaletteType = actualPalette;
|
||
Log($"已更新内部状态为设备实际值: {actualPalette}");
|
||
}
|
||
else
|
||
{
|
||
Log($"警告:设备返回的色彩模式值 {currentValue} 不在枚举定义范围内,使用默认值");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"从设备读取色彩模式时出错: {ex.Message}");
|
||
// 尝试重新创建SDK实例
|
||
Log("尝试重新创建SDK实例...");
|
||
try
|
||
{
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Log("SDK实例已重新创建");
|
||
}
|
||
catch (Exception recreateEx)
|
||
{
|
||
Log($"重新创建SDK实例失败: {recreateEx.Message}");
|
||
}
|
||
throw;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"同步色彩模式失败:设备未连接或SDK未初始化");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"同步色彩模式时发生异常: {ex.Message}");
|
||
throw; // 重新抛出异常,让调用方知道发生了错误
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置色彩模式
|
||
/// </summary>
|
||
/// <param name="paletteType">色彩模式</param>
|
||
/// <returns>设置是否成功</returns>
|
||
public bool SetPaletteType(PaletteType paletteType)
|
||
{
|
||
// 添加线程锁保护,防止多线程同时操作SDK
|
||
lock (_sdkOperationLock)
|
||
{
|
||
// 暂停心跳检测,避免设置过程中发生冲突
|
||
bool wasHeartbeatRunning = _heartbeatTimer != null;
|
||
if (wasHeartbeatRunning)
|
||
{
|
||
_heartbeatTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||
Log("设置色彩模式前暂停心跳检测");
|
||
}
|
||
|
||
try
|
||
{
|
||
// 最大重试次数
|
||
const int maxRetries = 3;
|
||
// 重试间隔(毫秒)
|
||
const int retryDelayMs = 100;
|
||
|
||
// 确保SDK实例存在,如果不存在则尝试创建
|
||
if (_a8Sdk == null)
|
||
{
|
||
Log("SDK实例为空,尝试创建新实例...");
|
||
try
|
||
{
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Log("SDK实例创建成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"创建SDK实例失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 先获取原始值,只读取一次,避免嵌套调用
|
||
int originalValue = -1;
|
||
try
|
||
{
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
originalValue = _a8Sdk.Color_plate;
|
||
Log($"成功读取当前色彩模式值: {originalValue}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"获取当前色彩模式值时出错: {ex.Message}");
|
||
// 尝试重新创建SDK实例
|
||
Log("尝试重新创建SDK实例...");
|
||
try
|
||
{
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Log("SDK实例已重新创建");
|
||
}
|
||
catch (Exception recreateEx)
|
||
{
|
||
Log($"重新创建SDK实例失败: {recreateEx.Message}");
|
||
}
|
||
// 即使获取失败,仍尝试设置新值
|
||
}
|
||
|
||
// 将PaletteType枚举转换为int类型
|
||
int paletteValue = (int)paletteType;
|
||
|
||
// 检查新的色彩模式是否与当前值相同,如果相同则不需要设置
|
||
if (originalValue == paletteValue)
|
||
{
|
||
Log($"当前色彩模式已为目标值,无需设置");
|
||
return true;
|
||
}
|
||
|
||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||
{
|
||
try
|
||
{
|
||
// 再次确保SDK实例存在
|
||
if (_a8Sdk == null)
|
||
{
|
||
Log("SDK实例为空,尝试重新创建...");
|
||
try
|
||
{
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Log("SDK实例重新创建成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"重新创建SDK实例失败: {ex.Message}");
|
||
continue; // 继续下一次尝试
|
||
}
|
||
}
|
||
|
||
// 检查连接状态
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
Log($"色彩模式设置失败: {(attempt > 0 ? "重试中" : "")}SDK实例为空或设备未连接");
|
||
Thread.Sleep(retryDelayMs);
|
||
continue;
|
||
}
|
||
|
||
// 已在循环外部计算paletteValue,避免重复计算
|
||
|
||
// 直接设置色彩模式,不再在每次尝试中读取原始值
|
||
bool setSuccess = false;
|
||
try
|
||
{
|
||
// 直接调用SetColorPlate方法设置色彩模式
|
||
setSuccess = _a8Sdk.SetColorPlate(paletteValue);
|
||
Log($"SetColorPlate返回结果: {setSuccess}");
|
||
|
||
// 短暂延迟,确保设置生效
|
||
Thread.Sleep(50);
|
||
|
||
// 再次读取当前值进行验证,不依赖SetColorPlate的返回值
|
||
int currentValue = _a8Sdk.GetColorPlate();
|
||
Log($"验证读取到的当前色彩模式值: {currentValue},目标值: {paletteValue}");
|
||
setSuccess = (currentValue == paletteValue);
|
||
|
||
// 如果设置成功,更新内部状态
|
||
if (setSuccess)
|
||
{
|
||
_currentPaletteType = paletteType;
|
||
Log($"内部状态_currentPaletteType已更新为: {paletteType}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"色彩模式设置异常: {ex.Message}");
|
||
}
|
||
|
||
// 基于实际结果记录日志和返回
|
||
if (setSuccess)
|
||
{
|
||
Log($"色彩模式设置成功: {paletteType} (值: {paletteValue})");
|
||
_currentPaletteType = paletteType;
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
Log($"色彩模式设置失败: 实际设置未生效 (尝试 {attempt + 1}/{maxRetries})");
|
||
if (attempt < maxRetries - 1)
|
||
{
|
||
Thread.Sleep(retryDelayMs);
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
// 最后一次尝试失败,尝试恢复原始值
|
||
if (originalValue != -1 && _a8Sdk != null && _connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
try
|
||
{
|
||
Log($"尝试恢复色彩模式为原始值: {originalValue}");
|
||
_a8Sdk.Color_plate = originalValue;
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移除图像接收重启逻辑,因为色彩模式不影响图像接收
|
||
// 仅保留短暂延迟确保设置生效
|
||
Thread.Sleep(200); // 给设备处理时间
|
||
// return true; // 已在验证成功后返回
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"设置色彩模式异常 (尝试 {attempt + 1}/{maxRetries}): {ex.Message}");
|
||
|
||
// 简化SDK状态检查,避免触发额外的UDP命令
|
||
Log($"SDK状态监控: 色彩模式设置操作失败");
|
||
|
||
// 重试前等待
|
||
Thread.Sleep(retryDelayMs);
|
||
}
|
||
}
|
||
|
||
// 所有尝试都失败
|
||
Log($"色彩模式设置最终失败: 已尝试{maxRetries}次");
|
||
|
||
// 最后一次尝试失败后,仅在有原始值的情况下尝试恢复
|
||
if (originalValue >= 0 && _a8Sdk != null && _connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
try
|
||
{
|
||
_a8Sdk.Color_plate = originalValue;
|
||
Log($"已恢复原始色彩模式值: {originalValue}");
|
||
}
|
||
catch (Exception restoreEx)
|
||
{
|
||
Log($"恢复原始色彩模式失败: {restoreEx.Message}");
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
// 无论设置成功与否,都恢复心跳检测
|
||
if (wasHeartbeatRunning && _heartbeatTimer != null)
|
||
{
|
||
_heartbeatTimer.Change(_heartbeatInterval, _heartbeatInterval);
|
||
Log("设置色彩模式后恢复心跳检测");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始接收图像(已弃用,使用HTTP方式替代)
|
||
/// </summary>
|
||
public void StartReceiveImage()
|
||
{
|
||
Log("已弃用的StartReceiveImage方法,自动切换到HTTP方式");
|
||
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
throw new InvalidOperationException("设备未连接,无法开始接收图像");
|
||
}
|
||
|
||
// 直接调用HTTP方式的图像接收
|
||
StartImageReceiving();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止接收图像
|
||
/// </summary>
|
||
public void StopReceiveImage()
|
||
{
|
||
StopAllImageReceivers();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图像接收线程 (已弃用,使用HTTP方式替代)
|
||
/// </summary>
|
||
private void ReceiveImageThread()
|
||
{
|
||
// 在设计模式下,跳过实际的图像接收
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的图像接收");
|
||
return;
|
||
}
|
||
|
||
Console.WriteLine("警告: 已弃用的图像接收方式,使用HTTP方式替代");
|
||
try
|
||
{
|
||
// 如果调用了这个方法,自动切换到HTTP方式
|
||
if (!_stopRequested.WaitOne(0))
|
||
{
|
||
Console.WriteLine("自动切换到HTTP方式获取图像数据");
|
||
if (_imageReceiveThread == null || !_imageReceiveThread.IsAlive)
|
||
{
|
||
StartImageReceiving(); // 启动HTTP方式
|
||
}
|
||
return;
|
||
}
|
||
// 持续获取图像数据
|
||
while (!_stopRequested.WaitOne(0))
|
||
{
|
||
if (_a8Sdk != null)
|
||
{
|
||
try
|
||
{
|
||
// 使用A8SDK获取图像数据
|
||
byte[] imageData = _a8Sdk.GetImageData();
|
||
|
||
if (imageData != null && imageData.Length > 0 && IsValidImageData(imageData))
|
||
{
|
||
// 创建内存流
|
||
using (MemoryStream ms = new MemoryStream(imageData))
|
||
{
|
||
// 创建图像
|
||
using (Image image = Image.FromStream(ms))
|
||
{
|
||
// 将Image转换为byte[]并提供mode参数
|
||
using (MemoryStream imageMs = new MemoryStream())
|
||
{
|
||
Image clonedImage = image.Clone() as Image;
|
||
clonedImage.Save(imageMs, clonedImage.RawFormat);
|
||
OnImageReceived(new ImageReceivedEventArgs(imageMs.ToArray(), _currentImageMode));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"SDK获取图像失败: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "SDK获取图像数据异常"));
|
||
|
||
// 检查连接状态
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK获取图像失败,设备可能已断开", ex);
|
||
|
||
// 如果启用了自动重连,开始重连
|
||
if (_isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
break; // 退出图像接收线程
|
||
}
|
||
}
|
||
|
||
// 短暂休眠,控制帧率
|
||
Thread.Sleep(100);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"图像接收线程异常: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "图像接收线程异常"));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证图像数据是否有效
|
||
/// </summary>
|
||
/// <param name="data">图像数据</param>
|
||
/// <returns>是否为有效图像数据</returns>
|
||
private bool IsValidImageData(byte[] data)
|
||
{
|
||
if (data == null || data.Length < 8)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 检查JPEG文件头(FF D8)
|
||
if (data[0] == 0xFF && data[1] == 0xD8)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 检查PNG文件头
|
||
byte[] pngHeader = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||
if (data.Length >= pngHeader.Length)
|
||
{
|
||
bool isPng = true;
|
||
for (int i = 0; i < pngHeader.Length; i++)
|
||
{
|
||
if (data[i] != pngHeader[i])
|
||
{
|
||
isPng = false;
|
||
break;
|
||
}
|
||
}
|
||
if (isPng)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 检查BMP文件头
|
||
if (data[0] == 0x42 && data[1] == 0x4D) // "BM"
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#endregion 视频流处理方法
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 搜索设备(同步版本)
|
||
/// </summary>
|
||
public List<int> SearchDevices()
|
||
{
|
||
try
|
||
{
|
||
if (!_isInitialized && !Initialize())
|
||
{
|
||
Console.WriteLine("设备管理器未初始化,无法搜索设备");
|
||
return new List<int>();
|
||
}
|
||
|
||
List<int> foundDevices = new List<int>();
|
||
_deviceIds.Clear();
|
||
_deviceList.Clear();
|
||
|
||
// 使用A8SDK的静态方法进行设备搜索
|
||
List<string> deviceIds = A8SDK.SearchDevices();
|
||
|
||
Console.WriteLine($"搜索到 {deviceIds.Count} 个设备");
|
||
|
||
// 将SDK返回的设备ID和IP转换为我们的格式
|
||
int deviceId = 1;
|
||
foreach (string id in deviceIds)
|
||
{
|
||
try
|
||
{
|
||
// 假设ID字符串中包含IP信息,格式可能是 "设备ID|IP地址"
|
||
string[] parts = id.Split('|');
|
||
string ipAddress = parts.Length > 1 ? parts[1] : "未知IP";
|
||
|
||
// 创建设备信息对象
|
||
DeviceInfo deviceInfo = new DeviceInfo
|
||
{
|
||
DeviceID = deviceId,
|
||
IPAddress = ipAddress,
|
||
Model = "A8", // 假设是A8型号,可以根据实际情况获取
|
||
SerialNumber = parts[0] // 使用原始ID作为序列号
|
||
};
|
||
|
||
_deviceIds.Add(id);
|
||
_deviceList.Add(deviceInfo);
|
||
foundDevices.Add(deviceId);
|
||
|
||
Console.WriteLine($"发现设备: ID={deviceId}, IP={ipAddress}, 序列号={parts[0]}");
|
||
|
||
deviceId++;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"解析设备信息失败: {ex.Message}, 原始ID: {id}");
|
||
// 继续处理下一个设备
|
||
}
|
||
}
|
||
|
||
// 如果通过ID连接模式,且目标设备ID已设置,尝试自动连接
|
||
if (_targetDeviceId > 0 && _deviceList.Count > 0)
|
||
{
|
||
var targetDevice = _deviceList.FirstOrDefault(d => d.DeviceID == _targetDeviceId);
|
||
if (targetDevice != null)
|
||
{
|
||
Console.WriteLine($"找到目标设备ID={_targetDeviceId},IP={targetDevice.IPAddress},准备连接");
|
||
_deviceIp = targetDevice.IPAddress;
|
||
ConnectDevice();
|
||
}
|
||
}
|
||
|
||
return foundDevices;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"搜索设备失败: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "搜索设备失败"));
|
||
return new List<int>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 搜索设备(异步版本)
|
||
/// </summary>
|
||
public Task<List<int>> SearchDevicesAsync()
|
||
{
|
||
return Task.Factory.StartNew(() =>
|
||
{
|
||
try
|
||
{
|
||
if (!_isInitialized && !Initialize())
|
||
{
|
||
Console.WriteLine("设备管理器未初始化,无法搜索设备");
|
||
return new List<int>();
|
||
}
|
||
|
||
List<int> foundDevices = new List<int>();
|
||
_deviceIds.Clear();
|
||
_deviceList.Clear();
|
||
|
||
// 使用A8SDK的静态方法进行设备搜索
|
||
List<string> deviceIds = A8SDK.SearchDevices();
|
||
|
||
Console.WriteLine($"搜索到 {deviceIds.Count} 个设备");
|
||
|
||
// 将SDK返回的设备ID和IP转换为我们的格式
|
||
int deviceId = 1;
|
||
foreach (string id in deviceIds)
|
||
{
|
||
try
|
||
{
|
||
// 假设ID字符串中包含IP信息,格式可能是 "设备ID|IP地址"
|
||
string[] parts = id.Split('|');
|
||
string ipAddress = parts.Length > 1 ? parts[1] : "未知IP";
|
||
|
||
// 创建设备信息对象
|
||
DeviceInfo deviceInfo = new DeviceInfo
|
||
{
|
||
DeviceID = deviceId,
|
||
IPAddress = ipAddress,
|
||
Model = "A8", // 假设是A8型号,可以根据实际情况获取
|
||
SerialNumber = parts[0] // 使用原始ID作为序列号
|
||
};
|
||
|
||
_deviceIds.Add(id);
|
||
_deviceList.Add(deviceInfo);
|
||
foundDevices.Add(deviceId);
|
||
|
||
Console.WriteLine($"发现设备: ID={deviceId}, IP={ipAddress}, 序列号={parts[0]}");
|
||
|
||
deviceId++;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"解析设备信息失败: {ex.Message}, 原始ID: {id}");
|
||
// 继续处理下一个设备
|
||
}
|
||
}
|
||
|
||
// 如果通过ID连接模式,且目标设备ID已设置,尝试自动连接
|
||
if (_targetDeviceId > 0 && _deviceList.Count > 0)
|
||
{
|
||
var targetDevice = _deviceList.FirstOrDefault(d => d.DeviceID == _targetDeviceId);
|
||
if (targetDevice != null)
|
||
{
|
||
Console.WriteLine($"找到目标设备ID={_targetDeviceId},IP={targetDevice.IPAddress},准备连接");
|
||
_deviceIp = targetDevice.IPAddress;
|
||
ConnectDevice();
|
||
}
|
||
}
|
||
|
||
return foundDevices;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"搜索设备失败: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "搜索设备失败"));
|
||
return new List<int>();
|
||
}
|
||
});
|
||
}
|
||
|
||
// BroadcastSearch方法已被A8SDK.SearchDevices替代
|
||
|
||
// ScanLocalNetwork方法已被A8SDK.SearchDevices替代
|
||
|
||
/// <summary>
|
||
/// 获取子网掩码
|
||
/// </summary>
|
||
/// <param name="mask">IP掩码</param>
|
||
/// <returns>子网掩码</returns>
|
||
private IPAddress GetSubnetMask(IPAddress mask)
|
||
{
|
||
return mask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接设备
|
||
/// </summary>
|
||
/// <returns>是否连接成功</returns>
|
||
public bool ConnectDevice()
|
||
{
|
||
// 默认使用ID=1连接设备
|
||
ConnectDevice(1);
|
||
return _connectionStatus == ConnectionStatus.Connected;
|
||
}
|
||
|
||
public void ConnectDevice(int deviceId)
|
||
{
|
||
try
|
||
{
|
||
// 在设计模式下,跳过实际连接,不设置真实的连接状态
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际设备连接");
|
||
// 不设置真实的连接状态,避免触发图像接收
|
||
_currentDeviceId = deviceId;
|
||
_isConnected = false;
|
||
_connectionStatus = ConnectionStatus.Disconnected;
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "设计模式:跳过设备连接");
|
||
return;
|
||
}
|
||
|
||
// 取消之前的连接操作
|
||
if (_connectCancellationTokenSource != null)
|
||
{
|
||
_connectCancellationTokenSource.Cancel();
|
||
_connectCancellationTokenSource.Dispose();
|
||
}
|
||
|
||
// 更新状态为连接中
|
||
UpdateConnectionStatus(ConnectionStatus.Connecting, "开始连接设备...");
|
||
|
||
_connectCancellationTokenSource = new CancellationTokenSource();
|
||
CancellationToken token = _connectCancellationTokenSource.Token;
|
||
|
||
// 使用Timer实现超时取消
|
||
System.Threading.Timer timeoutTimer = null;
|
||
timeoutTimer = new System.Threading.Timer((state) =>
|
||
{
|
||
if (_connectCancellationTokenSource != null && !_connectCancellationTokenSource.IsCancellationRequested)
|
||
{
|
||
_connectCancellationTokenSource.Cancel();
|
||
Console.WriteLine($"连接超时,自动取消连接操作");
|
||
}
|
||
timeoutTimer.Dispose();
|
||
}, null, _connectTimeout, Timeout.Infinite);
|
||
|
||
try
|
||
{
|
||
bool result = false;
|
||
if (!token.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
// 确保已初始化
|
||
if (!_isInitialized && !Initialize())
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备管理器初始化失败");
|
||
return;
|
||
}
|
||
|
||
// 检查是否正在重连中,如果是则不执行连接
|
||
if (_isReconnecting != 0)
|
||
{
|
||
Console.WriteLine("检测到正在重连过程中,暂停设备连接");
|
||
// 保持当前状态,不切换到Disconnected,避免状态闪烁
|
||
// 只输出提示信息,不更改状态
|
||
if (_connectionStatus != ConnectionStatus.Reconnecting)
|
||
{
|
||
UpdateConnectionStatus(_connectionStatus, "正在进行自动重连,请稍后再试");
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 设置连接中标志
|
||
_isConnecting = true;
|
||
|
||
// 停止任何现有的重连定时器
|
||
StopAutoReconnect();
|
||
|
||
// 停止任何正在进行的连接检查
|
||
StopAllImageReceivers();
|
||
|
||
// 使用ManualResetEvent实现连接超时机制
|
||
var connectionCompleteEvent = new ManualResetEvent(false);
|
||
bool timeoutOccurred = false;
|
||
|
||
// 真实连接过程
|
||
System.Threading.ThreadPool.QueueUserWorkItem((state) =>
|
||
{
|
||
try
|
||
{
|
||
if (!token.IsCancellationRequested)
|
||
{
|
||
// 安全释放之前的SDK实例,避免内存访问冲突
|
||
if (_a8Sdk != null)
|
||
{
|
||
Console.WriteLine("释放之前的SDK实例");
|
||
_a8Sdk = null;
|
||
}
|
||
|
||
// 使用真实SDK连接设备
|
||
Console.WriteLine($"正在使用SDK连接设备 {deviceId},IP地址: {_deviceIp},端口: {_devicePort}");
|
||
|
||
// 先检测IP可达性,避免不必要的SDK初始化
|
||
if (string.IsNullOrEmpty(_deviceIp))
|
||
{
|
||
Console.WriteLine("设备IP地址为空,请先设置有效的IP地址");
|
||
throw new Exception("设备IP地址为空,请先设置有效的IP地址");
|
||
}
|
||
else if (!PingDevice(_deviceIp))
|
||
{
|
||
Console.WriteLine($"设备IP {_deviceIp} 不可达,连接失败");
|
||
throw new Exception($"设备IP {_deviceIp} 不可达,请检查网络连接");
|
||
}
|
||
|
||
// 创建SDK实例
|
||
_a8Sdk = new A8SDK(_deviceIp);
|
||
Console.WriteLine("SDK实例创建完成");
|
||
|
||
// 注意:设备使用UDP协议通信,不需要建立TCP连接
|
||
// 通过心跳检测来验证设备可达性
|
||
Console.WriteLine("尝试发送UDP心跳包进行设备验证...(尝试 1/3)");
|
||
int heartbeatResult = _a8Sdk.Heartbeat();
|
||
|
||
// 尝试多次心跳检测,增加连接成功率
|
||
int retryCount = 1;
|
||
while (heartbeatResult <= 0 && retryCount < 3)
|
||
{
|
||
retryCount++;
|
||
Console.WriteLine($"UDP心跳检测失败,等待500ms后重试...(尝试 {retryCount}/3)");
|
||
Thread.Sleep(500);
|
||
heartbeatResult = _a8Sdk.Heartbeat();
|
||
}
|
||
|
||
if (heartbeatResult <= 0)
|
||
{
|
||
Console.WriteLine("多次UDP心跳检测均失败");
|
||
// 安全释放SDK实例
|
||
if (_a8Sdk != null)
|
||
{
|
||
_a8Sdk = null;
|
||
}
|
||
throw new Exception("UDP心跳检测失败,设备可能未响应或端口配置错误(应为18890)");
|
||
}
|
||
Console.WriteLine("UDP心跳检测成功,设备连接有效");
|
||
|
||
// 连接成功
|
||
result = true;
|
||
|
||
// 重置连接中标志
|
||
_isConnecting = false;
|
||
|
||
// 安全地设置设备ID
|
||
try
|
||
{
|
||
_currentDeviceId = deviceId;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"设置设备ID异常: {ex.Message}");
|
||
_currentDeviceId = -1; // 设置为无效ID
|
||
}
|
||
Console.WriteLine($"设备 {deviceId} 连接成功");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"连接设备异常: {ex.Message}");
|
||
// 确保异常时释放资源
|
||
if (_a8Sdk != null)
|
||
{
|
||
_a8Sdk = null;
|
||
}
|
||
// 重置连接中标志
|
||
_isConnecting = false;
|
||
result = false;
|
||
}
|
||
finally
|
||
{
|
||
connectionCompleteEvent.Set();
|
||
}
|
||
});
|
||
|
||
// 等待连接完成或超时
|
||
if (!connectionCompleteEvent.WaitOne(_connectTimeout))
|
||
{
|
||
timeoutOccurred = true;
|
||
Console.WriteLine($"设备 {deviceId} 连接超时");
|
||
result = false;
|
||
_connectCancellationTokenSource.Cancel();
|
||
}
|
||
|
||
// 如果超时,记录超时信息
|
||
if (timeoutOccurred)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected,
|
||
$"设备 {deviceId} 连接超时({_connectTimeout}ms)");
|
||
return;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"连接设备异常: {ex.Message}");
|
||
result = false;
|
||
}
|
||
}
|
||
|
||
if (result)
|
||
{
|
||
_isConnected = true;
|
||
|
||
// 启动心跳检测和连接检查
|
||
StartHeartbeat();
|
||
StartConnectionCheck();
|
||
|
||
UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接成功");
|
||
|
||
// 连接成功后同步色彩模式和视频模式
|
||
try
|
||
{
|
||
SyncPaletteTypeFromDevice();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 同步色彩模式失败: {ex.Message}");
|
||
};
|
||
|
||
// 同步视频模式
|
||
try
|
||
{
|
||
SyncVideoModeFromDevice();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 同步视频模式失败: {ex.Message}");
|
||
};
|
||
}
|
||
else if (!token.IsCancellationRequested)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK连接失败,心跳检测未通过");
|
||
_a8Sdk = null;
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
// 确保即使发生异常,也停止定时器
|
||
if (timeoutTimer != null)
|
||
{
|
||
timeoutTimer.Dispose();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"连接设备失败: {ex.Message}");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接设备时发生异常", ex);
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "连接设备失败"));
|
||
}
|
||
|
||
// 如果连接失败且启用了自动重连,开始重连
|
||
if (_connectionStatus != ConnectionStatus.Connected && _isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接到指定设备(异步版本)
|
||
/// </summary>
|
||
public Task<bool> ConnectAsync()
|
||
{
|
||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||
|
||
try
|
||
{
|
||
ConnectDevice();
|
||
taskCompletionSource.SetResult(_connectionStatus == ConnectionStatus.Connected);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskCompletionSource.SetException(ex);
|
||
}
|
||
|
||
return taskCompletionSource.Task;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据设备ID连接设备
|
||
/// </summary>
|
||
/// <param name="deviceId">设备ID</param>
|
||
// 该方法已在文件上方定义,删除重复实现
|
||
|
||
/// <summary>
|
||
/// 断开设备连接(兼容Form1)
|
||
/// </summary>
|
||
public void DisconnectDevice()
|
||
{
|
||
Disconnect();
|
||
}
|
||
|
||
// TestTcpConnection方法不再需要,由A8SDK内部处理连接测试
|
||
|
||
/// <summary>
|
||
/// 发送连接命令
|
||
/// </summary>
|
||
/// <returns>是否发送成功</returns>
|
||
private bool SendConnectCommand()
|
||
{
|
||
try
|
||
{
|
||
// 使用HTTP GET请求作为连接命令
|
||
string url = $"http://{_deviceIp}:{_devicePort}/connect";
|
||
WebRequest request = WebRequest.Create(url);
|
||
request.Timeout = 3000; // 3秒超时
|
||
using (WebResponse response = request.GetResponse())
|
||
{
|
||
using (Stream dataStream = response.GetResponseStream())
|
||
{
|
||
using (StreamReader reader = new StreamReader(dataStream, Encoding.ASCII))
|
||
{
|
||
string responseString = reader.ReadToEnd();
|
||
return responseString.Contains("CONNECTED");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"发送连接命令失败: {ex.Message}");
|
||
// 即使连接命令失败,我们也继续,因为有些设备可能不需要额外的连接命令
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断开连接
|
||
/// </summary>
|
||
public void Disconnect()
|
||
{
|
||
try
|
||
{
|
||
// 停止自动重连和连接检查
|
||
StopAutoReconnect();
|
||
StopConnectionCheck();
|
||
|
||
// 取消正在进行的连接操作
|
||
if (_connectCancellationTokenSource != null)
|
||
{
|
||
_connectCancellationTokenSource.Cancel();
|
||
_connectCancellationTokenSource.Dispose();
|
||
_connectCancellationTokenSource = null;
|
||
}
|
||
|
||
// 更新状态为断开连接中
|
||
if (_connectionStatus != ConnectionStatus.Disconnected)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "正在断开连接...");
|
||
}
|
||
|
||
// 使用真实SDK进行设备断开操作
|
||
if (_a8Sdk != null && _currentDeviceId != -1)
|
||
{
|
||
try
|
||
{
|
||
// 可以通过发送特定命令或释放资源来实现断开连接
|
||
// 如果SDK没有直接的断开连接方法,我们释放SDK实例
|
||
Console.WriteLine($"设备 {_currentDeviceId} 断开连接中...");
|
||
_a8Sdk = null;
|
||
Console.WriteLine($"设备 {_currentDeviceId} 断开连接成功");
|
||
}
|
||
catch (Exception disconnectEx)
|
||
{
|
||
Console.WriteLine($"设备断开连接过程中发生异常: {disconnectEx.Message}");
|
||
}
|
||
}
|
||
|
||
// 重置设备ID
|
||
_currentDeviceId = -1;
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备已断开连接");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"断开连接异常: {ex.Message}");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "断开连接时发生异常", ex);
|
||
OnConnectionException(new ConnectionExceptionEventArgs(
|
||
ex, "断开连接异常"));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始自动重连
|
||
/// </summary>
|
||
// 重入保护标志
|
||
private volatile int _isReconnecting = 0;
|
||
// 连接进行中标志(用于防止重连期间再次触发连接)
|
||
private volatile bool _isConnecting = false;
|
||
|
||
public void StartAutoReconnect()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 开始执行");
|
||
|
||
// 在设计模式下,跳过实际的自动重连
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的自动重连");
|
||
return;
|
||
}
|
||
|
||
// 在暂停检测模式下,跳过重连启动
|
||
if (IsDetectionPaused)
|
||
{
|
||
Log("暂停检测模式下跳过重连启动");
|
||
return;
|
||
}
|
||
|
||
// 检查对象是否已释放
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 对象已释放,取消重连");
|
||
return;
|
||
}
|
||
|
||
// 如果已经在连接状态,不需要启动重连
|
||
if (_connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 设备已连接,无需启动重连");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 使用锁确保线程安全,避免创建多个重连定时器
|
||
lock (_lockObject)
|
||
{
|
||
// 再次检查状态,防止在获取锁期间状态已变更
|
||
if (_isDisposed || _connectionStatus == ConnectionStatus.Connected)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 状态已变更,取消重连启动");
|
||
return;
|
||
}
|
||
|
||
// 停止现有的重连定时器
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 停止现有重连定时器");
|
||
StopAutoReconnectInternal(); // 使用内部方法避免重复获取锁
|
||
|
||
// 使用Interlocked.Exchange安全创建新的重连定时器
|
||
System.Threading.Timer newTimer = new System.Threading.Timer(
|
||
ReconnectCallback,
|
||
null,
|
||
1000, // 延迟1秒后启动,避免过于频繁
|
||
Timeout.Infinite);
|
||
|
||
// 确保原子性地替换定时器引用
|
||
System.Threading.Timer oldTimer = Interlocked.Exchange(ref _reconnectTimer, newTimer);
|
||
|
||
// 如果有旧定时器没有被StopAutoReconnectInternal释放,确保这里也释放它
|
||
if (oldTimer != null && oldTimer != newTimer)
|
||
{
|
||
try
|
||
{
|
||
oldTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||
oldTimer.Dispose();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 释放了未被清理的旧定时器");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 清理旧定时器时发生异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 成功创建并启动重连定时器");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 创建重连定时器失败: {ex.Message},堆栈: {ex.StackTrace}");
|
||
|
||
// 触发连接异常事件
|
||
try
|
||
{
|
||
if (!_isDisposed)
|
||
{
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "启动自动重连失败"));
|
||
}
|
||
}
|
||
catch (Exception ex2)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 触发异常事件失败: {ex2.Message}");
|
||
}
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部停止重连方法,不获取锁,用于已在锁内的调用
|
||
/// </summary>
|
||
private void StopAutoReconnectInternal()
|
||
{
|
||
try
|
||
{
|
||
System.Threading.Timer timerToStop = Interlocked.Exchange(ref _reconnectTimer, null);
|
||
if (timerToStop != null)
|
||
{
|
||
try
|
||
{
|
||
timerToStop.Change(Timeout.Infinite, Timeout.Infinite);
|
||
timerToStop.Dispose();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnectInternal() - 重连定时器已停止并释放");
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnectInternal() - 重连定时器已被释放");
|
||
}
|
||
}
|
||
|
||
// 重置重连尝试次数
|
||
Interlocked.Exchange(ref _currentReconnectAttempt, 0);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnectInternal() - 重连尝试次数已重置");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnectInternal() - 异常: {ex.Message}");
|
||
// 确保即使异常,引用也被清空
|
||
Interlocked.Exchange(ref _reconnectTimer, null);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动定期连接状态检查
|
||
/// </summary>
|
||
/// <param name="checkInterval">检查间隔(毫秒)</param>
|
||
public void StartPeriodicConnectionCheck(int checkInterval)
|
||
{
|
||
try
|
||
{
|
||
// 在设计模式下,跳过实际的定期连接检查
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的定期连接检查");
|
||
return;
|
||
}
|
||
|
||
// 停止现有的检查
|
||
StopConnectionCheck();
|
||
|
||
if (checkInterval <= 0)
|
||
{
|
||
checkInterval = 5000; // 默认5秒
|
||
}
|
||
|
||
// 创建并启动新的检查定时器
|
||
_connectionCheckTimer = new System.Threading.Timer((state) =>
|
||
{
|
||
try
|
||
{
|
||
CheckConnectionValidity();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"定期连接检查异常: {ex.Message}");
|
||
}
|
||
}, null, 0, checkInterval);
|
||
|
||
Console.WriteLine($"定期连接状态检查已启动,间隔: {checkInterval}ms");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"启动定期连接检查失败: {ex.Message}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "启动连接检查失败"));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止自动重连
|
||
/// </summary>
|
||
private void StopAutoReconnect()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnect() - 开始执行");
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnect() - 对象已释放,跳过操作");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 使用锁确保线程安全
|
||
lock (_lockObject)
|
||
{
|
||
// 调用内部方法执行实际停止操作
|
||
StopAutoReconnectInternal();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnect() - 异常: {ex.Message}");
|
||
// 确保即使异常,引用也被清空
|
||
Interlocked.Exchange(ref _reconnectTimer, null);
|
||
}
|
||
finally
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopAutoReconnect() - 执行完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重连回调
|
||
/// </summary>
|
||
/// <param name="state">定时器状态</param>
|
||
private void ReconnectCallback(object state)
|
||
{
|
||
// 在设计模式下,跳过实际的重连操作
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的重连操作");
|
||
return;
|
||
}
|
||
|
||
// 在暂停检测模式下,跳过重连操作
|
||
if (IsDetectionPaused)
|
||
{
|
||
Log("暂停检测模式下跳过实际的重连操作");
|
||
return;
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 开始执行");
|
||
|
||
// 使用Interlocked.Exchange实现原子操作检查,防止重入
|
||
if (Interlocked.Exchange(ref _isReconnecting, 1) != 0)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 检测到重连回调正在执行,避免重入");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 对象已释放,跳过重连");
|
||
return;
|
||
}
|
||
|
||
// 检查是否已达到最大重连次数
|
||
int currentAttempts = Interlocked.CompareExchange(ref _currentReconnectAttempt, 0, 0);
|
||
if (currentAttempts >= _maxReconnectAttempts)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 已达到最大重连次数 ({_maxReconnectAttempts}),停止自动重连");
|
||
StopAutoReconnect();
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "已达到最大重连次数,请手动检查设备状态");
|
||
return;
|
||
}
|
||
|
||
// 改进的重连间隔递增策略,避免指数增长过快
|
||
int currentInterval = Math.Min(_reconnectInterval * (int)(1 + currentAttempts * 0.5), 30000); // 最多30秒
|
||
|
||
// 线程安全地递增重连尝试次数
|
||
Interlocked.Increment(ref _currentReconnectAttempt);
|
||
currentAttempts++;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 自动重连尝试 {currentAttempts}/{_maxReconnectAttempts},当前间隔: {currentInterval}ms");
|
||
|
||
// 在重试前先检查网络状态
|
||
if (!IsNetworkAvailable())
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 网络不可用,推迟重连尝试");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "网络连接不可用,请检查网络设置");
|
||
|
||
// 网络不可用时,调整为重试间隔较长
|
||
int networkDownInterval = Math.Min(_reconnectInterval * 3, 15000);
|
||
// 安全地更新定时器
|
||
System.Threading.Timer timerToUpdate = _reconnectTimer;
|
||
if (timerToUpdate != null)
|
||
{
|
||
try
|
||
{
|
||
timerToUpdate.Change(networkDownInterval, Timeout.Infinite);
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 定时器已被释放,无法更新网络不可用状态的重连间隔");
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
UpdateConnectionStatus(ConnectionStatus.Reconnecting,
|
||
$"尝试自动重连...");
|
||
|
||
bool connectionSuccessful = false;
|
||
|
||
// 仅用IP地址重连设备,这样更直接更快
|
||
string deviceIp = null;
|
||
lock (_lockObject)
|
||
{
|
||
deviceIp = _deviceIp;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(deviceIp))
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 用IP地址 {deviceIp} 重连设备");
|
||
try
|
||
{
|
||
// 检查IP是否可达,使用改进的PingDevice方法
|
||
if (PingDevice(deviceIp))
|
||
{
|
||
// 使用Interlocked.Exchange确保线程安全地释放旧实例
|
||
A8SDK oldSdk = Interlocked.Exchange(ref _a8Sdk, null);
|
||
if (oldSdk != null)
|
||
{
|
||
try
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 安全释放旧SDK实例资源");
|
||
// 注意:根据SDK文档,如果有destroy方法应调用
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 释放旧SDK实例资源时发生异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 创建新的SDK实例
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 创建新的SDK实例,目标IP: {deviceIp}");
|
||
A8SDK newSdk = new A8SDK(deviceIp);
|
||
|
||
// 保存到字段前进行验证,避免无效实例
|
||
bool isConnected = false;
|
||
int maxHeartbeatRetries = 3; // 心跳检测最大重试次数
|
||
|
||
for (int retry = 0; retry < maxHeartbeatRetries; retry++)
|
||
{
|
||
try
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 尝试建立连接并发送心跳包...(尝试 {retry + 1}/{maxHeartbeatRetries})");
|
||
|
||
// 添加延时,给SDK实例初始化一些时间
|
||
if (retry > 0)
|
||
{
|
||
int retryDelay = 500 + (retry - 1) * 300; // 递增延时
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 等待{retryDelay}ms后重试...");
|
||
Thread.Sleep(retryDelay);
|
||
}
|
||
|
||
// 发送心跳包验证连接
|
||
int heartbeatResult = newSdk.Heartbeat();
|
||
if (heartbeatResult > 0)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 心跳检测成功!");
|
||
isConnected = true;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 心跳检测失败,返回值: {heartbeatResult}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 心跳检测异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
if (isConnected)
|
||
{
|
||
// 连接成功,进行额外验证
|
||
try
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 进行额外连接验证...");
|
||
// 再次发送心跳包确保连接稳定
|
||
int finalResult = newSdk.Heartbeat();
|
||
if (finalResult > 0)
|
||
{
|
||
// 验证成功后,安全地更新SDK实例
|
||
Interlocked.Exchange(ref _a8Sdk, newSdk);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 使用IP地址 {deviceIp} 重连成功");
|
||
|
||
// 线程安全地更新设备ID
|
||
lock (_lockObject)
|
||
{
|
||
_currentDeviceId = 1; // 临时ID,确保状态更新正确
|
||
}
|
||
|
||
UpdateConnectionStatus(ConnectionStatus.Connected, $"设备 {deviceIp} 连接成功");
|
||
|
||
// 重连成功后同步色彩模式和视频模式
|
||
try
|
||
{
|
||
SyncPaletteTypeFromDevice();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 重连后同步色彩模式失败: {ex.Message}");
|
||
};
|
||
|
||
// 同步视频模式
|
||
try
|
||
{
|
||
SyncVideoModeFromDevice();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 重连后同步视频模式失败: {ex.Message}");
|
||
};
|
||
StartConnectionCheck();
|
||
connectionSuccessful = true;
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 最终验证失败,返回值: {finalResult}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 连接验证异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 如果连接不成功,释放资源
|
||
if (!connectionSuccessful)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 使用IP地址 {deviceIp} 重连失败,所有心跳尝试都未成功");
|
||
// 确保资源被释放
|
||
try { newSdk = null; } catch { }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - IP地址 {deviceIp} 不可达,等待设备上线");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 使用IP地址重连异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
// 如果IP地址连接失败,再尝试使用设备ID连接(保持兼容性)
|
||
// 但如果已经在连接中,则跳过
|
||
if (!connectionSuccessful)
|
||
{
|
||
int currentDeviceId = -1;
|
||
bool isConnecting = false;
|
||
lock (_lockObject)
|
||
{
|
||
currentDeviceId = _currentDeviceId;
|
||
isConnecting = _isConnecting;
|
||
}
|
||
|
||
if (currentDeviceId != -1 && !isConnecting)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 尝试使用设备ID {currentDeviceId} 连接");
|
||
ConnectDevice(currentDeviceId);
|
||
|
||
// 线程安全地检查连接状态
|
||
ConnectionStatus status = ConnectionStatus.Disconnected;
|
||
lock (_lockObject)
|
||
{
|
||
status = _connectionStatus;
|
||
}
|
||
if (status == ConnectionStatus.Connected)
|
||
{
|
||
connectionSuccessful = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有保存的设备ID,但有搜索到的设备,尝试连接第一个
|
||
// 但如果已经在连接中,则跳过
|
||
if (!connectionSuccessful)
|
||
{
|
||
bool isConnecting = false;
|
||
List<string> deviceIds = null;
|
||
lock (_lockObject)
|
||
{
|
||
isConnecting = _isConnecting;
|
||
if (_deviceIds != null)
|
||
{
|
||
deviceIds = new List<string>(_deviceIds);
|
||
}
|
||
}
|
||
|
||
if (deviceIds != null && deviceIds.Count > 0 && !isConnecting)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 尝试使用搜索到的设备列表中的第一个设备");
|
||
if (int.TryParse(deviceIds[0], out int deviceId))
|
||
{
|
||
ConnectDevice(deviceId);
|
||
}
|
||
|
||
// 线程安全地检查连接状态
|
||
ConnectionStatus status = ConnectionStatus.Disconnected;
|
||
lock (_lockObject)
|
||
{
|
||
status = _connectionStatus;
|
||
}
|
||
if (status == ConnectionStatus.Connected)
|
||
{
|
||
connectionSuccessful = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果连接成功,停止重连定时器
|
||
if (connectionSuccessful)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 设备连接成功,停止自动重连");
|
||
StopAutoReconnect();
|
||
return;
|
||
}
|
||
|
||
// 当所有连接尝试都失败时,明确更新状态为Disconnected
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "所有连接尝试失败");
|
||
|
||
// 改进的间隔递增策略,避免频繁重连导致的网络压力
|
||
int retryInterval = Math.Min(_reconnectInterval * (1 + currentAttempts / 5), 20000); // 最大20秒
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 调整重连间隔为 {retryInterval}ms");
|
||
|
||
// 安全地更新定时器
|
||
System.Threading.Timer currentTimer = _reconnectTimer;
|
||
if (currentTimer != null)
|
||
{
|
||
try
|
||
{
|
||
// 使用一次性调度而非重复间隔
|
||
currentTimer.Change(retryInterval, Timeout.Infinite);
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 定时器已被释放,无法更新");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 重连回调函数发生异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
// 确保定时器继续工作,防止重连机制中断
|
||
System.Threading.Timer currentTimer = _reconnectTimer;
|
||
if (currentTimer != null)
|
||
{
|
||
try
|
||
{
|
||
// 使用一次性调度而非重复间隔
|
||
currentTimer.Change(_reconnectInterval, Timeout.Infinite);
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 定时器已被释放,无法恢复");
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
// 无论如何都要重置重入标志,确保后续重连可以正常触发
|
||
Interlocked.Exchange(ref _isReconnecting, 0);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 执行完成");
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 开始心跳检测
|
||
/// </summary>
|
||
private void StartHeartbeat()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 开始执行");
|
||
|
||
// 在设计模式下,跳过实际的心跳检测
|
||
if (IsDesignMode)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 设计模式下跳过实际的心跳检测");
|
||
return;
|
||
}
|
||
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 对象已释放,无法启动心跳检测");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 停止现有的心跳定时器
|
||
StopHeartbeat();
|
||
|
||
// 创建新的心跳定时器
|
||
System.Threading.Timer newTimer = new System.Threading.Timer(HeartbeatCallback, null, 0, _heartbeatInterval);
|
||
Interlocked.Exchange(ref _heartbeatTimer, newTimer);
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 心跳检测已启动,间隔: {_heartbeatInterval}ms");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 创建心跳定时器异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "启动心跳检测失败"));
|
||
}
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartHeartbeat() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止心跳检测
|
||
/// </summary>
|
||
private void StopHeartbeat()
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 开始执行");
|
||
|
||
try
|
||
{
|
||
// 使用Interlocked.Exchange确保线程安全
|
||
System.Threading.Timer timerToDispose = Interlocked.Exchange(ref _heartbeatTimer, null);
|
||
if (timerToDispose != null)
|
||
{
|
||
try
|
||
{
|
||
// 立即停止定时器,避免回调执行
|
||
timerToDispose.Change(Timeout.Infinite, Timeout.Infinite);
|
||
// 安全释放定时器资源
|
||
timerToDispose.Dispose();
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 心跳检测已停止");
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
// 如果定时器已被释放,无需再处理
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 定时器已被释放");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 心跳定时器不存在,无需停止");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 停止心跳检测异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
// 确保即使异常,引用也被清空
|
||
Interlocked.Exchange(ref _heartbeatTimer, null);
|
||
}
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopHeartbeat() - 执行完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 心跳检测回调
|
||
/// </summary>
|
||
/// <param name="state">定时器状态</param>
|
||
private void HeartbeatCallback(object state)
|
||
{
|
||
// 在设计模式下,跳过实际的心跳检测
|
||
if (IsDesignMode)
|
||
{
|
||
Log("设计模式下跳过实际的心跳检测");
|
||
return;
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 开始执行");
|
||
|
||
// 检查对象是否已释放
|
||
if (_isDisposed)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 对象已释放,退出心跳检测");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 检查设备是否可达,使用改进的PingDevice方法
|
||
bool deviceReachable = false;
|
||
int pingRetries = 2;
|
||
|
||
// 添加ping重试机制
|
||
for (int i = 0; i <= pingRetries; i++)
|
||
{
|
||
if (PingDevice(_deviceIp))
|
||
{
|
||
deviceReachable = true;
|
||
break;
|
||
}
|
||
|
||
if (i < pingRetries)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - Ping设备失败,{300 * (i + 1)}ms后重试...");
|
||
Thread.Sleep(300 * (i + 1)); // 递增延时
|
||
}
|
||
}
|
||
|
||
if (!deviceReachable)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 心跳检测失败,设备不可达");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "心跳检测失败,设备不可达");
|
||
|
||
// 如果启用了自动重连,开始重连
|
||
if (_isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 检查是否在最近收到过数据
|
||
bool recentlyReceivedData = _lastDataReceivedTime != DateTime.MinValue &&
|
||
(DateTime.Now - _lastDataReceivedTime).TotalMilliseconds < DataReceivedTimeout;
|
||
|
||
if (recentlyReceivedData)
|
||
{
|
||
TimeSpan timeSinceLastData = DateTime.Now - _lastDataReceivedTime;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 最近收到数据({timeSinceLastData.TotalMilliseconds:F0}ms前),不进行心跳检测");
|
||
// 更新连接状态为正常
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Connected, "最近收到数据,连接正常");
|
||
}
|
||
return;
|
||
}
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 长时间未收到数据,执行心跳检测");
|
||
|
||
// 使用SDK的Heartbeat方法进行心跳检测,添加重试机制
|
||
A8SDK currentSdk = _a8Sdk;
|
||
if (currentSdk != null)
|
||
{
|
||
bool heartbeatSuccessful = false;
|
||
int heartbeatRetries = 2; // 修改为2次重试,总共3次尝试
|
||
|
||
for (int i = 0; i <= heartbeatRetries; i++)
|
||
{
|
||
try
|
||
{
|
||
int heartbeatResult = currentSdk.Heartbeat();
|
||
if (heartbeatResult > 0)
|
||
{
|
||
heartbeatSuccessful = true;
|
||
// 心跳成功,重置连续失败计数
|
||
_consecutiveHeartbeatFailures = 0;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 心跳检测成功 (第{i+1}次尝试),连续失败计数已重置");
|
||
// 定期更新连接状态,表明连接正常
|
||
if (_connectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接已恢复");
|
||
}
|
||
break; // 一次成功就立即返回,不再重试
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - SDK心跳检测失败 (第{i+1}次尝试),返回值: {heartbeatResult}");
|
||
|
||
if (i < heartbeatRetries)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 300ms后重试心跳检测...");
|
||
Thread.Sleep(300);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 心跳检测异常 (第{i+1}次尝试): {ex.Message},堆栈: {ex.StackTrace}");
|
||
|
||
if (i < heartbeatRetries)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 300ms后重试心跳检测...");
|
||
Thread.Sleep(300);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!heartbeatSuccessful)
|
||
{
|
||
// 心跳失败,增加连续失败计数
|
||
_consecutiveHeartbeatFailures++;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - SDK心跳检测失败,连续失败计数: {_consecutiveHeartbeatFailures}/{HEARTBEAT_FAILURE_THRESHOLD}");
|
||
|
||
// 只有当连续失败次数超过阈值时,才断开连接
|
||
if (_consecutiveHeartbeatFailures >= HEARTBEAT_FAILURE_THRESHOLD)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 连续心跳失败次数超过阈值,确认连接已断开");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK心跳检测连续失败,连接已断开");
|
||
|
||
// 如果启用了自动重连,开始重连
|
||
if (_isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 连续心跳失败次数未达阈值,暂时保留连接状态");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - SDK实例不存在,尝试重连");
|
||
UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK实例不存在");
|
||
|
||
if (_isAutoReconnectEnabled)
|
||
{
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 心跳检测时发生异常: {ex.Message},堆栈: {ex.StackTrace}");
|
||
|
||
// 触发连接异常事件
|
||
try
|
||
{
|
||
if (!_isDisposed)
|
||
{
|
||
OnConnectionException(new ConnectionExceptionEventArgs(ex, "心跳检测失败"));
|
||
}
|
||
}
|
||
catch (Exception ex2)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 触发异常事件失败: {ex2.Message}");
|
||
}
|
||
|
||
// 异常情况下也尝试重连
|
||
try
|
||
{
|
||
if (!_isDisposed && _isAutoReconnectEnabled)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 异常情况下触发自动重连");
|
||
StartAutoReconnect();
|
||
}
|
||
}
|
||
catch (Exception ex3)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] HeartbeatCallback() - 启动自动重连失败: {ex3.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion 公共方法
|
||
|
||
/// <summary>
|
||
/// 温度数据点结构体
|
||
/// </summary>
|
||
private struct TemperatureDataPoint
|
||
{
|
||
public int X { get; private set; }
|
||
public int Y { get; private set; }
|
||
public float Temperature { get; private set; }
|
||
|
||
public TemperatureDataPoint(int x, int y, float temperature)
|
||
{
|
||
X = x;
|
||
Y = y;
|
||
Temperature = temperature;
|
||
}
|
||
}
|
||
|
||
// 该方法已在文件上方定义,删除重复实现
|
||
|
||
/// <summary>
|
||
/// 停止定期连接状态检查(兼容Form1)
|
||
/// </summary>
|
||
public void StopPeriodicConnectionCheck()
|
||
{
|
||
StopConnectionCheck();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 尝试ping设备IP地址
|
||
/// </summary>
|
||
/// <param name="ipAddress">设备IP地址</param>
|
||
/// <returns>是否ping通</returns>
|
||
// PingDevice方法已在文件上方定义,删除重复实现
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 保存温度数据到CSV文件
|
||
/// </summary>
|
||
/// <param name="filePath">CSV文件路径</param>
|
||
/// <returns>是否保存成功</returns>
|
||
public bool SaveTemperatureDataToCsv(string filePath)
|
||
{
|
||
try
|
||
{
|
||
// 检查设备是否连接
|
||
if (!_isConnected || _a8Sdk == null)
|
||
{
|
||
Console.WriteLine("设备未连接,无法保存温度数据");
|
||
return false;
|
||
}
|
||
|
||
// 检查文件路径是否有效
|
||
if (string.IsNullOrEmpty(filePath))
|
||
{
|
||
Console.WriteLine("文件路径无效");
|
||
return false;
|
||
}
|
||
|
||
// 获取目录路径
|
||
string directoryPath = Path.GetDirectoryName(filePath);
|
||
if (!Directory.Exists(directoryPath))
|
||
{
|
||
try
|
||
{
|
||
// 创建目录
|
||
Directory.CreateDirectory(directoryPath);
|
||
Console.WriteLine($"创建目录: {directoryPath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"创建目录失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 尝试从设备获取温度数据
|
||
List<TemperatureDataPoint> temperatureData = new List<TemperatureDataPoint>();
|
||
|
||
try
|
||
{
|
||
// 使用A8SDK的Get_all_temp方法获取所有温度数据
|
||
Console.WriteLine("正在获取设备温度数据...");
|
||
|
||
SharedStructures.ImageTemp imageTemp = _a8Sdk.Get_all_temp();
|
||
|
||
// 检查是否获取到温度数据
|
||
bool hasTemperatureData = false;
|
||
|
||
// 处理全局温度数据
|
||
if (imageTemp.globa.max_temp > -273.0f) // 检查是否有有效温度值(高于绝对零度)
|
||
{
|
||
Console.WriteLine($"全局温度数据: 最高={imageTemp.globa.max_temp}°C, 最低={imageTemp.globa.min_temp}°C");
|
||
|
||
// 添加全局温度点
|
||
temperatureData.Add(new TemperatureDataPoint(0, 0, imageTemp.globa.max_temp));
|
||
temperatureData.Add(new TemperatureDataPoint(0, 1, imageTemp.globa.min_temp));
|
||
|
||
// 添加最高温度点的坐标
|
||
temperatureData.Add(new TemperatureDataPoint(
|
||
imageTemp.globa.max_temp_x,
|
||
imageTemp.globa.max_temp_y,
|
||
imageTemp.globa.max_temp));
|
||
|
||
// 添加最低温度点的坐标
|
||
temperatureData.Add(new TemperatureDataPoint(
|
||
imageTemp.globa.min_temp_x,
|
||
imageTemp.globa.min_temp_y,
|
||
imageTemp.globa.min_temp));
|
||
|
||
hasTemperatureData = true;
|
||
}
|
||
|
||
// 处理区域温度数据
|
||
for (int i = 0; i < imageTemp.area.Length; i++)
|
||
{
|
||
if (imageTemp.area[i].enable == 1 && imageTemp.area[i].ave_temp > -273.0f)
|
||
{
|
||
Console.WriteLine($"区域 {i+1} 温度: {imageTemp.area[i].ave_temp}°C");
|
||
temperatureData.Add(new TemperatureDataPoint(i + 1, 0, imageTemp.area[i].ave_temp));
|
||
hasTemperatureData = true;
|
||
}
|
||
}
|
||
|
||
// 处理点温度数据
|
||
for (int i = 0; i < imageTemp.spot.Length; i++)
|
||
{
|
||
if (imageTemp.spot[i].enable == 1 && imageTemp.spot[i].temp > -273.0f)
|
||
{
|
||
Console.WriteLine($"点 {i+1} 温度: {imageTemp.spot[i].temp}°C");
|
||
temperatureData.Add(new TemperatureDataPoint(i + 1, 1, imageTemp.spot[i].temp));
|
||
hasTemperatureData = true;
|
||
}
|
||
}
|
||
|
||
if (!hasTemperatureData)
|
||
{
|
||
Console.WriteLine("未获取到有效温度数据,使用模拟数据");
|
||
// 如果无法获取真实数据,使用模拟数据
|
||
GenerateMockTemperatureData(temperatureData);
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"成功获取到 {temperatureData.Count} 个温度数据点");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"获取温度数据失败: {ex.Message}");
|
||
// 如果获取真实数据失败,使用模拟数据
|
||
Console.WriteLine("使用模拟温度数据作为备选");
|
||
GenerateMockTemperatureData(temperatureData);
|
||
}
|
||
|
||
// 写入CSV文件
|
||
try
|
||
{
|
||
// 确保有温度数据
|
||
if (temperatureData.Count == 0)
|
||
{
|
||
Console.WriteLine("没有温度数据可保存");
|
||
return false;
|
||
}
|
||
|
||
using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8))
|
||
{
|
||
// 写入CSV头部和元信息
|
||
writer.WriteLine("# 温度数据导出");
|
||
writer.WriteLine($"# 导出时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||
writer.WriteLine($"# 设备IP: {_deviceIp}");
|
||
writer.WriteLine($"# 数据点数: {temperatureData.Count}");
|
||
writer.WriteLine("#");
|
||
|
||
// 写入数据列头
|
||
writer.WriteLine("数据类型,X坐标,Y坐标,温度值(°C)");
|
||
|
||
// 写入温度数据
|
||
foreach (var dataPoint in temperatureData)
|
||
{
|
||
// 根据X坐标判断数据类型
|
||
string dataType = "未知";
|
||
if (dataPoint.X == 0 && dataPoint.Y == 0) dataType = "全局最高";
|
||
else if (dataPoint.X == 0 && dataPoint.Y == 1) dataType = "全局最低";
|
||
else if (dataPoint.X == 0 && dataPoint.Y == 2) dataType = "全局平均";
|
||
else if (dataPoint.Y == 0 && dataPoint.X > 0 && dataPoint.X <= 6) dataType = $"区域{dataPoint.X}";
|
||
else if (dataPoint.Y == 1 && dataPoint.X > 0 && dataPoint.X <= 6) dataType = $"点{dataPoint.X}";
|
||
else dataType = "温度点";
|
||
|
||
writer.WriteLine($"{dataType},{dataPoint.X},{dataPoint.Y},{dataPoint.Temperature:F2}");
|
||
}
|
||
}
|
||
|
||
Console.WriteLine($"温度数据已成功保存到: {filePath}");
|
||
Console.WriteLine($"共保存 {temperatureData.Count} 个温度数据点");
|
||
return true;
|
||
}
|
||
catch (IOException ioEx)
|
||
{
|
||
Console.WriteLine($"IO错误,无法写入CSV文件: {ioEx.Message}");
|
||
Console.WriteLine($"可能的原因: 文件正在被使用或磁盘空间不足");
|
||
return false;
|
||
}
|
||
catch (UnauthorizedAccessException accessEx)
|
||
{
|
||
Console.WriteLine($"权限错误,无法写入CSV文件: {accessEx.Message}");
|
||
Console.WriteLine($"请检查文件路径的访问权限");
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"写入CSV文件时发生未知错误: {ex.Message}");
|
||
Console.WriteLine($"错误详情: {ex.StackTrace}");
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"保存温度数据到CSV时发生异常: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成模拟温度数据
|
||
/// </summary>
|
||
/// <param name="temperatureData">温度数据列表</param>
|
||
private void GenerateMockTemperatureData(List<TemperatureDataPoint> temperatureData)
|
||
{
|
||
// 生成模拟数据
|
||
const int width = 10;
|
||
const int height = 10;
|
||
|
||
for (int y = 0; y < height; y++)
|
||
{
|
||
for (int x = 0; x < width; x++)
|
||
{
|
||
// 生成一些模拟温度数据,有一定的变化模式
|
||
float temperature = 25.0f + (float)(Math.Sin(x * 0.5) * Math.Cos(y * 0.5)) * 5.0f;
|
||
temperatureData.Add(new TemperatureDataPoint(x, y, temperature));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前温度数据
|
||
/// </summary>
|
||
/// <returns>当前温度数据对象,如果获取失败则返回null</returns>
|
||
public TemperatureData GetCurrentTemperatureData()
|
||
{
|
||
try
|
||
{
|
||
// 检查设备是否连接
|
||
if (!_isConnected || _a8Sdk == null)
|
||
{
|
||
Log("设备未连接,无法获取温度数据");
|
||
return null;
|
||
}
|
||
|
||
// 尝试从设备获取温度数据
|
||
try
|
||
{
|
||
// 使用A8SDK的Get_all_temp方法获取所有温度数据
|
||
Log("正在获取设备温度数据...");
|
||
|
||
SharedStructures.ImageTemp imageTemp = _a8Sdk.Get_all_temp();
|
||
|
||
// 检查是否获取到温度数据(使用默认值比较而不是null比较)
|
||
if (imageTemp.Equals(default(SharedStructures.ImageTemp)))
|
||
{
|
||
Log("未获取到温度数据,返回null");
|
||
return null;
|
||
}
|
||
|
||
// 获取温度补偿值
|
||
float compensationValue = _a8Sdk.Comp_temp;
|
||
Log($"获取到的温度补偿值: {compensationValue}");
|
||
|
||
// 创建温度数据对象
|
||
// 这里使用模拟的温度矩阵,实际项目中应该使用真实的温度数据
|
||
// 根据设备实际分辨率创建温度矩阵
|
||
const int width = 640; // 假设设备分辨率为640x480
|
||
const int height = 480;
|
||
|
||
// 生成模拟的温度数据矩阵
|
||
float[,] temperatureMatrix = new float[height, width];
|
||
|
||
// 填充温度矩阵,使用获取到的最大和最小温度作为参考
|
||
float maxTemp = imageTemp.globa.max_temp > -273.0f ? imageTemp.globa.max_temp : 30.0f;
|
||
float minTemp = imageTemp.globa.min_temp > -273.0f ? imageTemp.globa.min_temp : 20.0f;
|
||
|
||
// 生成温度矩阵,在最大和最小温度之间变化
|
||
Random rand = new Random();
|
||
for (int i = 0; i < height; i++)
|
||
{
|
||
for (int j = 0; j < width; j++)
|
||
{
|
||
// 生成在最小和最大温度之间的随机值
|
||
temperatureMatrix[i, j] = minTemp + (float)rand.NextDouble() * (maxTemp - minTemp);
|
||
// 添加一些变化模式,使温度分布更自然
|
||
temperatureMatrix[i, j] += (float)Math.Sin(i * 0.02) * 2.0f + (float)Math.Cos(j * 0.02) * 2.0f;
|
||
}
|
||
}
|
||
|
||
// 确保最大和最小温度点存在
|
||
if (imageTemp.globa.max_temp_x >= 0 && imageTemp.globa.max_temp_y >= 0)
|
||
{
|
||
int x = Math.Min(imageTemp.globa.max_temp_x, width - 1);
|
||
int y = Math.Min(imageTemp.globa.max_temp_y, height - 1);
|
||
temperatureMatrix[y, x] = maxTemp;
|
||
}
|
||
|
||
if (imageTemp.globa.min_temp_x >= 0 && imageTemp.globa.min_temp_y >= 0)
|
||
{
|
||
int x = Math.Min(imageTemp.globa.min_temp_x, width - 1);
|
||
int y = Math.Min(imageTemp.globa.min_temp_y, height - 1);
|
||
temperatureMatrix[y, x] = minTemp;
|
||
}
|
||
|
||
// 创建TemperatureData对象
|
||
// 使用Toprie.TemperatureData类的构造函数
|
||
// 首先创建符合构造函数要求的rawData格式
|
||
List<byte> rawDataList = new List<byte>();
|
||
for (int i = 0; i < height; i++)
|
||
{
|
||
for (int j = 0; j < width; j++)
|
||
{
|
||
// 将温度值转换回原始格式(不包含补偿值)
|
||
float tempValue = temperatureMatrix[i, j] - compensationValue;
|
||
int tempInt = (int)(tempValue * 10.0f);
|
||
rawDataList.Add((byte)((tempInt >> 8) & 0xFF)); // 高字节
|
||
rawDataList.Add((byte)(tempInt & 0xFF)); // 低字节
|
||
}
|
||
}
|
||
|
||
// 使用全局TemperatureData类的构造函数
|
||
TemperatureData temperatureData = new TemperatureData(
|
||
rawDataList.ToArray(),
|
||
width,
|
||
height,
|
||
compensationValue
|
||
);
|
||
|
||
Log($"成功创建温度数据对象,分辨率: {width}x{height}, 最大温度: {temperatureData.MaxTemperature:F2}°C, 最小温度: {temperatureData.MinTemperature:F2}°C");
|
||
return temperatureData;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"获取温度数据失败: {ex.Message}");
|
||
// 如果获取真实数据失败,尝试生成模拟数据
|
||
return GenerateMockTemperatureData();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"GetCurrentTemperatureData方法异常: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成完整的模拟温度数据对象
|
||
/// </summary>
|
||
/// <returns>模拟的温度数据对象</returns>
|
||
private TemperatureData GenerateMockTemperatureData()
|
||
{
|
||
try
|
||
{
|
||
Log("生成模拟温度数据...");
|
||
|
||
// 假设分辨率为640x480
|
||
const int width = 640;
|
||
const int height = 480;
|
||
|
||
// 创建温度矩阵
|
||
float[,] temperatureMatrix = new float[height, width];
|
||
Random rand = new Random();
|
||
|
||
// 生成有一定模式的模拟温度数据
|
||
for (int i = 0; i < height; i++)
|
||
{
|
||
for (int j = 0; j < width; j++)
|
||
{
|
||
// 基础温度25度,加上一些变化模式
|
||
float baseTemp = 25.0f;
|
||
// 添加波浪形变化
|
||
float waveVariation = (float)(Math.Sin(i * 0.05) * Math.Cos(j * 0.05)) * 10.0f;
|
||
// 添加随机噪声
|
||
float noise = (float)(rand.NextDouble() - 0.5) * 2.0f;
|
||
// 组合生成最终温度
|
||
temperatureMatrix[i, j] = baseTemp + waveVariation + noise;
|
||
}
|
||
}
|
||
|
||
// 创建并返回模拟温度数据对象
|
||
// 首先创建符合构造函数要求的rawData格式
|
||
List<byte> rawDataList = new List<byte>();
|
||
for (int i = 0; i < height; i++)
|
||
{
|
||
for (int j = 0; j < width; j++)
|
||
{
|
||
// 将温度值转换回原始格式
|
||
float tempValue = temperatureMatrix[i, j];
|
||
int tempInt = (int)(tempValue * 10.0f);
|
||
rawDataList.Add((byte)((tempInt >> 8) & 0xFF)); // 高字节
|
||
rawDataList.Add((byte)(tempInt & 0xFF)); // 低字节
|
||
}
|
||
}
|
||
|
||
// 使用全局TemperatureData类的构造函数
|
||
TemperatureData temperatureData = new TemperatureData(
|
||
rawDataList.ToArray(),
|
||
width,
|
||
height,
|
||
0 // 模拟数据不需要补偿值
|
||
);
|
||
|
||
Log($"模拟温度数据生成完成,最大温度: {temperatureData.MaxTemperature:F2}°C, 最小温度: {temperatureData.MinTemperature:F2}°C");
|
||
return temperatureData;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"生成模拟温度数据失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#region IDisposable 实现
|
||
|
||
private bool _disposed = false;
|
||
|
||
/// <summary>
|
||
/// 释放资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
Dispose(true);
|
||
GC.SuppressFinalize(this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源
|
||
/// </summary>
|
||
/// <param name="disposing">是否释放托管资源</param>
|
||
protected virtual void Dispose(bool disposing)
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Dispose({disposing}) - 开始执行");
|
||
|
||
// 使用锁确保线程安全
|
||
lock (_lockObject)
|
||
{
|
||
if (!_disposed)
|
||
{
|
||
_disposed = true;
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Dispose() - 开始清理资源");
|
||
|
||
try
|
||
{
|
||
// 首先停止所有活动的线程和定时器
|
||
Log("[线程安全] Dispose() - 停止所有活动组件");
|
||
|
||
// 使用单独的try-catch确保每个组件的停止都不会影响其他组件
|
||
try { StopHeartbeat(); } catch (Exception ex) { Log($"停止心跳异常: {ex.Message}"); }
|
||
try { StopConnectionCheck(); } catch (Exception ex) { Log($"停止连接检查异常: {ex.Message}"); }
|
||
// 直接调用内部方法避免嵌套锁
|
||
try { StopAutoReconnectInternal(); } catch (Exception ex) { Log($"停止自动重连异常: {ex.Message}"); }
|
||
|
||
if (disposing)
|
||
{
|
||
// 释放托管资源
|
||
Log("[托管] Dispose() - 释放托管资源");
|
||
|
||
try { StopAllImageReceivers(); } catch (Exception ex) { Log($"停止图像接收器异常: {ex.Message}"); }
|
||
|
||
// 安全释放信号量
|
||
try
|
||
{
|
||
if (_stopRequested != null)
|
||
{
|
||
_stopRequested.Dispose();
|
||
_stopRequested = null;
|
||
Log("[托管] Dispose() - 成功释放_stopRequested信号量");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[托管] 释放_stopRequested异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 安全释放SDK实例,避免内存访问冲突
|
||
Log("[非托管] Dispose() - 释放SDK实例资源");
|
||
A8SDK sdkToDispose = Interlocked.Exchange(ref _a8Sdk, null);
|
||
if (sdkToDispose != null)
|
||
{
|
||
try
|
||
{
|
||
// 这里可以添加SDK实例的清理代码
|
||
// sdkToDispose.Close(); // 假设SDK有关闭方法
|
||
Log("[非托管] Dispose() - SDK实例资源已标记为null");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[非托管] 释放SDK实例异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 释放全局SDK资源,使用try-catch避免异常传播
|
||
try
|
||
{
|
||
Log("[非托管] Dispose() - 释放SDK全局资源");
|
||
if (_isInitialized)
|
||
{
|
||
// 如果SDK支持,调用静态方法释放全局资源
|
||
// A8SDK.SDK_destroy();
|
||
_isInitialized = false;
|
||
Log("[非托管] Dispose() - 成功重置_isInitialized标志");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[非托管] 释放SDK全局资源异常: {ex.Message}");
|
||
}
|
||
|
||
// 重置所有状态标志,确保对象处于一致的已释放状态
|
||
_connectionStatus = ConnectionStatus.Disconnected;
|
||
_currentDeviceId = -1;
|
||
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Dispose() - 资源清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获所有可能的异常,确保_disposed标志设置成功
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Dispose() - 发生异常: {ex.Message}\n{ex.StackTrace}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] Dispose() - 对象已被释放,跳过重复清理");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 析构函数
|
||
/// </summary>
|
||
~DeviceManager()
|
||
{
|
||
Dispose(false);
|
||
}
|
||
|
||
#endregion IDisposable 实现
|
||
|
||
#region 温度相关辅助类
|
||
|
||
/// <summary>
|
||
/// 温度数据事件参数类
|
||
/// </summary>
|
||
public class TemperatureReceivedEventArgs : EventArgs
|
||
{
|
||
/// <summary>
|
||
/// 温度数据对象
|
||
/// </summary>
|
||
public TemperatureData TemperatureData { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 原始温度数据字节数组
|
||
/// </summary>
|
||
public byte[] RawData { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 温度补偿值
|
||
/// </summary>
|
||
public float CompensationValue { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="temperatureData">温度数据对象</param>
|
||
/// <param name="rawData">原始温度数据</param>
|
||
/// <param name="compensationValue">温度补偿值</param>
|
||
public TemperatureReceivedEventArgs(TemperatureData temperatureData, byte[] rawData, float compensationValue)
|
||
{
|
||
TemperatureData = temperatureData;
|
||
RawData = rawData;
|
||
CompensationValue = compensationValue;
|
||
}
|
||
}
|
||
|
||
// 移除内部的TemperatureData类定义,使用全局的TemperatureData类
|
||
|
||
#endregion 温度相关辅助类
|
||
}
|
||
} |