移除DeviceManager.cs中多余的_isReceivingTemperatureData变量,优化代码结构
This commit is contained in:
@@ -121,6 +121,7 @@ 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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// 从列表中读取第一个数组
|
||||
byte[] currentArray = dataAccumulator[0];
|
||||
Log($"检查第一个数组,长度: {currentArray.Length} 字节");
|
||||
|
||||
// 查看是否有头信息
|
||||
int headerIndex = FindHeader(currentArray, HEADER_MARKER);
|
||||
// 按照README中温度数据处理逻辑执行
|
||||
// 1. 收到的数组已经在待处理列表中
|
||||
|
||||
if (headerIndex == -1)
|
||||
// 2. 开始循环处理列表
|
||||
while (true)
|
||||
{
|
||||
// 如果没有头信息,将数组从列表中移除
|
||||
Log("未在数组中找到头部信息,移除该数组");
|
||||
// 3. 如果列表为空,退出循环
|
||||
if (dataAccumulator.Count == 0)
|
||||
{
|
||||
Log("温度数据待处理列表为空,退出处理循环");
|
||||
break;
|
||||
}
|
||||
|
||||
// 4. 从列表中读取第一个数组,查看是否有头信息
|
||||
byte[] currentData = dataAccumulator[0];
|
||||
int headerPosition = FindHeaderPosition(currentData);
|
||||
|
||||
// 5. 如果没有头信息,将数组从列表中移除,跳转到3
|
||||
if (headerPosition < 0)
|
||||
{
|
||||
Log("未在当前数据包中找到有效头信息,移除该数据包");
|
||||
dataAccumulator.RemoveAt(0);
|
||||
// 跳转到循环开始,检查列表是否为空
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果头部不在数组的开始位置,需要移除头部之前的无效数据
|
||||
if (headerIndex > 0)
|
||||
// 如果头信息不在数据开头,移除前面的无效数据
|
||||
if (headerPosition > 0)
|
||||
{
|
||||
Log($"头部不在数组开始位置,移除前 {headerIndex} 个无效字节");
|
||||
byte[] newArray = new byte[currentArray.Length - headerIndex];
|
||||
Array.Copy(currentArray, headerIndex, newArray, 0, newArray.Length);
|
||||
dataAccumulator[0] = newArray;
|
||||
currentArray = newArray;
|
||||
Log($"头信息位于位置 {headerPosition},移除前面的无效数据");
|
||||
byte[] trimmedData = new byte[currentData.Length - headerPosition];
|
||||
Array.Copy(currentData, headerPosition, trimmedData, 0, trimmedData.Length);
|
||||
dataAccumulator[0] = trimmedData;
|
||||
currentData = trimmedData;
|
||||
}
|
||||
|
||||
// 查看头信息中数据长度信息
|
||||
// 确保数组至少包含完整的头部
|
||||
if (currentArray.Length < HEADER_SIZE)
|
||||
// 6. 如果有头信息,查看头信息中数据长度信息
|
||||
int payloadLength = GetPayloadLengthFromHeader(currentData);
|
||||
int totalFrameSize = HEADER_SIZE + payloadLength;
|
||||
Log($"检测到有效头信息,payload长度: {payloadLength},总帧大小: {totalFrameSize}");
|
||||
|
||||
// 7. 检查数组中是否有足够的数据长度
|
||||
while (currentData.Length < totalFrameSize)
|
||||
{
|
||||
Log("数组长度不足头部大小,退出循环");
|
||||
// 8. 如果没有足够的数据长度,看列表中是否有下一个数组
|
||||
if (dataAccumulator.Count < 2)
|
||||
{
|
||||
Log($"当前数据不足一帧,等待更多数据,已接收: {currentData.Length} 字节,需要: {totalFrameSize} 字节");
|
||||
// 9. 如果没有,则退出循环
|
||||
break;
|
||||
}
|
||||
|
||||
// 从头部解析数据长度
|
||||
int dataLength = CalculateDataLength(currentArray);
|
||||
int totalFrameSize = HEADER_SIZE + dataLength;
|
||||
// 9. 否则将下一个数组合并到当前数组中尾部,将下一个数组从列表中移除
|
||||
byte[] nextData = dataAccumulator[1];
|
||||
byte[] mergedData = CombineDataArrays(new List<byte[]> { currentData, nextData });
|
||||
Log($"合并数据包,当前长度: {currentData.Length},添加长度: {nextData.Length},合并后长度: {mergedData.Length}");
|
||||
|
||||
// 检查数组中是否有足够的数据长度
|
||||
while (currentArray.Length < totalFrameSize)
|
||||
{
|
||||
// 如果没有足够的数据长度,看列表中是否有下一个数组
|
||||
if (dataAccumulator.Count <= 1)
|
||||
{
|
||||
// 如果没有,则退出循环
|
||||
Log("数据长度不足,且没有更多数组可以合并,退出循环");
|
||||
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;
|
||||
// 更新当前数据和列表
|
||||
currentData = mergedData;
|
||||
dataAccumulator[0] = currentData;
|
||||
dataAccumulator.RemoveAt(1);
|
||||
currentArray = mergedArray;
|
||||
Log($"合并后数组长度: {currentArray.Length} 字节");
|
||||
}
|
||||
|
||||
// 然后跳转到7,继续检查数据长度
|
||||
}
|
||||
|
||||
// 检查数据是否足够(可能因为没有更多数组可以合并而不足)
|
||||
if (currentArray.Length < totalFrameSize)
|
||||
// 如果数据仍然不足,退出循环等待更多数据
|
||||
if (currentData.Length < totalFrameSize)
|
||||
{
|
||||
Log("数据长度仍不足,退出循环等待更多数据");
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查数据长度范围内是否又出现了头信息
|
||||
int nextHeaderIndex = FindHeader(currentArray, HEADER_MARKER, HEADER_SIZE, totalFrameSize);
|
||||
if (nextHeaderIndex != -1)
|
||||
// 10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息
|
||||
int nextHeaderPosition = FindHeaderPosition(currentData, HEADER_SIZE, totalFrameSize);
|
||||
|
||||
// 11. 如果有,则将数组中第二个头信息之前的数据移除,然后跳转到6
|
||||
if (nextHeaderPosition >= 0)
|
||||
{
|
||||
// 如果有,则将数组中第二个头信息之前的数据移除
|
||||
Log($"在数据长度范围内发现另一个头信息,索引位置: {nextHeaderIndex},移除前面的数据");
|
||||
byte[] newArray = new byte[currentArray.Length - nextHeaderIndex];
|
||||
Array.Copy(currentArray, nextHeaderIndex, newArray, 0, newArray.Length);
|
||||
dataAccumulator[0] = newArray;
|
||||
// 跳转到检查头信息的步骤
|
||||
continue;
|
||||
Log($"在当前帧数据范围内发现另一个头信息,位置: {nextHeaderPosition},移除前面的数据重新处理");
|
||||
byte[] trimmedData = new byte[currentData.Length - nextHeaderPosition];
|
||||
Array.Copy(currentData, nextHeaderPosition, trimmedData, 0, trimmedData.Length);
|
||||
dataAccumulator[0] = trimmedData;
|
||||
continue; // 跳转到6,重新处理
|
||||
}
|
||||
|
||||
// 如果没有重复头信息,则将数据长度范围内的数据从数组取出进行处理
|
||||
Log($"数据有效,提取完整帧数据,长度: {totalFrameSize} 字节");
|
||||
byte[] temperatureFrame = new byte[totalFrameSize];
|
||||
Array.Copy(currentArray, 0, temperatureFrame, 0, totalFrameSize);
|
||||
// 12. 如果没有,则将数据长度范围内的数据从数组取出进行解析处理
|
||||
byte[] frameData = new byte[totalFrameSize];
|
||||
Array.Copy(currentData, 0, frameData, 0, totalFrameSize);
|
||||
Log($"提取完整帧数据,长度: {frameData.Length} 字节");
|
||||
|
||||
// 保留剩余部分到数组中
|
||||
if (currentArray.Length > totalFrameSize)
|
||||
// 处理剩余部分
|
||||
if (currentData.Length > totalFrameSize)
|
||||
{
|
||||
Log($"保留剩余数据,长度: {currentArray.Length - totalFrameSize} 字节");
|
||||
byte[] remainingBytes = new byte[currentArray.Length - totalFrameSize];
|
||||
Array.Copy(currentArray, totalFrameSize, remainingBytes, 0, remainingBytes.Length);
|
||||
dataAccumulator[0] = remainingBytes;
|
||||
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} 字节");
|
||||
// 解析处理完整的帧数据
|
||||
ProcessTemperatureFrame(frameData, WIDTH, HEIGHT);
|
||||
|
||||
// 获取温度补偿值
|
||||
float compensationValue = GetTemperatureCompensationValue();
|
||||
Log($"获取到温度补偿值: {compensationValue}");
|
||||
|
||||
// 创建温度数据对象,使用正确的分辨率参数
|
||||
TemperatureData temperatureData = new TemperatureData(temperatureFrame, WIDTH, HEIGHT, compensationValue);
|
||||
Log($"温度数据对象创建成功,分辨率: {WIDTH}x{HEIGHT}");
|
||||
|
||||
// 触发温度数据接收事件
|
||||
OnTemperatureReceived(new TemperatureReceivedEventArgs(temperatureData, temperatureFrame, compensationValue));
|
||||
Log($"温度数据接收事件触发完成");
|
||||
// 跳转到4,继续处理下一个数据
|
||||
}
|
||||
|
||||
Log($"温度数据处理完成,剩余数据包数量: {dataAccumulator.Count}");
|
||||
// 14. 循环完成,进入等待状态
|
||||
Log("温度数据处理循环完成,等待新数据");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1714,95 +1668,152 @@ namespace JoyD.Windows.CS.Toprie
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
for (int i = 0; i <= data.Length - headerBytes.Length; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < headerBytes.Length; j++)
|
||||
{
|
||||
if (data[i + j] != headerBytes[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (data == null || data.Length <= startIndex)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在字节数组的指定范围内查找头部标识
|
||||
/// </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)
|
||||
{
|
||||
byte[] headerBytes = Encoding.ASCII.GetBytes(headerMarker);
|
||||
|
||||
// 确保参数有效
|
||||
if (startIndex < 0)
|
||||
startIndex = 0;
|
||||
if (endIndex > data.Length)
|
||||
if (endIndex < 0 || endIndex > data.Length)
|
||||
endIndex = data.Length;
|
||||
if (startIndex >= endIndex)
|
||||
|
||||
// 确保有足够的数据检查头信息
|
||||
int searchEnd = endIndex - 4; // 至少需要4个字节检查标记
|
||||
if (searchEnd < startIndex)
|
||||
return -1;
|
||||
|
||||
// 调整结束索引,确保有足够空间容纳完整的头部标识
|
||||
int adjustedEndIndex = endIndex - headerBytes.Length + 1;
|
||||
if (startIndex >= adjustedEndIndex)
|
||||
return -1;
|
||||
|
||||
for (int i = startIndex; i < adjustedEndIndex; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < headerBytes.Length; j++)
|
||||
{
|
||||
if (data[i + j] != headerBytes[j])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
// 从startIndex开始查找+TEMP标记
|
||||
for (int i = startIndex; i <= searchEnd; i++)
|
||||
{
|
||||
if (IsValidHeaderMarker(data, i))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据头部计算数据长度
|
||||
/// 从头部数据中提取payload长度
|
||||
/// </summary>
|
||||
/// <param name="headerData">包含头部的字节数组</param>
|
||||
/// <returns>数据长度</returns>
|
||||
private int CalculateDataLength(byte[] headerData)
|
||||
/// <param name="headerData">包含头信息的数据流</param>
|
||||
/// <returns>payload长度</returns>
|
||||
private int GetPayloadLengthFromHeader(byte[] headerData)
|
||||
{
|
||||
// 这里需要根据实际的头部格式来解析数据长度
|
||||
// 假设头部的第5-8字节包含数据长度信息(4字节整数)
|
||||
// 实际实现需要根据设备的协议规范进行调整
|
||||
if (headerData.Length >= 9) // 确保至少有9字节头部
|
||||
if (headerData == null || headerData.Length < HEADER_SIZE)
|
||||
throw new ArgumentException("无效的头部数据");
|
||||
|
||||
// 根据SDK文档,从头部中获取payload_length
|
||||
// 假设payload_length在头部中的特定位置(例如第5-8字节)
|
||||
// 注意:需要根据实际的头部结构修改此实现
|
||||
int payloadLength = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// 假设数据长度是固定的:256x192分辨率,每个温度值2字节
|
||||
return 256 * 192 * 2;
|
||||
// 假设payload_length是从索引5开始的4个字节(低字节在前)
|
||||
payloadLength = BitConverter.ToInt32(headerData, 5);
|
||||
|
||||
// 如果解析失败或长度不合理,使用默认值
|
||||
if (payloadLength <= 0 || payloadLength > 1000000) // 合理性检查
|
||||
{
|
||||
Log($"警告: 从头部解析的payload长度不合理 ({payloadLength}),使用默认值");
|
||||
payloadLength = WIDTH * HEIGHT * 2; // 使用默认分辨率计算
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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>
|
||||
/// 获取或设置当前视频模式
|
||||
|
||||
@@ -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
|
||||
5. 图像更新时,保存LastImage,调用更新到UI
|
||||
6. 2-5 只在非暂停状态下调用更新,暂停状态下不更新Info和UI
|
||||
### 更新Info:
|
||||
|
||||
### 更新Info
|
||||
1. 以透明色清空Info
|
||||
2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息 否则满足就绪条件
|
||||
3. 在就绪条件下,如果有温度数据,显示最高温度
|
||||
4. 最后调用更新UI
|
||||
### 更新UI:
|
||||
|
||||
### 更新UI
|
||||
1. 先将LastImage绘制到全局缓冲
|
||||
2. 再将InfoImage绘制到缓冲
|
||||
3. 最后一次性绘制到图像框的bitmap
|
||||
### Tcp温度数据接收
|
||||
1. 系统初始化时,创建后台线程。
|
||||
2. 设置接收状态为false,然后线程循环执行
|
||||
3. 如果接收状态为false,{如果暂停则Sleep 1秒后继续,否则同步创建tcp连接,并同步接收和处理数据。}
|
||||
4. 如果接收状态为true,{如果暂停,接收后丢弃。否则同步接收并处理数据。}
|
||||
5. Dispose时,关闭后台线程。
|
||||
|
||||
## 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. 原始温度数据更新频率约为每秒一条
|
||||
@@ -80,56 +80,35 @@ namespace JoyD.Windows.CS.Toprie
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析原始温度数据,支持根据数据长度自适应宽高
|
||||
/// 解析原始温度数据
|
||||
/// </summary>
|
||||
/// <param name="rawData">原始温度数据</param>
|
||||
/// <param name="compensationValue">温度补偿值</param>
|
||||
private void ParseRawData(byte[] rawData, float compensationValue)
|
||||
{
|
||||
// 根据SDK实际实现,温度数据格式为:
|
||||
// 9字节头部 (+TEMP + 4字节长度) + 低字节数据 + 高字节数据
|
||||
// 温度计算公式:(低字节 + 高字节*256 - 2730)/10
|
||||
|
||||
// 首先检查数据长度是否足够包含头部信息
|
||||
if (rawData.Length < 9)
|
||||
// 检查数据是否有效
|
||||
if (rawData == null || rawData.Length == 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("警告: 原始数据长度小于9字节,无法包含有效的头部信息");
|
||||
throw new ArgumentException("原始数据长度不足以包含头部信息");
|
||||
throw new ArgumentNullException(nameof(rawData), "原始温度数据为空");
|
||||
}
|
||||
|
||||
// 验证头部标识字符是否为"+TEMP"
|
||||
string headerMark = Encoding.ASCII.GetString(rawData, 0, 5).TrimEnd('\0');
|
||||
if (headerMark != "+TEMP")
|
||||
// 根据SDK文档要求,使用24字节头部
|
||||
const int HEADER_SIZE = 24;
|
||||
|
||||
// 确保数据长度足够(至少包含头部)
|
||||
if (rawData.Length < HEADER_SIZE)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"警告: 头部标识不匹配,期望'+TEMP',实际为'{headerMark}'");
|
||||
// 即使头部不匹配,我们也尝试继续处理,因为有些情况下数据可能没有标准头部
|
||||
throw new ArgumentException("数据长度不足,无法解析");
|
||||
}
|
||||
|
||||
// 计算像素总数 - 根据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 dataLength = rawData.Length - HEADER_SIZE;
|
||||
int pixelCount = dataLength / 2; // 每个像素2字节
|
||||
|
||||
// 计算最大可处理的像素数
|
||||
int maxPixels = Math.Min(pixelCount, Width * Height);
|
||||
|
||||
// 按照SDK实现的数据布局处理:低字节在前半部分,高字节在后半部分
|
||||
int lowByteOffset = 9; // 低字节数据起始位置(跳过9字节头部)
|
||||
int highByteOffset = 9 + pixelCount; // 高字节数据起始位置(在低字节数据之后)
|
||||
|
||||
// 遍历所有像素点,按照SDK的方式计算温度
|
||||
// 跳过24字节头部,按照行优先顺序解析温度数据
|
||||
for (int i = 0; i < maxPixels; i++)
|
||||
{
|
||||
// 计算行列索引
|
||||
@@ -138,10 +117,21 @@ namespace JoyD.Windows.CS.Toprie
|
||||
|
||||
if (row < Height && col < Width)
|
||||
{
|
||||
// 按照SDK实现计算温度值:(低字节 + 高字节*256 - 2730)/10
|
||||
int lowByte = rawData[lowByteOffset + i];
|
||||
int highByte = rawData[highByteOffset + i];
|
||||
float rawTemperature = (lowByte + highByte * 256 - 2730) / 10.0f;
|
||||
// 计算数据偏移量(跳过24字节头部)
|
||||
int offset = HEADER_SIZE + i * 2;
|
||||
|
||||
if (offset + 1 < rawData.Length)
|
||||
{
|
||||
// 根据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的实际温度矩阵
|
||||
|
||||
Reference in New Issue
Block a user