From 2e5bc392f0c91cb4c4fd147e329c0fc104a8cb9b Mon Sep 17 00:00:00 2001 From: zqm Date: Fri, 24 Oct 2025 12:15:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0sdk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Windows/CS/Framework4.0/Toprie/Toprie.sln | 6 - .../Toprie/Toprie/DeviceManager.cs | 1314 +++++++++++++++++ .../Framework4.0/Toprie/Toprie/Toprie.csproj | 1 + 3 files changed, 1315 insertions(+), 6 deletions(-) create mode 100644 Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs diff --git a/Windows/CS/Framework4.0/Toprie/Toprie.sln b/Windows/CS/Framework4.0/Toprie/Toprie.sln index 60116d7..6ae5176 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie.sln +++ b/Windows/CS/Framework4.0/Toprie/Toprie.sln @@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toprie", "Toprie\Toprie.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{C309F3A2-0E77-42E4-BE3D-1448E7DFA433}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToprieA8", "..\..\Standard1.0\ToprieA8\ToprieA8.csproj", "{D47E2A7E-9E74-4541-B828-885267F17AE8}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,10 +21,6 @@ Global {C309F3A2-0E77-42E4-BE3D-1448E7DFA433}.Debug|Any CPU.Build.0 = Debug|Any CPU {C309F3A2-0E77-42E4-BE3D-1448E7DFA433}.Release|Any CPU.ActiveCfg = Release|Any CPU {C309F3A2-0E77-42E4-BE3D-1448E7DFA433}.Release|Any CPU.Build.0 = Release|Any CPU - {D47E2A7E-9E74-4541-B828-885267F17AE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D47E2A7E-9E74-4541-B828-885267F17AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D47E2A7E-9E74-4541-B828-885267F17AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D47E2A7E-9E74-4541-B828-885267F17AE8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs new file mode 100644 index 0000000..990c155 --- /dev/null +++ b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs @@ -0,0 +1,1314 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.IO; +using System.Drawing; +using System.Diagnostics; +using System.Net; +using System.Text; + +namespace JoyD.Windows.CS.Toprie +{ + /// + /// 连接状态枚举 + /// + public enum ConnectionStatus + { + Disconnected, + Connecting, + Connected, + Reconnecting + } + + /// + /// 图像模式枚举 + /// + public enum ImageMode + { + /// + /// 热图模式(红外热成像) + /// + Thermal, + /// + /// 全彩模式(可见光图像) + /// + FullColor + } + + /// + /// 连接状态变更事件参数 + /// + 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 ConnectionStatusChangedEventArgs(ConnectionStatus oldStatus, ConnectionStatus newStatus, string message = null, Exception exception = null) + { + OldStatus = oldStatus; + NewStatus = newStatus; + Message = message; + Exception = exception; + Timestamp = DateTime.Now; + } + } + + /// + /// 连接异常事件参数 + /// + public class ConnectionExceptionEventArgs : EventArgs + { + public Exception Exception { get; private set; } + public string ContextMessage { get; private set; } + public DateTime Timestamp { get; private set; } + + public ConnectionExceptionEventArgs(Exception exception, string contextMessage) + { + Exception = exception; + ContextMessage = contextMessage; + Timestamp = DateTime.Now; + } + } + + /// + /// 图像数据接收事件参数 + /// + public class ImageReceivedEventArgs : EventArgs + { + public Image ImageData { get; private set; } + public DateTime Timestamp { get; private set; } + + public ImageReceivedEventArgs(Image imageData) + { + ImageData = imageData; + Timestamp = DateTime.Now; + } + } + + /// + /// 设备管理类,负责设备搜索、连接和断开(纯C#实现) + /// + public class DeviceManager : IDisposable + { + #region 私有成员 + + // 设备IP地址 + private string _deviceIp = "192.168.100.2"; // 默认IP + private int _devicePort = 8080; // 默认端口 + + // 设备ID列表 + private List _deviceIds; + + // 当前连接的设备ID + private int _currentDeviceId = -1; + + // 是否已初始化 + private bool _isInitialized; + + // 连接状态 + private ConnectionStatus _connectionStatus = ConnectionStatus.Disconnected; + + // 图像模式 + private ImageMode _currentImageMode = ImageMode.Thermal; + + // 自动重连相关 + private bool _isAutoReconnectEnabled = false; + private int _reconnectInterval = 5000; // 默认5秒 + private int _maxReconnectAttempts = 5; + private int _reconnectAttempts = 0; + private System.Threading.Timer _reconnectTimer; + private ManualResetEventSlim _stopRequested = new ManualResetEventSlim(false); + + // 心跳检测相关 + private System.Threading.Timer _heartbeatTimer; + private int _heartbeatInterval = 30000; // 30秒心跳 + + #endregion 私有成员 + + #region 私有方法 + + /// + /// 从设备字符串中解析设备ID + /// + /// 设备字符串 + /// 设备ID + private int ParseDeviceId(string deviceString) + { + try + { + // 简单的ID解析逻辑,实际应该根据设备返回的数据格式来解析 + if (int.TryParse(deviceString.Trim(), out int id)) + { + return id; + } + return -1; + } + catch (Exception) + { + return -1; + } + } + + /// + /// 更新连接状态 + /// + /// 新的连接状态 + /// 状态变更消息 + /// 相关异常(如果有) + private void UpdateConnectionStatus(ConnectionStatus newStatus, string message = null, Exception exception = null) + { + if (_connectionStatus != newStatus) + { + ConnectionStatus oldStatus = _connectionStatus; + _connectionStatus = newStatus; + OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(oldStatus, newStatus, message, exception)); + } + } + + /// + /// 初始化设备管理器 + /// + /// 是否初始化成功 + private bool Initialize() + { + try + { + _deviceIds = new List(); + _reconnectTimer = null; + _heartbeatTimer = null; + _isInitialized = true; + return true; + } + catch (Exception ex) + { + _isInitialized = false; + OnConnectionException(new ConnectionExceptionEventArgs(ex, "初始化设备管理器失败")); + return false; + } + } + + /// + /// 检查IP是否可达 + /// + /// IP地址 + /// 是否可达 + private bool IsIpReachable(string ipAddress) + { + try + { + using (Ping ping = new Ping()) + { + PingReply reply = ping.Send(ipAddress, 1000); // 1秒超时 + return reply.Status == IPStatus.Success; + } + } + catch (Exception) + { + return false; + } + } + + /// + /// 停止所有图像接收线程 + /// + private void StopAllImageReceivers() + { + _stopRequested.Set(); + // 给线程一点时间来结束 + Thread.Sleep(100); + } + + /// + /// 重置停止标志 + /// + private void ResetStopFlag() + { + _stopRequested.Reset(); + } + + #endregion 私有方法 + + #region 公共事件 + + /// + /// 连接状态变更事件 + /// + public event EventHandler ConnectionStatusChanged; + + /// + /// 连接异常事件 + /// + public event EventHandler ConnectionException; + + /// + /// 图像数据接收事件 + /// + public event EventHandler ImageReceived; + + #endregion 公共事件 + + /// + /// 触发连接状态变更事件 + /// + /// 事件参数 + protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs e) + { + ConnectionStatusChanged?.Invoke(this, e); + } + + /// + /// 触发连接异常事件 + /// + /// 事件参数 + protected virtual void OnConnectionException(ConnectionExceptionEventArgs e) + { + ConnectionException?.Invoke(this, e); + } + + /// + /// 触发图像接收事件 + /// + /// 事件参数 + protected virtual void OnImageReceived(ImageReceivedEventArgs e) + { + ImageReceived?.Invoke(this, e); + } + + #region 公共属性 + + /// + /// 当前图像模式 + /// + public ImageMode CurrentImageMode + { + get { return _currentImageMode; } + set + { + if (_currentImageMode != value) + { + _currentImageMode = value; + // 如果已连接,发送模式变更命令 + if (_connectionStatus == ConnectionStatus.Connected) + { + SendModeChangeCommand(value); + } + } + } + } + + /// + /// 连接状态 + /// + public ConnectionStatus ConnectionStatus + { + get { return _connectionStatus; } + } + + /// + /// 是否启用自动重连 + /// + public bool AutoReconnectEnabled + { + get { return _isAutoReconnectEnabled; } + set + { + _isAutoReconnectEnabled = value; + if (value && _connectionStatus == ConnectionStatus.Disconnected) + { + StartAutoReconnect(); + } + else if (!value) + { + StopAutoReconnect(); + } + } + } + + /// + /// 重连间隔(毫秒) + /// + public int ReconnectInterval + { + get { return _reconnectInterval; } + set + { + if (value > 0) + { + _reconnectInterval = value; + // 如果已经在自动重连中,更新定时器 + if (_isAutoReconnectEnabled && _reconnectTimer != null) + { + _reconnectTimer.Change(0, _reconnectInterval); + } + } + } + } + + /// + /// 最大重连次数 + /// + public int MaxReconnectAttempts + { + get { return _maxReconnectAttempts; } + set + { + if (value >= 0) + { + _maxReconnectAttempts = value; + } + } + } + + /// + /// 设备IP地址 + /// + public string DeviceIp + { + get { return _deviceIp; } + set + { + if (_deviceIp != value) + { + _deviceIp = value; + // 如果已连接,重新连接 + if (_connectionStatus == ConnectionStatus.Connected) + { + Disconnect(); + ConnectDevice(); + } + } + } + } + + /// + /// 设备端口 + /// + public int DevicePort + { + get { return _devicePort; } + set + { + if (_devicePort != value && value > 0 && value <= 65535) + { + _devicePort = value; + // 如果已连接,重新连接 + if (_connectionStatus == ConnectionStatus.Connected) + { + Disconnect(); + ConnectDevice(); + } + } + } + } + + /// + /// 心跳间隔(毫秒) + /// + public int HeartbeatInterval + { + get { return _heartbeatInterval; } + set + { + if (value > 0) + { + _heartbeatInterval = value; + // 如果已经启用心跳检测,更新定时器 + if (_heartbeatTimer != null) + { + _heartbeatTimer.Change(0, _heartbeatInterval); + } + } + } + } + + #endregion 公共属性 + + #region 视频流处理方法 + + /// + /// 设置视频模式 + /// + /// 图像模式 + public void SetImageMode(ImageMode mode) + { + CurrentImageMode = mode; + } + + /// + /// 发送模式变更命令 + /// + /// 图像模式 + private void 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()) + { + client.DownloadData(url); // 发送请求但不处理响应 + } + } + catch (Exception ex) + { + Console.WriteLine($"切换图像模式失败: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "发送模式变更命令失败")); + } + } + + /// + /// 开始接收图像 + /// + public void StartReceiveImage() + { + if (_connectionStatus != ConnectionStatus.Connected) + { + throw new InvalidOperationException("设备未连接,无法开始接收图像"); + } + + ResetStopFlag(); + Thread imageThread = new Thread(ReceiveImageThread) + { + IsBackground = true, + Name = "ImageReceiver" + }; + imageThread.Start(); + } + + /// + /// 停止接收图像 + /// + public void StopReceiveImage() + { + StopAllImageReceivers(); + } + + /// + /// 图像接收线程 + /// + private void ReceiveImageThread() + { + try + { + while (!_stopRequested.IsSet) + { + try + { + // 根据当前模式选择图像路径 + 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()) + { + if (response.StatusCode == HttpStatusCode.OK) + { + using (Stream stream = response.GetResponseStream()) + { + using (MemoryStream memoryStream = new MemoryStream()) + { + 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)); + } + } + } + } + } + } + } + catch (WebException webEx) + { + // 处理超时等网络错误 + if (webEx.Status == WebExceptionStatus.Timeout) + { + Console.WriteLine("图像接收超时"); + } + else if (webEx.Status == WebExceptionStatus.ConnectFailure) + { + Console.WriteLine("图像连接失败,可能设备已断开"); + // 触发连接断开事件 + UpdateConnectionStatus(ConnectionStatus.Disconnected, "图像连接失败,设备可能已断开", webEx); + // 如果启用了自动重连,开始重连 + if (_isAutoReconnectEnabled) + { + StartAutoReconnect(); + } + break; // 退出图像接收线程 + } + else + { + Console.WriteLine($"图像接收错误: {webEx.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(webEx, "图像接收过程中发生异常")); + } + } + catch (Exception ex) + { + Console.WriteLine($"图像处理错误: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "图像处理过程中发生异常")); + } + + // 短暂休眠,控制帧率 + Thread.Sleep(100); + } + } + catch (Exception ex) + { + Console.WriteLine($"图像接收线程异常: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "图像接收线程异常")); + } + } + + /// + /// 验证图像数据是否有效 + /// + /// 图像数据 + /// 是否为有效图像数据 + 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 公共方法 + + /// + /// 搜索设备(同步版本) + /// + public List SearchDevices() + { + try + { + if (!_isInitialized && !Initialize()) + { + return new List(); + } + + List foundDevices = new List(); + _deviceIds.Clear(); + + // 1. 尝试UDP广播搜索 + List discoveredIps = BroadcastSearch(); + + // 2. 如果UDP广播没有找到设备,尝试在本地网络扫描 + if (discoveredIps.Count == 0) + { + discoveredIps = ScanLocalNetwork(); + } + + // 为每个发现的IP分配一个设备ID + int deviceId = 1; + foreach (string ip in discoveredIps) + { + _deviceIds.Add(deviceId); + foundDevices.Add(deviceId); + deviceId++; + } + + return foundDevices; + } + catch (Exception ex) + { + Console.WriteLine($"搜索设备失败: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "搜索设备失败")); + return new List(); + } + } + + /// + /// 搜索设备(异步版本) + /// + public Task> SearchDevicesAsync() + { + return Task.Factory.StartNew(() => SearchDevices()); + } + + /// + /// UDP广播搜索设备 + /// + /// 发现的设备IP列表 + private List BroadcastSearch() + { + List discoveredIps = new List(); + + try + { + // 使用UDP广播查找设备 + using (UdpClient udpClient = new UdpClient()) + { + udpClient.EnableBroadcast = true; + udpClient.Client.ReceiveTimeout = 2000; + + // 广播地址和端口 + IPEndPoint broadcastEndpoint = new IPEndPoint(IPAddress.Broadcast, 8080); + + // 发送搜索命令 + byte[] searchCommand = Encoding.ASCII.GetBytes("SEARCH_DEVICE"); + udpClient.Send(searchCommand, searchCommand.Length, broadcastEndpoint); + + // 接收响应 + IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0); + + try + { + byte[] response = udpClient.Receive(ref remoteEndpoint); + string responseString = Encoding.ASCII.GetString(response); + + // 检查响应是否来自我们的设备 + if (responseString.Contains("DEVICE_FOUND")) + { + 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) + { + 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); + } + } + } + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"本地网络扫描失败: {ex.Message}"); + } + + return discoveredIps; + } + + /// + /// 获取子网掩码 + /// + /// IP掩码 + /// 子网掩码 + private IPAddress GetSubnetMask(IPAddress mask) + { + return mask; + } + + /// + /// 连接设备 + /// + /// 是否连接成功 + public bool ConnectDevice() + { + try + { + UpdateConnectionStatus(ConnectionStatus.Connecting, "开始连接设备..."); + + // 确保已初始化 + if (!_isInitialized && !Initialize()) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备管理器初始化失败"); + return false; + } + + // 停止任何现有的重连定时器 + StopAutoReconnect(); + + // 停止任何正在进行的连接检查 + StopAllImageReceivers(); + + // 检查IP是否可达 + if (!IsIpReachable(_deviceIp)) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, $"设备IP {_deviceIp} 不可达"); + if (_isAutoReconnectEnabled) + { + StartAutoReconnect(); + } + return false; + } + + // 尝试TCP连接测试 + bool tcpConnected = TestTcpConnection(_deviceIp, _devicePort); + if (!tcpConnected) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, $"TCP连接到 {_deviceIp}:{_devicePort} 失败"); + if (_isAutoReconnectEnabled) + { + StartAutoReconnect(); + } + return false; + } + + // 发送连接命令 + bool commandSent = SendConnectCommand(); + if (!commandSent) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, "发送连接命令失败"); + if (_isAutoReconnectEnabled) + { + StartAutoReconnect(); + } + return false; + } + + // 启动心跳检测 + StartHeartbeat(); + + // 设置连接状态 + _currentDeviceId = 1; // 简化处理,实际应该根据设备返回的数据设置 + 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; + } + } + + /// + /// 测试TCP连接 + /// + /// IP地址 + /// 端口 + /// 是否连接成功 + private bool TestTcpConnection(string ip, int port) + { + try + { + using (TcpClient client = new TcpClient()) + { + IAsyncResult result = client.BeginConnect(ip, port, null, null); + bool success = result.AsyncWaitHandle.WaitOne(2000); // 2秒超时 + if (success) + { + client.EndConnect(result); + return true; + } + else + { + client.Close(); + return false; + } + } + } + catch (Exception) + { + return false; + } + } + + /// + /// 发送连接命令 + /// + /// 是否发送成功 + 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; + } + } + + /// + /// 断开连接 + /// + public void Disconnect() + { + try + { + // 停止重连定时器 + StopAutoReconnect(); + + // 停止心跳检测 + StopHeartbeat(); + + // 停止图像接收 + StopAllImageReceivers(); + + // 设置断开状态 + UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备已断开连接"); + + // 重置设备ID + _currentDeviceId = -1; + } + catch (Exception ex) + { + Console.WriteLine($"断开连接时发生异常: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "断开连接失败")); + } + } + + /// + /// 开始自动重连 + /// + private void StartAutoReconnect() + { + // 停止现有的重连定时器 + StopAutoReconnect(); + + // 创建新的重连定时器 + _reconnectTimer = new System.Threading.Timer(ReconnectCallback, null, 0, _reconnectInterval); + } + + /// + /// 停止自动重连 + /// + private void StopAutoReconnect() + { + if (_reconnectTimer != null) + { + _reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite); + _reconnectTimer.Dispose(); + _reconnectTimer = null; + } + } + + /// + /// 重连回调 + /// + /// 定时器状态 + private void ReconnectCallback(object state) + { + try + { + // 检查是否达到最大重连次数 + if (_maxReconnectAttempts > 0 && _reconnectAttempts >= _maxReconnectAttempts) + { + UpdateConnectionStatus(ConnectionStatus.Disconnected, $"达到最大重连次数 {_maxReconnectAttempts}"); + StopAutoReconnect(); + return; + } + + _reconnectAttempts++; + UpdateConnectionStatus(ConnectionStatus.Reconnecting, $"尝试重连... (第 {_reconnectAttempts} 次)"); + + // 检查IP是否可达 + if (IsIpReachable(_deviceIp)) + { + // 尝试连接 + bool connected = ConnectDevice(); + if (connected) + { + UpdateConnectionStatus(ConnectionStatus.Connected, "自动重连成功"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"自动重连时发生异常: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "自动重连失败")); + } + } + + /// + /// 开始心跳检测 + /// + private void StartHeartbeat() + { + // 停止现有的心跳定时器 + StopHeartbeat(); + + // 创建新的心跳定时器 + _heartbeatTimer = new System.Threading.Timer(HeartbeatCallback, null, 0, _heartbeatInterval); + } + + /// + /// 停止心跳检测 + /// + private void StopHeartbeat() + { + if (_heartbeatTimer != null) + { + _heartbeatTimer.Change(Timeout.Infinite, Timeout.Infinite); + _heartbeatTimer.Dispose(); + _heartbeatTimer = null; + } + } + + /// + /// 心跳检测回调 + /// + /// 定时器状态 + private void HeartbeatCallback(object state) + { + try + { + // 检查设备是否可达 + if (!IsIpReachable(_deviceIp)) + { + Console.WriteLine("心跳检测失败,设备不可达"); + UpdateConnectionStatus(ConnectionStatus.Disconnected, "心跳检测失败,设备不可达"); + + // 如果启用了自动重连,开始重连 + if (_isAutoReconnectEnabled) + { + StartAutoReconnect(); + } + return; + } + + // 尝试发送心跳请求 + string url = $"http://{_deviceIp}:{_devicePort}/heartbeat"; + try + { + WebRequest request = WebRequest.Create(url); + request.Timeout = 2000; // 2秒超时 + using (WebResponse response = request.GetResponse()) + { + using (Stream dataStream = response.GetResponseStream()) + { + // 只需确保请求成功,不需要读取内容 + dataStream.ReadByte(); + } + } + } + catch (Exception) + { + // 心跳请求失败,但继续使用PING作为备用检测 + Console.WriteLine("心跳请求失败,但PING检测通过"); + } + } + catch (Exception ex) + { + Console.WriteLine($"心跳检测时发生异常: {ex.Message}"); + OnConnectionException(new ConnectionExceptionEventArgs(ex, "心跳检测失败")); + } + } + + #endregion 公共方法 + + #region IDisposable 实现 + + private bool _disposed = false; + + /// + /// 释放资源 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 释放资源 + /// + /// 是否释放托管资源 + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // 释放托管资源 + StopAutoReconnect(); + StopHeartbeat(); + StopAllImageReceivers(); + _stopRequested.Dispose(); + } + + // 释放非托管资源 + _isInitialized = false; + _currentDeviceId = -1; + + _disposed = true; + } + } + + /// + /// 析构函数 + /// + ~DeviceManager() + { + Dispose(false); + } + + #endregion IDisposable 实现 + + #region 辅助方法 + + /// + /// 检查是否为HTTP响应 + /// + /// 数据 + /// 是否为HTTP响应 + private bool IsHttpResponse(byte[] data) + { + if (data == null || data.Length < 10) + { + return false; + } + + // 检查是否以HTTP开头 + string startText = Encoding.ASCII.GetString(data, 0, Math.Min(10, data.Length)); + return startText.StartsWith("HTTP/"); + } + + /// + /// 查找图像数据的起始位置 + /// + /// 数据 + /// 图像数据起始位置 + private int FindImageStartPosition(byte[] data) + { + if (data == null || data.Length < 2) + { + return -1; + } + + // 查找JPEG起始标记 (FF D8) + for (int i = 0; i < data.Length - 1; i++) + { + if (data[i] == 0xFF && data[i + 1] == 0xD8) + { + return i; + } + } + + // 查找PNG起始标记 + byte[] pngHeader = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + if (data.Length >= pngHeader.Length) + { + for (int i = 0; i <= data.Length - pngHeader.Length; i++) + { + bool isPngHeader = true; + for (int j = 0; j < pngHeader.Length; j++) + { + if (data[i + j] != pngHeader[j]) + { + isPngHeader = false; + break; + } + } + if (isPngHeader) + { + return i; + } + } + } + + // 查找BMP起始标记 (BM) + for (int i = 0; i < data.Length - 1; i++) + { + if (data[i] == 0x42 && data[i + 1] == 0x4D) + { + return i; + } + } + + return -1; + } + + /// + /// 查找图像数据的结束位置 + /// + /// 数据 + /// 起始位置 + /// 图像数据结束位置 + private int FindImageEndPosition(byte[] data, int startPos) + { + if (data == null || startPos < 0 || startPos >= data.Length - 1) + { + return -1; + } + + // 检查是否是JPEG格式 + if (data[startPos] == 0xFF && data[startPos + 1] == 0xD8) + { + // 查找JPEG结束标记 (FF D9) + for (int i = startPos + 2; i < data.Length - 1; i++) + { + if (data[i] == 0xFF && data[i + 1] == 0xD9) + { + return i + 2; // 包含结束标记 + } + } + } + + // 对于PNG和BMP,我们假设数据是完整的 + return data.Length; + } + + /// + /// 计算图像数据长度 + /// + /// 图像数据 + /// 图像数据长度 + private int CalculateImageLength(byte[] data) + { + if (data == null) + { + return 0; + } + + // 简化实现:如果是JPEG,查找结束标记 + for (int i = 0; i < data.Length - 1; i++) + { + if (data[i] == 0xFF && data[i + 1] == 0xD9) + { + return i + 2; + } + } + + // PNG和BMP通常会有文件长度信息,这里简化处理 + // 对于完整的实现,应该解析文件头中的长度信息 + return data.Length; + } + + #endregion 辅助方法 + } +} \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj index d6f940a..173d850 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Toprie.csproj @@ -66,6 +66,7 @@ Camera.cs +