移除DeviceManager.cs中多余的_isReceivingTemperatureData变量,优化代码结构

This commit is contained in:
zqm
2025-11-04 10:32:04 +08:00
parent e11553e122
commit 493dee5366
4 changed files with 413 additions and 322 deletions

View File

@@ -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

View File

@@ -264,7 +264,6 @@ namespace JoyD.Windows.CS.Toprie
// 温度数据接收相关字段
private Thread _temperatureReceiveThread;
private bool _isReceivingTemperatureData = false;
private bool _isTemperatureReceivingPaused = false; // 暂停标志,用于控制是否处理接收到的温度数据
/// <summary>
@@ -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
/// <summary>
/// 设置温度接收状态标志
/// </summary>
private void SetReceivingState(bool isReceiving)
{
lock (_lockObject)
{
_isReceivingTemperatureData = isReceiving;
}
}
// SetReceivingState方法已移除因为_isReceivingTemperatureData变量未被使用
/// <summary>
/// 处理接收到的温度数据
/// </summary>
// 根据SDK文档第4章要求使用24字节头部
private const int HEADER_SIZE = 24; // 24字节头部结构体
private const int WIDTH = 256;
private const int HEIGHT = 192;
/// <param name="dataAccumulator">累积的温度数据包列表</param>
private void ProcessReceivedTemperatureData(List<byte[]> 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<byte[]> { 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();
}
}
/// <summary>
/// 在字节数组中查找头部标识
/// 在数据数组中查找头信息位置
/// </summary>
/// <param name="data">要搜索的字节数组</param>
/// <param name="headerMarker">头部标识字符串</param>
/// <returns>头部标识的起始索引,未找到返回-1</returns>
private int FindHeader(byte[] data, string headerMarker)
/// <param name="data">数据数组</param>
/// <param name="startIndex">开始查找的位置</param>
/// <param name="endIndex">结束查找的位置(不包含)</param>
/// <returns>头信息位置,-1表示未找到</returns>
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;
}
/// <summary>
/// 在字节数组的指定范围内查找头部标识
/// 从头部数据中提取payload长度
/// </summary>
/// <param name="data">要搜索的字节数组</param>
/// <param name="headerMarker">头部标识字符串</param>
/// <param name="startIndex">搜索的起始索引</param>
/// <param name="endIndex">搜索的结束索引(不包含)</param>
/// <returns>头部标识的起始索引,未找到返回-1</returns>
private int FindHeader(byte[] data, string headerMarker, int startIndex, int endIndex)
/// <param name="headerData">包含头信息的数据流</param>
/// <returns>payload长度</returns>
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;
}
/// <summary>
/// 处理单个温度帧数据
/// </summary>
/// <param name="frameData">完整的帧数据</param>
/// <param name="width">温度矩阵宽度</param>
/// <param name="height">温度矩阵高度</param>
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}");
}
}
/// <summary>
/// 检查是否为有效的头部标记
/// </summary>
/// <param name="data">数据数组</param>
/// <param name="position">起始位置</param>
/// <returns>是否为有效头部</returns>
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个字节是结束符
}
/// <summary>
/// 合并所有数据数组为一个字节数组
/// </summary>
/// <param name="dataArrays">数据数组列表</param>
/// <returns>合并后的字节数组</returns>
private byte[] CombineDataArrays(List<byte[]> 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;
}
/// <summary>
/// 根据头部计算数据长度
/// </summary>
/// <param name="headerData">包含头部的字节数组</param>
/// <returns>数据长度</returns>
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方法
/// <summary>
/// 触发温度数据接收事件
@@ -3466,19 +3477,7 @@ namespace JoyD.Windows.CS.Toprie
/// </summary>
public TemperatureMode CurrentTemperatureMode { get; set; } = TemperatureMode.Celsius;
/// <summary>
/// 是否正在接收温度数据
/// </summary>
public bool IsReceivingTemperatureData
{
get
{
lock (_lockObject)
{
return _isReceivingTemperatureData;
}
}
}
// IsReceivingTemperatureData属性已移除因为未被使用
/// <summary>
/// 获取或设置当前视频模式

View File

@@ -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时关闭后台线程。
## 操作流程
### 修改流程
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的结果
- 最终温度计算公式:
```
最终温度 = 原始温度 + 温补值
```
- 示例原始温度为24324.3°C温补值为50.5°C则最终温度为24824.8°C
#### 温度数据解析处理流程
1. 接收TCP数据并解析头部信息
2. 验证mark标识是否为"+TEMP"
3. 根据payload_length确定温度数据长度
4. 按像素点顺序解析每个像素的温度值
5. 获取温补值并应用到每个像素的温度上
6. 计算得到最终的温度数据
#### 注意事项
1. 时间戳转换时需注意低字节在前、高字节在后的顺序
2. 温补值是设备补偿值,必须应用到原始温度上才能得到与网页计算结果一致的温度
3. 分辨率不同时,温度数据长度会相应变化,解析时需根据实际分辨率计算
4. 原始温度数据更新频率约为每秒一条

View File

@@ -80,68 +80,58 @@ namespace JoyD.Windows.CS.Toprie
}
/// <summary>
/// 解析原始温度数据,支持根据数据长度自适应宽高
/// </summary>
/// <param name="rawData">原始温度数据</param>
/// <param name="compensationValue">温度补偿值</param>
private void ParseRawData(byte[] rawData, float compensationValue)
/// 解析原始温度数据
/// </summary>
/// <param name="rawData">原始温度数据</param>
/// <param name="compensationValue">温度补偿值</param>
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
}
}
}
}
/// <summary>
/// 将原始温度数据映射到512×384的实际温度矩阵