diff --git a/README.md b/README.md index f0e9b72..71b9659 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # JoyD -久鼎代码库 \ No newline at end of file +久鼎代码库 +### 修改流程: + 1. 暂停或恢复时,设置暂停状态,调用更新Info + 2. 断开或连接时,设置连接状态,调用更新Info + 3. Ping通状态变化时,修改Ping状态,调用更新Info + 4. 图像更新时, 保存LastImage, 调用更新到UI + ### 更新Info: + 1. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则清空InfoImage + 2. 最后调用更新UI + ### 更新UI: + 1. 先将LastImage绘制到全局缓冲 + 2. 再将InfoImage绘制到缓冲 + 3. 最后一次性绘制到图像框的bitmap \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs index 0e05154..49d6ec0 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs @@ -35,6 +35,8 @@ namespace JoyD.Windows.CS.Toprie this.ironGrayToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.redHotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.rainbow2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.pauseDetectionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)(this.imageBox)).BeginInit(); this.contextMenuStrip1.SuspendLayout(); this.SuspendLayout(); @@ -54,9 +56,11 @@ namespace JoyD.Windows.CS.Toprie // this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.imageModeToolStripMenuItem, - this.colorModeToolStripMenuItem}); + this.colorModeToolStripMenuItem, + this.toolStripSeparator1, + this.pauseDetectionToolStripMenuItem}); this.contextMenuStrip1.Name = "contextMenuStrip1"; - this.contextMenuStrip1.Size = new System.Drawing.Size(161, 74); + this.contextMenuStrip1.Size = new System.Drawing.Size(161, 100); // // imageModeToolStripMenuItem // @@ -72,14 +76,14 @@ namespace JoyD.Windows.CS.Toprie this.thermalModeToolStripMenuItem.Name = "thermalModeToolStripMenuItem"; this.thermalModeToolStripMenuItem.Size = new System.Drawing.Size(152, 22); this.thermalModeToolStripMenuItem.Text = "红外模式"; - this.thermalModeToolStripMenuItem.Click += new System.EventHandler(this.thermalModeToolStripMenuItem_Click); + this.thermalModeToolStripMenuItem.Click += new System.EventHandler(this.ThermalModeToolStripMenuItem_Click); // // visibleModeToolStripMenuItem // this.visibleModeToolStripMenuItem.Name = "visibleModeToolStripMenuItem"; this.visibleModeToolStripMenuItem.Size = new System.Drawing.Size(152, 22); this.visibleModeToolStripMenuItem.Text = "自然模式"; - this.visibleModeToolStripMenuItem.Click += new System.EventHandler(this.visibleModeToolStripMenuItem_Click); + this.visibleModeToolStripMenuItem.Click += new System.EventHandler(this.VisibleModeToolStripMenuItem_Click); // // colorModeToolStripMenuItem // @@ -92,6 +96,18 @@ namespace JoyD.Windows.CS.Toprie this.ironGrayToolStripMenuItem, this.redHotToolStripMenuItem, this.rainbow2ToolStripMenuItem}); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(157, 6); + // + // pauseDetectionToolStripMenuItem + // + this.pauseDetectionToolStripMenuItem.Name = "pauseDetectionToolStripMenuItem"; + this.pauseDetectionToolStripMenuItem.Size = new System.Drawing.Size(160, 22); + this.pauseDetectionToolStripMenuItem.Text = "暂停检测"; + this.pauseDetectionToolStripMenuItem.Click += new System.EventHandler(this.PauseDetectionToolStripMenuItem_Click); this.colorModeToolStripMenuItem.Name = "colorModeToolStripMenuItem"; this.colorModeToolStripMenuItem.Size = new System.Drawing.Size(160, 22); this.colorModeToolStripMenuItem.Text = "色彩模式"; @@ -101,56 +117,56 @@ namespace JoyD.Windows.CS.Toprie this.rainbowToolStripMenuItem.Name = "rainbowToolStripMenuItem"; this.rainbowToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.rainbowToolStripMenuItem.Text = "彩虹"; - this.rainbowToolStripMenuItem.Click += new System.EventHandler(this.rainbowToolStripMenuItem_Click); + this.rainbowToolStripMenuItem.Click += new System.EventHandler(this.RainbowToolStripMenuItem_Click); // // ironRedToolStripMenuItem // this.ironRedToolStripMenuItem.Name = "ironRedToolStripMenuItem"; this.ironRedToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.ironRedToolStripMenuItem.Text = "铁红"; - this.ironRedToolStripMenuItem.Click += new System.EventHandler(this.ironRedToolStripMenuItem_Click); + this.ironRedToolStripMenuItem.Click += new System.EventHandler(this.IronRedToolStripMenuItem_Click); // // lavaToolStripMenuItem // this.lavaToolStripMenuItem.Name = "lavaToolStripMenuItem"; this.lavaToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.lavaToolStripMenuItem.Text = "熔岩"; - this.lavaToolStripMenuItem.Click += new System.EventHandler(this.lavaToolStripMenuItem_Click); + this.lavaToolStripMenuItem.Click += new System.EventHandler(this.LavaToolStripMenuItem_Click); // // ironGrayToolStripMenuItem // this.ironGrayToolStripMenuItem.Name = "ironGrayToolStripMenuItem"; this.ironGrayToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.ironGrayToolStripMenuItem.Text = "铁灰"; - this.ironGrayToolStripMenuItem.Click += new System.EventHandler(this.ironGrayToolStripMenuItem_Click); + this.ironGrayToolStripMenuItem.Click += new System.EventHandler(this.IronGrayToolStripMenuItem_Click); // // redHotToolStripMenuItem // this.redHotToolStripMenuItem.Name = "redHotToolStripMenuItem"; this.redHotToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.redHotToolStripMenuItem.Text = "红热"; - this.redHotToolStripMenuItem.Click += new System.EventHandler(this.redHotToolStripMenuItem_Click); + this.redHotToolStripMenuItem.Click += new System.EventHandler(this.RedHotToolStripMenuItem_Click); // // rainbow2ToolStripMenuItem // this.rainbow2ToolStripMenuItem.Name = "rainbow2ToolStripMenuItem"; this.rainbow2ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.rainbow2ToolStripMenuItem.Text = "彩虹2"; - this.rainbow2ToolStripMenuItem.Click += new System.EventHandler(this.rainbow2ToolStripMenuItem_Click); + this.rainbow2ToolStripMenuItem.Click += new System.EventHandler(this.Rainbow2ToolStripMenuItem_Click); // // whiteHotToolStripMenuItem // this.whiteHotToolStripMenuItem.Name = "whiteHotToolStripMenuItem"; this.whiteHotToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.whiteHotToolStripMenuItem.Text = "白热"; - this.whiteHotToolStripMenuItem.Click += new System.EventHandler(this.whiteHotToolStripMenuItem_Click); + this.whiteHotToolStripMenuItem.Click += new System.EventHandler(this.WhiteHotToolStripMenuItem_Click); // // blackHotToolStripMenuItem // this.blackHotToolStripMenuItem.Name = "blackHotToolStripMenuItem"; this.blackHotToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.blackHotToolStripMenuItem.Text = "黑热"; - this.blackHotToolStripMenuItem.Click += new System.EventHandler(this.blackHotToolStripMenuItem_Click); + this.blackHotToolStripMenuItem.Click += new System.EventHandler(this.BlackHotToolStripMenuItem_Click); // // 已移除蓝红菜单项(不在SDK的8种标准色板中) // @@ -158,6 +174,7 @@ namespace JoyD.Windows.CS.Toprie // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + // 添加imageBox this.Controls.Add(this.imageBox); this.Name = "Camera"; this.Size = new System.Drawing.Size(512, 384); @@ -172,6 +189,8 @@ namespace JoyD.Windows.CS.Toprie private System.Windows.Forms.PictureBox imageBox; private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem pauseDetectionToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripMenuItem colorModeToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem whiteHotToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem blackHotToolStripMenuItem; diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs index cfe5736..aa822b0 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; +using System.Threading.Tasks; namespace JoyD.Windows.CS.Toprie { @@ -19,6 +20,26 @@ namespace JoyD.Windows.CS.Toprie // 是否正在接收图像 private bool _isReceivingImage = false; + // 全局图像缓冲区bitmap,用于在其上绘制图像和mask信息 + private Bitmap _imageBuffer = null; + private const int BUFFER_WIDTH = 512; + private const int BUFFER_HEIGHT = 384; + + // 最后接收的图像 + private Image _lastImage = null; + + // 信息图像,用于显示额外信息 + private Image _infoImage = null; + + // 用于保护_lastImage的线程锁 + private readonly object _lastImageLock = new object(); + + // 用于保护_infoImage的线程锁 + private readonly object _infoImageLock = new object(); + + // 是否显示信息图像 + private bool _isDisplayingInfo = false; + // 项目路径,用于数据文件的存取 private string _projectPath = ""; @@ -30,24 +51,24 @@ namespace JoyD.Windows.CS.Toprie [DefaultValue("")] public string ProjectPath { - get { return _projectPath; } + get { return _projectPath; } set { // 只有当值发生变化时才进行同步 if (_projectPath != value) - { - _projectPath = value; + { + _projectPath = value; // 如果DeviceManager已经初始化,则同步更新其ProjectPath属性 - if (_deviceManager != null) - { - _deviceManager.ProjectPath = _projectPath; + if (_deviceManager != null) + { + _deviceManager.ProjectPath = _projectPath; } - } } - } + } + } // 显示错误的定时器 - private System.Windows.Forms.Timer _errorDisplayTimer; + /// /// 更新设计模式状态到DeviceManager @@ -57,7 +78,177 @@ namespace JoyD.Windows.CS.Toprie DeviceManager.IsDesignMode = DesignMode; Console.WriteLine($"相机控件设计模式状态已更新: {DesignMode}"); } + + /// + /// 更新InfoImage显示 + /// 1. 如果暂停,显示暂停信息 + /// 2. 否则如果Ping不通或断开,显示重连信息 + /// 3. 否则清空InfoImage + /// 4. 最后调用更新UI + /// + private void UpdateInfo() + { + // 更新Ping状态到Info文本 + Console.WriteLine($"Ping状态更新: {(IsDevicePingable ? "可Ping通" : "不可Ping通")}"); + if (DesignMode) return; + + try + { + lock (_infoImageLock) + { + // 检查连接状态 + bool isDisconnected = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Disconnected; + bool isReconnecting = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Reconnecting; + bool isPaused = _isPaused; // 使用_isPaused标志判断暂停状态 + bool isPingFailed = !IsDevicePingable; + // 根据用户要求的优先级显示信息:先检查暂停状态,然后再检查Ping状态和连接状态 + + using (Graphics g = Graphics.FromImage(_infoImage)) + { + // 设置半透明背景 + g.Clear(Color.FromArgb(128, Color.Transparent)); + if (isPaused) + { + // 暂停状态 - 最高优先级 + // 绘制暂停文本 + string text = "暂停"; + Color textColor = Color.Red; + + using (Font font = new Font("Arial", 48, FontStyle.Bold)) + using (SolidBrush textBrush = new SolidBrush(textColor)) + { + StringFormat format = new StringFormat() { Alignment = StringAlignment.Center }; + + // 将主文本居中显示 + g.DrawString(text, font, textBrush, + new RectangleF(0, BUFFER_HEIGHT / 3, BUFFER_WIDTH, BUFFER_HEIGHT / 3), + format); + } + } + else if (isPingFailed || isDisconnected || isReconnecting) + { + // 非暂停状态下,检查Ping状态和连接状态 + + // 确定显示的文本和颜色 + string text = ""; + Color textColor = Color.White; + + if (isReconnecting) + { + text = "正在重连..."; + textColor = Color.Yellow; + } + else if (isDisconnected) + { + text = "连接断开"; + textColor = Color.Red; + } + else if (isPingFailed) + { + text = "网络断开"; + textColor = Color.Red; + } + + // 绘制文本 + using (Font font = new Font("Arial", 48, FontStyle.Bold)) + using (SolidBrush textBrush = new SolidBrush(textColor)) + { + StringFormat format = new StringFormat() { Alignment = StringAlignment.Center }; + + // 将主文本居中显示 + g.DrawString(text, font, textBrush, + new RectangleF(0, BUFFER_HEIGHT / 3, BUFFER_WIDTH, BUFFER_HEIGHT / 3), + format); + } + } + else g.Clear(Color.Transparent); + } + // 否则清空InfoImage(已在开头处理) + + // 设置显示标志 + _isDisplayingInfo = (isPaused || isDisconnected || isReconnecting); + } + + // 调用更新UI + UpdateImageOnUI(); + } + catch (Exception ex) + { + Console.WriteLine($"更新Info显示时出错: {ex.Message}"); + } + } + + /// + /// 暂停/恢复检测菜单项点击事件处理 + /// 1、暂停或恢复时,设置暂停状态,调用更新Info + /// + private void PauseDetectionToolStripMenuItem_Click(object sender, EventArgs e) + { + if (DesignMode) return; + + try + { + // 切换暂停状态 + _isPaused = !_isPaused; + + if (_isPaused) + { + // 设置暂停状态 + pauseDetectionToolStripMenuItem.Text = "恢复检测"; + + // 暂停时停止图像接收并更新DeviceManager的暂停检测状态 + if (_deviceManager != null) + { + _deviceManager.IsDetectionPaused = true; + + if (_isReceivingImage) + { + _deviceManager.StopImageReceiving(); + _isReceivingImage = false; + } + } + + Console.WriteLine("检测已暂停"); + } + else + { + // 设置恢复状态 + pauseDetectionToolStripMenuItem.Text = "暂停检测"; + + // 恢复时更新DeviceManager的暂停检测状态并重新开始图像接收 + if (_deviceManager != null) + { + _deviceManager.IsDetectionPaused = false; + + if (_deviceManager.ConnectionStatus == ConnectionStatus.Connected) + { + _deviceManager.StopImageReceiving(); + _deviceManager.StartImageReceiving(); + _isReceivingImage = true; + + // 恢复检测后,启动连接检查以确保连接正常 + _deviceManager.StartConnectionCheck(); + } + // 如果当前是断开状态但启用了自动重连,尝试启动重连 + else if (_deviceManager.AutoReconnectEnabled) + { + _deviceManager.StartAutoReconnect(); + } + } + + Console.WriteLine("检测已恢复"); + } + + // 按照用户要求:暂停或恢复时,设置暂停状态,调用更新Info + UpdateInfo(); + } + catch (Exception ex) + { + Console.WriteLine($"处理暂停/恢复检测时出错: {ex.Message}"); + } + } + public Camera() { InitializeComponent(); @@ -68,6 +259,12 @@ namespace JoyD.Windows.CS.Toprie // 将设计模式状态传递给DeviceManager UpdateDesignModeStatus(); + // 初始化图像缓冲区 + InitializeImageBuffer(); + + // 初始化Ping定时器 + _pingTimer = new System.Threading.Timer(PingTimer_Tick, null, Timeout.Infinite, Timeout.Infinite); + // 只有在非设计模式下才初始化设备管理器和错误定时器 if (!DesignMode) { @@ -89,7 +286,6 @@ namespace JoyD.Windows.CS.Toprie catch { } InitializeDeviceManager(); - InitializeErrorTimer(); } } @@ -143,21 +339,75 @@ namespace JoyD.Windows.CS.Toprie _deviceManager.ConnectionException += DeviceManager_ConnectionException; } } - + /// - /// 初始化错误显示定时器 + /// 初始化图像缓冲区和相关图像资源 /// - private void InitializeErrorTimer() - { - _errorDisplayTimer = new System.Windows.Forms.Timer { Interval = 3000 }; // 显示3秒后清除 - _errorDisplayTimer.Tick += ErrorDisplayTimer_Tick; + private void InitializeImageBuffer() + { + try + { + // 创建512*384大小的透明bitmap作为图像缓冲区 + _imageBuffer = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT); + Console.WriteLine($"图像缓冲区已初始化: {BUFFER_WIDTH}x{BUFFER_HEIGHT}"); + + // 初始化缓冲区为黑色背景 + using (Graphics g = Graphics.FromImage(_imageBuffer)) + { + g.Clear(Color.Black); + } + + // 初始化InfoImage为透明bitmap + lock (_infoImageLock) + { + _infoImage = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT); + using (Graphics g = Graphics.FromImage(_infoImage)) + { + g.Clear(Color.Transparent); + } + Console.WriteLine("InfoImage已初始化为透明bitmap"); + } + + // 初始化图像框的bitmap为透明 + if (imageBox != null && !imageBox.IsDisposed) + { + imageBox.Image = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT); + using (Graphics g = Graphics.FromImage(imageBox.Image)) + { + g.Clear(Color.Transparent); + } + Console.WriteLine("图像框bitmap已初始化为透明"); + } + } + catch (Exception ex) + { + Console.WriteLine($"初始化图像资源失败: {ex.Message}"); + // 发生异常时释放已创建的资源 + if (_imageBuffer != null) + { + _imageBuffer.Dispose(); + _imageBuffer = null; + } + lock (_infoImageLock) + { + if (_infoImage != null) + { + _infoImage.Dispose(); + _infoImage = null; + } + } + } } + + /// /// 启动相机 /// public void StartCamera() - { + { + // 启动设备Ping + StartDevicePing(); if (DesignMode) return; try { @@ -266,7 +516,9 @@ namespace JoyD.Windows.CS.Toprie /// 停止接收图像 /// public void StopCamera() - { + { + // 停止设备Ping + StopDevicePing(); if (DesignMode) return; try { @@ -282,327 +534,270 @@ namespace JoyD.Windows.CS.Toprie } } + private bool _isPaused = false; // 暂停状态标志 + + // Ping相关字段 + private System.Threading.Timer _pingTimer; + private bool _isDevicePingable = false; + private const int _pingInterval = 500; // 0.5秒Ping一次 + + /// + /// 获取设备是否可Ping通 + /// + public bool IsDevicePingable + { + get { return _isDevicePingable; } + private set + { + if (_isDevicePingable != value) + { + _isDevicePingable = value; + Console.WriteLine($"设备Ping状态变更: {(_isDevicePingable ? "可Ping通" : "不可Ping通")}"); + // 状态变化时调用更新Info + UpdateInfo(); + } + } + } + /// /// 设备管理器图像接收事件处理 /// private void DeviceManager_ImageReceived(object sender, ImageReceivedEventArgs e) - { + { if (DesignMode) return; - Image image = null; + try - { + { if (e.ImageData != null && e.ImageData.Length > 0) - { + { // 创建内存流并从流中创建图像 using (MemoryStream ms = new MemoryStream(e.ImageData)) - { + { // 检查流是否可读且有效 if (ms.CanRead && ms.Length > 0) - { + { // 从流中创建图像 - using (Image tempImage = System.Drawing.Image.FromStream(ms)) + using (Image newImage = System.Drawing.Image.FromStream(ms)) { - // 创建一个全新的位图而不仅仅是克隆,确保数据完整性 - image = new Bitmap(tempImage); - // 立即验证新创建的图像是否有效 try - { + { // 访问Width和Height属性来验证图像是否有效 - int width = image.Width; - int height = image.Height; + int width = newImage.Width; + int height = newImage.Height; if (width <= 0 || height <= 0) - { + { Console.WriteLine("创建的图像尺寸无效"); - image.Dispose(); - image = null; + return; + } + if (_lastImage == null) _lastImage = new Bitmap(newImage); + else + { + using(Graphics g= Graphics.FromImage(_lastImage)) + { + g.DrawImage(newImage,Point.Empty); + } } } catch (Exception) - { + { Console.WriteLine("创建的图像无效"); - image.Dispose(); - image = null; + return; } - } - } - } - } - } - catch (Exception ex) - { - Console.WriteLine($"创建图像失败: {ex.Message}"); - // 确保在异常情况下释放资源 - if (image != null) - { - image.Dispose(); - image = null; - } - } - - if (image == null) - { - Console.WriteLine("接收到空或无效的图像数据"); - return; - } - - try - { - // 尝试创建图像的克隆以传递给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 - { - // 创建本地副本以避免闭包问题 - Image imageForUI = clonedImage; - clonedImage = null; // 防止在异步操作期间被修改 - - // 使用BeginInvoke在UI线程上更新图像,避免可能的死锁问题 - this.BeginInvoke(new Action(() => - { - try - { - UpdateImageOnUI(imageForUI); - } - catch (Exception ex) - { - Console.WriteLine($"更新UI图像失败: {ex.Message}"); - // 如果UI更新失败,确保克隆的图像被释放 - if (imageForUI != null) - { + + // 按照用户要求:调用更新到UI + // 只有当图像更新未暂停时才更新UI + if (!_isPaused) + { + this.BeginInvoke(new Action(() => + { try - { - imageForUI.Dispose(); + { + UpdateImageOnUI(); } - catch {} - } + catch (Exception ex) + { + Console.WriteLine($"更新UI图像失败: {ex.Message}"); + } + })); } - })); - } - catch (Exception) - { - // 异常情况下确保释放图像资源 - if (clonedImage != null) - { - try - { - clonedImage.Dispose(); - } - catch {} - clonedImage = null; } - throw; } } - else - { - // 在UI线程上直接更新图像 - UpdateImageOnUI(clonedImage); - } } - else - { - // 如果控件已释放,确保释放图像资源 - clonedImage.Dispose(); - } - - // 释放原始图像资源 - image.Dispose(); } catch (Exception ex) - { - Console.WriteLine($"处理图像接收事件失败: {ex.Message}"); - // 确保在任何异常情况下都释放原始图像 - if (image != null) - { - try - { - image.Dispose(); - } - catch {} - } + { + Console.WriteLine($"处理接收到的图像时出错: {ex.Message}"); } } + + /// - /// 在UI线程上更新图像 + /// 在UI线程上更新图像 - 新实现,按照用户要求: + /// 1. 先将LastImage绘制到全局缓冲 + /// 2. 再将InfoImage绘制到缓冲 + /// 3. 最后一次性绘制到图像框的bitmap /// - private void UpdateImageOnUI(Image image) - { + private void UpdateImageOnUI() + { if (DesignMode) return; + // 线程安全检查 - 确保在UI线程上执行 if (this.InvokeRequired) - { + { try - { - this.BeginInvoke(new Action(UpdateImageOnUI), image); + { + this.BeginInvoke(new Action(UpdateImageOnUI)); } catch (ObjectDisposedException) - { - // 如果控件已被释放,确保释放图像资源 - if (image != null) - { - try { image.Dispose(); } catch {} - } + { Console.WriteLine("控件已释放,跳过UI更新"); } return; } - // 空值检查 - if (image == null) - { - Console.WriteLine("传入UpdateImageOnUI的图像为空"); - return; - } - // 连接状态检查 - 只在设备实际连接时处理图像 - if (_deviceManager.ConnectionStatus != ConnectionStatus.Connected) - { - Console.WriteLine("设备未连接,跳过图像更新"); - try { image.Dispose(); } catch {} + + // 一次性控件有效性检查,避免重复检查 + if (this.IsDisposed || imageBox == null || imageBox.IsDisposed) + { + Console.WriteLine("控件已释放,无法更新图像"); return; } - // 增强图像有效性检查 - bool isImageValid = false; - Image safeImage = null; + Image lastImage = null; + Image infoImage = null; + Image oldImage = null; + Bitmap displayImage = null; try - { - // 创建一个安全的图像副本,确保原图像不被破坏 - safeImage = new Bitmap(image); - - // 预先验证图像是否有效 - if (safeImage.Width > 0 && safeImage.Height > 0) - { - // 尝试访问图像的像素数据,这是更严格的验证方式 - using (Graphics g = Graphics.FromImage(new Bitmap(1, 1))) - { - // 简单绘制操作验证图像是否可绘制 - g.DrawImage(safeImage, 0, 0, 1, 1); + { + // 检查图像缓冲区是否有效 + if (_imageBuffer == null) + { + Console.WriteLine("图像缓冲区未初始化,尝试重新初始化"); + InitializeImageBuffer(); + if (_imageBuffer == null) + { + Console.WriteLine("重新初始化图像缓冲区失败"); + return; } - isImageValid = true; - Console.WriteLine($"图像验证成功: {safeImage.Width}x{safeImage.Height}"); - } - else - { - Console.WriteLine($"图像尺寸无效: {safeImage.Width}x{safeImage.Height}"); - } - } - catch (Exception ex) - { - Console.WriteLine($"图像验证失败: {ex.Message}"); - // 释放临时创建的安全图像 - if (safeImage != null) - { - try { safeImage.Dispose(); } catch {} - } - // 释放原始图像 - try { image.Dispose(); } catch {} - return; - } - - try - { - // 更新图像前先检查控件是否存在/已释放 - if (this.IsDisposed || imageBox == null || imageBox.IsDisposed) - { - // 如果控件已释放,确保图像也被释放 - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - Console.WriteLine("控件已释放,无法更新图像"); - return; } // 保存旧图像引用,以便在设置新图像后释放 - Image oldImage = imageBox.Image; + oldImage = imageBox.Image; - try - { - // 确保控件仍处于有效状态 - if (imageBox.IsDisposed || !isImageValid || safeImage == null) - { - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - return; - } - - // 尝试设置新图像(使用验证过的安全副本) - imageBox.Image = safeImage; - - // 验证图像是否成功设置 - if (imageBox.Image != safeImage) - { - Console.WriteLine("图像设置失败,可能参数无效"); - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - return; - } - - // 释放原始图像,因为我们现在使用的是安全副本 - try { image.Dispose(); } catch {} - - // 仅在新图像成功设置后释放旧图像 - if (oldImage != null && oldImage != safeImage) // 防止自引用释放 - { - try - { - oldImage.Dispose(); - } - catch {} + // 获取当前的LastImage引用 + lock (_lastImageLock) + { + if (_lastImage != null) + { + lastImage = (Image)_lastImage.Clone(); } } - catch (ArgumentException ex) when (ex.Message.Contains("参数无效")) - { - // 特别处理"参数无效"异常 - Console.WriteLine($"图像参数无效异常: {ex.Message}"); - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} + + // 获取当前的InfoImage引用 + lock (_infoImageLock) + { + if (_infoImage != null) + { + infoImage = (Image)_infoImage.Clone(); + } + } + + // 合并锁定,减少锁的数量,提高性能 + lock (_imageBuffer) + { + using (Graphics g = Graphics.FromImage(_imageBuffer)) + { + // 清除缓冲区背景为黑色 + g.Clear(Color.Black); + + // 步骤1:先将LastImage绘制到全局缓冲 + if (lastImage != null) + { + // 保持原始图像比例,居中显示 + float scale = Math.Min((float)BUFFER_WIDTH / lastImage.Width, (float)BUFFER_HEIGHT / lastImage.Height); + int scaledWidth = (int)(lastImage.Width * scale); + int scaledHeight = (int)(lastImage.Height * scale); + int x = (BUFFER_WIDTH - scaledWidth) / 2; + int y = (BUFFER_HEIGHT - scaledHeight) / 2; + + g.DrawImage(lastImage, x, y, scaledWidth, scaledHeight); + } + + // 步骤2:再将InfoImage绘制到缓冲 + if (infoImage != null) + { + g.DrawImage(infoImage, 0, 0); + } + else if (_isDisplayingInfo) + { + // 如果没有InfoImage但需要显示信息,则绘制默认信息 + using (Font font = new Font("微软雅黑", 12, FontStyle.Bold)) + using (Brush textBrush = new SolidBrush(Color.Red)) + { + g.DrawString("测试信息", font, textBrush, 10, 10); + } + } + } - // 尝试设置旧图像回来,如果可能的话 - if (oldImage != null) - { - try - { - imageBox.Image = oldImage; - } - catch - { - // 如果设置旧图像也失败,释放它 - try { oldImage.Dispose(); } catch {} - } + // 在同一个锁内创建缓冲区的副本,避免重复锁定 + displayImage = (Bitmap)_imageBuffer.Clone(); + } + + // 将全局缓冲一次性绘制到图像框的bitmap + imageBox.Image = displayImage; + + if (lastImage != null) + { + Console.WriteLine($"图像更新成功: {lastImage.Width}x{lastImage.Height}"); + } + } + catch (ArgumentException ex) when (ex.Message.Contains("参数无效")) + { + // 特别处理"参数无效"异常 + Console.WriteLine($"图像参数无效异常: {ex.Message}"); + + // 尝试设置旧图像回来,不需要再次检查控件状态(已在方法开始处检查) + if (oldImage != null) + { + try + { + imageBox.Image = oldImage; + } + catch + { + // 如果设置旧图像也失败,释放它 + DisposeImage(oldImage); } } } catch (Exception ex) - { + { Console.WriteLine($"更新图像UI异常: {ex.Message}"); - // 确保在任何异常情况下都释放所有图像资源 - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} + } + finally + { + // 确保在任何情况下都释放资源 + DisposeImage(lastImage); + DisposeImage(infoImage); + // 只有当旧图像不再被使用时才释放 + if (oldImage != null && oldImage != imageBox.Image) + { + DisposeImage(oldImage); + } + } + } + + /// + /// 安全释放图像资源的辅助方法 + /// + private void DisposeImage(Image image) + { + if (image != null) + { + try { image.Dispose(); } catch { } } } @@ -683,6 +878,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 处理连接状态变更 + /// 2、断开或连接时,设置连接状态,调用更新Info /// private void HandleConnectionStatusChanged(ConnectionStatusChangedEventArgs e) { @@ -711,9 +907,6 @@ namespace JoyD.Windows.CS.Toprie case ConnectionStatus.Connected: Console.WriteLine("设备已连接"); - // 清除错误信息 - ShowError(string.Empty); - // 仅在首次连接时设置为热图模式,重连时保留之前的模式 if (!_isReceivingImage) // 首次连接时_isReceivingImage为false { @@ -737,9 +930,19 @@ namespace JoyD.Windows.CS.Toprie // 开始接收图像(包含在try-catch中) if (!_isReceivingImage) - { - StartReceiveImage(); + { + try + { + StartReceiveImage(); + } + catch (Exception ex) + { + Console.WriteLine($"开始接收图像失败: {ex.Message}"); + } } + + // 设置连接状态后,调用更新Info + UpdateInfo(); break; case ConnectionStatus.Disconnected: Console.WriteLine("设备已断开连接"); @@ -759,6 +962,9 @@ namespace JoyD.Windows.CS.Toprie } } + // 设置连接状态后,调用更新Info + UpdateInfo(); + if (!string.IsNullOrEmpty(e.DeviceInfo)) { ShowError(e.DeviceInfo); @@ -769,10 +975,16 @@ namespace JoyD.Windows.CS.Toprie } break; case ConnectionStatus.Connecting: - case ConnectionStatus.Reconnecting: Console.WriteLine($"正在连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}"); ShowError(string.Empty); // 清除之前的错误信息 break; + case ConnectionStatus.Reconnecting: + Console.WriteLine($"正在重新连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}"); + ShowError(string.Empty); // 清除之前的错误信息 + + // 设置重连状态后,调用更新Info + UpdateInfo(); + break; } } catch (Exception ex) @@ -935,176 +1147,134 @@ namespace JoyD.Windows.CS.Toprie { if (DesignMode) return; Console.WriteLine(message); - - // 可以根据需要添加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(width, height)) - using (Graphics g = Graphics.FromImage(errorBitmap)) - { - g.Clear(Color.Black); - using (Font font = new Font("Arial", 10)) - using (Brush brush = new SolidBrush(Color.Red)) - { - SizeF textSize = g.MeasureString(message, font); - PointF textLocation = new PointF( - (errorBitmap.Width - textSize.Width) / 2, - (errorBitmap.Height - textSize.Height) / 2); - g.DrawString(message, font, brush, textLocation); - } - - UpdateImageOnUI(errorBitmap); - } - - // 启动定时器,3秒后清除错误显示 - _errorDisplayTimer.Stop(); - _errorDisplayTimer.Start(); + // 错误消息仅写入日志即可,不需要在UI上显示 } - /// - /// 错误显示定时器事件 - /// - private void ErrorDisplayTimer_Tick(object sender, EventArgs e) - { - if (DesignMode) return; - _errorDisplayTimer.Stop(); - // 清除错误显示,恢复到等待图像状态 - - // 检查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); - using (Font font = new Font("Arial", 10)) - using (Brush brush = new SolidBrush(Color.White)) - { - string waitingText = "正在等待热图数据..."; - SizeF textSize = g.MeasureString(waitingText, font); - PointF textLocation = new PointF( - (waitingBitmap.Width - textSize.Width) / 2, - (waitingBitmap.Height - textSize.Height) / 2); - g.DrawString(waitingText, font, brush, textLocation); - } - - UpdateImageOnUI(waitingBitmap); - } - } /// /// 右键菜单显示前的事件处理方法 /// 用于更新色彩模式菜单项的选中状态 /// + /// + /// 右键菜单打开事件处理 + /// private void ContextMenuStrip1_Opening(object sender, System.ComponentModel.CancelEventArgs e) { + // 暂停菜单项的文本已经在点击事件中更新,这里无需再次更新 if (DesignMode) return; try { - // 根据当前图像模式控制色彩模式菜单的可见性 - if (_deviceManager != null) + // 检查是否处于暂停状态 + bool isPaused = pauseDetectionToolStripMenuItem.Text == "恢复检测"; + + // 检查设备是否已连接 + bool isConnected = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected; + + // 在暂停状态或未连接状态下,隐藏图像模式根菜单和色彩模式菜单 + // 注意:根菜单隐藏后,其所有子菜单会自动隐藏,不需要单独设置 + if (isPaused || !isConnected) { - colorModeToolStripMenuItem.Visible = _deviceManager.CurrentImageMode == ImageMode.Infrared; - } - - // 清除视频模式菜单项的选中状态 - thermalModeToolStripMenuItem.Checked = false; - visibleModeToolStripMenuItem.Checked = false; - fusionMode1ToolStripMenuItem.Checked = false; - fusionMode2ToolStripMenuItem.Checked = false; - fusionMode3ToolStripMenuItem.Checked = false; - fusionMode4ToolStripMenuItem.Checked = false; - fusionMode5ToolStripMenuItem.Checked = false; - - // 清除色彩模式菜单项的选中状态 - whiteHotToolStripMenuItem.Checked = false; - blackHotToolStripMenuItem.Checked = false; - ironRedToolStripMenuItem.Checked = false; - lavaToolStripMenuItem.Checked = false; - rainbowToolStripMenuItem.Checked = false; - ironGrayToolStripMenuItem.Checked = false; - redHotToolStripMenuItem.Checked = false; - rainbow2ToolStripMenuItem.Checked = false; - - // 尝试获取当前色彩模式并更新对应菜单项的选中状态 - if (_deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected) - { - try - { - // 获取当前色彩模式 - PaletteType currentPalette = _deviceManager.CurrentPaletteType; - - // 根据当前色彩模式设置对应菜单项的选中状态 - switch (currentPalette) - { - case PaletteType.WhiteHot: - whiteHotToolStripMenuItem.Checked = true; - break; - case PaletteType.BlackHot: - blackHotToolStripMenuItem.Checked = true; - break; - case PaletteType.IronRed: - ironRedToolStripMenuItem.Checked = true; - break; - case PaletteType.Lava: - lavaToolStripMenuItem.Checked = true; - break; - case PaletteType.Rainbow: - rainbowToolStripMenuItem.Checked = true; - break; - case PaletteType.IronGray: - ironGrayToolStripMenuItem.Checked = true; - break; - case PaletteType.RedHot: - redHotToolStripMenuItem.Checked = true; - break; - case PaletteType.Rainbow2: - rainbow2ToolStripMenuItem.Checked = true; - break; - } - } - catch (Exception ex) - { - Console.WriteLine("获取当前色彩模式失败: " + ex.Message); - } + // 隐藏图像模式根菜单 + if (imageModeToolStripMenuItem != null) + imageModeToolStripMenuItem.Visible = false; - // 更新视频模式菜单项的选中状态 - try + // 隐藏色彩模式菜单 + colorModeToolStripMenuItem.Visible = false; + + // 当只有一个菜单项可见时,隐藏分隔符 + toolStripSeparator1.Visible = false; + } + else + { + // 在非暂停状态且已连接状态下,显示图像模式根菜单 + // 注意:根菜单显示后,其所有子菜单会自动显示,不需要单独设置 + if (imageModeToolStripMenuItem != null) + imageModeToolStripMenuItem.Visible = true; + + // 在非暂停状态且已连接状态下,显示分隔符 + toolStripSeparator1.Visible = true; + + // 根据当前图像模式控制色彩模式菜单的可见性 + colorModeToolStripMenuItem.Visible = true; + // 清除视频模式菜单项的选中状态 + thermalModeToolStripMenuItem.Checked = false; + visibleModeToolStripMenuItem.Checked = false; + fusionMode1ToolStripMenuItem.Checked = false; + fusionMode2ToolStripMenuItem.Checked = false; + fusionMode3ToolStripMenuItem.Checked = false; + fusionMode4ToolStripMenuItem.Checked = false; + fusionMode5ToolStripMenuItem.Checked = false; + + // 清除色彩模式菜单项的选中状态 + whiteHotToolStripMenuItem.Checked = false; + blackHotToolStripMenuItem.Checked = false; + ironRedToolStripMenuItem.Checked = false; + lavaToolStripMenuItem.Checked = false; + rainbowToolStripMenuItem.Checked = false; + ironGrayToolStripMenuItem.Checked = false; + redHotToolStripMenuItem.Checked = false; + rainbow2ToolStripMenuItem.Checked = false; + + // 尝试获取当前色彩模式并更新对应菜单项的选中状态 + if (_deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected) { - // 更改为使用ImageMode枚举 - var currentImageMode = _deviceManager.CurrentImageMode; - thermalModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Infrared; - visibleModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Natural; - } - catch (Exception ex) - { - Console.WriteLine("获取当前图像模式失败: " + ex.Message); + try + { + // 获取当前色彩模式 + PaletteType currentPalette = _deviceManager.CurrentPaletteType; + + // 根据当前色彩模式设置对应菜单项的选中状态 + switch (currentPalette) + { + case PaletteType.WhiteHot: + whiteHotToolStripMenuItem.Checked = true; + break; + case PaletteType.BlackHot: + blackHotToolStripMenuItem.Checked = true; + break; + case PaletteType.IronRed: + ironRedToolStripMenuItem.Checked = true; + break; + case PaletteType.Lava: + lavaToolStripMenuItem.Checked = true; + break; + case PaletteType.Rainbow: + rainbowToolStripMenuItem.Checked = true; + break; + case PaletteType.IronGray: + ironGrayToolStripMenuItem.Checked = true; + break; + case PaletteType.RedHot: + redHotToolStripMenuItem.Checked = true; + break; + case PaletteType.Rainbow2: + rainbow2ToolStripMenuItem.Checked = true; + break; + } + } + catch (Exception ex) + { + Console.WriteLine($"获取当前色彩模式失败: {ex.Message}"); + } + + // 更新视频模式菜单项的选中状态 + try + { + // 更改为使用ImageMode枚举 + var currentImageMode = _deviceManager.CurrentImageMode; + thermalModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Infrared; + visibleModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Natural; + } + catch (Exception ex) + { + Console.WriteLine($"获取当前图像模式失败: {ex.Message}"); + } } } } catch (Exception ex) { - Console.WriteLine("更新右键菜单选中状态失败: " + ex.Message); + Console.WriteLine($"更新右键菜单选中状态失败: {ex.Message}"); } } @@ -1113,7 +1283,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 白热色彩模式 /// - private void whiteHotToolStripMenuItem_Click(object sender, EventArgs e) + private void WhiteHotToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1132,7 +1302,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 黑热色彩模式 /// - private void blackHotToolStripMenuItem_Click(object sender, EventArgs e) + private void BlackHotToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1151,7 +1321,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 铁红色彩模式 /// - private void ironRedToolStripMenuItem_Click(object sender, EventArgs e) + private void IronRedToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1170,7 +1340,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 熔岩色彩模式 /// - private void lavaToolStripMenuItem_Click(object sender, EventArgs e) + private void LavaToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1189,7 +1359,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 彩虹色彩模式 /// - private void rainbowToolStripMenuItem_Click(object sender, EventArgs e) + private void RainbowToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1208,7 +1378,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 铁灰色彩模式 /// - private void ironGrayToolStripMenuItem_Click(object sender, EventArgs e) + private void IronGrayToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1227,7 +1397,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 红热色彩模式 /// - private void redHotToolStripMenuItem_Click(object sender, EventArgs e) + private void RedHotToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1246,7 +1416,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 彩虹2色彩模式 /// - private void rainbow2ToolStripMenuItem_Click(object sender, EventArgs e) + private void Rainbow2ToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1269,7 +1439,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 红外模式 /// - private void thermalModeToolStripMenuItem_Click(object sender, EventArgs e) + private void ThermalModeToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1282,7 +1452,7 @@ namespace JoyD.Windows.CS.Toprie } } - private void visibleModeToolStripMenuItem_Click(object sender, EventArgs e) + private void VisibleModeToolStripMenuItem_Click(object sender, EventArgs e) { try { @@ -1297,6 +1467,110 @@ namespace JoyD.Windows.CS.Toprie #endregion + #region 设备Ping相关方法 + + /// + /// Ping定时器的回调方法 + /// + /// 状态对象 + private void PingTimer_Tick(object state) + { + if (_deviceManager != null && !string.IsNullOrEmpty(_deviceManager.IPAddress)) + { + Task.Factory.StartNew(() => + { + bool pingResult = PingDevice(_deviceManager.IPAddress); + try + { + // 在线程安全的方式下更新状态 + if (this.InvokeRequired) + { + this.Invoke(new Action(UpdatePingState), pingResult); + } + else + { + UpdatePingState(pingResult); + } + } + catch (ObjectDisposedException) + { + // 控件可能已被释放,忽略此更新 + } + }); + } + } + + /// + /// 执行Ping操作 + /// + /// 要Ping的IP地址 + /// 是否Ping通 + private bool PingDevice(string ipAddress) + { + try + { + using (var ping = new System.Net.NetworkInformation.Ping()) + { + var reply = ping.Send(ipAddress, 2000); // 2秒超时 + return reply != null && reply.Status == System.Net.NetworkInformation.IPStatus.Success; + } + } + catch (Exception ex) + { + Console.WriteLine($"Ping设备失败: {ex.Message}"); + return false; + } + } + + /// + /// 更新Ping状态 + /// + /// 是否可Ping通 + private void UpdatePingState(bool isPingable) + { + IsDevicePingable = isPingable; + } + + /// + /// 开始设备Ping + /// + private void StartDevicePing() + { + try + { + if (_pingTimer != null) + { + _pingTimer.Change(0, _pingInterval); // 立即开始,然后按间隔执行 + Console.WriteLine("设备Ping已启动"); + } + } + catch (Exception ex) + { + Console.WriteLine($"启动设备Ping失败: {ex.Message}"); + } + } + + /// + /// 停止设备Ping + /// + private void StopDevicePing() + { + try + { + if (_pingTimer != null) + { + _pingTimer.Change(Timeout.Infinite, Timeout.Infinite); + Console.WriteLine("设备Ping已停止"); + } + } + catch (Exception ex) + { + Console.WriteLine($"停止设备Ping失败: {ex.Message}"); + } + } + + #endregion + /// /// 清理资源 /// @@ -1331,21 +1605,73 @@ namespace JoyD.Windows.CS.Toprie _deviceManager = null; } - // 无论是否在设计模式下,都需要释放定时器 - if (_errorDisplayTimer != null) - { - _errorDisplayTimer.Stop(); - _errorDisplayTimer.Dispose(); - _errorDisplayTimer = null; - } + // 无论是否在设计模式下,都需要释放图像资源 + + // 释放Ping定时器 + if (_pingTimer != null) + { + _pingTimer.Dispose(); + _pingTimer = null; + } if (imageBox != null && !imageBox.IsDisposed && imageBox.Image != null) - { + { imageBox.Image.Dispose(); imageBox.Image = null; } + // 释放图像缓冲区资源 + if (_imageBuffer != null) + { + try + { + _imageBuffer.Dispose(); + _imageBuffer = null; + Console.WriteLine("图像缓冲区资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理ImageBuffer资源异常: {ex.Message}"); + } + } + + // 释放LastImage资源 + lock (_lastImageLock) + { + if (_lastImage != null) + { + try + { + _lastImage.Dispose(); + _lastImage = null; + Console.WriteLine("LastImage资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理LastImage资源异常: {ex.Message}"); + } + } + } + + // 释放InfoImage资源 + lock (_infoImageLock) + { + if (_infoImage != null) + { + try + { + _infoImage.Dispose(); + _infoImage = null; + Console.WriteLine("InfoImage资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理InfoImage资源异常: {ex.Message}"); + } + } + } + // 释放组件资源 if (components != null) { diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs index 2f31099..830af27 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs @@ -153,6 +153,9 @@ namespace JoyD.Windows.CS.Toprie // 设计模式标志,用于在设计模式下跳过实际的设备连接和初始化 public static bool IsDesignMode { get; set; } = true; + // 暂停检测标志,用于控制是否进行连接检测和重连操作 + public bool IsDetectionPaused { get; set; } = false; + // 项目路径,用于数据文件的存取 private string _projectPath = ""; @@ -165,7 +168,7 @@ namespace JoyD.Windows.CS.Toprie private List _deviceList = new List(); // 目标设备ID,用于指定ID连接 - private int _targetDeviceId = -1; + private readonly int _targetDeviceId = -1; // 当前设备ID private int _currentDeviceId = -1; // 默认设备IP @@ -177,7 +180,7 @@ namespace JoyD.Windows.CS.Toprie // 是否已初始化 private bool _isInitialized = false; // 是否已释放 - private bool _isDisposed = false; + private readonly bool _isDisposed = false; // 图像模式 private ImageMode _currentImageMode = ImageMode.Infrared; // 当前色彩模式 @@ -185,14 +188,12 @@ namespace JoyD.Windows.CS.Toprie // 当前视频模式 private VideoMode _currentVideoMode = VideoMode.Infrared; // 默认红外模式 // 自动重连是否启用 - private bool _autoReconnectEnabled = true; // 自动重连定时器 private System.Threading.Timer _reconnectTimer; // 重连间隔(毫秒) private int _reconnectInterval = 2000; - private int _connectionCheckInterval = 5000; // 重连尝试次数 - private int _reconnectAttempts = 0; + // 最大重连尝试次数 public static int MaxReconnectAttempts = 5; // 连接检查定时器 @@ -211,22 +212,17 @@ namespace JoyD.Windows.CS.Toprie private const int DataReceivedTimeout = 15000; // 15秒内未收到数据则认为连接可能断开 // TCP客户端 // 该变量已在文件上方定义,删除重复实现 - // 网络流 - private NetworkStream _imageNetworkStream; - // 图像接收任务 - private Task _imageReceivingTask; - // 取消令牌源 - private CancellationTokenSource _imageReceivingCts; + // 停止请求事件 private ManualResetEvent _stopRequested = new ManualResetEvent(false); // 缓冲区 - private byte[] _imageBuffer = new byte[4096]; + private readonly byte[] _imageBuffer = new byte[4096]; // 图像数据累积缓冲区 - private byte[] _imageDataAccumulator = new byte[0]; + private readonly byte[] _imageDataAccumulator = new byte[0]; // 多部分请求边界 - private string _multipartBoundary = string.Empty; + private readonly string _multipartBoundary = string.Empty; // 锁对象 - private object _lockObject = new object(); + private readonly object _lockObject = new object(); // 是否启用自动重连 private bool _isAutoReconnectEnabled = true; // 最大重连次数 @@ -234,7 +230,7 @@ namespace JoyD.Windows.CS.Toprie // 是否已连接 private bool _isConnected = false; // 连接超时设置 - private int _connectTimeout = 5000; + private readonly int _connectTimeout = 5000; // 当前重连尝试次数 private int _currentReconnectAttempt = 0; // 连接取消令牌源 @@ -384,7 +380,7 @@ namespace JoyD.Windows.CS.Toprie // 保存状态变更相关信息供后续处理 ConnectionStatus finalNewStatus = newStatus; - bool shouldReconnect = (newStatus == ConnectionStatus.Disconnected && _autoReconnectEnabled && oldStatus != ConnectionStatus.Connecting); + bool shouldReconnect = (newStatus == ConnectionStatus.Disconnected && _isAutoReconnectEnabled && oldStatus != ConnectionStatus.Connecting); bool shouldReset = (newStatus == ConnectionStatus.Connected); // 添加状态转换验证,避免不合理的状态切换 @@ -872,7 +868,7 @@ namespace JoyD.Windows.CS.Toprie /// /// 启动连接状态检查 /// - private void StartConnectionCheck() + public void StartConnectionCheck() { lock (_lockObject) // 添加线程同步锁 { @@ -885,6 +881,13 @@ namespace JoyD.Windows.CS.Toprie return; } + // 在暂停检测模式下,跳过连接检查 + if (IsDetectionPaused) + { + Log("暂停检测模式下跳过连接检查"); + return; + } + // 首先停止现有的连接检查,确保资源释放 StopConnectionCheck(); @@ -931,7 +934,7 @@ namespace JoyD.Windows.CS.Toprie Log("重新初始化失败,确认连接已断开"); UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备未初始化,连接已断开"); // 启动自动重连 - if (_autoReconnectEnabled) + if (_isAutoReconnectEnabled) { StartAutoReconnect(); } @@ -980,8 +983,8 @@ namespace JoyD.Windows.CS.Toprie { UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接检查异常", ex); _isInitialized = false; - // 启动自动重连 - if (_autoReconnectEnabled) + // 启动自动重连,只有在未暂停检测时才执行 + if (_isAutoReconnectEnabled && !IsDetectionPaused) { StartAutoReconnect(); } @@ -1033,9 +1036,11 @@ namespace JoyD.Windows.CS.Toprie // 先检查对象状态,避免不必要的操作 lock (_lockObject) { - if (_isDisposed || _connectionStatus != ConnectionStatus.Connected) + if (_isDisposed || _connectionStatus != ConnectionStatus.Connected || IsDetectionPaused) { - Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 对象已释放或连接状态非Connected,跳过检查"); + string reason = _isDisposed ? "对象已释放" : + (_connectionStatus != ConnectionStatus.Connected ? "连接状态非Connected" : "检测已暂停"); + Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - {reason},跳过检查"); return; } } @@ -1092,8 +1097,8 @@ namespace JoyD.Windows.CS.Toprie UpdateConnectionStatus(ConnectionStatus.Disconnected, "连接已断开:设备离线"); - // 断开连接后自动启动重连,但在锁外执行 - if (_autoReconnectEnabled) + // 断开连接后自动启动重连,但在锁外执行,只有在未暂停检测时才执行 + if (_isAutoReconnectEnabled && !IsDetectionPaused) { Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] CheckConnectionWrapper() - 连接断开,将启动自动重连"); // 在锁外启动自动重连,避免潜在的死锁 @@ -1120,6 +1125,13 @@ namespace JoyD.Windows.CS.Toprie Log("设计模式下跳过实际的连接有效性检查,模拟连接有效"); return true; } + + // 在暂停检测模式下,跳过连接有效性检查,直接返回连接有效 + if (IsDetectionPaused) + { + Log("暂停检测模式下跳过连接有效性检查,模拟连接有效"); + return true; + } // 注意:此方法被CheckConnectionWrapper调用,已经在线程安全的上下文中 // 不需要额外加锁,但需要确保SDK实例的访问是线程安全的 @@ -1598,7 +1610,6 @@ namespace JoyD.Windows.CS.Toprie } Log("开始使用HTTP方式接收图像数据"); - bool existed = false; try { // 确保之前的连接已关闭 @@ -2448,29 +2459,52 @@ namespace JoyD.Windows.CS.Toprie { try { - // 确保设备已连接且SDK实例有效 - if (_connectionStatus == ConnectionStatus.Connected && _a8Sdk != null) + // 使用SDK操作锁保护对_a8Sdk的访问 + lock (_sdkOperationLock) { - // 获取当前设备的色彩模式值 - int currentValue = _a8Sdk.GetColorPlate(); - Log($"从设备读取的色彩模式值: {currentValue}"); - - // 尝试将读取到的值转换为PaletteType枚举并更新内部状态 - if (Enum.IsDefined(typeof(PaletteType), currentValue)) + // 确保设备已连接且SDK实例有效 + if (_connectionStatus == ConnectionStatus.Connected && _a8Sdk != null) { - PaletteType actualPalette = (PaletteType)currentValue; - _currentPaletteType = actualPalette; - Log($"已更新内部状态为设备实际值: {actualPalette}"); + try + { + // 获取当前设备的色彩模式值 + int currentValue = _a8Sdk.GetColorPlate(); + Log($"从设备读取的色彩模式值: {currentValue}"); + + // 尝试将读取到的值转换为PaletteType枚举并更新内部状态 + if (Enum.IsDefined(typeof(PaletteType), currentValue)) + { + PaletteType actualPalette = (PaletteType)currentValue; + _currentPaletteType = actualPalette; + Log($"已更新内部状态为设备实际值: {actualPalette}"); + } + else + { + Log($"警告:设备返回的色彩模式值 {currentValue} 不在枚举定义范围内,使用默认值"); + } + } + catch (Exception ex) + { + Log($"从设备读取色彩模式时出错: {ex.Message}"); + // 尝试重新创建SDK实例 + Log("尝试重新创建SDK实例..."); + try + { + _a8Sdk = new A8SDK(_deviceIp); + Log("SDK实例已重新创建"); + } + catch (Exception recreateEx) + { + Log($"重新创建SDK实例失败: {recreateEx.Message}"); + } + throw; + } } else { - Log($"警告:设备返回的色彩模式值 {currentValue} 不在枚举定义范围内,使用默认值"); + Log($"同步色彩模式失败:设备未连接或SDK未初始化"); } } - else - { - Log($"同步色彩模式失败:设备未连接或SDK未初始化"); - } } catch (Exception ex) { @@ -2504,11 +2538,27 @@ namespace JoyD.Windows.CS.Toprie // 重试间隔(毫秒) const int retryDelayMs = 100; + // 确保SDK实例存在,如果不存在则尝试创建 + if (_a8Sdk == null) + { + Log("SDK实例为空,尝试创建新实例..."); + try + { + _a8Sdk = new A8SDK(_deviceIp); + Log("SDK实例创建成功"); + } + catch (Exception ex) + { + Log($"创建SDK实例失败: {ex.Message}"); + return false; + } + } + // 先获取原始值,只读取一次,避免嵌套调用 int originalValue = -1; try { - if (_a8Sdk != null && _connectionStatus == ConnectionStatus.Connected) + if (_connectionStatus == ConnectionStatus.Connected) { originalValue = _a8Sdk.Color_plate; Log($"成功读取当前色彩模式值: {originalValue}"); @@ -2517,6 +2567,17 @@ namespace JoyD.Windows.CS.Toprie catch (Exception ex) { Log($"获取当前色彩模式值时出错: {ex.Message}"); + // 尝试重新创建SDK实例 + Log("尝试重新创建SDK实例..."); + try + { + _a8Sdk = new A8SDK(_deviceIp); + Log("SDK实例已重新创建"); + } + catch (Exception recreateEx) + { + Log($"重新创建SDK实例失败: {recreateEx.Message}"); + } // 即使获取失败,仍尝试设置新值 } @@ -2534,8 +2595,24 @@ namespace JoyD.Windows.CS.Toprie { try { - // 检查对象状态和连接状态 - if (_a8Sdk == null || _connectionStatus != ConnectionStatus.Connected) + // 再次确保SDK实例存在 + if (_a8Sdk == null) + { + Log("SDK实例为空,尝试重新创建..."); + try + { + _a8Sdk = new A8SDK(_deviceIp); + Log("SDK实例重新创建成功"); + } + catch (Exception ex) + { + Log($"重新创建SDK实例失败: {ex.Message}"); + continue; // 继续下一次尝试 + } + } + + // 检查连接状态 + if (_connectionStatus != ConnectionStatus.Connected) { Log($"色彩模式设置失败: {(attempt > 0 ? "重试中" : "")}SDK实例为空或设备未连接"); Thread.Sleep(retryDelayMs); @@ -3195,7 +3272,6 @@ namespace JoyD.Windows.CS.Toprie if (result) { _isConnected = true; - _reconnectAttempts = 0; // 启动心跳检测和连接检查 StartHeartbeat(); @@ -3383,7 +3459,7 @@ namespace JoyD.Windows.CS.Toprie // 连接进行中标志(用于防止重连期间再次触发连接) private volatile bool _isConnecting = false; - private void StartAutoReconnect() + public void StartAutoReconnect() { Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartAutoReconnect() - 开始执行"); @@ -3394,6 +3470,13 @@ namespace JoyD.Windows.CS.Toprie return; } + // 在暂停检测模式下,跳过重连启动 + if (IsDetectionPaused) + { + Log("暂停检测模式下跳过重连启动"); + return; + } + // 检查对象是否已释放 if (_isDisposed) { @@ -3599,6 +3682,13 @@ namespace JoyD.Windows.CS.Toprie return; } + // 在暂停检测模式下,跳过重连操作 + if (IsDetectionPaused) + { + Log("暂停检测模式下跳过实际的重连操作"); + return; + } + Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReconnectCallback() - 开始执行"); // 使用Interlocked.Exchange实现原子操作检查,防止重入 diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/README.md b/Windows/CS/Framework4.0/Toprie/Toprie/README.md new file mode 100644 index 0000000..44edfae --- /dev/null +++ b/Windows/CS/Framework4.0/Toprie/Toprie/README.md @@ -0,0 +1,18 @@ +# JoyD + +### InfoImage, ImageBuffer, 图像框的bitmap, LastImage +1. 初始化时,都创建成512x384的透明bitmap +2. 中途不进行Dispose和设置为null,只在上面进行绘制 +3. 仅当控件被Dispose时,才进行Dispose和设置为null +### 修改流程: + 1. 暂停或恢复时,设置暂停状态,调用更新Info + 2. 断开或连接时,设置连接状态,调用更新Info + 3. Ping通状态变化时,修改Ping状态,调用更新Info + 4. 图像更新时, 保存LastImage, 调用更新到UI + ### 更新Info: + 1. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则清空InfoImage + 2. 最后调用更新UI + ### 更新UI: + 1. 先将LastImage绘制到全局缓冲 + 2. 再将InfoImage绘制到缓冲 + 3. 最后一次性绘制到图像框的bitmap \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/UdpCommunicationManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/UdpCommunicationManager.cs index 6f5bf00..46b4d53 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/UdpCommunicationManager.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/UdpCommunicationManager.cs @@ -69,8 +69,7 @@ namespace JoyD.Windows.CS.Toprie // 启动工作线程 _isRunning = true; - _workerThread = new Thread(ProcessRequests); - _workerThread.IsBackground = true; + _workerThread = new Thread(ProcessRequests) { IsBackground = true }; _workerThread.Start(); Console.WriteLine("UDP通信管理器已初始化"); diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/V8.cs b/Windows/CS/Framework4.0/Toprie/Toprie/V8.cs index 01d0bdc..970386b 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/V8.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/V8.cs @@ -128,16 +128,14 @@ namespace JoyD.Windows.CS.Toprie } // 私有字段 - private string deviceIp; - private Socket socket; - private bool isConnected; + private readonly string deviceIp; + private Socket socket = null; private static bool isSdkInitialized = false; - private static Dictionary deviceInstances = new Dictionary(); + private static readonly Dictionary deviceInstances = new Dictionary(); public V8(string ip) { deviceIp = ip; - isConnected = false; } ~V8() @@ -158,10 +156,9 @@ namespace JoyD.Windows.CS.Toprie if (socket != null) { socket.Close(); - socket = null; - } - isConnected = false; - Console.WriteLine("UDP通信状态已重置"); + socket = null; + } + Console.WriteLine("UDP通信状态已重置"); } catch (Exception ex) { @@ -1132,9 +1129,10 @@ namespace JoyD.Windows.CS.Toprie if (SendCommand(command, out string response)) { // 创建默认的ImagePos对象 - SharedStructures.ImagePos data = new SharedStructures.ImagePos(); - data.area = new SharedStructures.AreaPos[6]; - data.spot = new SharedStructures.SpotPos[6]; + SharedStructures.ImagePos data = new SharedStructures.ImagePos() { + area = new SharedStructures.AreaPos[6], + spot = new SharedStructures.SpotPos[6] + }; // 这里应该解析完整的响应数据 // 简化实现,返回默认值 diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md b/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md new file mode 100644 index 0000000..fef0a0e --- /dev/null +++ b/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md @@ -0,0 +1,54 @@ +# 托普瑞热像仪应用开发任务列表 + +## 1. SDK集成准备 + +- [x] 1.1 复制托普瑞SDK文件到项目目录 +- [x] 1.2 在Visual Studio项目中添加SDK引用 +- [x] 1.3 阅读SDK文档,了解API接口 + +## 2. 设备连接功能 + +- [x] 2.1 实现设备连接模块 +- [x] 2.2 完善连接状态管理 + +## 3. 温度数据处理 + +### 3.1 设备连接建立与管理 +- [ ] 3.1.1 实现设备连接建立流程 + - 使用SDK的`sdk_initialize()`初始化套接字库 + - 利用`sdk_search_device()`搜索设备IP地址 + - 验证设备IP地址是否为默认地址(192.168.100.2)或自定义地址 + - 建立与热像仪的TCP连接,使用8081端口获取原始温度数据 +- [ ] 3.1.2 连接状态维护机制 + - 利用现有的`_heartbeatTimer`实现SDK心跳检测(`sdk_heartbeat`) + - 扩展`UpdateConnectionStatus()`方法,支持温度数据连接状态管理 + - 实现基于`_connectionCheckTimer`的定时连接状态检测 + - 利用现有的自动重连机制,当温度数据连接断开时自动重连 +- [ ] 3.1.3 连接条件判断 + - 调用`IsNetworkAvailable()`验证网络连接状态 + - 检查设备型号兼容性,支持IRAY-A8系列设备 + - 验证设备版本信息,确保SDK兼容性 + - 在获取温度数据前检查设备是否处于正常工作状态 + +### 3.2 原始温度数据获取 +- [ ] 3.2.1 温度数据获取方法实现 + - 在`DeviceManager.cs`中添加`GetRawTemperatureData()`方法 + - 创建TCP客户端连接到设备的8081端口 + - 实现数据接收线程,使用类似现有的`_imageReceiveThread`模式 + - 实现24字节头部结构体解析,提取mark("+TEMP")、payload_length和timestamp +- [ ] 3.2.2 温度数据解析与处理 + - 根据设备分辨率计算温度数据总长度(宽*高*2字节) + - 实现温度数据解码算法:摄氏温度 = (H*256 + L)/10,其中L是低8位,H是高8位 + - 调用`sdk_get_comp_temp()`获取设备温补值并进行叠加修正 + - 创建温度数据模型类,存储解析后的温度矩阵 +- [ ] 3.2.3 数据获取条件控制与异常处理 + - 添加连接状态验证,确保`_connectionStatus`为`Connected` + - 实现数据接收超时处理,基于`_lastDataReceivedTime`和`DataReceivedTimeout` + - 添加数据校验机制,确保接收到的温度数据格式正确 + - 实现断线自动重连,利用现有的`_reconnectTimer`机制 + - 添加异常捕获和日志记录,使用现有的`Log()`方法 +- [ ] 3.2.4 温度数据接口设计 + - 添加温度数据接收事件`TemperatureDataReceived` + - 实现线程安全的数据访问方法 + - 添加温度数据缓存机制,保留最近几帧数据 + - 提供获取特定区域温度数据的辅助方法 \ No newline at end of file