diff --git a/Windows/CS/Framework4.0/Toprie/Toprie.ico b/Windows/CS/Framework4.0/Toprie/Toprie.ico new file mode 100644 index 0000000..2cf24ed Binary files /dev/null and b/Windows/CS/Framework4.0/Toprie/Toprie.ico differ diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs index de50a30..626decf 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.Data; +using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; +using System.Threading; namespace JoyD.Windows.CS.Toprie { @@ -59,7 +60,12 @@ namespace JoyD.Windows.CS.Toprie /// private void InitializeDeviceManager() { - _deviceManager = new DeviceManager(); + _deviceManager = new DeviceManager + { + AutoReconnectEnabled = true, + ReconnectInterval = 2000, // 2秒 + MaxReconnectAttempts = 5 + }; // 注册图像接收事件 _deviceManager.ImageReceived += DeviceManager_ImageReceived; @@ -69,6 +75,8 @@ namespace JoyD.Windows.CS.Toprie // 注册连接异常事件 _deviceManager.ConnectionException += DeviceManager_ConnectionException; + + } /// @@ -81,34 +89,98 @@ namespace JoyD.Windows.CS.Toprie } /// - /// 启动并连接设备 + /// 启动相机 /// public void StartCamera() { try { - // 设置为热图模式 - _deviceManager.CurrentImageMode = ImageMode.Thermal; - - // 启用自动重连 - _deviceManager.AutoReconnectEnabled = true; - - // 连接设备 - bool connected = _deviceManager.ConnectDevice(); - - if (connected) + // 只有在没有接收图像时才启动 + if (!_isReceivingImage) { - // 开始接收图像 - StartReceiveImage(); + // 清理错误显示 + ShowError(string.Empty); + + // 使用异步方式连接设备和设置模式,避免UI线程阻塞 + ThreadPool.QueueUserWorkItem(delegate + { + try + { + // 设置为热图模式 + bool modeSet = false; + try + { + _deviceManager.SetImageMode(ImageMode.Thermal); + modeSet = true; + Console.WriteLine("已设置热图模式"); + } + catch (Exception ex) + { + Console.WriteLine($"设置热图模式失败: {ex.Message}"); + } + + // 启用自动重连 + _deviceManager.AutoReconnectEnabled = true; + + // 尝试连接设备 + if (_deviceManager.ConnectDevice()) + { + Console.WriteLine("设备连接成功"); + + // 如果热图模式未成功设置,在连接成功后再次尝试 + if (!modeSet) + { + try + { + _deviceManager.SetImageMode(ImageMode.Thermal); + Console.WriteLine("连接后已设置热图模式"); + } + catch (Exception ex) + { + Console.WriteLine($"连接后设置热图模式失败: {ex.Message}"); + } + } + + // 在连接成功后开始接收图像 + this.Invoke(new Action(() => + { + // 再次检查是否已在UI线程设置为接收状态 + if (!_isReceivingImage) + { + StartReceiveImage(); + } + })); + } + else + { + // 连接失败时显示错误 + string errorMsg = "设备连接失败,请检查设备状态或网络连接"; + this.Invoke(new Action(() => ShowError(errorMsg))); + Console.WriteLine(errorMsg); + } + } + catch (Exception ex) + { + // 捕获所有异常,防止后台线程崩溃 + string errorMsg = $"启动相机异常: {ex.Message}"; + this.Invoke(new Action(() => ShowError(errorMsg))); + Console.WriteLine(errorMsg); + Console.WriteLine(ex.StackTrace); + } + }); + + Console.WriteLine("相机启动流程已开始"); } else { - ShowError("无法连接到设备,请检查设备是否在线"); + Console.WriteLine("相机已在运行状态"); } } catch (Exception ex) { ShowError($"启动相机失败: {ex.Message}"); + Console.WriteLine($"启动相机主流程错误: {ex.Message}"); + Console.WriteLine(ex.StackTrace); } } @@ -155,36 +227,176 @@ namespace JoyD.Windows.CS.Toprie /// private void DeviceManager_ImageReceived(object sender, ImageReceivedEventArgs e) { - if (this.InvokeRequired) + // 不在这里使用using,因为我们需要将图像传递给UI线程 + Image image = e.ImageData; + + if (image == null) { - // 在UI线程上更新图像 - this.Invoke(new Action(() => UpdateImage(e.ImageData))); + Console.WriteLine("接收到空图像数据"); + return; } - else + + try { - UpdateImage(e.ImageData); + // 尝试创建图像的克隆以传递给UI线程 + Image clonedImage = null; + try + { + clonedImage = (Image)image.Clone(); + + // 验证克隆的图像是否有效 + if (clonedImage.Width <= 0 || clonedImage.Height <= 0) + { + Console.WriteLine("克隆的图像尺寸无效"); + clonedImage.Dispose(); + image.Dispose(); // 确保原始图像也被释放 + return; + } + } + catch (Exception ex) + { + Console.WriteLine($"克隆图像失败: {ex.Message}"); + image.Dispose(); // 确保原始图像被释放 + return; + } + + // 确保在UI线程上更新,并且检查控件是否已被释放 + if (!this.IsDisposed && !this.Disposing) + { + if (this.InvokeRequired) + { + try + { + // 使用BeginInvoke在UI线程上更新图像,避免可能的死锁问题 + this.BeginInvoke(new Action(() => + { + try + { + UpdateImageOnUI(clonedImage); + } + catch (Exception ex) + { + Console.WriteLine($"更新UI图像失败: {ex.Message}"); + // 如果UI更新失败,确保克隆的图像被释放 + if (clonedImage != null) + { + clonedImage.Dispose(); + } + } + })); + } + catch (Exception) + { + // 异常情况下确保释放图像资源 + clonedImage?.Dispose(); + throw; + } + } + else + { + // 在UI线程上直接更新图像 + UpdateImageOnUI(clonedImage); + } + } + else + { + // 如果控件已释放,确保释放图像资源 + clonedImage.Dispose(); + } + + // 释放原始图像资源 + image.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"处理图像接收事件失败: {ex.Message}"); + // 确保在任何异常情况下都释放原始图像 + if (image != null) + { + try + { + image.Dispose(); + } + catch {} + } } } /// - /// 更新图像显示 + /// 在UI线程上更新图像 /// - private void UpdateImage(Image image) + private void UpdateImageOnUI(Image image) { + // 空值检查 + if (image == null) + { + Console.WriteLine("传入UpdateImageOnUI的图像为空"); + return; + } + try { - // 释放之前的图像资源 - if (imageBox.Image != null) + // 更新图像前先检查控件是否存在/已释放 + if (this.IsDisposed || imageBox == null || imageBox.IsDisposed) { - imageBox.Image.Dispose(); + // 如果控件已释放,确保图像也被释放 + image.Dispose(); + Console.WriteLine("控件已释放,无法更新图像"); + return; } - // 设置新图像 - imageBox.Image = (Image)image.Clone(); + // 保存旧图像引用,以便在设置新图像后释放 + Image oldImage = imageBox.Image; + + try + { + // 尝试设置新图像 + imageBox.Image = image; + + // 验证图像是否成功设置 + if (imageBox.Image != image) + { + Console.WriteLine("图像设置失败,可能参数无效"); + image.Dispose(); // 释放未成功设置的图像 + return; + } + + // 仅在新图像成功设置后释放旧图像 + if (oldImage != null && oldImage != image) // 防止自引用释放 + { + oldImage.Dispose(); + } + } + catch (ArgumentException ex) when (ex.Message.Contains("参数无效")) + { + // 特别处理"参数无效"异常 + Console.WriteLine($"图像参数无效: {ex.Message}"); + image.Dispose(); // 释放无效图像 + + // 尝试设置旧图像回来,如果可能的话 + if (oldImage != null) + { + try + { + imageBox.Image = oldImage; + } + catch + { + // 如果设置旧图像也失败,释放它 + oldImage.Dispose(); + } + } + } } catch (Exception ex) { - Console.WriteLine($"更新图像失败: {ex.Message}"); + Console.WriteLine($"更新图像UI失败: {ex.Message}"); + // 确保在任何异常情况下都释放传入的图像资源 + try + { + image.Dispose(); + } + catch {} } } @@ -193,13 +405,26 @@ namespace JoyD.Windows.CS.Toprie /// private void DeviceManager_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs e) { - if (this.InvokeRequired) + // 确保在UI线程上更新,并且检查控件是否已被释放 + if (!this.IsDisposed && !this.Disposing) { - this.Invoke(new Action(() => HandleConnectionStatusChanged(e))); - } - else - { - HandleConnectionStatusChanged(e); + if (this.InvokeRequired) + { + try + { + // 使用BeginInvoke代替Invoke,避免可能的死锁问题 + this.BeginInvoke(new Action(() => HandleConnectionStatusChanged(e))); + } + catch (ObjectDisposedException) + { + // 捕获控件已释放异常,避免程序崩溃 + Console.WriteLine("控件已释放,跳过UI更新"); + } + } + else + { + HandleConnectionStatusChanged(e); + } } } @@ -208,10 +433,28 @@ namespace JoyD.Windows.CS.Toprie /// private void HandleConnectionStatusChanged(ConnectionStatusChangedEventArgs e) { + // 更新UI状态 + UpdateUIState(e.NewStatus == ConnectionStatus.Connected); + switch (e.NewStatus) { case ConnectionStatus.Connected: Console.WriteLine("设备已连接"); + + // 清除错误信息 + ShowError(string.Empty); + + // 确保设置为热图模式 + try + { + _deviceManager.SetImageMode(ImageMode.Thermal); + Console.WriteLine("连接成功后确认热图模式"); + } + catch (Exception ex) + { + Console.WriteLine($"连接成功后设置热图模式失败: {ex.Message}"); + } + if (!_isReceivingImage) { StartReceiveImage(); @@ -219,31 +462,109 @@ namespace JoyD.Windows.CS.Toprie break; case ConnectionStatus.Disconnected: Console.WriteLine("设备已断开连接"); - _isReceivingImage = false; + + // 停止接收图像 + if (_isReceivingImage) + { + _deviceManager.StopReceiveImage(); + _isReceivingImage = false; + } + if (!string.IsNullOrEmpty(e.Message)) { ShowError(e.Message); + } + else + { + ShowError("设备连接已断开"); } break; case ConnectionStatus.Connecting: case ConnectionStatus.Reconnecting: - Console.WriteLine("正在连接设备..."); + Console.WriteLine($"正在连接设备...{(!string.IsNullOrEmpty(e.Message) ? " " + e.Message : "")}"); + ShowError(string.Empty); // 清除之前的错误信息 break; } } + + /// + /// 更新UI状态 + /// + /// 是否已连接 + private void UpdateUIState(bool isConnected) + { + try + { + // 根据连接状态更新图像框状态 + if (imageBox != null && !imageBox.IsDisposed) + { + if (isConnected) + { + imageBox.BorderStyle = BorderStyle.FixedSingle; + } + else + { + imageBox.BorderStyle = BorderStyle.Fixed3D; + } + } + + // 更新图像框的边框颜色,提供视觉反馈 + if (imageBox != null) + { + imageBox.BorderStyle = isConnected ? BorderStyle.FixedSingle : BorderStyle.Fixed3D; + // 可选:设置不同的边框颜色以提供更好的视觉反馈 + if (isConnected) + { + imageBox.BackColor = Color.LightGreen; + } + else + { + imageBox.BackColor = Color.LightGray; + } + } + + Console.WriteLine($"UI状态已更新为: {(isConnected ? "已连接" : "未连接")}"); + } + catch (Exception ex) + { + Console.WriteLine($"更新UI状态时出错: {ex.Message}"); + } + } /// /// 设备管理器连接异常事件处理 /// private void DeviceManager_ConnectionException(object sender, ConnectionExceptionEventArgs e) { - if (this.InvokeRequired) + // 确保在UI线程上更新,并且检查控件是否已被释放 + if (!this.IsDisposed && !this.Disposing) { - this.Invoke(new Action(() => ShowError($"连接异常: {e.ContextMessage}"))); - } - else - { - ShowError($"连接异常: {e.ContextMessage}"); + if (this.InvokeRequired) + { + try + { + // 使用BeginInvoke在UI线程上显示错误 + this.BeginInvoke(new Action(() => + { + // 记录异常日志 + Console.WriteLine($"连接异常发生于 {e.Timestamp}: {e.ContextMessage}\n{e.Exception.Message}\n{e.Exception.StackTrace}"); + + ShowError($"连接异常: {e.ContextMessage}"); + })); + } + catch (ObjectDisposedException) + { + // 捕获控件已释放异常,避免程序崩溃 + Console.WriteLine("控件已释放,跳过异常处理"); + } + } + else + { + // 记录异常日志 + Console.WriteLine($"连接异常发生于 {e.Timestamp}: {e.ContextMessage}\n{e.Exception.Message}\n{e.Exception.StackTrace}"); + + ShowError($"连接异常: {e.ContextMessage}"); + } } } @@ -257,8 +578,19 @@ namespace JoyD.Windows.CS.Toprie // 可以根据需要添加UI上的错误显示 // 这里简化处理,只在控制台输出 + // 检查imageBox是否存在且尺寸有效 + if (imageBox == null || imageBox.Width <= 0 || imageBox.Height <= 0) + { + Console.WriteLine("imageBox尺寸无效,跳过错误显示图像创建"); + return; + } + + // 确保使用有效的尺寸创建Bitmap + int width = Math.Max(100, imageBox.Width); + int height = Math.Max(100, imageBox.Height); + // 如果需要在图像区域显示错误文字,可以使用以下代码 - using (Bitmap errorBitmap = new Bitmap(imageBox.Width, imageBox.Height)) + using (Bitmap errorBitmap = new Bitmap(width, height)) using (Graphics g = Graphics.FromImage(errorBitmap)) { g.Clear(Color.Black); @@ -272,7 +604,7 @@ namespace JoyD.Windows.CS.Toprie g.DrawString(message, font, brush, textLocation); } - UpdateImage(errorBitmap); + UpdateImageOnUI(errorBitmap); } // 启动定时器,3秒后清除错误显示 @@ -287,7 +619,19 @@ namespace JoyD.Windows.CS.Toprie { _errorDisplayTimer.Stop(); // 清除错误显示,恢复到等待图像状态 - using (Bitmap waitingBitmap = new Bitmap(imageBox.Width, imageBox.Height)) + + // 检查imageBox是否存在且尺寸有效 + if (imageBox == null || imageBox.Width <= 0 || imageBox.Height <= 0) + { + Console.WriteLine("imageBox尺寸无效,跳过等待图像创建"); + return; + } + + // 确保使用有效的尺寸创建Bitmap + int width = Math.Max(100, imageBox.Width); + int height = Math.Max(100, imageBox.Height); + + using (Bitmap waitingBitmap = new Bitmap(width, height)) using (Graphics g = Graphics.FromImage(waitingBitmap)) { g.Clear(Color.Black); @@ -302,7 +646,7 @@ namespace JoyD.Windows.CS.Toprie g.DrawString(waitingText, font, brush, textLocation); } - UpdateImage(waitingBitmap); + UpdateImageOnUI(waitingBitmap); } } @@ -330,9 +674,12 @@ namespace JoyD.Windows.CS.Toprie // 取消注册事件并释放设备管理器 if (_deviceManager != null) { + + // 移除所有事件监听 _deviceManager.ImageReceived -= DeviceManager_ImageReceived; _deviceManager.ConnectionStatusChanged -= DeviceManager_ConnectionStatusChanged; _deviceManager.ConnectionException -= DeviceManager_ConnectionException; + // 释放设备管理器资源 _deviceManager.Dispose(); _deviceManager = null; } @@ -346,7 +693,7 @@ namespace JoyD.Windows.CS.Toprie } // 无论是否在设计模式下,都需要释放图像资源 - if (imageBox.Image != null) + if (imageBox != null && !imageBox.IsDisposed && imageBox.Image != null) { imageBox.Image.Dispose(); imageBox.Image = null; diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs index 6e56918..5858900 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs @@ -1,20 +1,24 @@ using System; -using System.Linq; using System.Collections.Generic; -using System.Threading; +using System.Linq; +using System.Text; using System.Threading.Tasks; -using System.Net.NetworkInformation; +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; -using System.Text; +using Toprie; // 修改引用路径,使用本地的A8SDK.cs +using System.Net.NetworkInformation; -namespace JoyD.Windows.CS.Toprie +namespace Toprie { /// - /// 连接状态枚举 + /// 设备连接状态枚举 /// public enum ConnectionStatus { @@ -23,40 +27,44 @@ namespace JoyD.Windows.CS.Toprie Connected, Reconnecting } - + /// /// 图像模式枚举 /// public enum ImageMode { - /// - /// 热图模式(红外热成像) - /// Thermal, - /// - /// 全彩模式(可见光图像) - /// - FullColor + Visible, + Fusion } /// - /// 连接状态变更事件参数 + /// 连接状态改变事件参数 /// public class ConnectionStatusChangedEventArgs : EventArgs { - public ConnectionStatus NewStatus { get; set; } - public ConnectionStatus OldStatus { get; set; } - public string Message { get; set; } - public Exception Exception { get; set; } - public DateTime Timestamp { get; private set; } + public ConnectionStatus Status { get; set; } + public string DeviceInfo { get; set; } - public ConnectionStatusChangedEventArgs(ConnectionStatus oldStatus, ConnectionStatus newStatus, string message = null, Exception exception = null) + public ConnectionStatusChangedEventArgs(ConnectionStatus status, string deviceInfo = null) { - OldStatus = oldStatus; - NewStatus = newStatus; - Message = message; - Exception = exception; - Timestamp = DateTime.Now; + Status = status; + DeviceInfo = deviceInfo; + } + } + + /// + /// 图像接收事件参数 + /// + public class ImageReceivedEventArgs : EventArgs + { + public byte[] ImageData { get; set; } + public ImageMode Mode { get; set; } + + public ImageReceivedEventArgs(byte[] imageData, ImageMode mode) + { + ImageData = imageData; + Mode = mode; } } @@ -65,69 +73,134 @@ namespace JoyD.Windows.CS.Toprie /// public class ConnectionExceptionEventArgs : EventArgs { - public Exception Exception { get; private set; } - public string ContextMessage { get; private set; } - public DateTime Timestamp { get; private set; } + public Exception Exception { get; set; } + public string Message { get; set; } - public ConnectionExceptionEventArgs(Exception exception, string contextMessage) + public ConnectionExceptionEventArgs(Exception ex, string message = null) { - Exception = exception; - ContextMessage = contextMessage; - Timestamp = DateTime.Now; + Exception = ex; + Message = message ?? ex?.Message; } } - + /// - /// 图像数据接收事件参数 + /// 设备管理器类 /// - public class ImageReceivedEventArgs : EventArgs + /// + /// 设备信息类,用于存储设备的ID、IP等信息 + /// + public class DeviceInfo { - public Image ImageData { get; private set; } - public DateTime Timestamp { get; private set; } - - public ImageReceivedEventArgs(Image imageData) + /// + /// 设备ID + /// + public int DeviceID { get; set; } + + /// + /// 设备IP地址 + /// + public string IPAddress { get; set; } + + /// + /// 设备型号 + /// + public string Model { get; set; } + + /// + /// 设备序列号 + /// + public string SerialNumber { get; set; } + + public override string ToString() { - ImageData = imageData; - Timestamp = DateTime.Now; + return $"设备 {DeviceID} ({IPAddress})"; } } - /// - /// 设备管理类,负责设备搜索、连接和断开(纯C#实现) - /// public class DeviceManager : IDisposable { - #region 私有成员 - - // 设备IP地址 - private string _deviceIp = "192.168.100.2"; // 默认IP - private int _devicePort = 8080; // 默认端口 - + // A8SDK实例 + private A8SDK _a8Sdk; // 设备ID列表 - private List _deviceIds; - - // 是否已初始化 - private bool _isInitialized; - - // 连接状态 - private ConnectionStatus _connectionStatus = ConnectionStatus.Disconnected; + private List _deviceIds = new List(); + // 设备信息列表 + private List _deviceList = new List(); + + // 目标设备ID,用于指定ID连接 + private 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 bool _isDisposed = false; // 图像模式 private ImageMode _currentImageMode = ImageMode.Thermal; - - // 自动重连相关 - private bool _isAutoReconnectEnabled = false; - private int _reconnectInterval = 5000; // 默认5秒 - private int _maxReconnectAttempts = 5; - private int _reconnectAttempts = 0; + // 自动重连是否启用 + private bool _autoReconnectEnabled = true; + // 自动重连定时器 private System.Threading.Timer _reconnectTimer; - private ManualResetEventSlim _stopRequested = new ManualResetEventSlim(false); - - // 心跳检测相关 + // 重连间隔(毫秒) + private int _reconnectInterval = 2000; + // 重连尝试次数 + private int _reconnectAttempts = 0; + // 最大重连尝试次数 + private const int MaxReconnectAttempts = 5; + // 连接检查定时器 + private System.Threading.Timer _connectionCheckTimer; + // 心跳定时器 private System.Threading.Timer _heartbeatTimer; - private int _heartbeatInterval = 30000; // 30秒心跳 - - #endregion 私有成员 + // 连接超时时间(毫秒) + private const int ConnectionTimeout = 5000; + // 连接检查间隔(毫秒) + private const int ConnectionCheckInterval = 5000; + // 心跳间隔(毫秒) + private int _heartbeatInterval = 5000; + // TCP客户端 + private TcpClient _imageTcpClient; + // 网络流 + private NetworkStream _imageNetworkStream; + // 图像接收任务 + private Task _imageReceivingTask; + // 取消令牌源 + private CancellationTokenSource _imageReceivingCts; + // 停止请求事件 + private ManualResetEvent _stopRequested = new ManualResetEvent(false); + // 缓冲区 + private byte[] _imageBuffer = new byte[4096]; + // 图像数据累积缓冲区 + private byte[] _imageDataAccumulator = new byte[0]; + // 多部分请求边界 + private string _multipartBoundary = string.Empty; + // 锁对象 + private object _lockObject = new object(); + // 是否启用自动重连 + private bool _isAutoReconnectEnabled = true; + // 最大重连次数 + private int _maxReconnectAttempts = 5; + // 是否已连接 + private bool _isConnected = false; + // 连接超时设置 + private 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 TcpClient _imageTcpClient; + private bool _isInfraredMode = true; #region 私有方法 @@ -161,11 +234,59 @@ namespace JoyD.Windows.CS.Toprie /// 相关异常(如果有) private void UpdateConnectionStatus(ConnectionStatus newStatus, string message = null, Exception exception = null) { + // 检查对象是否已被释放 + if (_isDisposed) + { + Console.WriteLine("警告: 尝试在已释放对象上更新连接状态"); + return; + } + if (_connectionStatus != newStatus) { ConnectionStatus oldStatus = _connectionStatus; _connectionStatus = newStatus; - OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(oldStatus, newStatus, message, exception)); + + // 触发连接状态变更事件前再次检查对象是否已被释放 + if (!_isDisposed) + { + try + { + OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(newStatus, message)); + } + catch (Exception ex) + { + Console.WriteLine($"触发连接状态变更事件异常: {ex.Message}"); + } + } + + Console.WriteLine($"连接状态变更: {oldStatus} -> {newStatus}"); + if (!string.IsNullOrEmpty(message)) + { + Console.WriteLine($"状态消息: {message}"); + } + if (exception != null) + { + Console.WriteLine($"异常信息: {exception.Message}"); + } + + // 如果断开连接且启用了自动重连,启动重连机制 + if (newStatus == ConnectionStatus.Disconnected && _autoReconnectEnabled && oldStatus != ConnectionStatus.Connecting) + { + StartAutoReconnect(); + } + // 如果连接成功,重置重连计数并启动图像接收 + else if (newStatus == ConnectionStatus.Connected) + { + _currentReconnectAttempt = 0; + StopAutoReconnect(); + // 启动图像接收 + StartImageReceiving(); + } + // 如果断开连接,停止图像接收 + else if (newStatus == ConnectionStatus.Disconnected) + { + StopImageReceiving(); + } } } @@ -175,16 +296,33 @@ namespace JoyD.Windows.CS.Toprie /// 是否初始化成功 private bool Initialize() { + if (_isInitialized) + return true; + try { - _deviceIds = new List(); + // 初始化设备ID列表 + _deviceIds = new List(); // 保持与类中定义一致 _reconnectTimer = null; _heartbeatTimer = null; + _connectionCheckTimer = null; + + // 首先调用SDK静态初始化方法 - 这是连接成功的关键步骤! + int initResult = A8SDK.SDK_initialize(); + if (initResult != 0) + { + Console.WriteLine($"SDK静态初始化失败,返回值: {initResult}"); + _isInitialized = false; + return false; + } + + Console.WriteLine("SDK静态初始化成功"); _isInitialized = true; return true; } catch (Exception ex) { + Console.WriteLine($"SDK初始化失败: {ex.Message}"); _isInitialized = false; OnConnectionException(new ConnectionExceptionEventArgs(ex, "初始化设备管理器失败")); return false; @@ -192,22 +330,65 @@ namespace JoyD.Windows.CS.Toprie } /// - /// 检查IP是否可达 + /// 尝试ping设备IP地址 /// - /// IP地址 - /// 是否可达 - private bool IsIpReachable(string ipAddress) + /// 设备IP地址 + /// 是否ping通 + // PingDevice方法已在下方定义,删除重复实现 + + /// + /// 检查系统网络连接是否可用 + /// + /// 网络是否可用 + private bool IsNetworkAvailable() { try { - using (Ping ping = new Ping()) + // 检查系统网络连接状态 + System.Net.NetworkInformation.NetworkInterface[] interfaces = + System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); + + // 检查是否有活动的以太网或WiFi连接 + foreach (var ni in interfaces) { - PingReply reply = ping.Send(ipAddress, 1000); // 1秒超时 - return reply.Status == IPStatus.Success; + // 仅检查物理网络接口(以太网、WiFi等) + if (ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up && + (ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Ethernet || + ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Wireless80211) && + ni.Name != "本地连接*" && !ni.Description.Contains("虚拟") && !ni.Description.Contains("Virtual")) + { + // 获取该网络接口的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 + { + // 检查设备IP是否与当前网络接口在同一子网(粗略判断) + var deviceAddress = System.Net.IPAddress.Parse(_deviceIp); + if (deviceAddress.GetAddressBytes()[0] == addr.Address.GetAddressBytes()[0]) + { + return true; + } + } + catch { } + } + return true; + } + } + } } + return false; } - catch (Exception) + catch (Exception ex) { + Console.WriteLine($"检查网络可用性异常: {ex.Message}"); + // 发生异常时默认为网络不可用,避免误判 return false; } } @@ -259,6 +440,246 @@ namespace JoyD.Windows.CS.Toprie { ConnectionStatusChanged?.Invoke(this, e); } + + /// + /// 启动连接状态检查 + /// + private void StartConnectionCheck() + { + try + { + // 首先停止现有的连接检查,确保资源释放 + StopConnectionCheck(); + + Console.WriteLine("启动连接状态检查,每5秒检查一次"); + + // 使用try-catch包装定时器创建,避免异常 + try + { + // 每5秒检查一次连接状态 + _connectionCheckTimer = new System.Threading.Timer(state => + { + // 确保定时器状态检查在工作线程中安全执行 + try + { + // 避免在定时器回调中可能的资源访问冲突 + if (_connectionCheckTimer == null) + return; + + // 关键修改:即使_isInitialized为false,也需要检查连接状态 + // 当显示为已连接但初始化状态为false时,必须检查连接 + if (_connectionStatus == ConnectionStatus.Connected) + { + // 如果初始化失败但显示已连接,尝试重新初始化 + if (!_isInitialized) + { + Console.WriteLine("警告: 显示已连接但初始化状态为false,尝试重新初始化..."); + if (!Initialize()) + { + Console.WriteLine("重新初始化失败,确认连接已断开"); + UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备未初始化,连接已断开"); + // 启动自动重连 + if (_autoReconnectEnabled) + { + StartAutoReconnect(); + } + } + } + + // 无论初始化状态如何,只要显示为已连接就进行检查 + CheckConnectionWrapper(); + } + // 正常情况下的连接检查 + else if (_isInitialized && _a8Sdk != null && _currentDeviceId != -1) + { + CheckConnectionWrapper(); + } + } + catch (Exception ex) + { + Console.WriteLine($"定时器回调异常: {ex.Message}"); + // 异常时如果是已连接状态,将其设为断开 + if (_connectionStatus == ConnectionStatus.Connected) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接检查异常", ex); + _isInitialized = false; + // 启动自动重连 + if (_autoReconnectEnabled) + { + StartAutoReconnect(); + } + } + // 发生异常时停止定时器,避免持续报错 + StopConnectionCheck(); + } + }, null, 5000, 5000); + } + catch (Exception ex) + { + Console.WriteLine($"创建连接检查定时器失败: {ex.Message}"); + _connectionCheckTimer = null; + } + } + catch (Exception ex) + { + Console.WriteLine($"启动连接检查异常: {ex.Message}"); + StopConnectionCheck(); + } + } + + // 连接检查的安全包装方法 + private void CheckConnectionWrapper() + { + try + { + // 检查连接有效性 + bool isStillConnected = CheckConnectionValidity(); + + // 连接状态变化时更新状态 + if (!isStillConnected && _connectionStatus == ConnectionStatus.Connected) + { + Console.WriteLine("检测到连接已断开"); + // 断开连接时重置初始化状态和SDK实例 + _isInitialized = false; + _a8Sdk = null; + UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接已断开:设备离线"); + // 断开连接后自动启动重连 + if (_autoReconnectEnabled) + { + Console.WriteLine("连接断开,启动自动重连"); + StartAutoReconnect(); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"检查连接状态异常: {ex.Message}"); + // 检查异常时,将状态设为断开 + if (_connectionStatus == ConnectionStatus.Connected) + { + // 异常时重置初始化状态和SDK实例 + _isInitialized = false; + _a8Sdk = null; + UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接状态检查异常", ex); + // 异常时启动自动重连 + if (_autoReconnectEnabled) + { + Console.WriteLine("检查异常,启动自动重连"); + StartAutoReconnect(); + } + } + } + } + + /// + /// 检查连接有效性的内部方法 + /// + /// 连接是否有效 + private bool CheckConnectionValidity() + { + try + { + // 重要修改:先检查设备ID有效性,避免后续无效检查 + if (_currentDeviceId == -1) + { + Console.WriteLine("当前设备ID无效"); + return false; + } + + // 优化1: 优先尝试ping设备IP地址,这是物理连接断开的最直接检测 + if (!PingDevice(_deviceIp)) + { + Console.WriteLine($"设备IP {_deviceIp} 不可达,物理连接可能已断开"); + return false; + } + + // 优化2: 系统网络接口检测作为辅助检查,不再作为首要条件 + if (!IsNetworkAvailable()) + { + Console.WriteLine("警告: 系统网络连接不可用,但ping检测通过"); + } + + // 优化3: 加强SDK连接状态验证,参考热像仪示例的实现方式 + try + { + // 确保SDK实例存在 + if (_a8Sdk == null) + { + Console.WriteLine("SDK实例不存在,重新初始化..."); + _a8Sdk = new A8SDK(_deviceIp); + } + + // 简化心跳检测逻辑,避免在心跳失败时过度访问可能无效的资源 + bool heartbeatResult = _a8Sdk.Heartbeat(); + if (!heartbeatResult) + { + Console.WriteLine("SDK心跳检测失败"); + // 心跳失败时,安全释放SDK实例 + if (_a8Sdk != null) + { + _a8Sdk = null; + } + return false; + } + Console.WriteLine("SDK心跳检测成功"); + + // 设备名称获取使用try-catch,避免在心跳成功但设备响应异常时崩溃 + try + { + // 如果SDK支持获取设备名称,可以尝试获取 + // string deviceName = _a8Sdk.Get_device_name(); + // if (string.IsNullOrEmpty(deviceName)) + // { + // Console.WriteLine("设备名称获取失败,可能连接不稳定"); + // } + } + catch (Exception ex) + { + Console.WriteLine($"获取设备名称异常,但心跳检测成功: {ex.Message}"); + // 设备名称获取失败不应导致整体连接失效 + } + } + catch (Exception ex) + { + Console.WriteLine($"SDK心跳检测失败: {ex.Message}"); + return false; + } + + // 所有检查通过,连接有效 + return true; + } + catch (Exception ex) + { + Console.WriteLine($"连接有效性检查异常: {ex.Message}"); + return false; + } + } + + /// + /// 停止连接状态检查 + /// + private void StopConnectionCheck() + { + try + { + // 使用局部变量暂存,避免多线程访问时的空引用问题 + System.Threading.Timer timerToDispose = Interlocked.Exchange(ref _connectionCheckTimer, null); + if (timerToDispose != null) + { + // 立即停止定时器,避免回调执行 + timerToDispose.Change(Timeout.Infinite, Timeout.Infinite); + // 安全释放定时器资源 + timerToDispose.Dispose(); + Console.WriteLine("连接检查已停止"); + } + } + catch (Exception ex) + { + Console.WriteLine($"停止连接检查异常: {ex.Message}"); + // 确保即使异常,引用也被清空 + _connectionCheckTimer = null; + } + } /// /// 触发连接异常事件 @@ -427,6 +848,520 @@ namespace JoyD.Windows.CS.Toprie #endregion 公共属性 + #region 图像接收方法 + + /// + /// 开始接收图像数据 + /// + public void StartImageReceiving() + { + try + { + // 确保之前的连接已关闭 + StopImageReceiving(); + + // 重置停止事件 + _stopImageEvent = new ManualResetEvent(false); + _isReceivingImages = true; + + // 创建并启动图像接收线程 + _imageReceiveThread = new Thread(ReceiveImageDataWithHttpWebRequest) + { + IsBackground = true, + Name = "ImageReceiveThread" + }; + _imageReceiveThread.Start(); + } + catch (Exception ex) + { + // 记录异常 + Console.WriteLine("StartImageReceiving error: " + ex.Message); + _isReceivingImages = false; + } + } + + /// + /// 停止接收图像数据 + /// + public void StopImageReceiving() + { + try + { + _isReceivingImages = false; + + // 通知线程停止 + if (_stopImageEvent != null) + { + _stopImageEvent.Set(); + } + + // 等待线程结束 + if (_imageReceiveThread != null && _imageReceiveThread.IsAlive) + { + _imageReceiveThread.Join(3000); // 最多等待3秒 + _imageReceiveThread = null; + } + + // 停止重连线程 + if (_imageReconnectThread != null && _imageReconnectThread.IsAlive) + { + _imageReconnectThread.Join(1000); + _imageReconnectThread = null; + } + + // 释放资源 + if (_imageStream != null) + { + _imageStream.Close(); + _imageStream.Dispose(); + _imageStream = null; + } + + if (_imageTcpClient != null) + { + _imageTcpClient.Close(); + _imageTcpClient = null; + } + } + catch (Exception ex) + { + Console.WriteLine("StopImageReceiving error: " + ex.Message); + } + } + + /// + /// 使用HttpWebRequest接收图像数据 + /// + private void ReceiveImageDataWithHttpWebRequest() + { + try + { + string url = string.Format("http://{0}:8080{1}", DeviceIp, _isInfraredMode ? "/img.jpg" : "/visible.jpg"); + while (_isReceivingImages) + { + // 添加时间戳避免缓存 + 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"; // 模拟浏览器 + + 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); + StartImageReconnect(); + } + } + } + + /// + /// 处理接收到的图像数据 + /// + /// 原始数据 + private void ProcessReceivedImageData(byte[] data) + { + if (data == null || data.Length == 0) + return; + + // 检查是否为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); + } + } + + /// + /// 验证并处理图像数据 + /// + /// 图像数据 + 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 receivedImage = Image.FromStream(ms); + OnImageReceived(new ImageReceivedEventArgs(imageData, _currentImageMode)); + return; + } + } + } + + // 对于其他格式或无法确认完整度的情况,直接尝试创建 + Image receivedImage = Image.FromStream(ms); + OnImageReceived(new ImageReceivedEventArgs(imageData, _currentImageMode)); + } + } + catch (Exception ex) + { + 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 receivedImage = Image.FromStream(ms); + OnImageReceived(new ImageReceivedEventArgs(validImageData, _currentImageMode)); + } + } + catch + { + // 二次尝试也失败,放弃处理 + } + } + } + } + } + + /// + /// 启动图像重连 + /// + private void StartImageReconnect() + { + // 避免重复创建重连线程 + 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 图像处理辅助方法 + + /// + /// 查找图像数据的结束位置(特别是JPEG的EOF标记) + /// + /// 数据缓冲区 + /// 开始查找的位置 + /// 图像结束位置索引,-1表示未找到 + 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; + } + + /// + /// 检查数据是否为HTTP响应 + /// + /// 待检查的数据 + /// 是否为HTTP响应 + 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/"); + } + + /// + /// 查找图像数据的开始位置,跳过HTTP头部 + /// + /// 数据缓冲区 + /// 图像数据开始位置索引,-1表示未找到 + 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; + } + + /// + /// 从HTTP响应中提取Content-Type + /// + /// HTTP响应数据 + /// Content-Type字符串 + 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; + } + + /// + /// 从Content-Type中提取multipart boundary + /// + /// Content-Type字符串 + /// boundary字符串 + 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; + } + + /// + /// 处理multipart格式的图像数据 + /// + /// 数据缓冲区 + /// multipart boundary + /// 原始图像数据缓冲区 + /// 处理到的位置索引,-1表示未找到完整的图像块 + 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 receivedImage = Image.FromStream(ms); + OnImageReceived(new ImageReceivedEventArgs(receivedImage, _currentImageMode)); + } + return nextBoundaryPos; + } + catch (Exception ex) + { + Console.WriteLine($"处理multipart图像失败: {ex.Message}"); + } + } + } + return nextBoundaryPos; + } + nextBoundaryPos++; + } + break; + } + startPos++; + } + return -1; + } + + #endregion 图像处理辅助方法 + #region 视频流处理方法 /// @@ -439,26 +1374,26 @@ namespace JoyD.Windows.CS.Toprie } /// - /// 发送模式变更命令 + /// 发送模式切换命令 /// /// 图像模式 - private void SendModeChangeCommand(ImageMode mode) + private bool SendModeChangeCommand(ImageMode mode) { try { - // 使用HTTP请求来切换图像模式 - string modePath = mode == ImageMode.Thermal ? "thermal.jpg" : "color.jpg"; - string url = $"http://{_deviceIp}:{_devicePort}/{modePath}?mode={mode}"; - - using (WebClient client = new WebClient()) + if (_a8Sdk != null) { - client.DownloadData(url); // 发送请求但不处理响应 + // 根据当前图像模式设置SDK的图像模式 + bool success = _a8Sdk.SetImageMode(mode); + return success; } + return false; } catch (Exception ex) { Console.WriteLine($"切换图像模式失败: {ex.Message}"); - OnConnectionException(new ConnectionExceptionEventArgs(ex, "发送模式变更命令失败")); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "切换图像模式失败")); + return false; } } @@ -496,60 +1431,38 @@ namespace JoyD.Windows.CS.Toprie { try { + // 持续获取图像数据 while (!_stopRequested.IsSet) { - try + if (_a8Sdk != null) { - // 根据当前模式选择图像路径 - string imagePath = _currentImageMode == ImageMode.Thermal ? "thermal.jpg" : "color.jpg"; - string url = $"http://{_deviceIp}:{_devicePort}/{imagePath}?{DateTime.Now.Ticks}"; - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); - request.Timeout = 5000; // 5秒超时 - request.Method = "GET"; - request.KeepAlive = true; - - using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + try { - if (response.StatusCode == HttpStatusCode.OK) + // 使用A8SDK获取图像数据 + byte[] imageData = _a8Sdk.GetImageData(); + + if (imageData != null && imageData.Length > 0 && IsValidImageData(imageData)) { - using (Stream stream = response.GetResponseStream()) + // 创建内存流 + using (MemoryStream ms = new MemoryStream(imageData)) { - using (MemoryStream memoryStream = new MemoryStream()) + // 创建图像 + using (Image image = Image.FromStream(ms)) { - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) - { - memoryStream.Write(buffer, 0, bytesRead); - } - - byte[] imageData = memoryStream.ToArray(); - if (IsValidImageData(imageData)) - { - using (MemoryStream imageStream = new MemoryStream(imageData)) - { - Image image = Image.FromStream(imageStream); - OnImageReceived(new ImageReceivedEventArgs(image)); - } - } + // 触发图像接收事件 + OnImageReceived(new ImageReceivedEventArgs(image.Clone() as Image)); } } } } - } - catch (WebException webEx) - { - // 处理超时等网络错误 - if (webEx.Status == WebExceptionStatus.Timeout) + catch (Exception ex) { - Console.WriteLine("图像接收超时"); - } - else if (webEx.Status == WebExceptionStatus.ConnectFailure) - { - Console.WriteLine("图像连接失败,可能设备已断开"); - // 触发连接断开事件 - UpdateConnectionStatus(ConnectionStatus.Disconnected, "图像连接失败,设备可能已断开", webEx); + Console.WriteLine($"SDK获取图像失败: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "SDK获取图像数据异常")); + + // 检查连接状态 + UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK获取图像失败,设备可能已断开", ex); + // 如果启用了自动重连,开始重连 if (_isAutoReconnectEnabled) { @@ -557,18 +1470,8 @@ namespace JoyD.Windows.CS.Toprie } break; // 退出图像接收线程 } - else - { - Console.WriteLine($"图像接收错误: {webEx.Message}"); - OnConnectionException(new ConnectionExceptionEventArgs(webEx, "图像接收过程中发生异常")); - } } - catch (Exception ex) - { - Console.WriteLine($"图像处理错误: {ex.Message}"); - OnConnectionException(new ConnectionExceptionEventArgs(ex, "图像处理过程中发生异常")); - } - + // 短暂休眠,控制帧率 Thread.Sleep(100); } @@ -639,30 +1542,65 @@ namespace JoyD.Windows.CS.Toprie { if (!_isInitialized && !Initialize()) { + Console.WriteLine("设备管理器未初始化,无法搜索设备"); return new List(); } List foundDevices = new List(); _deviceIds.Clear(); + _deviceList.Clear(); - // 1. 尝试UDP广播搜索 - List discoveredIps = BroadcastSearch(); + // 使用A8SDK的静态方法进行设备搜索 + List deviceIds = A8SDK.SearchDevices(); - // 2. 如果UDP广播没有找到设备,尝试在本地网络扫描 - if (discoveredIps.Count == 0) - { - discoveredIps = ScanLocalNetwork(); - } - - // 为每个发现的IP分配一个设备ID + Console.WriteLine($"搜索到 {deviceIds.Count} 个设备"); + + // 将SDK返回的设备ID和IP转换为我们的格式 int deviceId = 1; - foreach (string ip in discoveredIps) + foreach (string id in deviceIds) { - _deviceIds.Add(deviceId); - foundDevices.Add(deviceId); - deviceId++; + 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) @@ -678,128 +1616,86 @@ namespace JoyD.Windows.CS.Toprie /// public Task> SearchDevicesAsync() { - return Task.Factory.StartNew(() => SearchDevices()); - } - - /// - /// UDP广播搜索设备 - /// - /// 发现的设备IP列表 - private List BroadcastSearch() - { - List discoveredIps = new List(); - - try + return Task.Factory.StartNew(() => { - // 使用UDP广播查找设备 - using (UdpClient udpClient = new UdpClient()) + try { - udpClient.EnableBroadcast = true; - udpClient.Client.ReceiveTimeout = 2000; + if (!_isInitialized && !Initialize()) + { + Console.WriteLine("设备管理器未初始化,无法搜索设备"); + return new List(); + } - // 广播地址和端口 - IPEndPoint broadcastEndpoint = new IPEndPoint(IPAddress.Broadcast, 8080); + List foundDevices = new List(); + _deviceIds.Clear(); + _deviceList.Clear(); + + // 使用A8SDK的静态方法进行设备搜索 + List deviceIds = A8SDK.SearchDevices(); - // 发送搜索命令 - byte[] searchCommand = Encoding.ASCII.GetBytes("SEARCH_DEVICE"); - udpClient.Send(searchCommand, searchCommand.Length, broadcastEndpoint); - - // 接收响应 - IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0); + Console.WriteLine($"搜索到 {deviceIds.Count} 个设备"); - try + // 将SDK返回的设备ID和IP转换为我们的格式 + int deviceId = 1; + foreach (string id in deviceIds) { - byte[] response = udpClient.Receive(ref remoteEndpoint); - string responseString = Encoding.ASCII.GetString(response); - - // 检查响应是否来自我们的设备 - if (responseString.Contains("DEVICE_FOUND")) + try { - discoveredIps.Add(remoteEndpoint.Address.ToString()); - } - } - catch (SocketException ex) - { - // 超时异常,正常处理 - if (ex.SocketErrorCode != SocketError.TimedOut) - { - Console.WriteLine($"UDP接收异常: {ex.Message}"); - } - } - } - - // 额外检查默认IP是否可达 - if (IsIpReachable(_deviceIp)) - { - if (!discoveredIps.Contains(_deviceIp)) - { - discoveredIps.Add(_deviceIp); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"UDP广播搜索失败: {ex.Message}"); - } - - return discoveredIps; - } - - /// - /// 扫描本地网络 - /// - /// 发现的设备IP列表 - private List ScanLocalNetwork() - { - List discoveredIps = new List(); - - try - { - // 获取本地IP地址和子网 - foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) - { - if (ni.OperationalStatus == OperationalStatus.Up && ni.NetworkInterfaceType != NetworkInterfaceType.Loopback) - { - foreach (UnicastIPAddressInformation ipInfo in ni.GetIPProperties().UnicastAddresses) - { - if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork) + // 假设ID字符串中包含IP信息,格式可能是 "设备ID|IP地址" + string[] parts = id.Split('|'); + string ipAddress = parts.Length > 1 ? parts[1] : "未知IP"; + + // 创建设备信息对象 + DeviceInfo deviceInfo = new DeviceInfo { - IPAddress ip = ipInfo.Address; - IPAddress subnet = GetSubnetMask(ipInfo.IPv4Mask); - - // 扫描常见的设备IP范围 - // 这里简化处理,只扫描几个常用的IP - string[] commonDeviceIps = new string[] - { - "192.168.100.2", // 默认IP - "192.168.1.10", - "192.168.0.10", - "10.0.0.10" - }; - - foreach (string deviceIp in commonDeviceIps) - { - if (IsIpReachable(deviceIp)) - { - if (!discoveredIps.Contains(deviceIp)) - { - discoveredIps.Add(deviceIp); - } - } - } - } + 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}"); + // 继续处理下一个设备 } } - } - } - catch (Exception ex) - { - Console.WriteLine($"本地网络扫描失败: {ex.Message}"); - } - return discoveredIps; + // 如果通过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(); + } + }); } + // BroadcastSearch方法已被A8SDK.SearchDevices替代 + + // ScanLocalNetwork方法已被A8SDK.SearchDevices替代 + /// /// 获取子网掩码 /// @@ -815,116 +1711,278 @@ namespace JoyD.Windows.CS.Toprie /// /// 是否连接成功 public bool ConnectDevice() + { + // 默认使用ID=1连接设备 + ConnectDevice(1); + return _connectionStatus == ConnectionStatus.Connected; + } + + public void ConnectDevice(int deviceId) { try { + // 取消之前的连接操作 + if (_connectCancellationTokenSource != null) + { + _connectCancellationTokenSource.Cancel(); + _connectCancellationTokenSource.Dispose(); + } + + // 更新状态为连接中 UpdateConnectionStatus(ConnectionStatus.Connecting, "开始连接设备..."); - - // 确保已初始化 - if (!_isInitialized && !Initialize()) - { - UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备管理器初始化失败"); - return false; - } - // 停止任何现有的重连定时器 - StopAutoReconnect(); + _connectCancellationTokenSource = new CancellationTokenSource(); + CancellationToken token = _connectCancellationTokenSource.Token; - // 停止任何正在进行的连接检查 - StopAllImageReceivers(); - - // 检查IP是否可达 - if (!IsIpReachable(_deviceIp)) + // 使用Timer实现超时取消 + System.Threading.Timer timeoutTimer = null; + timeoutTimer = new System.Threading.Timer((state) => { - UpdateConnectionStatus(ConnectionStatus.Disconnected, $"设备IP {_deviceIp} 不可达"); - if (_isAutoReconnectEnabled) + if (_connectCancellationTokenSource != null && !_connectCancellationTokenSource.IsCancellationRequested) { - StartAutoReconnect(); + _connectCancellationTokenSource.Cancel(); + Console.WriteLine($"连接超时,自动取消连接操作"); } - return false; - } + timeoutTimer.Dispose(); + }, null, _connectTimeout, Timeout.Infinite); - // 尝试TCP连接测试 - bool tcpConnected = TestTcpConnection(_deviceIp, _devicePort); - if (!tcpConnected) + try { - UpdateConnectionStatus(ConnectionStatus.Disconnected, $"TCP连接到 {_deviceIp}:{_devicePort} 失败"); - if (_isAutoReconnectEnabled) + bool result = false; + if (!token.IsCancellationRequested) { - StartAutoReconnect(); - } - return false; - } + try + { + // 确保已初始化 + if (!_isInitialized && !Initialize()) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备管理器初始化失败"); + return; + } - // 发送连接命令 - bool commandSent = SendConnectCommand(); - if (!commandSent) + // 停止任何现有的重连定时器 + 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}"); + + // 先检测IP可达性,避免不必要的SDK初始化 + if (!PingDevice(_deviceIp)) + { + Console.WriteLine($"设备IP {_deviceIp} 不可达,连接失败"); + throw new Exception($"设备IP {_deviceIp} 不可达"); + } + + // 创建SDK实例 + _a8Sdk = new A8SDK(_deviceIp); + Console.WriteLine("SDK实例创建完成"); + + // 验证连接是否成功(通过心跳检测) + if (!_a8Sdk.Heartbeat()) + { + Console.WriteLine("心跳检测失败"); + // 安全释放SDK实例 + if (_a8Sdk != null) + { + _a8Sdk = null; + } + throw new Exception("心跳检测失败"); + } + Console.WriteLine("心跳检测成功,设备连接有效"); + + // 连接成功 + result = true; + + // 安全地设置设备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; + } + 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; + _reconnectAttempts = 0; + + // 启动心跳检测和连接检查 + StartHeartbeat(); + StartConnectionCheck(); + + UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接成功"); + } + else if (!token.IsCancellationRequested) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK连接失败,心跳检测未通过"); + _a8Sdk = null; + } + } + finally { - UpdateConnectionStatus(ConnectionStatus.Disconnected, "发送连接命令失败"); - if (_isAutoReconnectEnabled) + // 确保即使发生异常,也停止定时器 + if (timeoutTimer != null) { - StartAutoReconnect(); + timeoutTimer.Dispose(); } - return false; } - - // 启动心跳检测 - StartHeartbeat(); - - UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接成功"); - - // 重置重连尝试计数 - _reconnectAttempts = 0; - - return true; } catch (Exception ex) { Console.WriteLine($"连接设备失败: {ex.Message}"); UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接设备时发生异常", ex); OnConnectionException(new ConnectionExceptionEventArgs(ex, "连接设备失败")); - - // 如果启用了自动重连,开始重连 - if (_isAutoReconnectEnabled) - { - StartAutoReconnect(); - } - - return false; + } + + // 如果连接失败且启用了自动重连,开始重连 + if (_connectionStatus != ConnectionStatus.Connected && _isAutoReconnectEnabled) + { + StartAutoReconnect(); } } - + /// - /// 测试TCP连接 + /// 连接到指定设备(异步版本) /// - /// IP地址 - /// 端口 - /// 是否连接成功 - private bool TestTcpConnection(string ip, int port) + public Task ConnectAsync() + { + var taskCompletionSource = new TaskCompletionSource(); + + try + { + ConnectDevice(); + taskCompletionSource.SetResult(_connectionStatus == ConnectionStatus.Connected); + } + catch (Exception ex) + { + taskCompletionSource.SetException(ex); + } + + return taskCompletionSource.Task; + } + + /// + /// 根据设备ID连接设备 + /// + /// 设备ID + public void ConnectDevice(int deviceId) { try { - using (TcpClient client = new TcpClient()) + _targetDeviceId = deviceId; + + // 检查是否已有设备列表 + if (_deviceList != null && _deviceList.Count > 0) { - IAsyncResult result = client.BeginConnect(ip, port, null, null); - bool success = result.AsyncWaitHandle.WaitOne(2000); // 2秒超时 - if (success) + // 查找指定ID的设备 + var device = _deviceList.FirstOrDefault(d => d.DeviceID == deviceId); + if (device != null) { - client.EndConnect(result); - return true; - } - else - { - client.Close(); - return false; + _deviceIp = device.IPAddress; + ConnectDevice(); + return; } } + + // 如果找不到设备,先搜索设备 + SearchDevicesAsync(); + + // 延迟后尝试连接 + Task.Delay(500).ContinueWith(t => + { + if (_deviceList != null && _deviceList.Count > 0) + { + var device = _deviceList.FirstOrDefault(d => d.DeviceID == deviceId); + if (device != null) + { + _deviceIp = device.IPAddress; + ConnectDevice(); + } + } + }); } - catch (Exception) + catch (Exception ex) { - return false; + UpdateConnectionStatus(ConnectionStatus.Disconnected, $"通过设备ID连接异常: {ex.Message}", ex); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "通过设备ID连接失败")); } } + + /// + /// 断开设备连接(兼容Form1) + /// + public void DisconnectDevice() + { + Disconnect(); + } + + // TestTcpConnection方法不再需要,由A8SDK内部处理连接测试 /// /// 发送连接命令 @@ -965,22 +2023,51 @@ namespace JoyD.Windows.CS.Toprie { try { - // 停止重连定时器 + // 停止自动重连和连接检查 StopAutoReconnect(); + StopConnectionCheck(); - // 停止心跳检测 - StopHeartbeat(); - - // 停止图像接收 - StopAllImageReceivers(); - - // 设置断开状态 - UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备已断开连接"); + // 取消正在进行的连接操作 + 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}"); - OnConnectionException(new ConnectionExceptionEventArgs(ex, "断开连接失败")); + Console.WriteLine($"断开连接异常: {ex.Message}"); + UpdateConnectionStatus(ConnectionStatus.Disconnected, "断开连接时发生异常", ex); + OnConnectionException(new ConnectionExceptionEventArgs( + ex, "断开连接异常")); } } @@ -996,15 +2083,69 @@ namespace JoyD.Windows.CS.Toprie _reconnectTimer = new System.Threading.Timer(ReconnectCallback, null, 0, _reconnectInterval); } + /// + /// 启动定期连接状态检查 + /// + /// 检查间隔(毫秒) + public void StartPeriodicConnectionCheck(int checkInterval) + { + try + { + // 停止现有的检查 + 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, "启动连接检查失败")); + } + } + /// /// 停止自动重连 /// private void StopAutoReconnect() { - if (_reconnectTimer != null) + try { - _reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite); - _reconnectTimer.Dispose(); + // 使用局部变量暂存,避免多线程访问时的空引用问题 + System.Threading.Timer timerToDispose = Interlocked.Exchange(ref _reconnectTimer, null); + if (timerToDispose != null) + { + // 立即停止定时器,避免回调执行 + timerToDispose.Change(Timeout.Infinite, Timeout.Infinite); + // 安全释放定时器资源 + timerToDispose.Dispose(); + Console.WriteLine("自动重连已停止"); + } + + // 重置重连尝试次数 + _currentReconnectAttempt = 0; + } + catch (Exception ex) + { + Console.WriteLine($"停止自动重连异常: {ex.Message}"); + // 确保即使异常,引用也被清空 _reconnectTimer = null; } } @@ -1017,35 +2158,184 @@ namespace JoyD.Windows.CS.Toprie { try { - // 检查是否达到最大重连次数 - if (_maxReconnectAttempts > 0 && _reconnectAttempts >= _maxReconnectAttempts) + _currentReconnectAttempt++; + Console.WriteLine($"自动重连尝试 {_currentReconnectAttempt}"); + + UpdateConnectionStatus(ConnectionStatus.Reconnecting, + $"尝试自动重连..."); + + bool connectionSuccessful = false; + + // 仅用IP地址重连设备,这样更直接更快 + if (!string.IsNullOrEmpty(_deviceIp)) { - UpdateConnectionStatus(ConnectionStatus.Disconnected, $"达到最大重连次数 {_maxReconnectAttempts}"); + Console.WriteLine($"用IP地址 {_deviceIp} 重连设备"); + try + { + // 检查IP是否可达 + if (PingDevice(_deviceIp)) + { + // 注意:这里需要先释放旧实例,避免资源泄漏 + if (_a8Sdk != null) + { + try + { + // 调用静态SDK_destroy方法释放全局资源 + // A8SDK.SDK_destroy(); + Console.WriteLine("重连前SDK全局资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"释放旧SDK实例资源时发生异常: {ex.Message}"); + } + finally + { + _a8Sdk = null; // 确保引用被清空 + } + } + + // 创建新的SDK实例 + Console.WriteLine($"创建新的SDK实例,目标IP: {_deviceIp}"); + _a8Sdk = new A8SDK(_deviceIp); + + // 添加心跳检测重试机制,适应网络不稳定情况 + bool isConnected = false; + int maxHeartbeatRetries = 3; // 心跳检测最大重试次数 + + for (int retry = 0; retry < maxHeartbeatRetries; retry++) + { + try + { + Console.WriteLine($"尝试建立连接并发送心跳包...(尝试 {retry + 1}/{maxHeartbeatRetries})"); + + // 添加延时,给SDK实例初始化一些时间 + if (retry > 0) + { + Console.WriteLine("等待500ms后重试..."); + Thread.Sleep(500); + } + + // 发送心跳包验证连接 + bool heartbeatResult = _a8Sdk.Heartbeat(); + if (heartbeatResult) + { + Console.WriteLine("心跳检测成功!"); + isConnected = true; + break; + } + else + { + Console.WriteLine("心跳检测失败"); + } + } + catch (Exception ex) + { + Console.WriteLine($"心跳检测异常: {ex.Message}"); + } + } + + if (isConnected) + { + // 连接成功,进行额外验证 + try + { + Console.WriteLine("进行额外连接验证..."); + // 再次发送心跳包确保连接稳定 + bool finalResult = _a8Sdk.Heartbeat(); + if (finalResult) + { + Console.WriteLine($"使用IP地址 {_deviceIp} 重连成功"); + _currentDeviceId = 1; // 临时ID,确保状态更新正确 + UpdateConnectionStatus(ConnectionStatus.Connected, $"设备 {_deviceIp} 连接成功"); + StartConnectionCheck(); + connectionSuccessful = true; + } + else + { + Console.WriteLine("最终验证失败"); + } + } + catch (Exception ex) + { + Console.WriteLine($"连接验证异常: {ex.Message}"); + } + } + + // 如果连接不成功,释放资源 + if (!connectionSuccessful) + { + Console.WriteLine($"使用IP地址 {_deviceIp} 重连失败,所有心跳尝试都未成功"); + _a8Sdk = null; + } + } + else + { + Console.WriteLine($"IP地址 {_deviceIp} 不可达,等待设备上线"); + } + } + catch (Exception ex) + { + Console.WriteLine($"使用IP地址重连异常: {ex.Message}"); + } + } + + // 如果IP地址连接失败,再尝试使用设备ID连接(保持兼容性) + if (!connectionSuccessful && _currentDeviceId != -1) + { + Console.WriteLine($"尝试使用设备ID {_currentDeviceId} 连接"); + ConnectDevice(_currentDeviceId); + if (_connectionStatus == ConnectionStatus.Connected) + { + connectionSuccessful = true; + } + } + + // 如果没有保存的设备ID,但有搜索到的设备,尝试连接第一个 + if (!connectionSuccessful && _deviceIds != null && _deviceIds.Count > 0) + { + Console.WriteLine($"尝试使用搜索到的设备列表中的第一个设备"); + ConnectDevice(_deviceIds[0]); + if (_connectionStatus == ConnectionStatus.Connected) + { + connectionSuccessful = true; + } + } + + // 如果连接成功,停止重连定时器 + if (connectionSuccessful) + { + Console.WriteLine("设备连接成功,停止自动重连"); StopAutoReconnect(); return; } - - _reconnectAttempts++; - UpdateConnectionStatus(ConnectionStatus.Reconnecting, $"尝试重连... (第 {_reconnectAttempts} 次)"); - // 检查IP是否可达 - if (IsIpReachable(_deviceIp)) + // 设置下一次重连的时间间隔 + int retryInterval = _reconnectInterval; + if (_currentReconnectAttempt % 5 == 0) // 每5次尝试后增加间隔 { - // 尝试连接 - bool connected = ConnectDevice(); - if (connected) - { - UpdateConnectionStatus(ConnectionStatus.Connected, "自动重连成功"); - } + retryInterval = Math.Min(retryInterval * 2, 10000); // 最大10秒 + Console.WriteLine($"调整重连间隔为 {retryInterval}ms"); + } + Console.WriteLine($"重连失败,{retryInterval}ms后再次尝试"); + + // 更新定时器,确保下一次重连 + if (_reconnectTimer != null) + { + _reconnectTimer.Change(retryInterval, Timeout.Infinite); } } catch (Exception ex) { - Console.WriteLine($"自动重连时发生异常: {ex.Message}"); - OnConnectionException(new ConnectionExceptionEventArgs(ex, "自动重连失败")); + Console.WriteLine($"重连回调函数发生异常: {ex.Message}"); + // 确保定时器继续工作,防止重连机制中断 + if (_reconnectTimer != null) + { + _reconnectTimer.Change(_reconnectInterval, Timeout.Infinite); + } } } + /// /// 开始心跳检测 /// @@ -1080,7 +2370,7 @@ namespace JoyD.Windows.CS.Toprie try { // 检查设备是否可达 - if (!IsIpReachable(_deviceIp)) + if (!PingDevice(_deviceIp)) { Console.WriteLine("心跳检测失败,设备不可达"); UpdateConnectionStatus(ConnectionStatus.Disconnected, "心跳检测失败,设备不可达"); @@ -1093,25 +2383,24 @@ namespace JoyD.Windows.CS.Toprie return; } - // 尝试发送心跳请求 - string url = $"http://{_deviceIp}:{_devicePort}/heartbeat"; - try + // 使用SDK的Heartbeat方法进行心跳检测 + if (_a8Sdk != null) { - WebRequest request = WebRequest.Create(url); - request.Timeout = 2000; // 2秒超时 - using (WebResponse response = request.GetResponse()) + if (!_a8Sdk.Heartbeat()) { - using (Stream dataStream = response.GetResponseStream()) + Console.WriteLine("SDK心跳检测失败"); + UpdateConnectionStatus(ConnectionStatus.Disconnected, "SDK心跳检测失败"); + + // 如果启用了自动重连,开始重连 + if (_isAutoReconnectEnabled) { - // 只需确保请求成功,不需要读取内容 - dataStream.ReadByte(); + StartAutoReconnect(); } } } - catch (Exception) + else { - // 心跳请求失败,但继续使用PING作为备用检测 - Console.WriteLine("心跳请求失败,但PING检测通过"); + Console.WriteLine("SDK实例不存在"); } } catch (Exception ex) @@ -1127,6 +2416,383 @@ namespace JoyD.Windows.CS.Toprie private bool _disposed = false; + /// + /// 释放资源 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + Console.WriteLine($"温度数据已成功保存到: {filePath}"); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"写入CSV文件失败: {ex.Message}"); + return false; + } + } + catch (Exception ex) + { + Console.WriteLine($"保存温度数据到CSV时发生异常: {ex.Message}"); + return false; + } + } + + /// + /// 温度数据点结构体 + /// + 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; + } + } + + /// + /// 启动定期连接状态检查(兼容Form1) + /// + public void StartPeriodicConnectionCheck(int intervalMs) + { + StartConnectionCheck(); + } + + /// + /// 停止定期连接状态检查(兼容Form1) + /// + public void StopPeriodicConnectionCheck() + { + StopConnectionCheck(); + } + + /// + /// 检查系统网络连接是否可用 + /// + /// 网络是否可用 + private bool IsNetworkAvailable() + { + try + { + // 检查系统网络连接状态 + System.Net.NetworkInformation.NetworkInterface[] interfaces = + System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); + + // 检查是否有活动的以太网或WiFi连接 + foreach (var ni in interfaces) + { + // 仅检查物理网络接口(以太网、WiFi等) + if (ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up && + (ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Ethernet || + ni.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Wireless80211) && + ni.Name != "本地连接*" && !ni.Description.Contains("虚拟") && !ni.Description.Contains("Virtual")) + { + // 获取该网络接口的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 + { + // 检查设备IP是否与当前网络接口在同一子网(粗略判断) + var deviceAddress = System.Net.IPAddress.Parse(_deviceIp); + if (deviceAddress.GetAddressBytes()[0] == addr.Address.GetAddressBytes()[0]) + { + return true; + } + } + catch { } + } + return true; + } + } + } + } + return false; + } + catch (Exception ex) + { + Console.WriteLine($"检查网络可用性异常: {ex.Message}"); + // 发生异常时默认为网络不可用,避免误判 + return false; + } + } + + /// + /// 尝试ping设备IP地址 + /// + /// 设备IP地址 + /// 是否ping通 + // PingDevice方法已在文件上方定义,删除重复实现 + + // 图像相关变量 + private Thread _imageReceiveThread; + private Thread _imageReconnectThread; + private bool _isReceivingImages = false; + private ManualResetEvent _stopImageThreadEvent = new ManualResetEvent(false); + private const int IMAGE_PORT = 8080; // HTTP端口,用于获取图像 + private TcpClient _imageTcpClient; + private NetworkStream _imageStream; + + /// + /// 温度数据点结构体,用于存储单个位置的温度数据 + /// + public struct TemperatureDataPoint + { + /// + /// X坐标 + /// + public int X { get; set; } + + /// + /// Y坐标 + /// + public int Y { get; set; } + + /// + /// 温度值 + /// + public float Temperature { get; set; } + + /// + /// 构造函数 + /// + /// X坐标 + /// Y坐标 + /// 温度值 + public TemperatureDataPoint(int x, int y, float temperature) + { + X = x; + Y = y; + Temperature = temperature; + } + } + + /// + /// 保存温度数据到CSV文件 + /// + /// CSV文件路径 + /// 是否保存成功 + 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 temperatureData = new List(); + + try + { + // 使用A8SDK的Get_all_temp方法获取所有温度数据 + Console.WriteLine("正在获取设备温度数据..."); + + 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, 平均={imageTemp.globa.avg_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(0, 2, imageTemp.globa.avg_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].temp > -273.0f) + { + Console.WriteLine($"区域 {i+1} 温度: {imageTemp.area[i].temp}°C"); + temperatureData.Add(new TemperatureDataPoint(i + 1, 0, imageTemp.area[i].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; + } + } + + /// + /// 生成模拟温度数据 + /// + /// 温度数据列表 + private void GenerateMockTemperatureData(List 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)); + } + } + } + + #region IDisposable 实现 + + private bool _disposed = false; + /// /// 释放资源 /// @@ -1148,13 +2814,48 @@ namespace JoyD.Windows.CS.Toprie { // 释放托管资源 StopAutoReconnect(); + StopConnectionCheck(); StopHeartbeat(); StopAllImageReceivers(); _stopRequested.Dispose(); } + // 安全释放SDK实例,避免内存访问冲突 + try + { + if (_a8Sdk != null) + { + Console.WriteLine("释放SDK实例资源"); + // 先设置为null,避免在Dispose过程中的访问 + _a8Sdk = null; + } + } + catch (Exception ex) + { + Console.WriteLine($"释放SDK实例异常: {ex.Message}"); + // 即使异常也确保引用被清空 + _a8Sdk = null; + } + + // 释放全局SDK资源,使用try-catch避免异常传播 + try + { + Console.WriteLine("释放SDK全局资源"); + if (_isInitialized) + { + // 如果SDK支持,调用静态方法释放全局资源 + // A8SDK.SDK_destroy(); + } + } + catch (Exception ex) + { + Console.WriteLine($"释放SDK全局资源异常: {ex.Message}"); + } + // 释放非托管资源 _isInitialized = false; + _connectionStatus = ConnectionStatus.Disconnected; + _currentDeviceId = -1; _disposed = true; } diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj index cde2b8c..b208f93 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj @@ -25,6 +25,9 @@ true true + + Toprie.ico + @@ -74,5 +77,8 @@ Camera.cs + + + \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.ico b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.ico new file mode 100644 index 0000000..2cf24ed Binary files /dev/null and b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.ico differ