diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs index 882a414..c515207 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.Designer.cs @@ -58,7 +58,6 @@ namespace JoyD.Windows.CS.Toprie this.imageBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.imageBox.TabIndex = 0; this.imageBox.TabStop = false; - this.imageBox.DoubleClick += new System.EventHandler(this.ImageBox_DoubleClick); // // contextMenuStrip1 // diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs index 34ae342..d828752 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs @@ -1,9 +1,9 @@ -using System; -using System.IO; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; +using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; @@ -266,6 +266,9 @@ namespace JoyD.Windows.CS.Toprie // 修改流程第1点和第5点:暂停或恢复时,设置暂停状态,调用更新Info(在暂停状态下会显示暂停信息) UpdateInfo(); + + // 保存菜单状态 + SaveMenuConfig(); } catch (Exception ex) { @@ -290,6 +293,9 @@ namespace JoyD.Windows.CS.Toprie // 初始化图像缓冲区 InitializeImageBuffer(); + // 加载保存的菜单配置 + LoadMenuConfig(); + // 初始化Ping定时器 _pingTimer = new System.Threading.Timer(PingTimer_Tick, null, Timeout.Infinite, Timeout.Infinite); @@ -1578,6 +1584,11 @@ namespace JoyD.Windows.CS.Toprie Console.WriteLine("切换到红外模式失败: " + ex.Message); ShowError("切换到红外模式失败"); } + finally + { + // 菜单状态需要保存和恢复 + SaveMenuConfig(); + } } private void VisibleModeToolStripMenuItem_Click(object sender, EventArgs e) @@ -1591,6 +1602,11 @@ namespace JoyD.Windows.CS.Toprie Console.WriteLine("切换到自然模式失败: " + ex.Message); ShowError("切换到自然模式失败"); } + finally + { + // 菜单状态需要保存和恢复 + SaveMenuConfig(); + } } #endregion @@ -1852,10 +1868,13 @@ namespace JoyD.Windows.CS.Toprie } // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 - if (!_isPaused) - { - UpdateRealTimeInfoOnUI(); - } + if (!_isPaused) + { + UpdateRealTimeInfoOnUI(); + } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); } /// @@ -1879,6 +1898,9 @@ namespace JoyD.Windows.CS.Toprie { UpdateRealTimeInfoOnUI(); } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); } /// @@ -1894,6 +1916,9 @@ namespace JoyD.Windows.CS.Toprie { UpdateRealTimeInfoOnUI(); } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); } /// @@ -1909,6 +1934,9 @@ namespace JoyD.Windows.CS.Toprie { UpdateRealTimeInfoOnUI(); } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); } /// @@ -1924,6 +1952,9 @@ namespace JoyD.Windows.CS.Toprie { UpdateRealTimeInfoOnUI(); } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); } /// @@ -1945,6 +1976,466 @@ namespace JoyD.Windows.CS.Toprie // 日志禁用时的清理(如果需要) WriteLog("日志保存功能已禁用"); } + + // 菜单状态变更时自动静默保存配置 + SaveMenuConfig(); + } + + /// + /// 保存菜单配置到CSV文件 + /// + private void SaveMenuConfig() + { + try + { + WriteLog("开始保存菜单配置"); + + string configFilePath; + + // 如果项目路径存在,使用项目路径下的Config目录 + if (!string.IsNullOrEmpty(_projectPath) && Directory.Exists(_projectPath)) + { + try + { + // 创建Config目录(如果不存在) + string configDir = Path.Combine(_projectPath, "Config"); + Directory.CreateDirectory(configDir); + + // 配置文件路径 + configFilePath = Path.Combine(configDir, "menu_config.csv"); + WriteLog($"使用项目路径保存配置: {configFilePath}"); + } + catch (UnauthorizedAccessException accessEx) + { + string errorMsg = $"项目路径访问权限不足: {accessEx.Message}"; + WriteLog($"{errorMsg} - {accessEx.StackTrace}"); + Console.WriteLine(errorMsg); + // 回退到应用程序目录 + configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); + Directory.CreateDirectory(Path.GetDirectoryName(configFilePath)); + WriteLog($"回退到应用程序目录保存: {configFilePath}"); + } + } + else + { + // 如果项目路径不存在,使用应用程序目录下的Config目录 + string configDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config"); + Directory.CreateDirectory(configDir); + configFilePath = Path.Combine(configDir, "menu_config.csv"); + WriteLog($"使用应用程序目录保存配置: {configFilePath}"); + } + + // 创建CSV内容 + StringBuilder csvContent = new StringBuilder(); + // 添加标题行 + csvContent.AppendLine("MenuName,Status,Checked"); + + try + { + // 保存暂停检测菜单状态 + if (pauseDetectionToolStripMenuItem != null) + { + csvContent.AppendLine($"pauseDetection,{pauseDetectionToolStripMenuItem.Text},{_isPaused}"); + } + else + { + WriteLog("警告: pauseDetectionToolStripMenuItem未初始化"); + } + + // 保存日志保存菜单状态 + if (saveLogToolStripMenuItem != null) + { + csvContent.AppendLine($"saveLog,{saveLogToolStripMenuItem.Text},{saveLogToolStripMenuItem.Checked}"); + } + else + { + WriteLog("警告: saveLogToolStripMenuItem未初始化"); + } + + // 保存温度显示菜单状态 + if (globalTemperatureToolStripMenuItem != null) + csvContent.AppendLine($"globalTemperature,{globalTemperatureToolStripMenuItem.Text},{globalTemperatureToolStripMenuItem.Checked}"); + if (areaTemperatureToolStripMenuItem != null) + csvContent.AppendLine($"areaTemperature,{areaTemperatureToolStripMenuItem.Text},{areaTemperatureToolStripMenuItem.Checked}"); + if (maxTemperatureToolStripMenuItem != null) + csvContent.AppendLine($"maxTemperature,{maxTemperatureToolStripMenuItem.Text},{maxTemperatureToolStripMenuItem.Checked}"); + if (avgTemperatureToolStripMenuItem != null) + csvContent.AppendLine($"avgTemperature,{avgTemperatureToolStripMenuItem.Text},{avgTemperatureToolStripMenuItem.Checked}"); + if (minTemperatureToolStripMenuItem != null) + csvContent.AppendLine($"minTemperature,{minTemperatureToolStripMenuItem.Text},{minTemperatureToolStripMenuItem.Checked}"); + + // 保存色彩模式菜单状态 + if (whiteHotToolStripMenuItem != null) + csvContent.AppendLine($"whiteHot,{whiteHotToolStripMenuItem.Text},{whiteHotToolStripMenuItem.Checked}"); + if (blackHotToolStripMenuItem != null) + csvContent.AppendLine($"blackHot,{blackHotToolStripMenuItem.Text},{blackHotToolStripMenuItem.Checked}"); + if (ironRedToolStripMenuItem != null) + csvContent.AppendLine($"ironRed,{ironRedToolStripMenuItem.Text},{ironRedToolStripMenuItem.Checked}"); + if (lavaToolStripMenuItem != null) + csvContent.AppendLine($"lava,{lavaToolStripMenuItem.Text},{lavaToolStripMenuItem.Checked}"); + if (rainbowToolStripMenuItem != null) + csvContent.AppendLine($"rainbow,{rainbowToolStripMenuItem.Text},{rainbowToolStripMenuItem.Checked}"); + if (ironGrayToolStripMenuItem != null) + csvContent.AppendLine($"ironGray,{ironGrayToolStripMenuItem.Text},{ironGrayToolStripMenuItem.Checked}"); + if (redHotToolStripMenuItem != null) + csvContent.AppendLine($"redHot,{redHotToolStripMenuItem.Text},{redHotToolStripMenuItem.Checked}"); + if (rainbow2ToolStripMenuItem != null) + csvContent.AppendLine($"rainbow2,{rainbow2ToolStripMenuItem.Text},{rainbow2ToolStripMenuItem.Checked}"); + + // 保存图像模式菜单状态 + if (thermalModeToolStripMenuItem != null) + csvContent.AppendLine($"thermalMode,{thermalModeToolStripMenuItem.Text},{thermalModeToolStripMenuItem.Checked}"); + if (visibleModeToolStripMenuItem != null) + csvContent.AppendLine($"visibleMode,{visibleModeToolStripMenuItem.Text},{visibleModeToolStripMenuItem.Checked}"); + } + catch (Exception ex) + { + // 捕获配置数据收集过程中的异常 + string errorMsg = $"收集菜单状态数据失败: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + Console.WriteLine(errorMsg); + throw; + } + + // 写入文件 + try + { + File.WriteAllText(configFilePath, csvContent.ToString(), Encoding.UTF8); + string successMsg = $"菜单配置已成功保存到CSV文件: {configFilePath}"; + Console.WriteLine(successMsg); + WriteLog(successMsg); + } + catch (IOException ioEx) + { + // 捕获IO异常并记录详细信息 + string errorMsg = $"配置文件IO异常: {ioEx.Message}"; + WriteLog($"{errorMsg} - {ioEx.StackTrace}"); + Console.WriteLine(errorMsg); + } + catch (UnauthorizedAccessException accessEx) + { + // 捕获权限异常并记录详细信息 + string errorMsg = $"配置文件访问权限异常: {accessEx.Message}"; + WriteLog($"{errorMsg} - {accessEx.StackTrace}"); + Console.WriteLine(errorMsg); + } + } + catch (Exception ex) + { + // 记录保存失败的综合日志 + string errorMsg = $"保存菜单配置过程中发生未预期的错误: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + Console.WriteLine(errorMsg); + } + finally + { + WriteLog("菜单配置保存操作完成"); + } + } + + /// + /// 从CSV文件加载菜单配置 + /// + private void LoadMenuConfig() + { + WriteLog("开始加载菜单配置"); + + try + { + string configFilePath = null; + + // 检查项目路径下的配置文件 + if (!string.IsNullOrEmpty(_projectPath) && Directory.Exists(_projectPath)) + { + try + { + configFilePath = Path.Combine(_projectPath, "Config", "menu_config.csv"); + WriteLog($"检查项目路径配置文件: {configFilePath}"); + + // 如果项目路径下没有配置文件,检查应用程序目录 + if (!File.Exists(configFilePath)) + { + configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); + WriteLog($"项目路径配置文件不存在,检查应用程序目录: {configFilePath}"); + } + } + catch (UnauthorizedAccessException accessEx) + { + string errorMsg = $"项目路径访问权限不足: {accessEx.Message}"; + WriteLog($"{errorMsg} - {accessEx.StackTrace}"); + Console.WriteLine(errorMsg); + // 回退到应用程序目录 + configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); + WriteLog($"回退到应用程序目录: {configFilePath}"); + } + } + else + { + // 直接检查应用程序目录下的配置文件 + configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); + WriteLog($"检查应用程序目录配置文件: {configFilePath}"); + } + + // 如果配置文件不存在,直接返回 + if (!File.Exists(configFilePath)) + { + string infoMsg = $"菜单配置文件不存在,使用默认配置: {configFilePath}"; + Console.WriteLine(infoMsg); + WriteLog(infoMsg); + return; + } + + // 读取CSV文件内容 + string[] lines = null; + try + { + lines = File.ReadAllLines(configFilePath, Encoding.UTF8); + WriteLog($"成功读取配置文件,共{lines.Length}行"); + } + catch (IOException ioEx) + { + string errorMsg = $"配置文件读取失败: {ioEx.Message}"; + WriteLog($"{errorMsg} - {ioEx.StackTrace}"); + Console.WriteLine(errorMsg); + return; + } + catch (UnauthorizedAccessException accessEx) + { + string errorMsg = $"配置文件访问权限不足: {accessEx.Message}"; + WriteLog($"{errorMsg} - {accessEx.StackTrace}"); + Console.WriteLine(errorMsg); + return; + } + + // 创建菜单状态字典 + Dictionary> menuStates = new Dictionary>(); + + // 解析CSV内容 + try + { + for (int i = 1; i < lines.Length; i++) // 跳过标题行 + { + string line = lines[i]; + if (string.IsNullOrWhiteSpace(line)) + { + WriteLog($"警告: 配置文件第{i+1}行为空,已跳过"); + continue; + } + + try + { + // 简单的CSV解析,这里假设没有包含逗号的字段 + string[] parts = line.Split(','); + if (parts.Length >= 3) + { + string menuName = parts[0]; + string status = parts[1]; + string isChecked = parts[2]; + + menuStates[menuName] = new Dictionary + { + { "status", status }, + { "checked", isChecked } + }; + } + else + { + WriteLog($"警告: 配置文件第{i+1}行格式不正确,字段数量不足: {line}"); + } + } + catch (Exception ex) + { + WriteLog($"解析配置文件第{i+1}行时出错: {ex.Message} - {ex.StackTrace}"); + // 继续处理下一行,不中断整个解析过程 + } + } + + WriteLog($"成功解析配置文件,共加载{menuStates.Count}个菜单状态"); + } + catch (Exception ex) + { + string errorMsg = $"配置文件解析过程中发生错误: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + Console.WriteLine(errorMsg); + return; + } + + // 应用菜单状态 + try + { + // 暂停检测菜单 + if (menuStates.ContainsKey("pauseDetection") && pauseDetectionToolStripMenuItem != null) + { + try + { + _isPaused = bool.Parse(menuStates["pauseDetection"]["checked"]); + pauseDetectionToolStripMenuItem.Text = menuStates["pauseDetection"]["status"]; + WriteLog($"应用暂停检测菜单状态: {_isPaused}"); + } + catch (FormatException ex) + { + WriteLog($"暂停检测菜单状态格式错误: {ex.Message}"); + } + } + + // 日志保存菜单 + if (menuStates.ContainsKey("saveLog") && saveLogToolStripMenuItem != null) + { + try + { + saveLogToolStripMenuItem.Checked = bool.Parse(menuStates["saveLog"]["checked"]); + saveLogToolStripMenuItem.Text = menuStates["saveLog"]["status"]; + // 如果有DeviceManager实例,同步状态 + if (_deviceManager != null) + { + DeviceManager.LogToFile = saveLogToolStripMenuItem.Checked; + WriteLog($"同步日志保存状态到DeviceManager: {saveLogToolStripMenuItem.Checked}"); + } + WriteLog($"应用日志保存菜单状态: {saveLogToolStripMenuItem.Checked}"); + } + catch (FormatException ex) + { + WriteLog($"日志保存菜单状态格式错误: {ex.Message}"); + } + } + + // 温度显示菜单 - 添加空值检查 + if (globalTemperatureToolStripMenuItem != null) + ApplyMenuState(menuStates, "globalTemperature", globalTemperatureToolStripMenuItem); + if (areaTemperatureToolStripMenuItem != null) + ApplyMenuState(menuStates, "areaTemperature", areaTemperatureToolStripMenuItem); + if (maxTemperatureToolStripMenuItem != null) + ApplyMenuState(menuStates, "maxTemperature", maxTemperatureToolStripMenuItem); + if (avgTemperatureToolStripMenuItem != null) + ApplyMenuState(menuStates, "avgTemperature", avgTemperatureToolStripMenuItem); + if (minTemperatureToolStripMenuItem != null) + ApplyMenuState(menuStates, "minTemperature", minTemperatureToolStripMenuItem); + + // 色彩模式菜单 - 添加空值检查 + if (whiteHotToolStripMenuItem != null) + ApplyMenuState(menuStates, "whiteHot", whiteHotToolStripMenuItem); + if (blackHotToolStripMenuItem != null) + ApplyMenuState(menuStates, "blackHot", blackHotToolStripMenuItem); + if (ironRedToolStripMenuItem != null) + ApplyMenuState(menuStates, "ironRed", ironRedToolStripMenuItem); + if (lavaToolStripMenuItem != null) + ApplyMenuState(menuStates, "lava", lavaToolStripMenuItem); + if (rainbowToolStripMenuItem != null) + ApplyMenuState(menuStates, "rainbow", rainbowToolStripMenuItem); + if (ironGrayToolStripMenuItem != null) + ApplyMenuState(menuStates, "ironGray", ironGrayToolStripMenuItem); + if (redHotToolStripMenuItem != null) + ApplyMenuState(menuStates, "redHot", redHotToolStripMenuItem); + if (rainbow2ToolStripMenuItem != null) + ApplyMenuState(menuStates, "rainbow2", rainbow2ToolStripMenuItem); + + // 图像模式菜单 - 添加空值检查 + if (thermalModeToolStripMenuItem != null) + ApplyMenuState(menuStates, "thermalMode", thermalModeToolStripMenuItem); + if (visibleModeToolStripMenuItem != null) + ApplyMenuState(menuStates, "visibleMode", visibleModeToolStripMenuItem); + + string successMsg = "菜单配置已从CSV文件成功加载并应用"; + Console.WriteLine(successMsg); + WriteLog(successMsg); + } + catch (Exception ex) + { + string errorMsg = $"应用菜单状态过程中发生错误: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + Console.WriteLine(errorMsg); + } + } + catch (Exception ex) + { + // 捕获未预期的异常 + string errorMsg = $"加载菜单配置过程中发生未预期的错误: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + Console.WriteLine(errorMsg); + } + finally + { + WriteLog("菜单配置加载操作完成"); + } + } + + /// + /// 应用菜单状态的辅助方法 + /// + /// 菜单状态字典 + /// 菜单名称 + /// 菜单项控件 + private void ApplyMenuState(Dictionary> menuStates, string menuName, ToolStripMenuItem menuItem) + { + try + { + // 参数验证 + if (menuStates == null) + { + WriteLog($"警告: 应用菜单状态时,menuStates参数为null,菜单名称: {menuName}"); + return; + } + + if (string.IsNullOrEmpty(menuName)) + { + WriteLog("警告: 应用菜单状态时,menuName参数为空"); + return; + } + + if (menuItem == null) + { + WriteLog($"警告: 应用菜单状态时,菜单项为null,菜单名称: {menuName}"); + return; + } + + // 检查菜单状态字典中是否包含该菜单 + if (menuStates.ContainsKey(menuName)) + { + WriteLog($"应用菜单状态: {menuName}"); + + try + { + // 尝试解析和应用Checked状态 + string checkedValue = menuStates[menuName].ContainsKey("checked") ? menuStates[menuName]["checked"] : "false"; + bool isChecked = bool.Parse(checkedValue); + menuItem.Checked = isChecked; + WriteLog($"菜单 {menuName} 选中状态已设置为: {isChecked}"); + } + catch (FormatException ex) + { + string errorMsg = $"菜单 {menuName} 选中状态格式错误: {ex.Message}"; + WriteLog(errorMsg); + // 保留默认值,不抛出异常以允许其他菜单状态继续应用 + } + + try + { + // 尝试设置菜单项文本 + if (menuStates[menuName].ContainsKey("status")) + { + menuItem.Text = menuStates[menuName]["status"]; + WriteLog($"菜单 {menuName} 文本已设置为: {menuStates[menuName]["status"]}"); + } + } + catch (Exception ex) + { + string errorMsg = $"设置菜单 {menuName} 文本时出错: {ex.Message}"; + WriteLog(errorMsg); + // 保留默认值,不抛出异常以允许其他菜单状态继续应用 + } + } + else + { + WriteLog($"菜单配置中未找到菜单项: {menuName}"); + } + } + catch (Exception ex) + { + // 捕获所有其他未预期的异常 + string errorMsg = $"应用菜单 {menuName} 状态时发生未预期的错误: {ex.Message}"; + WriteLog($"{errorMsg} - {ex.StackTrace}"); + // 不重新抛出异常,避免影响其他菜单状态的应用 + } } /// @@ -2217,6 +2708,44 @@ namespace JoyD.Windows.CS.Toprie } } + /// + /// 验证菜单功能是否正常工作 - 用于测试 + /// 可以在调试时手动调用此方法来测试菜单配置的保存和加载 + /// + public void ValidateMenuFunctionality() + { + try + { + WriteLog("开始验证菜单功能"); + + // 1. 保存当前菜单配置 + SaveMenuConfig(); + WriteLog("菜单配置已保存"); + + // 2. 模拟菜单状态更改并再次保存 + if (globalTemperatureToolStripMenuItem != null) + { + globalTemperatureToolStripMenuItem.Checked = !globalTemperatureToolStripMenuItem.Checked; + WriteLog($"临时更改全局温度菜单状态为: {globalTemperatureToolStripMenuItem.Checked}"); + } + + // 3. 重新加载配置 + LoadMenuConfig(); + WriteLog("菜单配置已重新加载"); + + // 4. 输出验证信息 + WriteLog("菜单功能验证完成"); + Console.WriteLine("菜单功能验证完成: 保存和加载配置正常工作"); + } + catch (Exception ex) + { + WriteLog($"验证菜单功能时出错: {ex.Message}"); + Console.WriteLine($"验证菜单功能失败: {ex.Message}"); + } + } + } + } + /// /// imageBox双击事件处理方法 /// 双击后弹出检测配置窗口 diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/README.md b/Windows/CS/Framework4.0/Toprie/Toprie/README.md deleted file mode 100644 index 8fb4e5c..0000000 --- a/Windows/CS/Framework4.0/Toprie/Toprie/README.md +++ /dev/null @@ -1,220 +0,0 @@ -# JoyD - -## 图像处理相关 - -### InfoImage, ImageBuffer, 图像框的bitmap, LastImage, DisplayImage -1. 初始化时,都创建成512x384的透明bitmap -2. 中途不进行Dispose和设置为null,只在上面进行绘制 -3. 仅当控件被Dispose时,才进行Dispose和设置为null - -## 操作流程 - -### 修改流程 -1. 暂停或恢复时,设置暂停状态,调用更新Info -2. 断开或连接时,设置连接状态,调用更新Info -3. Ping通状态变化时,修改Ping状态,调用更新Info -4. 图像更新时,保存LastImage,调用更新UI -5. 2-4 只在非暂停状态下调用更新,暂停状态下不更新Info和UI -6. 数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 - -### 更新Info -1. 以透明色清空Info -2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则就绪 -3. 如果未就绪,调用更新UI - -### 更新UI - 1. 先将LastImage绘制到全局缓冲 - 2. 再将InfoImage绘制到缓冲 - 3. 在就绪条件下,调用更新实时信息,并将DisplayImage绘制到缓冲 - 4. 最后一次性绘制到图像框的bitmap - 5. 同步更新检测配置窗口的实时图像属性 - -### 更新实时信息 -1. 以透明色清空DisplayImage -2. 如果没有温度数据或温度数据的时间3秒之前,返回 -3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息 -4. 如果勾选了全局温度,则显示全局温度(居中显示),否则显示区域温度(居中显示) -5. 如果勾选了区域温度,则显示区域框,否则不显示区域框 -6. 如果勾选了平均温度,显示平均温度(居中显示) -7. 如果勾选了最低温度,显示最低温度(居中显示) -8. 如果勾选了最高温度,显示最高温度(居中显示) -9. 如果是全局温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于图像框居中显示 -10. 如果是区域温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于区域框居中显示 - -## TCP温度数据接收 - -1. 系统初始化时,创建后台线程 -2. 然后线程循环执行 -3. 判断是否存在tcp连接实例 -4. 如果不存在连接实例,则判断是否暂停。如果暂停,则Sleep 1秒后继续执行。否则创建一个新的连接实例。 -5. 如果存在连接实例,则判断是否连接。如果未连接,则重新连接 -6. 同步接收和处理数据 -7. 收到数据后,如果暂停,接收后丢弃,Sleep 1秒。否则同步将收到的数组复制放到待处理列表(线程安全列表)。 -8. 通知处理线程,有新数据到达 -9. 跳转到6 -10. Dispose时,关闭后台线程 - -### 温度数据接收时的补充说明 - -总之就是不主动断开连接: - -1. 同步机制简单,不用考虑阻塞,因为在后台线程里 -2. 不用心跳机制,协议约定是每秒1帧数据 -3. 异常情况处理,出现异常就跳转到第3步 -4. 不用考虑波动问题,这是在一个循环中,它会下次重试 -5. 在异常出现且需要重新创建对象前,先释放旧的资源 -6. 数据完整性验证放在了数据处理环节 -7. 连接失败,会跳转到第3步。下一循环会自动重试 - -### 温度数据处理逻辑 - -1. 收到的数组放到待处理列表中 -2. 开始循环处理列表 -3. 如果列表为空,退出循环 -4. 从列表中读取第一个数组,查看是否有头信息 -5. 如果没有头信息,将数组从列表中移除,跳转到3 -6. 如果有头信息,查看头信息中数据长度信息 -7. 检查数组中是否有足够的数据长度 -8. 如果没有足够的数据长度,看列表中是否有下一个数组 -9. 如果没有,则退出循环,否则将下一个数组合并到当前数组中尾部,将下一个数组从列表中移除,然后跳转到7 -10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息 -11. 如果有,则将数组中第二个头信息之前的数据移除,然后跳转到6 -12. 如果没有,则将数据长度范围内的数据从数组取出进行解析处理,剩余部分保留到数组中,跳转到4 -13. 处理完毕后,跳转到3 -14. 循环完成,进入等待状态 - -### 温度数据解析处理逻辑 - -根据热像仪SDK文档,原始温度数据的解析处理需遵循以下逻辑: - -#### 原始温度数据格式说明 -- 通过TCP端口8081获取温度数据 -- 数据包含24字节头部和温度数据两部分 -- 头部结构体(header)包含: - - mark[5]: 固定标识"+TEMP" - - payload_length: 温度数据长度 - - timestamp: 时间戳(前4字节为秒级时间,后2字节为毫秒级时间) -- 温度数据长度计算:宽×高×2字节,例如240×180分辨率时为240×180×2字节 -- 数据按字节低位在前读取,如读取为80 51 01 00时实际值为00 01 51 80的16进制值 - -#### 原始温度数据解析方法 -1. **单像素温度计算**: - - 每个像素点温度由两个字节组成(L为低8位,H为高8位) - - 公式:摄氏温度×10 = H×256 + L - - 实际温度:摄氏温度 = (H×256 + L)/10 - -2. **温度数据布局**: - - 数据按行优先顺序排列 - - 例如分辨率为4×3时,数据顺序为:L1 H1 L2 H2 L3 H3 L4 H4 L5 H5 ... - -#### 温补值处理 -1. **温补值获取**: - - 通过SDK函数`sdk_get_comp_temp`获取温补值 - - 函数原型:`sdk_get_comp_temp(const char* ip, int* comp_temp)` - - 返回值为0表示获取成功 - -2. **温补值应用**: - - 温补值也是摄氏度乘以10的结果 - - 最终温度计算公式: - ``` - 最终温度 = 原始温度 + 温补值 - ``` - - 示例:原始温度为243(24.3°C),温补值为5(0.5°C),则最终温度为248(24.8°C) - -#### 温度数据解析处理流程 -1. 接收TCP数据并解析头部信息 -2. 验证mark标识是否为"+TEMP" -3. 根据payload_length确定温度数据长度 -4. 按像素点顺序解析每个像素的温度值 -5. 获取温补值并应用到每个像素的温度上 -6. 计算得到最终的温度数据 - -#### 注意事项 -1. 时间戳转换时需注意低字节在前、高字节在后的顺序 -2. 温补值是设备补偿值,必须应用到原始温度上才能得到与网页计算结果一致的温度 -3. 分辨率不同时,温度数据长度会相应变化,解析时需根据实际分辨率计算 -4. 原始温度数据更新频率约为每秒一条 - -### 温度显示菜单 -1. 在右键菜单中增加温度显示菜单 -2. 温度显示菜单下显示全局温度(可勾选) -3. 温度显示菜单下显示区域温度(可勾选) -4. 区域温度和全局温度只能二选一显示 -5. 温度显示菜单下显示最高温度、平均温度和最低温度(可勾选) - -### 检测配置 -#### 区域绘制逻辑 -1. 创建独立的叠加层图像 :专门维护一个与显示图像同尺寸的Image对象作为矩形框的叠加层 -2. 分离绘制逻辑 :将临时绘制和最终绘制分离,临时矩形仍通过Paint事件显示,完成的矩形绘制到叠加层 -3. 图像合并机制 :在Paint事件中先绘制叠加层,再绘制临时矩形。 -4. 保存的矩形框位置和大小信息应该是相对于图像的,而不是相对于控件的。 -5. 当btnDrawRegion按下后,处于绘制状态, btnSelectColor才显示出来 -6. 当绘制状态时,右击鼠标,退出绘制状态,且清除所有临时绘制。 -7. 当绘制状态时,双击鼠标,随机生成一个颜色,用于绘制矩形框。 -8. 当就绪状态时,鼠标移到区域内,该区域内填充半透明色,当有多个重叠时,填充索引号最大的区域 -9. 当鼠标在半透明区域内单击时,该区域填充半透明色,且显示八个句柄,表示选中该区域。 -10. 选中区域时,工具栏显示按钮btnDeleteRegion和btnSelectColor;隐藏btnDrawRegion。 -11. 当选中区域时,btnSelectColor用于改变选中区域的颜色。 -12. 当选中区域时,btnDeleteRegion用于删除该区域,删除后btnSelectColor隐藏,btnDrawRegion显示。 -13. 当选中区域时,鼠标移动到八个句柄上,显示对应的光标,用于调整区域大小。 -14. 当选中区域时,鼠标可以移动区域,用于调整区域位置。 -15. 当选中区域时,右击鼠标,退出选中状态,转为就绪状态 -16. 当就绪状态时,工具栏显示绘制温差图按钮(btnDrawTempDiff) -17. 当绘制温差图按钮按下时,进入绘制温差图状态,显示添加和删除温差图例按钮(btnAddTempDiff、btnDeleteTempDiff),显示温差图例列表(dataGridViewTempDiff) -18. 当绘制温差图状态时,右击鼠标进入就绪状态 -19. 初始状态/就绪状态,显示dataGridViewTempDiff,但只允许查看 - -### btnNewTempRegion(新建测温区) -1. 移除所有已有的测温区列表 -2. 用透明色清空叠加层图像 -### btnLoadTempRegion(加载测温区) -1. 弹出用户打开文件对话框,用户选择要加载的csv文件 -2. 移除所有已有的测温区列表,用透明色清空叠加层图像 -3. 从csv文件中读取所有测温区位置大小和颜色信息,添加到测温区列表中 -4. 用读取的颜色填充叠加层图像对应的区域 -### btnSaveTempRegion(保存测温区) -1. 弹出用户保存文件对话框,用户选择保存路径和文件名 -2. 保存测温区列表中的所有测温区位置大小和颜色信息为csv文件 -### btnNewTempDiff(新建温差图) -1. 用透明色清空温差层图像 -2. 移除所有已有的温差图例列表 -### btnLoadTempDiff(加载温差图) -1. 弹出用户打开文件对话框,用户选择要加载的csv文件 -2. 用透明色清空温差层图像 -3. 移除所有已有的温差图例列表 -4. 从csv文件中读取所有温差图例信息,添加到温差图例列表中 -5. 从csv文件中读取所有像素的温度值,绘制温差层图像对应的像素 -### btnSaveTempDiff(保存温差图) -1. 弹出用户保存文件对话框,用户选择保存路径和文件名 -2. 保存温差图例列表中的所有温差图例信息到csv文件 -3. 每个温差图例信息占一行,格式为: - - 温度(如20°C) - - 颜色(如#FF0000表示红色) -4. 保存温差图每个像素的温度值到csv文件 - - 每个像素温度值占一行,格式为: - - X坐标(如100) - - Y坐标(如300) - - 温度值(°C)(如25.5) - - - -#### 配置状态说明 -1. 初始状态/就绪状态: - - 显示btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff - - 显示btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 - - 隐藏btnSelectColor、btnDeleteRegion按钮、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25、txtRegionNumber -2. 选中区域状态: - - 显示btnDeleteRegion和btnSelectColor按钮、txtRegionNumber - - 隐藏btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 - - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 -3. 绘制状态: - - 显示btnSelectColor按钮 - - 显示btnDrawRegion按钮、txtRegionNumber - - 隐藏btnDeleteRegion按钮 - - 隐藏btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 - - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 -4. 绘制温差图状态: - - 显示btnDrawTempDiff按钮,显示dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff - - 当选定温差图例后,显示btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 - - 隐藏btnSelectColor、btnDeleteRegion、btnDrawRegion按钮 - - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Setting.Designer.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Setting.Designer.cs index a62c702..6dd47b9 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Setting.Designer.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Setting.Designer.cs @@ -146,7 +146,6 @@ namespace JoyD.Windows.CS // toolStrip // this.toolStrip.AllowDrop = true; - this.toolStrip.CanOverflow = true; this.toolStrip.Dock = System.Windows.Forms.DockStyle.None; this.toolStrip.ImageScalingSize = new System.Drawing.Size(20, 20); this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -390,6 +389,7 @@ namespace JoyD.Windows.CS this.ClientSize = new System.Drawing.Size(793, 450); this.Controls.Add(this.splitContainer); this.Name = "Setting"; + this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.Text = "检测配置"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Setting_FormClosing); diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Setting.resx b/Windows/CS/Framework4.0/Toprie/Toprie/Setting.resx index 1156463..2cc47af 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Setting.resx +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Setting.resx @@ -120,20 +120,4 @@ 516, 17 - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK - YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X - /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t - I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM - cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh - 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD - lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A - HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb - 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC - nOccAdABIDXXE1nzAAAAAElFTkSuQmCC - - \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md b/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md index fef0a0e..8fb4e5c 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md +++ b/Windows/CS/Framework4.0/Toprie/Toprie/todolist.md @@ -1,54 +1,220 @@ -# 托普瑞热像仪应用开发任务列表 +# JoyD -## 1. SDK集成准备 +## 图像处理相关 -- [x] 1.1 复制托普瑞SDK文件到项目目录 -- [x] 1.2 在Visual Studio项目中添加SDK引用 -- [x] 1.3 阅读SDK文档,了解API接口 +### InfoImage, ImageBuffer, 图像框的bitmap, LastImage, DisplayImage +1. 初始化时,都创建成512x384的透明bitmap +2. 中途不进行Dispose和设置为null,只在上面进行绘制 +3. 仅当控件被Dispose时,才进行Dispose和设置为null -## 2. 设备连接功能 +## 操作流程 -- [x] 2.1 实现设备连接模块 -- [x] 2.2 完善连接状态管理 +### 修改流程 +1. 暂停或恢复时,设置暂停状态,调用更新Info +2. 断开或连接时,设置连接状态,调用更新Info +3. Ping通状态变化时,修改Ping状态,调用更新Info +4. 图像更新时,保存LastImage,调用更新UI +5. 2-4 只在非暂停状态下调用更新,暂停状态下不更新Info和UI +6. 数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 -## 3. 温度数据处理 +### 更新Info +1. 以透明色清空Info +2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则就绪 +3. 如果未就绪,调用更新UI -### 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兼容性 - - 在获取温度数据前检查设备是否处于正常工作状态 +### 更新UI + 1. 先将LastImage绘制到全局缓冲 + 2. 再将InfoImage绘制到缓冲 + 3. 在就绪条件下,调用更新实时信息,并将DisplayImage绘制到缓冲 + 4. 最后一次性绘制到图像框的bitmap + 5. 同步更新检测配置窗口的实时图像属性 -### 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 +### 更新实时信息 +1. 以透明色清空DisplayImage +2. 如果没有温度数据或温度数据的时间3秒之前,返回 +3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息 +4. 如果勾选了全局温度,则显示全局温度(居中显示),否则显示区域温度(居中显示) +5. 如果勾选了区域温度,则显示区域框,否则不显示区域框 +6. 如果勾选了平均温度,显示平均温度(居中显示) +7. 如果勾选了最低温度,显示最低温度(居中显示) +8. 如果勾选了最高温度,显示最高温度(居中显示) +9. 如果是全局温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于图像框居中显示 +10. 如果是区域温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于区域框居中显示 + +## TCP温度数据接收 + +1. 系统初始化时,创建后台线程 +2. 然后线程循环执行 +3. 判断是否存在tcp连接实例 +4. 如果不存在连接实例,则判断是否暂停。如果暂停,则Sleep 1秒后继续执行。否则创建一个新的连接实例。 +5. 如果存在连接实例,则判断是否连接。如果未连接,则重新连接 +6. 同步接收和处理数据 +7. 收到数据后,如果暂停,接收后丢弃,Sleep 1秒。否则同步将收到的数组复制放到待处理列表(线程安全列表)。 +8. 通知处理线程,有新数据到达 +9. 跳转到6 +10. Dispose时,关闭后台线程 + +### 温度数据接收时的补充说明 + +总之就是不主动断开连接: + +1. 同步机制简单,不用考虑阻塞,因为在后台线程里 +2. 不用心跳机制,协议约定是每秒1帧数据 +3. 异常情况处理,出现异常就跳转到第3步 +4. 不用考虑波动问题,这是在一个循环中,它会下次重试 +5. 在异常出现且需要重新创建对象前,先释放旧的资源 +6. 数据完整性验证放在了数据处理环节 +7. 连接失败,会跳转到第3步。下一循环会自动重试 + +### 温度数据处理逻辑 + +1. 收到的数组放到待处理列表中 +2. 开始循环处理列表 +3. 如果列表为空,退出循环 +4. 从列表中读取第一个数组,查看是否有头信息 +5. 如果没有头信息,将数组从列表中移除,跳转到3 +6. 如果有头信息,查看头信息中数据长度信息 +7. 检查数组中是否有足够的数据长度 +8. 如果没有足够的数据长度,看列表中是否有下一个数组 +9. 如果没有,则退出循环,否则将下一个数组合并到当前数组中尾部,将下一个数组从列表中移除,然后跳转到7 +10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息 +11. 如果有,则将数组中第二个头信息之前的数据移除,然后跳转到6 +12. 如果没有,则将数据长度范围内的数据从数组取出进行解析处理,剩余部分保留到数组中,跳转到4 +13. 处理完毕后,跳转到3 +14. 循环完成,进入等待状态 + +### 温度数据解析处理逻辑 + +根据热像仪SDK文档,原始温度数据的解析处理需遵循以下逻辑: + +#### 原始温度数据格式说明 +- 通过TCP端口8081获取温度数据 +- 数据包含24字节头部和温度数据两部分 +- 头部结构体(header)包含: + - mark[5]: 固定标识"+TEMP" + - payload_length: 温度数据长度 + - timestamp: 时间戳(前4字节为秒级时间,后2字节为毫秒级时间) +- 温度数据长度计算:宽×高×2字节,例如240×180分辨率时为240×180×2字节 +- 数据按字节低位在前读取,如读取为80 51 01 00时实际值为00 01 51 80的16进制值 + +#### 原始温度数据解析方法 +1. **单像素温度计算**: + - 每个像素点温度由两个字节组成(L为低8位,H为高8位) + - 公式:摄氏温度×10 = H×256 + L + - 实际温度:摄氏温度 = (H×256 + L)/10 + +2. **温度数据布局**: + - 数据按行优先顺序排列 + - 例如分辨率为4×3时,数据顺序为:L1 H1 L2 H2 L3 H3 L4 H4 L5 H5 ... + +#### 温补值处理 +1. **温补值获取**: + - 通过SDK函数`sdk_get_comp_temp`获取温补值 + - 函数原型:`sdk_get_comp_temp(const char* ip, int* comp_temp)` + - 返回值为0表示获取成功 + +2. **温补值应用**: + - 温补值也是摄氏度乘以10的结果 + - 最终温度计算公式: + ``` + 最终温度 = 原始温度 + 温补值 + ``` + - 示例:原始温度为243(24.3°C),温补值为5(0.5°C),则最终温度为248(24.8°C) + +#### 温度数据解析处理流程 +1. 接收TCP数据并解析头部信息 +2. 验证mark标识是否为"+TEMP" +3. 根据payload_length确定温度数据长度 +4. 按像素点顺序解析每个像素的温度值 +5. 获取温补值并应用到每个像素的温度上 +6. 计算得到最终的温度数据 + +#### 注意事项 +1. 时间戳转换时需注意低字节在前、高字节在后的顺序 +2. 温补值是设备补偿值,必须应用到原始温度上才能得到与网页计算结果一致的温度 +3. 分辨率不同时,温度数据长度会相应变化,解析时需根据实际分辨率计算 +4. 原始温度数据更新频率约为每秒一条 + +### 温度显示菜单 +1. 在右键菜单中增加温度显示菜单 +2. 温度显示菜单下显示全局温度(可勾选) +3. 温度显示菜单下显示区域温度(可勾选) +4. 区域温度和全局温度只能二选一显示 +5. 温度显示菜单下显示最高温度、平均温度和最低温度(可勾选) + +### 检测配置 +#### 区域绘制逻辑 +1. 创建独立的叠加层图像 :专门维护一个与显示图像同尺寸的Image对象作为矩形框的叠加层 +2. 分离绘制逻辑 :将临时绘制和最终绘制分离,临时矩形仍通过Paint事件显示,完成的矩形绘制到叠加层 +3. 图像合并机制 :在Paint事件中先绘制叠加层,再绘制临时矩形。 +4. 保存的矩形框位置和大小信息应该是相对于图像的,而不是相对于控件的。 +5. 当btnDrawRegion按下后,处于绘制状态, btnSelectColor才显示出来 +6. 当绘制状态时,右击鼠标,退出绘制状态,且清除所有临时绘制。 +7. 当绘制状态时,双击鼠标,随机生成一个颜色,用于绘制矩形框。 +8. 当就绪状态时,鼠标移到区域内,该区域内填充半透明色,当有多个重叠时,填充索引号最大的区域 +9. 当鼠标在半透明区域内单击时,该区域填充半透明色,且显示八个句柄,表示选中该区域。 +10. 选中区域时,工具栏显示按钮btnDeleteRegion和btnSelectColor;隐藏btnDrawRegion。 +11. 当选中区域时,btnSelectColor用于改变选中区域的颜色。 +12. 当选中区域时,btnDeleteRegion用于删除该区域,删除后btnSelectColor隐藏,btnDrawRegion显示。 +13. 当选中区域时,鼠标移动到八个句柄上,显示对应的光标,用于调整区域大小。 +14. 当选中区域时,鼠标可以移动区域,用于调整区域位置。 +15. 当选中区域时,右击鼠标,退出选中状态,转为就绪状态 +16. 当就绪状态时,工具栏显示绘制温差图按钮(btnDrawTempDiff) +17. 当绘制温差图按钮按下时,进入绘制温差图状态,显示添加和删除温差图例按钮(btnAddTempDiff、btnDeleteTempDiff),显示温差图例列表(dataGridViewTempDiff) +18. 当绘制温差图状态时,右击鼠标进入就绪状态 +19. 初始状态/就绪状态,显示dataGridViewTempDiff,但只允许查看 + +### btnNewTempRegion(新建测温区) +1. 移除所有已有的测温区列表 +2. 用透明色清空叠加层图像 +### btnLoadTempRegion(加载测温区) +1. 弹出用户打开文件对话框,用户选择要加载的csv文件 +2. 移除所有已有的测温区列表,用透明色清空叠加层图像 +3. 从csv文件中读取所有测温区位置大小和颜色信息,添加到测温区列表中 +4. 用读取的颜色填充叠加层图像对应的区域 +### btnSaveTempRegion(保存测温区) +1. 弹出用户保存文件对话框,用户选择保存路径和文件名 +2. 保存测温区列表中的所有测温区位置大小和颜色信息为csv文件 +### btnNewTempDiff(新建温差图) +1. 用透明色清空温差层图像 +2. 移除所有已有的温差图例列表 +### btnLoadTempDiff(加载温差图) +1. 弹出用户打开文件对话框,用户选择要加载的csv文件 +2. 用透明色清空温差层图像 +3. 移除所有已有的温差图例列表 +4. 从csv文件中读取所有温差图例信息,添加到温差图例列表中 +5. 从csv文件中读取所有像素的温度值,绘制温差层图像对应的像素 +### btnSaveTempDiff(保存温差图) +1. 弹出用户保存文件对话框,用户选择保存路径和文件名 +2. 保存温差图例列表中的所有温差图例信息到csv文件 +3. 每个温差图例信息占一行,格式为: + - 温度(如20°C) + - 颜色(如#FF0000表示红色) +4. 保存温差图每个像素的温度值到csv文件 + - 每个像素温度值占一行,格式为: + - X坐标(如100) + - Y坐标(如300) + - 温度值(°C)(如25.5) + + + +#### 配置状态说明 +1. 初始状态/就绪状态: + - 显示btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff + - 显示btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 + - 隐藏btnSelectColor、btnDeleteRegion按钮、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25、txtRegionNumber +2. 选中区域状态: + - 显示btnDeleteRegion和btnSelectColor按钮、txtRegionNumber + - 隐藏btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 + - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 +3. 绘制状态: + - 显示btnSelectColor按钮 + - 显示btnDrawRegion按钮、txtRegionNumber + - 隐藏btnDeleteRegion按钮 + - 隐藏btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 + - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 +4. 绘制温差图状态: + - 显示btnDrawTempDiff按钮,显示dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff + - 当选定温差图例后,显示btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25 + - 隐藏btnSelectColor、btnDeleteRegion、btnDrawRegion按钮 + - 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮 \ No newline at end of file