From 493dee53666ba2bb6b6beae0eb9344a642ac3c97 Mon Sep 17 00:00:00 2001 From: zqm Date: Tue, 4 Nov 2025 10:32:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4DeviceManager.cs=E4=B8=AD?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E7=9A=84=5FisReceivingTemperatureData?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CS/Framework4.0/Toprie/Toprie/Camera.cs | 13 +- .../Toprie/Toprie/DeviceManager.cs | 477 +++++++++--------- .../CS/Framework4.0/Toprie/Toprie/README.md | 140 ++++- .../Toprie/Toprie/TemperatureData.cs | 105 ++-- 4 files changed, 413 insertions(+), 322 deletions(-) diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs index 214b61e..8442252 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs @@ -120,7 +120,8 @@ namespace JoyD.Windows.CS.Toprie } } catch (Exception ex) - { + { + Console.WriteLine($"[PauseDetection] 操作失败 - 异常: {ex.Message}, 堆栈: {ex.StackTrace}"); Console.WriteLine($"处理接收到的温度数据时出错: {ex.Message}"); } } @@ -280,9 +281,15 @@ namespace JoyD.Windows.CS.Toprie try { + // 记录操作前的暂停状态 + Console.WriteLine($"[PauseDetection] 操作前状态: _isPaused = {_isPaused}"); + // 切换暂停状态 _isPaused = !_isPaused; + // 记录操作后的暂停状态 + Console.WriteLine($"[PauseDetection] 操作后状态: _isPaused = {_isPaused}"); + if (_isPaused) { // 设置暂停状态 @@ -301,7 +308,7 @@ namespace JoyD.Windows.CS.Toprie } } - Console.WriteLine("检测已暂停"); + Console.WriteLine($"[PauseDetection] 检测已暂停 - DeviceManager状态更新完成,当前时间: {DateTime.Now.ToString("HH:mm:ss.fff")}"); } else { @@ -330,7 +337,7 @@ namespace JoyD.Windows.CS.Toprie } } - Console.WriteLine("检测已恢复"); + Console.WriteLine($"[PauseDetection] 检测已恢复 - DeviceManager状态更新完成,连接状态: {_deviceManager?.ConnectionStatus}, 当前时间: {DateTime.Now.ToString("HH:mm:ss.fff")}"); } // 按照用户要求:暂停或恢复时,设置暂停状态,调用更新Info diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs index 860944d..793edad 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs @@ -264,7 +264,6 @@ namespace JoyD.Windows.CS.Toprie // 温度数据接收相关字段 private Thread _temperatureReceiveThread; - private bool _isReceivingTemperatureData = false; private bool _isTemperatureReceivingPaused = false; // 暂停标志,用于控制是否处理接收到的温度数据 /// @@ -939,9 +938,8 @@ namespace JoyD.Windows.CS.Toprie // 重置停止事件和状态 _stopTemperatureEvent?.Dispose(); _stopTemperatureEvent = new ManualResetEvent(false); - // 设置接收状态为false,符合要求:系统初始化时,设置接收状态为false - _isReceivingTemperatureData = false; - _isTemperatureReceivingPaused = false; // 确保暂停状态也重置 + // 确保暂停状态重置 + _isTemperatureReceivingPaused = false; // 创建并启动温度数据接收线程 newThread = new Thread(ReceiveTemperatureDataWithTcp) @@ -970,7 +968,6 @@ namespace JoyD.Windows.CS.Toprie lock (_lockObject) { - _isReceivingTemperatureData = false; _temperatureReceiveThread = null; } @@ -1083,7 +1080,6 @@ namespace JoyD.Windows.CS.Toprie // 在一个锁内收集所有需要清理的资源和更新状态 lock (_lockObject) { - _isReceivingTemperatureData = false; _isTemperatureReceivingPaused = false; // 同时重置暂停状态 stopEventToDispose = _stopTemperatureEvent; threadToJoin = _temperatureReceiveThread; @@ -1144,7 +1140,6 @@ namespace JoyD.Windows.CS.Toprie { lock (_lockObject) { - _isReceivingTemperatureData = false; _temperatureReceiveThread = null; } } @@ -1227,27 +1222,20 @@ namespace JoyD.Windows.CS.Toprie localStream = localTcpClient.GetStream(); localStream.ReadTimeout = RECEIVE_TIMEOUT; - // 更新状态标志 - lock (_lockObject) - { - _isReceivingTemperatureData = true; - } + // 温度数据接收状态更新代码已移除,因为未被使用 - // 发送开始温度数据传输的命令 - byte[] startCommand = Encoding.ASCII.GetBytes("start_temp_transfer\r\n"); + // 发送开始温度数据传输的命令 - 修改为热像仪可能接受的格式 + // 根据常见热像仪协议,尝试使用简单的命令格式 + byte[] startCommand = Encoding.ASCII.GetBytes("start\r\n"); localStream.Write(startCommand, 0, startCommand.Length); localStream.Flush(); - Log("已发送开始温度数据传输命令"); + Log("已发送开始温度数据传输命令: 'start\r\n'"); } catch (Exception ex) { Log($"TCP连接或初始化失败: {ex.Message}"); - // 更新状态标志 - lock (_lockObject) - { - _isReceivingTemperatureData = false; - } + // 温度数据接收状态更新代码已移除,因为未被使用 // 连接失败后等待一段时间再重试 Thread.Sleep(LONG_SLEEP_MS); @@ -1260,11 +1248,7 @@ namespace JoyD.Windows.CS.Toprie { try { - // 更新状态标志 - lock (_lockObject) - { - _isReceivingTemperatureData = true; - } + // 温度数据接收状态更新代码已移除,因为未被使用 // 记录上次心跳时间和上次接收数据时间 DateTime lastHeartbeatTime = DateTime.Now; @@ -1345,8 +1329,8 @@ namespace JoyD.Windows.CS.Toprie } else { - // 从暂停状态恢复时,确保连接仍然有效,必要时重新启动数据传输 - if (localTcpClient != null && !IsTcpClientConnected(localTcpClient)) + // 只有从暂停状态恢复时(状态从true变为false的瞬间),才检查和重建连接 + if (lastPaused && !isPaused && localTcpClient != null && !IsTcpClientConnected(localTcpClient)) { Log("恢复接收时检测到连接无效,需要重建连接"); CleanupConnectionResources(localStream, localTcpClient, out localStream, out localTcpClient); @@ -1449,11 +1433,7 @@ namespace JoyD.Windows.CS.Toprie // 连接已关闭 Log("远程主机关闭了连接"); - // 重置状态标志,下一次循环会创建新连接 - lock (_lockObject) - { - _isReceivingTemperatureData = false; - } + // 温度数据接收状态更新代码已移除,因为未被使用 // 清理连接资源 CleanupConnectionResources(localStream, localTcpClient, out localStream, out localTcpClient); @@ -1467,11 +1447,7 @@ namespace JoyD.Windows.CS.Toprie { Log($"接收数据异常: {ex.Message}, 堆栈: {ex.StackTrace}"); - // 更新状态标志 - lock (_lockObject) - { - _isReceivingTemperatureData = false; - } + // 温度数据接收状态更新代码已移除,因为未被使用 // 清理连接资源 CleanupConnectionResources(localStream, localTcpClient, out localStream, out localTcpClient); @@ -1487,10 +1463,7 @@ namespace JoyD.Windows.CS.Toprie Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 执行完成"); // 确保状态正确更新 - lock (_lockObject) - { - _isReceivingTemperatureData = false; - } + // 温度数据接收状态更新代码已移除,因为未被使用 // 清理资源 CleanupConnectionResources(localStream, localTcpClient, out localStream, out localTcpClient); @@ -1556,153 +1529,134 @@ namespace JoyD.Windows.CS.Toprie /// /// 设置温度接收状态标志 /// - private void SetReceivingState(bool isReceiving) - { - lock (_lockObject) - { - _isReceivingTemperatureData = isReceiving; - } - } + // SetReceivingState方法已移除,因为_isReceivingTemperatureData变量未被使用 /// /// 处理接收到的温度数据 /// + // 根据SDK文档第4章要求,使用24字节头部 + private const int HEADER_SIZE = 24; // 24字节头部结构体 + private const int WIDTH = 256; + private const int HEIGHT = 192; + /// 累积的温度数据包列表 private void ProcessReceivedTemperatureData(List dataAccumulator) { - // 头部标识 - const string HEADER_MARKER = "+TEMP"; - const int WIDTH = 256; - const int HEIGHT = 192; - const int HEADER_SIZE = 9; // 9字节头部("+TEMP"+数据长度) try { Log($"开始处理温度数据,当前累积数据包数量: {dataAccumulator.Count}"); - // 开始循环处理列表 - while (dataAccumulator.Count > 0) + + // 按照README中温度数据处理逻辑执行 + // 1. 收到的数组已经在待处理列表中 + + // 2. 开始循环处理列表 + while (true) { - // 从列表中读取第一个数组 - byte[] currentArray = dataAccumulator[0]; - Log($"检查第一个数组,长度: {currentArray.Length} 字节"); - - // 查看是否有头信息 - int headerIndex = FindHeader(currentArray, HEADER_MARKER); - - if (headerIndex == -1) + // 3. 如果列表为空,退出循环 + if (dataAccumulator.Count == 0) { - // 如果没有头信息,将数组从列表中移除 - Log("未在数组中找到头部信息,移除该数组"); - dataAccumulator.RemoveAt(0); - // 跳转到循环开始,检查列表是否为空 - continue; - } - - // 如果头部不在数组的开始位置,需要移除头部之前的无效数据 - if (headerIndex > 0) - { - Log($"头部不在数组开始位置,移除前 {headerIndex} 个无效字节"); - byte[] newArray = new byte[currentArray.Length - headerIndex]; - Array.Copy(currentArray, headerIndex, newArray, 0, newArray.Length); - dataAccumulator[0] = newArray; - currentArray = newArray; - } - - // 查看头信息中数据长度信息 - // 确保数组至少包含完整的头部 - if (currentArray.Length < HEADER_SIZE) - { - Log("数组长度不足头部大小,退出循环"); + Log("温度数据待处理列表为空,退出处理循环"); break; } - - // 从头部解析数据长度 - int dataLength = CalculateDataLength(currentArray); - int totalFrameSize = HEADER_SIZE + dataLength; - // 检查数组中是否有足够的数据长度 - while (currentArray.Length < totalFrameSize) + // 4. 从列表中读取第一个数组,查看是否有头信息 + byte[] currentData = dataAccumulator[0]; + int headerPosition = FindHeaderPosition(currentData); + + // 5. 如果没有头信息,将数组从列表中移除,跳转到3 + if (headerPosition < 0) { - // 如果没有足够的数据长度,看列表中是否有下一个数组 - if (dataAccumulator.Count <= 1) + Log("未在当前数据包中找到有效头信息,移除该数据包"); + dataAccumulator.RemoveAt(0); + continue; + } + + // 如果头信息不在数据开头,移除前面的无效数据 + if (headerPosition > 0) + { + Log($"头信息位于位置 {headerPosition},移除前面的无效数据"); + byte[] trimmedData = new byte[currentData.Length - headerPosition]; + Array.Copy(currentData, headerPosition, trimmedData, 0, trimmedData.Length); + dataAccumulator[0] = trimmedData; + currentData = trimmedData; + } + + // 6. 如果有头信息,查看头信息中数据长度信息 + int payloadLength = GetPayloadLengthFromHeader(currentData); + int totalFrameSize = HEADER_SIZE + payloadLength; + Log($"检测到有效头信息,payload长度: {payloadLength},总帧大小: {totalFrameSize}"); + + // 7. 检查数组中是否有足够的数据长度 + while (currentData.Length < totalFrameSize) + { + // 8. 如果没有足够的数据长度,看列表中是否有下一个数组 + if (dataAccumulator.Count < 2) { - // 如果没有,则退出循环 - Log("数据长度不足,且没有更多数组可以合并,退出循环"); + Log($"当前数据不足一帧,等待更多数据,已接收: {currentData.Length} 字节,需要: {totalFrameSize} 字节"); + // 9. 如果没有,则退出循环 break; } - else - { - // 否则将下一个数组合并到当前数组中尾部 - Log("数据长度不足,合并下一个数组到当前数组尾部"); - byte[] nextArray = dataAccumulator[1]; - byte[] mergedArray = new byte[currentArray.Length + nextArray.Length]; - Array.Copy(currentArray, 0, mergedArray, 0, currentArray.Length); - Array.Copy(nextArray, 0, mergedArray, currentArray.Length, nextArray.Length); - - // 更新当前数组和列表 - dataAccumulator[0] = mergedArray; - dataAccumulator.RemoveAt(1); - currentArray = mergedArray; - Log($"合并后数组长度: {currentArray.Length} 字节"); - } + + // 9. 否则将下一个数组合并到当前数组中尾部,将下一个数组从列表中移除 + byte[] nextData = dataAccumulator[1]; + byte[] mergedData = CombineDataArrays(new List { currentData, nextData }); + Log($"合并数据包,当前长度: {currentData.Length},添加长度: {nextData.Length},合并后长度: {mergedData.Length}"); + + // 更新当前数据和列表 + currentData = mergedData; + dataAccumulator[0] = currentData; + dataAccumulator.RemoveAt(1); + + // 然后跳转到7,继续检查数据长度 } - - // 检查数据是否足够(可能因为没有更多数组可以合并而不足) - if (currentArray.Length < totalFrameSize) + + // 如果数据仍然不足,退出循环等待更多数据 + if (currentData.Length < totalFrameSize) { - Log("数据长度仍不足,退出循环等待更多数据"); break; } - - // 检查数据长度范围内是否又出现了头信息 - int nextHeaderIndex = FindHeader(currentArray, HEADER_MARKER, HEADER_SIZE, totalFrameSize); - if (nextHeaderIndex != -1) - { - // 如果有,则将数组中第二个头信息之前的数据移除 - Log($"在数据长度范围内发现另一个头信息,索引位置: {nextHeaderIndex},移除前面的数据"); - byte[] newArray = new byte[currentArray.Length - nextHeaderIndex]; - Array.Copy(currentArray, nextHeaderIndex, newArray, 0, newArray.Length); - dataAccumulator[0] = newArray; - // 跳转到检查头信息的步骤 - continue; - } - - // 如果没有重复头信息,则将数据长度范围内的数据从数组取出进行处理 - Log($"数据有效,提取完整帧数据,长度: {totalFrameSize} 字节"); - byte[] temperatureFrame = new byte[totalFrameSize]; - Array.Copy(currentArray, 0, temperatureFrame, 0, totalFrameSize); - // 保留剩余部分到数组中 - if (currentArray.Length > totalFrameSize) + // 10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息 + int nextHeaderPosition = FindHeaderPosition(currentData, HEADER_SIZE, totalFrameSize); + + // 11. 如果有,则将数组中第二个头信息之前的数据移除,然后跳转到6 + if (nextHeaderPosition >= 0) { - Log($"保留剩余数据,长度: {currentArray.Length - totalFrameSize} 字节"); - byte[] remainingBytes = new byte[currentArray.Length - totalFrameSize]; - Array.Copy(currentArray, totalFrameSize, remainingBytes, 0, remainingBytes.Length); - dataAccumulator[0] = remainingBytes; + Log($"在当前帧数据范围内发现另一个头信息,位置: {nextHeaderPosition},移除前面的数据重新处理"); + byte[] trimmedData = new byte[currentData.Length - nextHeaderPosition]; + Array.Copy(currentData, nextHeaderPosition, trimmedData, 0, trimmedData.Length); + dataAccumulator[0] = trimmedData; + continue; // 跳转到6,重新处理 + } + + // 12. 如果没有,则将数据长度范围内的数据从数组取出进行解析处理 + byte[] frameData = new byte[totalFrameSize]; + Array.Copy(currentData, 0, frameData, 0, totalFrameSize); + Log($"提取完整帧数据,长度: {frameData.Length} 字节"); + + // 处理剩余部分 + if (currentData.Length > totalFrameSize) + { + Log($"当前数据有剩余,保留到下一帧处理"); + byte[] remainingData = new byte[currentData.Length - totalFrameSize]; + Array.Copy(currentData, totalFrameSize, remainingData, 0, remainingData.Length); + dataAccumulator[0] = remainingData; } else { - // 如果没有剩余数据,移除当前数组 + // 没有剩余数据,移除当前数组 dataAccumulator.RemoveAt(0); } - - // 处理提取出的温度数据帧 - Log($"成功提取完整温度数据帧,长度: {temperatureFrame.Length} 字节"); - - // 获取温度补偿值 - float compensationValue = GetTemperatureCompensationValue(); - Log($"获取到温度补偿值: {compensationValue}"); - - // 创建温度数据对象,使用正确的分辨率参数 - TemperatureData temperatureData = new TemperatureData(temperatureFrame, WIDTH, HEIGHT, compensationValue); - Log($"温度数据对象创建成功,分辨率: {WIDTH}x{HEIGHT}"); - - // 触发温度数据接收事件 - OnTemperatureReceived(new TemperatureReceivedEventArgs(temperatureData, temperatureFrame, compensationValue)); - Log($"温度数据接收事件触发完成"); + + // 解析处理完整的帧数据 + ProcessTemperatureFrame(frameData, WIDTH, HEIGHT); + + // 跳转到4,继续处理下一个数据 } - - Log($"温度数据处理完成,剩余数据包数量: {dataAccumulator.Count}"); + + // 14. 循环完成,进入等待状态 + Log("温度数据处理循环完成,等待新数据"); } catch (Exception ex) { @@ -1712,97 +1666,154 @@ namespace JoyD.Windows.CS.Toprie dataAccumulator.Clear(); } } - + /// - /// 在字节数组中查找头部标识 + /// 在数据数组中查找头信息位置 /// - /// 要搜索的字节数组 - /// 头部标识字符串 - /// 头部标识的起始索引,未找到返回-1 - private int FindHeader(byte[] data, string headerMarker) + /// 数据数组 + /// 开始查找的位置 + /// 结束查找的位置(不包含) + /// 头信息位置,-1表示未找到 + private int FindHeaderPosition(byte[] data, int startIndex = 0, int endIndex = -1) { - byte[] headerBytes = Encoding.ASCII.GetBytes(headerMarker); + if (data == null || data.Length <= startIndex) + return -1; - for (int i = 0; i <= data.Length - headerBytes.Length; i++) + if (endIndex < 0 || endIndex > data.Length) + endIndex = data.Length; + + // 确保有足够的数据检查头信息 + int searchEnd = endIndex - 4; // 至少需要4个字节检查标记 + if (searchEnd < startIndex) + return -1; + + // 从startIndex开始查找+TEMP标记 + for (int i = startIndex; i <= searchEnd; i++) { - bool match = true; - for (int j = 0; j < headerBytes.Length; j++) - { - if (data[i + j] != headerBytes[j]) - { - match = false; - break; - } - } - if (match) - { + if (IsValidHeaderMarker(data, i)) return i; - } } + return -1; } /// - /// 在字节数组的指定范围内查找头部标识 + /// 从头部数据中提取payload长度 /// - /// 要搜索的字节数组 - /// 头部标识字符串 - /// 搜索的起始索引 - /// 搜索的结束索引(不包含) - /// 头部标识的起始索引,未找到返回-1 - private int FindHeader(byte[] data, string headerMarker, int startIndex, int endIndex) + /// 包含头信息的数据流 + /// payload长度 + private int GetPayloadLengthFromHeader(byte[] headerData) { - byte[] headerBytes = Encoding.ASCII.GetBytes(headerMarker); + if (headerData == null || headerData.Length < HEADER_SIZE) + throw new ArgumentException("无效的头部数据"); - // 确保参数有效 - if (startIndex < 0) - startIndex = 0; - if (endIndex > data.Length) - endIndex = data.Length; - if (startIndex >= endIndex) - return -1; + // 根据SDK文档,从头部中获取payload_length + // 假设payload_length在头部中的特定位置(例如第5-8字节) + // 注意:需要根据实际的头部结构修改此实现 + int payloadLength = 0; - // 调整结束索引,确保有足够空间容纳完整的头部标识 - int adjustedEndIndex = endIndex - headerBytes.Length + 1; - if (startIndex >= adjustedEndIndex) - return -1; - - for (int i = startIndex; i < adjustedEndIndex; i++) + try { - bool match = true; - for (int j = 0; j < headerBytes.Length; j++) + // 假设payload_length是从索引5开始的4个字节(低字节在前) + payloadLength = BitConverter.ToInt32(headerData, 5); + + // 如果解析失败或长度不合理,使用默认值 + if (payloadLength <= 0 || payloadLength > 1000000) // 合理性检查 { - if (data[i + j] != headerBytes[j]) - { - match = false; - break; - } - } - if (match) - { - return i; + Log($"警告: 从头部解析的payload长度不合理 ({payloadLength}),使用默认值"); + payloadLength = WIDTH * HEIGHT * 2; // 使用默认分辨率计算 } } - return -1; + catch + { + Log("从头部解析payload长度失败,使用默认值"); + payloadLength = WIDTH * HEIGHT * 2; // 使用默认分辨率计算 + } + + return payloadLength; + } + + /// + /// 处理单个温度帧数据 + /// + /// 完整的帧数据 + /// 温度矩阵宽度 + /// 温度矩阵高度 + private void ProcessTemperatureFrame(byte[] frameData, int width, int height) + { + try + { + // 获取温度补偿值 + float compensationValue = GetTemperatureCompensationValue(); + Log($"获取到温度补偿值: {compensationValue}"); + + // 创建温度数据对象 + TemperatureData temperatureData = new TemperatureData(frameData, width, height, compensationValue); + Log($"温度数据对象创建成功,分辨率: {width}x{height}"); + + // 触发温度数据接收事件 + OnTemperatureReceived(new TemperatureReceivedEventArgs(temperatureData, frameData, compensationValue)); + Log($"温度数据接收事件触发完成"); + } + catch (Exception ex) + { + Log($"处理温度帧数据时出错: {ex.Message}"); + } + } + + /// + /// 检查是否为有效的头部标记 + /// + /// 数据数组 + /// 起始位置 + /// 是否为有效头部 + private bool IsValidHeaderMarker(byte[] data, int position) + { + // 根据SDK文档,头部包含5字节的固定标识"+TEMP" + if (data == null || position + 5 > data.Length) + return false; + + // 验证"+TEMP"标记 + return data[position] == '+' && + data[position + 1] == 'T' && + data[position + 2] == 'E' && + data[position + 3] == 'M' && + data[position + 4] == 0; // 假设第5个字节是结束符 + } + + /// + /// 合并所有数据数组为一个字节数组 + /// + /// 数据数组列表 + /// 合并后的字节数组 + private byte[] CombineDataArrays(List dataArrays) + { + int totalLength = 0; + foreach (byte[] array in dataArrays) + { + totalLength += array.Length; + } + + byte[] combinedArray = new byte[totalLength]; + int position = 0; + + foreach (byte[] array in dataArrays) + { + Array.Copy(array, 0, combinedArray, position, array.Length); + position += array.Length; + } + + return combinedArray; } - /// - /// 根据头部计算数据长度 - /// - /// 包含头部的字节数组 - /// 数据长度 - private int CalculateDataLength(byte[] headerData) - { - // 这里需要根据实际的头部格式来解析数据长度 - // 假设头部的第5-8字节包含数据长度信息(4字节整数) - // 实际实现需要根据设备的协议规范进行调整 - if (headerData.Length >= 9) // 确保至少有9字节头部 - { - // 假设数据长度是固定的:256x192分辨率,每个温度值2字节 - return 256 * 192 * 2; - } - return 0; - } + // ProcessReceivedTemperatureData方法的结束部分已经在正确位置 + // 这里不需要重复的方法结束代码 + + // 以下方法已被新的处理逻辑替代,保留注释供参考 + // private int FindHeader(byte[] data, string headerMarker) + // private int FindHeader(byte[] data, string headerMarker, int startIndex, int endIndex) + // private int CalculateDataLength(byte[] headerData) + // 新的实现使用了CombineDataArrays和IsValidHeaderMarker方法 /// /// 触发温度数据接收事件 @@ -3466,19 +3477,7 @@ namespace JoyD.Windows.CS.Toprie /// public TemperatureMode CurrentTemperatureMode { get; set; } = TemperatureMode.Celsius; - /// - /// 是否正在接收温度数据 - /// - public bool IsReceivingTemperatureData - { - get - { - lock (_lockObject) - { - return _isReceivingTemperatureData; - } - } - } + // IsReceivingTemperatureData属性已移除,因为未被使用 /// /// 获取或设置当前视频模式 diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/README.md b/Windows/CS/Framework4.0/Toprie/Toprie/README.md index 719bb8b..f829f49 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/README.md +++ b/Windows/CS/Framework4.0/Toprie/Toprie/README.md @@ -1,29 +1,123 @@ # JoyD +## 图像处理相关 + ### InfoImage, ImageBuffer, 图像框的bitmap, LastImage 1. 初始化时,都创建成512x384的透明bitmap 2. 中途不进行Dispose和设置为null,只在上面进行绘制 3. 仅当控件被Dispose时,才进行Dispose和设置为null -### 修改流程: - 1. 暂停或恢复时,设置暂停状态,调用更新Info - 2. 断开或连接时,设置连接状态,调用更新Info - 3. Ping通状态变化时,修改Ping状态,调用更新Info - 4. 温度数据更新时,调用更新Info - 5. 图像更新时, 保存LastImage, 调用更新到UI - 6. 2-5 只在非暂停状态下调用更新,暂停状态下不更新Info和UI - ### 更新Info: - 1. 以透明色清空Info - 2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息 否则满足就绪条件 - 3. 在就绪条件下,如果有温度数据,显示最高温度 - 4. 最后调用更新UI - ### 更新UI: - 1. 先将LastImage绘制到全局缓冲 - 2. 再将InfoImage绘制到缓冲 - 3. 最后一次性绘制到图像框的bitmap - ### Tcp温度数据接收 - 1. 系统初始化时,创建后台线程。 - 2. 设置接收状态为false,然后线程循环执行 - 3. 如果接收状态为false,{如果暂停则Sleep 1秒后继续,否则同步创建tcp连接,并同步接收和处理数据。} - 4. 如果接收状态为true,{如果暂停,接收后丢弃。否则同步接收并处理数据。} - 5. Dispose时,关闭后台线程。 - \ No newline at end of file + +## 操作流程 + +### 修改流程 +1. 暂停或恢复时,设置暂停状态,调用更新Info +2. 断开或连接时,设置连接状态,调用更新Info +3. Ping通状态变化时,修改Ping状态,调用更新Info +4. 温度数据更新时,调用更新Info +5. 图像更新时,保存LastImage,调用更新到UI +6. 2-5 只在非暂停状态下调用更新,暂停状态下不更新Info和UI + +### 更新Info +1. 以透明色清空Info +2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息 否则满足就绪条件 +3. 在就绪条件下,如果有温度数据,显示最高温度 +4. 最后调用更新UI + +### 更新UI +1. 先将LastImage绘制到全局缓冲 +2. 再将InfoImage绘制到缓冲 +3. 最后一次性绘制到图像框的bitmap + +## 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. 原始温度数据更新频率约为每秒一条 \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/TemperatureData.cs b/Windows/CS/Framework4.0/Toprie/Toprie/TemperatureData.cs index ef2f77a..713588c 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/TemperatureData.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/TemperatureData.cs @@ -80,68 +80,58 @@ namespace JoyD.Windows.CS.Toprie } /// - /// 解析原始温度数据,支持根据数据长度自适应宽高 - /// - /// 原始温度数据 - /// 温度补偿值 - private void ParseRawData(byte[] rawData, float compensationValue) + /// 解析原始温度数据 + /// + /// 原始温度数据 + /// 温度补偿值 + private void ParseRawData(byte[] rawData, float compensationValue) + { + // 检查数据是否有效 + if (rawData == null || rawData.Length == 0) { - // 根据SDK实际实现,温度数据格式为: - // 9字节头部 (+TEMP + 4字节长度) + 低字节数据 + 高字节数据 - // 温度计算公式:(低字节 + 高字节*256 - 2730)/10 + throw new ArgumentNullException(nameof(rawData), "原始温度数据为空"); + } + + // 根据SDK文档要求,使用24字节头部 + const int HEADER_SIZE = 24; + + // 确保数据长度足够(至少包含头部) + if (rawData.Length < HEADER_SIZE) + { + throw new ArgumentException("数据长度不足,无法解析"); + } + + // 计算温度数据长度和像素总数 + int dataLength = rawData.Length - HEADER_SIZE; + int pixelCount = dataLength / 2; // 每个像素2字节 + + // 计算最大可处理的像素数 + int maxPixels = Math.Min(pixelCount, Width * Height); + + // 跳过24字节头部,按照行优先顺序解析温度数据 + for (int i = 0; i < maxPixels; i++) + { + // 计算行列索引 + int row = i / Width; + int col = i % Width; - // 首先检查数据长度是否足够包含头部信息 - if (rawData.Length < 9) + if (row < Height && col < Width) { - System.Diagnostics.Debug.WriteLine("警告: 原始数据长度小于9字节,无法包含有效的头部信息"); - throw new ArgumentException("原始数据长度不足以包含头部信息"); - } - - // 验证头部标识字符是否为"+TEMP" - string headerMark = Encoding.ASCII.GetString(rawData, 0, 5).TrimEnd('\0'); - if (headerMark != "+TEMP") - { - System.Diagnostics.Debug.WriteLine($"警告: 头部标识不匹配,期望'+TEMP',实际为'{headerMark}'"); - // 即使头部不匹配,我们也尝试继续处理,因为有些情况下数据可能没有标准头部 - } - - // 计算像素总数 - 根据SDK实现,数据长度应该是:头部(9字节) + 像素数*2 - int totalDataSize = rawData.Length - 9; // 减去9字节头部 - int pixelCount = totalDataSize / 2; // 每个像素点使用2字节(低字节和高字节分开存储) - - // 根据像素数确定宽高 - 使用标准分辨率 - int[] widthHeight = GetWidthHeightByPixelCount(pixelCount); - int adaptiveWidth = widthHeight[0]; - int adaptiveHeight = widthHeight[1]; - - // 输出自适应宽高信息 - System.Diagnostics.Debug.WriteLine($"根据数据长度自适应宽高: {adaptiveWidth}x{adaptiveHeight} (像素数: {pixelCount})"); - - // 更新宽高并初始化矩阵 - Width = adaptiveWidth; - Height = adaptiveHeight; - TemperatureMatrix = new float[Height, Width]; - - // 计算最大可处理的像素数 - int maxPixels = Math.Min(pixelCount, Width * Height); - - // 按照SDK实现的数据布局处理:低字节在前半部分,高字节在后半部分 - int lowByteOffset = 9; // 低字节数据起始位置(跳过9字节头部) - int highByteOffset = 9 + pixelCount; // 高字节数据起始位置(在低字节数据之后) - - // 遍历所有像素点,按照SDK的方式计算温度 - for (int i = 0; i < maxPixels; i++) - { - // 计算行列索引 - int row = i / Width; - int col = i % Width; + // 计算数据偏移量(跳过24字节头部) + int offset = HEADER_SIZE + i * 2; - if (row < Height && col < Width) + if (offset + 1 < rawData.Length) { - // 按照SDK实现计算温度值:(低字节 + 高字节*256 - 2730)/10 - int lowByte = rawData[lowByteOffset + i]; - int highByte = rawData[highByteOffset + i]; - float rawTemperature = (lowByte + highByte * 256 - 2730) / 10.0f; + // 根据SDK文档要求,温度值计算方法:(H×256+L)/10,单位为摄氏度 + // H为高8位,L为低8位 + int highByte = rawData[offset + 1]; // 高8位 + int lowByte = rawData[offset]; // 低8位 + int tempValue = (highByte << 8) | lowByte; + + // 计算实际温度值:(H×256+L)/10 + float rawTemperature = tempValue / 10.0f; + + // 应用温度补偿值 float compensatedTemperature = rawTemperature + compensationValue; // 原始温度存入TemperatureMatrix(未温补) @@ -152,6 +142,7 @@ namespace JoyD.Windows.CS.Toprie } } } + } /// /// 将原始温度数据映射到512×384的实际温度矩阵