mrDarker
6 天以前 829fe6c6bc33d53fda9c31fd45a37e1df87befff
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -8,6 +8,7 @@
#include "SGMeasurementDlg.h"
#include "afxdialogex.h"
#include "SGIF.h"
#include "Logger.h"
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -16,6 +17,7 @@
#define DeviceID 0
// 托盘图标 ID 与消息宏
#define ID_TRAY_OPEN_DIR      2000            // 打开目录
#define ID_TRAY_RESTORE         2001            // 恢复窗口
#define ID_TRAY_EXIT         2002            // 退出程序
#define WM_TRAY_ICON_NOTIFY    (WM_USER + 1000) // 托盘图标回调消息 ID
@@ -33,6 +35,19 @@
// 定时器相关宏定义
#define TIMER_INTERVAL_MS      500
#define TIMER_ID_OUTPUT_UPDATE   1
// 计时宏定义
#define MEASURE_FUNC_START() \
   clock_t __startClock = clock();
#define MEASURE_FUNC_END() \
   do { \
      clock_t __endClock = clock(); \
      double __elapsedMs = 1000.0 * (__endClock - __startClock) / CLOCKS_PER_SEC; \
      CString __strElapsed; \
      __strElapsed.Format(_T("%s 执行耗时:%.1f ms"), _T(__FUNCTION__), __elapsedMs); \
      AppendLogLineRichStyled(__strElapsed, LOG_COLOR_SUCCESS); \
   } while (0)
class CAboutDlg : public CDialogEx
{
@@ -71,7 +86,7 @@
   , m_dOutValues{ 0.0, 0.0, 0.0, 0.0 }
   , m_nUseTrigger(0)
   , m_nSavePointCount(100000)
   , m_fJumpThreshold(1.0f)
   , m_fJumpThreshold(0.2f)
   , m_nJumpWindow(3)
   , m_nValleyMargin(0)
   , m_nMinGlass1Count(10)
@@ -80,6 +95,7 @@
   , m_nTrayIconID(0)
   , m_bTrayIconCreated(FALSE)
   , m_bExitingFromTray(FALSE)
   , m_nAutoStart(TRUE)
{
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
@@ -115,6 +131,11 @@
      Shell_NotifyIcon(NIM_DELETE, &m_trayIconData);
      m_bTrayIconCreated = FALSE;
   }
   m_plcListener.Stop();
   // 保存配置文件
   SaveConfig(GetConfigPath());
   DestroyWindow();
   CDialogEx::OnClose();
@@ -175,7 +196,7 @@
   m_editLog.ReplaceSel(_T(""));   // 删除
}
void CSGMeasurementDlg::AppendLogLineRichStyled(const CString& content, COLORREF color /*= RGB(0, 0, 0)*/)
void CSGMeasurementDlg::AppendLogLineRichStyled(const CString& strContent, COLORREF color /*= RGB(0, 0, 0)*/)
{
   if (!::IsWindow(GetSafeHwnd()) || !::IsWindow(m_editLog.GetSafeHwnd())) {
      return;
@@ -197,16 +218,41 @@
   m_editLog.SetSelectionCharFormat(cfTime);
   m_editLog.ReplaceSel(strTimestamp);
   // 生成日志级别标签
   CString strLevel;
   if (color == LOG_COLOR_WARNING) {
      strLevel = _T("[警告]");
   }
   else if (color == LOG_COLOR_ERROR) {
      strLevel = _T("[错误]");
   }
   else if (color == LOG_COLOR_NORMAL) {
      strLevel = _T("[信息]");
   }
   else if (color == LOG_COLOR_SUCCESS) {
      strLevel = _T("[成功]");
   }
   else {
      strLevel = _T("[未知]");
   }
   // 插入日志正文(传入颜色)
   CHARFORMAT2 cfMsg = {};
   cfMsg.cbSize = sizeof(cfMsg);
   cfMsg.dwMask = CFM_COLOR;
   cfMsg.crTextColor = color;
   m_editLog.SetSelectionCharFormat(cfMsg);
   m_editLog.ReplaceSel(content + _T("\r\n"));
   m_editLog.ReplaceSel(strLevel + strContent + _T("\r\n"));
   // 限制最大行数
   TrimRichEditLineLimit(100);
   // 拼接完整日志行
   CString strFullLogLine;
   strFullLogLine.Format(_T("%s %s"), strLevel, strContent);
   // 写入日志文件
   LOG_LINE(strFullLogLine);
}
void CSGMeasurementDlg::HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight/* = RGB(255, 165, 0)*/)
@@ -262,6 +308,149 @@
   }
}
CString CSGMeasurementDlg::GetAppDirectory()
{
   TCHAR szPath[MAX_PATH] = { 0 };
   GetModuleFileName(NULL, szPath, MAX_PATH);
   CString strPath = szPath;
   int pos = strPath.ReverseFind('\\');
   if (pos != -1) {
      strPath = strPath.Left(pos + 1);
   }
   return strPath;
}
CString CSGMeasurementDlg::GetConfigPath()
{
   return GetAppDirectory() + _T("SGConfig.ini");
}
bool CSGMeasurementDlg::LoadConfig(const CString& strFile)
{
   CString strSection = _T("StorageConfig");
   TCHAR buf[256];
   GetPrivateProfileString(strSection, _T("UseTrigger"), _T("0"), buf, 256, strFile);
   m_nUseTrigger = _ttoi(buf);
   GetPrivateProfileString(strSection, _T("SavePointCount"), _T("100000"), buf, 256, strFile);
   m_nSavePointCount = _ttoi(buf);
   // 输出端口
   GetPrivateProfileString(strSection, _T("OutputPort"), _T("OUT1"), buf, 256, strFile);
   {
      int idx = m_comboOutputPort.FindStringExact(-1, buf);
      if (idx != CB_ERR) {
         m_comboOutputPort.SetCurSel(idx);
      }
      else {
         m_comboOutputPort.SetCurSel(0);
      }
   }
   // 跳变检测参数
   GetPrivateProfileString(strSection, _T("JumpThreshold"), _T("0.2"), buf, 256, strFile);
   m_fJumpThreshold = static_cast<float>(_tstof(buf));
   GetPrivateProfileString(strSection, _T("JumpWindow"), _T("3"), buf, 256, strFile);
   m_nJumpWindow = _ttoi(buf);
   GetPrivateProfileString(strSection, _T("ValleyMargin"), _T("0"), buf, 256, strFile);
   m_nValleyMargin = _ttoi(buf);
   GetPrivateProfileString(strSection, _T("MinGlass1Count"), _T("10"), buf, 256, strFile);
   m_nMinGlass1Count = _ttoi(buf);
   // 稳定区域参数
   GetPrivateProfileString(strSection, _T("FixedCount"), _T("5"), buf, 256, strFile);
   m_nFixedCount = _ttoi(buf);
   GetPrivateProfileString(strSection, _T("MaxDelta"), _T("0.05"), buf, 256, strFile);
   m_fMaxDelta = static_cast<float>(_tstof(buf));
   // 自启动
   GetPrivateProfileString(strSection, _T("AutoStart"), _T("1"), buf, 256, strFile);
   m_nAutoStart = _ttoi(buf);
   return true;
}
bool CSGMeasurementDlg::SaveConfig(const CString& strFile)
{
   CString strSection = _T("StorageConfig");
   WritePrivateProfileString(strSection, _T("UseTrigger"), std::to_wstring(m_nUseTrigger).c_str(), strFile);
   WritePrivateProfileString(strSection, _T("SavePointCount"), std::to_wstring(m_nSavePointCount).c_str(), strFile);
   // 输出端口下拉框
   CString strPort;
   m_comboOutputPort.GetWindowText(strPort);
   WritePrivateProfileString(strSection, _T("OutputPort"), strPort, strFile);
   // 跳变检测
   WritePrivateProfileString(strSection, _T("JumpThreshold"), std::to_wstring(m_fJumpThreshold).c_str(), strFile);
   WritePrivateProfileString(strSection, _T("JumpWindow"), std::to_wstring(m_nJumpWindow).c_str(), strFile);
   WritePrivateProfileString(strSection, _T("ValleyMargin"), std::to_wstring(m_nValleyMargin).c_str(), strFile);
   WritePrivateProfileString(strSection, _T("MinGlass1Count"), std::to_wstring(m_nMinGlass1Count).c_str(), strFile);
   // 稳定区
   WritePrivateProfileString(strSection, _T("FixedCount"), std::to_wstring(m_nFixedCount).c_str(), strFile);
   WritePrivateProfileString(strSection, _T("MaxDelta"), std::to_wstring(m_fMaxDelta).c_str(), strFile);
   // 自启动
   WritePrivateProfileString(strSection, _T("AutoStart"), std::to_wstring(m_nAutoStart).c_str(), strFile);
   return true;
}
bool CSGMeasurementDlg::SetAutoStart(bool bEnable)
{
   // 获取当前程序路径
   TCHAR szPath[MAX_PATH] = { 0 };
   GetModuleFileName(NULL, szPath, MAX_PATH);
   CString strAppPath = szPath;
   // 获取应用程序名称
   CString strAppName = ::PathFindFileName(strAppPath);
   strAppName = strAppName.Left(strAppName.ReverseFind('.'));
   HKEY hKey;
   LONG lRet = RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_READ | KEY_WRITE, &hKey);
   if (lRet != ERROR_SUCCESS) {
      return false;
   }
   DWORD dwType = 0;
   TCHAR szValue[MAX_PATH] = { 0 };
   DWORD dwSize = sizeof(szValue);
   lRet = RegQueryValueEx(hKey, strAppName, 0, &dwType, (LPBYTE)szValue, &dwSize);
   if (bEnable) {
      if (lRet != ERROR_SUCCESS || _tcsicmp(szValue, strAppPath) != 0) {
         // 设置自启
         lRet = RegSetValueEx(hKey, strAppName, 0, REG_SZ, (BYTE*)(LPCTSTR)strAppPath, (strAppPath.GetLength() + 1) * sizeof(TCHAR));
      }
      else {
         lRet = ERROR_SUCCESS;
      }
   }
   else {
      if (lRet == ERROR_SUCCESS) {
         // 取消自启
         lRet = RegDeleteValue(hKey, strAppName);
      }
      else {
         lRet = ERROR_SUCCESS;
      }
   }
   RegCloseKey(hKey);
   return (lRet == ERROR_SUCCESS);
}
bool CSGMeasurementDlg::ConnectToDevice()
{
   if (m_bConnected) {
@@ -311,32 +500,38 @@
      return false;
   }
   RC nRet = SGIF_CloseDevice(DeviceID);
   if (nRet == RC_OK) {
      if (m_bSaving) {
         m_bSaving = FALSE;
         nRet = SGIF_DataStorageStop(DeviceID);
         if (nRet == RC_OK) {
            AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
         }
         else {
            CString strError;
            strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
            AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
         }
      }
   // 停止定时器,避免在断开后仍尝试更新输出数据
   KillTimer(TIMER_ID_OUTPUT_UPDATE);
   // 先停止数据存储(如果正在进行)
   RC nRet;
   if (m_bConnected && m_bSaving) {
      nRet = SGIF_DataStorageStop(DeviceID);
      if (nRet == RC_OK) {
         m_bSaving = FALSE;
         AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
      }
      else {
         CString strError;
         strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
         AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
      }
   }
   // 断开设备连接
   nRet = SGIF_CloseDevice(DeviceID);
   if (nRet == RC_OK) {
      AppendLogLineRichStyled(_T("断开连接成功!"), LOG_COLOR_SUCCESS);
      m_bConnected = FALSE;
      UpdateControlStatus(FALSE, FALSE);
      KillTimer(TIMER_ID_OUTPUT_UPDATE);
      return true;
   }
   else {
      CString strError;
      strError.Format(_T("断开连接失败,错误码:%#X"), nRet);
      AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
      AppendLogLineRichStyled(_T("保持当前状态,断开失败。"), LOG_COLOR_ERROR);
      UpdateControlStatus(m_bConnected, m_bSaving);
      return false;
@@ -345,6 +540,8 @@
void CSGMeasurementDlg::CleanInvalidValuesInPlace(int nOutNo, std::vector<float>& vecData, float fInvalid/* = -999.0f*/)
{
   MEASURE_FUNC_START();
   // 找到第一个有效值
   auto itStart = std::find_if(vecData.begin(), vecData.end(), [=](float v) { return v > fInvalid; });
@@ -380,62 +577,87 @@
   else {
      vecData.clear();
   }
   MEASURE_FUNC_END();
}
bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlass1Count /*= 10*/)
bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector<float>& vecData, std::vector<float>& vecGlass1, std::vector<float>& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlassCount /*= 10*/)
{
   const int n = static_cast<int>(vecData.size());
   if (n < 2 * nWindow + 1 + nMinGlass1Count) {
      CString strError;
      strError.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能进行切割。"), nOutNo, 2 * nWindow + 1 + nMinGlass1Count);
      AppendLogLineRichStyled(strError, LOG_COLOR_WARNING);
   MEASURE_FUNC_START();
   CString strLog;
   const int nTotal = static_cast<int>(vecData.size());
   if (nTotal < 2 * nWindow + 1 + nMinGlassCount) {
      strLog.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能切割。"), nOutNo, 2 * nWindow + 1 + nMinGlassCount);
      AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
      return false;
   }
   int nValleyIdx = -1;
   for (int i = nWindow; i < n - nWindow - nMinGlass1Count; ++i) {
      float fDelta = std::fabs(vecData[i + nWindow] - vecData[i - nWindow]);
      if (fDelta > fJumpThreshold) {
         // 找 valley 点(稍微往后跳几步以避开边缘抖动)
         int nSearchStart = i + nWindow + 2;
         int nSearchEnd = min(nSearchStart + 10, n);
   // 从中间向两边查找 valley 候选点
   int nMid = nTotal / 2;
   int nValleyLeft = -1, nValleyRight = -1;
         auto itValley = std::min_element(vecData.begin() + nSearchStart, vecData.begin() + nSearchEnd,
            [](float a, float b) {
               return (a > -900.0f && b > -900.0f) ? a < b : false;
            });
         if (itValley != vecData.begin() + nSearchEnd) {
            nValleyIdx = static_cast<int>(std::distance(vecData.begin(), itValley));
            break;
         }
   // 向左查找 valley
   for (int i = nMid; i >= nWindow; --i) {
      float fDelta = std::fabs(vecData[i] - vecData[i - nWindow]);
      if (fDelta > fJumpThreshold &&
         vecData[i] < vecData[i - 1] &&
         vecData[i] < vecData[i + 1]) {
         nValleyLeft = i;
         break;
      }
   }
   if (nValleyIdx < 0 || nValleyIdx < nMinGlass1Count) {
      AppendLogLineRichStyled(_T("未找到合适 valley 点,或 valley 太靠前。"), LOG_COLOR_WARNING);
      return false;
   // 向右查找 valley
   for (int i = nMid; i <= nTotal - nWindow - 2; ++i) {
      float fDelta = std::fabs(vecData[i + nWindow] - vecData[i]);
      if (fDelta > fJumpThreshold &&
         vecData[i] < vecData[i - 1] &&
         vecData[i] < vecData[i + 1]) {
         nValleyRight = i;
         break;
      }
   }
   // 从 valley 处开始切割,或者往后偏移
   int nCutStart = min(nValleyIdx + nValleyMargin, n);
   vecGlass1.assign(vecData.begin(), vecData.begin() + nCutStart);
   vecGlass2.assign(vecData.begin() + nCutStart, vecData.end());
   // 选定 valley 点
   int nValleyIdx = -1;
   if (nValleyLeft > 0 && nValleyRight > 0) {
      nValleyIdx = (vecData[nValleyLeft] < vecData[nValleyRight]) ? nValleyLeft : nValleyRight;
   }
   else if (nValleyLeft > 0) {
      nValleyIdx = nValleyLeft;
   }
   else if (nValleyRight > 0) {
      nValleyIdx = nValleyRight;
   }
   // fallback: valley 未找到,使用中间切割法
   if (nValleyIdx < 0) {
      AppendLogLineRichStyled(_T("未找到 valley 跳变点,使用中间位置切割。"), LOG_COLOR_WARNING);
      nValleyIdx = nMid;
   }
   // 应用切割位置,限制边界
   int nCutPos = max(1, min(nTotal - 1, nValleyIdx + nValleyMargin));
   vecGlass1.assign(vecData.begin(), vecData.begin() + nCutPos);
   vecGlass2.assign(vecData.begin() + nCutPos, vecData.end());
   int nGlass1Count = static_cast<int>(vecGlass1.size());
   int nGlass2Count = static_cast<int>(vecGlass2.size());
   // 日志输出
   CString strLog;
   strLog.Format(_T("OUT%d: 切割成功,第一片玻璃数据量 %d,第二片玻璃数据量 %d。"), nOutNo, nGlass1Count, nGlass2Count);
   strLog.Format(_T("OUT%d: 切割成功,第一片玻璃 %d 点,第二片玻璃 %d 点。"), nOutNo, nGlass1Count, nGlass2Count);
   AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
   if (nGlass1Count < nMinGlass1Count) {
      strLog.Format(_T("OUT%d: 第一片玻璃数据量过少,可能影响后续处理。"), nOutNo);
   if (nGlass1Count < nMinGlassCount) {
      strLog.Format(_T("OUT%d: 第一片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount);
      AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
   }
   if (nGlass2Count < nMinGlass1Count) {
      strLog.Format(_T("OUT%d: 第二片玻璃数据量过少,可能影响后续处理。"), nOutNo);
   if (nGlass2Count < nMinGlassCount) {
      strLog.Format(_T("OUT%d: 第二片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount);
      AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING);
   }
@@ -445,11 +667,61 @@
   AppendLogLineRichStyled(_T("第二片玻璃数据:"), LOG_COLOR_NORMAL);
   PrintSampleData(nOutNo, vecGlass2);
   return !vecGlass1.empty() && !vecGlass2.empty();
   MEASURE_FUNC_END();
   return true;
}
bool CSGMeasurementDlg::FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput)
{
   MEASURE_FUNC_START();
   if (vecInput.empty()) {
      AppendLogLineRichStyled(_T("输入数据为空,无法进行分组筛选。"), LOG_COLOR_WARNING);
      return false;
   }
   // 分组:map<int整数部分, vector<float>>
   std::map<int, std::vector<float>> mapGroup;
   for (float fVal : vecInput) {
      int nKey = static_cast<int>(fVal);
      mapGroup[nKey].push_back(fVal);
   }
   // 找出数量最多的那组
   size_t nMaxCount = 0;
   auto itMaxGroup = mapGroup.begin();
   for (auto it = mapGroup.begin(); it != mapGroup.end(); ++it) {
      if (it->second.size() > nMaxCount) {
         nMaxCount = it->second.size();
         itMaxGroup = it;
      }
   }
   if (nMaxCount == 0) {
      AppendLogLineRichStyled(_T("所有数据都被过滤或为空,无法筛选出主要分组。"), LOG_COLOR_WARNING);
      return false;
   }
   vecOutput = itMaxGroup->second;
   CString strLog;
   strLog.Format(_T("成功从 %d 个组中筛选出主要分组:值约为 %d,共 %zu 个点。"), static_cast<int>(mapGroup.size()), itMaxGroup->first, nMaxCount);
   AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
   // 打印筛选后的数据
   AppendLogLineRichStyled(_T("筛选后的主要分组数据:"), LOG_COLOR_NORMAL);
   PrintSampleData(nOutNo, vecOutput);
   MEASURE_FUNC_END();
   return true;
}
bool CSGMeasurementDlg::ExtractStableRegionFixed(int nOutNo, const std::vector<float>& vecIn, std::vector<float>& vecOut, int nFixedCount/* = 5*/, float fMaxDelta/* = 0.04f*/)
{
   MEASURE_FUNC_START();
   const int n = static_cast<int>(vecIn.size());
   if (n < nFixedCount) {
      CString strError;
@@ -494,11 +766,15 @@
   AppendLogLineRichStyled(_T("提取的稳定区数据:"), LOG_COLOR_NORMAL);
   PrintSampleData(nOutNo, vecOut);
   MEASURE_FUNC_END();
   return true;
}
bool CSGMeasurementDlg::CalcGlassOffset(const std::vector<float>& vecGlass1, const std::vector<float>& vecGlass2, float& fAvg1, float& fAvg2, float& fOffset)
{
   MEASURE_FUNC_START();
   if (vecGlass1.empty() || vecGlass2.empty()) {
      AppendLogLineRichStyled(_T("稳定区数据为空,无法计算平均值和偏移。"), LOG_COLOR_WARNING);
      return false;
@@ -511,13 +787,171 @@
   fAvg1 = CalcAverage(vecGlass1);
   fAvg2 = CalcAverage(vecGlass2);
   fOffset = std::fabs(fAvg2 - fAvg1);  // 第二片 - 第一片
   fOffset = fAvg2 - fAvg1;            // 第二片 - 第一片
   //fOffset = std::fabs(fAvg2 - fAvg1);   // 第二片 - 第一片
   CString strLog;
   strLog.Format(_T("第一片玻璃平均值: %.3f,第二片玻璃平均值: %.3f,偏移量: %.3f"), fAvg1, fAvg2, fOffset);
   AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS);
   MEASURE_FUNC_END();
   return true;
}
bool CSGMeasurementDlg::InitDataStorage()
{
   if (!m_bConnected) {
      AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
      return false;
   }
   if (m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING);
      return false;
   }
   RC nRet = SGIF_DataStorageInit(DeviceID);
   if (nRet == RC_OK) {
      AppendLogLineRichStyled(_T("数据存储已清除。"), LOG_COLOR_SUCCESS);
      return true;
   }
   CString strError;
   strError.Format(_T("清除数据存储失败,错误码:%#X"), nRet);
   AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   return false;
}
bool CSGMeasurementDlg::StartDataStorage()
{
   if (m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储已在进行中,请先停止存储。"), LOG_COLOR_WARNING);
      return false;
   }
   RC nRet = SGIF_DataStorageStart(DeviceID);
   if (nRet == RC_OK) {
      m_bSaving = TRUE;
      AppendLogLineRichStyled(_T("数据存储已开始。"), LOG_COLOR_SUCCESS);
      return true;
   }
   CString strError;
   strError.Format(_T("开始数据存储失败,错误码:%#X"), nRet);
   AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   return false;
}
bool CSGMeasurementDlg::StopDataStorage()
{
   if (!m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储未在进行中,请先开始存储。"), LOG_COLOR_WARNING);
      return false;
   }
   RC nRet = SGIF_DataStorageStop(DeviceID);
   if (nRet == RC_OK) {
      m_bSaving = FALSE;
      AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
      return true;
   }
   CString strError;
   strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
   AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   return false;
}
double CSGMeasurementDlg::AnalyzeStoredData(int nOutNo)
{
   MEASURE_FUNC_START();
   UpdateData(TRUE);
   if (m_nUseTrigger) {
      UpdateControlStatus(m_bConnected, m_bSaving);
      AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION);
      return DBL_MAX;
   }
   if (!m_bConnected) {
      AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
      return DBL_MAX;
   }
   if (m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING);
      return DBL_MAX;
   }
   if (nOutNo < 1 || nOutNo > 4) {
      AppendLogLineRichStyled(_T("输出端口编号无效,必须在 1 到 4 之间。"), LOG_COLOR_ERROR);
      return DBL_MAX;
   }
   if (m_nSavePointCount < 0) {
      AppendLogLineRichStyled(_T("数据点数必须大于 0。"), LOG_COLOR_ERROR);
      return DBL_MAX;
   }
   std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
   int nReceived = 0;
   RC nRet = SGIF_DataStorageGetData(DeviceID, nOutNo, m_nSavePointCount, vecBuffer.data(), &nReceived);
   if (nRet != RC_OK) {
      CString strError;
      strError.Format(_T("读取 OUT%d 数据失败,错误码:%#X"), nOutNo, nRet);
      AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
      return DBL_MAX;
   }
   vecBuffer.resize(nReceived);
   CleanInvalidValuesInPlace(nOutNo, vecBuffer);
   std::vector<float> vecGlass1, vecGlass2;
   if (!SplitGlassSegments(nOutNo, vecBuffer, vecGlass1, vecGlass2, m_fJumpThreshold, m_nJumpWindow, m_nValleyMargin, m_nMinGlass1Count)) {
      AppendLogLineRichStyled(_T("未能识别出两片玻璃的数据。"), LOG_COLOR_WARNING);
      return DBL_MAX;
   }
   std::vector<float> vecGlass1Filtered, vecGlass2Filtered;
   bool bGlass1Filtered = FilterDominantGroup(nOutNo, vecGlass1, vecGlass1Filtered);
   bool bGlass2Filtered = FilterDominantGroup(nOutNo, vecGlass2, vecGlass2Filtered);
   if (!bGlass1Filtered) {
      AppendLogLineRichStyled(_T("Glass1 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING);
      vecGlass1Filtered = vecGlass1;
   }
   else {
      AppendLogLineRichStyled(_T("Glass1 主数据组已提取。"), LOG_COLOR_SUCCESS);
   }
   if (!bGlass2Filtered) {
      AppendLogLineRichStyled(_T("Glass2 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING);
      vecGlass2Filtered = vecGlass2;
   }
   else {
      AppendLogLineRichStyled(_T("Glass2 主数据组已提取。"), LOG_COLOR_SUCCESS);
   }
   std::vector<float> vecStable1, vecStable2;
   bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1Filtered, vecStable1, m_nFixedCount, m_fMaxDelta);
   bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2Filtered, vecStable2, m_nFixedCount, m_fMaxDelta);
   float fAvg1 = 0.0f, fAvg2 = 0.0f, fOffset = 0.0f;
   if (bStable1 && bStable2) {
      AppendLogLineRichStyled(_T("成功提取到两片玻璃的稳定区数据。"), LOG_COLOR_SUCCESS);
      CalcGlassOffset(vecStable1, vecStable2, fAvg1, fAvg2, fOffset);
   }
   else {
      AppendLogLineRichStyled(_T("未能提取到稳定区数据,尝试使用原始分段数据计算偏移。"), LOG_COLOR_WARNING);
      CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset);
   }
   MEASURE_FUNC_END();
   return fOffset;
}
BEGIN_MESSAGE_MAP(CSGMeasurementDlg, CDialogEx)
@@ -529,6 +963,7 @@
   ON_WM_DRAWITEM()
   ON_WM_CLOSE()
   ON_MESSAGE(WM_TRAY_ICON_NOTIFY, &CSGMeasurementDlg::OnTrayIconClick)
   ON_COMMAND(ID_TRAY_OPEN_DIR, &CSGMeasurementDlg::OnTrayOpenDir)
   ON_COMMAND(ID_TRAY_RESTORE, &CSGMeasurementDlg::OnTrayRestore)
   ON_COMMAND(ID_TRAY_EXIT, &CSGMeasurementDlg::OnTrayExit)
   ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CSGMeasurementDlg::OnBnClickedButtonConnect)
@@ -597,6 +1032,86 @@
   // 初始化IP地址控件,设置为默认IP地址
   ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->SetAddress(192, 168, 0, 10);
   // 设置 PLC 监听器的日志回调函数
   m_plcListener.SetLogCallback([this](const CString& msg, int type) {
      if (type == -1) {
         AppendLogLineRichStyled(msg, LOG_COLOR_ERROR);
      }
      else if (type == 0) {
         AppendLogLineRichStyled(msg, LOG_COLOR_SUCCESS);
      }
      else if (type == 1) {
         AppendLogLineRichStyled(msg, LOG_COLOR_WARNING);
      }
      else if (type == 2) {
         AppendLogLineRichStyled(msg, LOG_COLOR_NORMAL);
      }
   });
   // 初始化 PLC 监听器
   m_plcListener.Initialize(StationIdentifier(0, 255), 200);
   // 设置 PLC 监听器的开始采集回调函数
   m_plcListener.SetStartCallback([this]() {
      if (!m_bConnected) {
         ConnectToDevice();
      }
      if (InitDataStorage()) {
         StartDataStorage();
         UpdateControlStatus(m_bConnected, m_bSaving);
      }
   });
   // 设置 PLC 监听器的停止采集回调函数
   m_plcListener.SetStopCallback([this]() {
      StopDataStorage();
      UpdateControlStatus(m_bConnected, m_bSaving);
   });
   // 设置 PLC 监听器的分析回调函数
   m_plcListener.SetAnalyzeCallback([this]() {
      if (!m_bConnected) {
         AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
         return std::array<double, 4>{ DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX };
      }
      std::array<double, 4> result;
      for (int i = 0; i < 4; ++i) {
         result[i] = AnalyzeStoredData(i + 1); // OUT1 ~ OUT4
      }
      std::string strProductID;
      m_plcListener.ReadProductID(strProductID);
      m_resultStorage.SaveAnalyzeResult(CString(strProductID.c_str()), result);
      CString strLog;
      strLog.Format(_T("分析结果:OUT1: %.3f, OUT2: %.3f, OUT3: %.3f, OUT4: %.3f"), result[0], result[1], result[2], result[3]);
      return result;
   });
   m_plcListener.Start();
   // 加载配置文件
   if (LoadConfig(GetConfigPath())) {
      AppendLogLineRichStyled(_T("配置已从 SGConfig.ini 加载成功"), LOG_COLOR_SUCCESS);
   }
   else {
      AppendLogLineRichStyled(_T("配置加载失败,使用默认参数"), LOG_COLOR_WARNING);
   }
   // 设置自动启动
   if (SetAutoStart(m_nAutoStart)) {
      if (m_nAutoStart) {
         AppendLogLineRichStyled(_T("已启用开机自启动"), LOG_COLOR_SUCCESS);
      }
      else {
         AppendLogLineRichStyled(_T("已取消开机自启动"), LOG_COLOR_WARNING);
      }
   }
   else {
      AppendLogLineRichStyled(_T("设置开机自启动失败,请检查权限"), LOG_COLOR_ERROR);
   }
   // 初始化日志框
   AppendLogLineRichStyled(_T("准备就绪..."), LOG_COLOR_SUCCESS);
@@ -653,16 +1168,31 @@
      RC nRet = SGIF_GetCalcDataALL(DeviceID, value);
      if (nRet == RC_OK) {
         for (int i = 0; i < 4; ++i) {
            m_dOutValues[i] = value[i].Value;
         }
            double dNew = value[i].Value;
            if (fabs(m_dOutValues[i] - dNew) > 1e-6) {
               m_dOutValues[i] = dNew;
         // 更新绑定控件
         UpdateData(FALSE);
               CString str;
               str.Format(_T("%.3f"), dNew);
               switch (i) {
               case 0: GetDlgItem(IDC_EDIT_OUT1)->SetWindowText(str); break;
               case 1: GetDlgItem(IDC_EDIT_OUT2)->SetWindowText(str); break;
               case 2: GetDlgItem(IDC_EDIT_OUT3)->SetWindowText(str); break;
               case 3: GetDlgItem(IDC_EDIT_OUT4)->SetWindowText(str); break;
               }
            }
         }
      }
      else {
         CString strError;
         strError.Format(_T("获取测量值失败,错误码:%#X"), nRet);
         AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
         // 断开连接
         if (m_bConnected) {
            DisconnectFromDevice();
         }
      }
   }
@@ -693,6 +1223,10 @@
   // 图标
   HICON hIcon = nullptr;
   if (id == ID_TRAY_OPEN_DIR) {
      hIcon = AfxGetApp()->LoadIcon(IDI_ICON_OPEN_DIR);
   }
   if (id == ID_TRAY_RESTORE) {
      hIcon = AfxGetApp()->LoadIcon(IDI_ICON_RESTORE);
   }
@@ -707,6 +1241,10 @@
   // 文本
   CString str;
   if (id == ID_TRAY_OPEN_DIR) {
      str = _T("打开目录");
   }
   if (id == ID_TRAY_RESTORE) { 
      str = _T("恢复界面");
   }
@@ -750,22 +1288,23 @@
         // 右键点击弹出菜单
         CMenu menu;
         menu.CreatePopupMenu();
         menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_OPEN_DIR, (LPCTSTR)ID_TRAY_OPEN_DIR);
         menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_RESTORE, (LPCTSTR)ID_TRAY_RESTORE);
         menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_EXIT, (LPCTSTR)ID_TRAY_EXIT);
         // 加载图标
         HICON hIconRestore = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_RESTORE), IMAGE_ICON, 16, 16, LR_SHARED);
         HICON hIconExit = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_EXIT), IMAGE_ICON, 16, 16, LR_SHARED);
         // 设置图标到菜单项
         MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
         mii.fMask = MIIM_BITMAP;
         // 恢复菜单项图标
         // 打开目录菜单项图标
         mii.hbmpItem = HBMMENU_CALLBACK;
         menu.SetMenuItemInfo(ID_TRAY_OPEN_DIR, &mii);
         // 恢复窗口菜单项图标
         mii.hbmpItem = HBMMENU_CALLBACK;
         menu.SetMenuItemInfo(ID_TRAY_RESTORE, &mii);
         // 退出菜单项图标
         // 退出程序菜单项图标
         mii.hbmpItem = HBMMENU_CALLBACK;
         menu.SetMenuItemInfo(ID_TRAY_EXIT, &mii);
@@ -777,6 +1316,18 @@
      }
   }
   return 0;
}
void CSGMeasurementDlg::OnTrayOpenDir()
{
   CString strDir = GetAppDirectory();
   if (PathFileExists(strDir)) {
      ShellExecute(NULL, _T("open"), strDir, NULL, NULL, SW_SHOWDEFAULT);
      AppendLogLineRichStyled(_T("已打开程序目录"), LOG_COLOR_SUCCESS);
   }
   else {
      AppendLogLineRichStyled(_T("目录不存在,无法打开"), LOG_COLOR_ERROR);
   }
}
void CSGMeasurementDlg::OnTrayRestore()
@@ -827,120 +1378,30 @@
void CSGMeasurementDlg::OnBnClickedButtonClearStore()
{
   // TODO: 在此添加控件通知处理程序代码
   if (!m_bConnected) {
      AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
      return;
   }
   if (m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING);
      return;
   }
   RC nRet = SGIF_DataStorageInit(DeviceID);
   if (nRet == RC_OK) {
      AppendLogLineRichStyled(_T("数据存储已清除。"), LOG_COLOR_SUCCESS);
   }
   else {
      CString strError;
      strError.Format(_T("清除数据存储失败,错误码:%#X"), nRet);
      AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   }
   InitDataStorage();
}
void CSGMeasurementDlg::OnBnClickedButtonStartStore()
{
   // TODO: 在此添加控件通知处理程序代码
   if (m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储已在进行中,请先停止存储。"), LOG_COLOR_WARNING);
      return;
   }
   RC nRet = SGIF_DataStorageStart(DeviceID);
   if (nRet == RC_OK) {
      m_bSaving = TRUE;
      AppendLogLineRichStyled(_T("数据存储已开始。"), LOG_COLOR_SUCCESS);
   }
   else {
      CString strError;
      strError.Format(_T("开始数据存储失败,错误码:%#X"), nRet);
      AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   }
   StartDataStorage();
   UpdateControlStatus(m_bConnected, m_bSaving);
}
void CSGMeasurementDlg::OnBnClickedButtonStopStore()
{
   // TODO: 在此添加控件通知处理程序代码
   UpdateData(TRUE);
   StopDataStorage();
   UpdateControlStatus(m_bConnected, m_bSaving);
   if (!m_bSaving) {
      AppendLogLineRichStyled(_T("数据存储未在进行中,请先开始存储。"), LOG_COLOR_WARNING);
   int nSel = m_comboOutputPort.GetCurSel();
   if (CB_ERR == nSel) {
      AppendLogLineRichStyled(_T("请选择一个有效的输出端口。"), LOG_COLOR_WARNING);
      return;
   }
   RC nRet = SGIF_DataStorageStop(DeviceID);
   if (nRet == RC_OK) {
      m_bSaving = FALSE;
      AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS);
      if (m_nUseTrigger) {
         UpdateControlStatus(m_bConnected, m_bSaving);
         AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION);
         return;
      }
      int nReceived = 0;
      std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
      int nSel = m_comboOutputPort.GetCurSel();
      if (CB_ERR == nSel) {
         AppendLogLineRichStyled(_T("请选择一个有效的输出端口。"), LOG_COLOR_WARNING);
         return;
      }
      int nOutNo = nSel + 1;
      nRet = SGIF_DataStorageGetData(DeviceID, nOutNo, m_nSavePointCount, vecBuffer.data(), &nReceived);
      CString strLog;
      if (nRet == RC_OK) {
         vecBuffer.resize(nReceived);
         CleanInvalidValuesInPlace(nOutNo, vecBuffer);
         std::vector<float> vecGlass1, vecGlass2;
         if (SplitGlassSegments(nOutNo, vecBuffer, vecGlass1, vecGlass2, m_fJumpThreshold, m_nJumpWindow, m_nValleyMargin, m_nMinGlass1Count)) {
            std::vector<float> vecStableGlass1, vecStableGlass2;
            bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1, vecStableGlass1, m_nFixedCount, m_fMaxDelta);
            bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2, vecStableGlass2, m_nFixedCount, m_fMaxDelta);
            float fAvg1 = 0.0f, fAvg2 = 0.0f, fOffset = 0.0f;
            if (bStable1 && bStable2) {
               AppendLogLineRichStyled(_T("成功提取到两片玻璃的稳定区数据。"), LOG_COLOR_SUCCESS);
               CalcGlassOffset(vecStableGlass1, vecStableGlass2, fAvg1, fAvg2, fOffset);
            }
            else {
               AppendLogLineRichStyled(_T("未能提取到稳定区数据,无法正常计算偏移。"), LOG_COLOR_WARNING);
               CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset);
            }
         }
         else {
            AppendLogLineRichStyled(_T("未能识别出两片玻璃的数据。"), LOG_COLOR_WARNING);
         }
      }
      else {
         strLog.Format(_T("读取 OUT%d 数据失败,错误码:%#X"), nOutNo, nRet);
         AppendLogLineRichStyled(strLog, LOG_COLOR_ERROR);
      }
   }
   else {
      CString strError;
      strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet);
      AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
   }
   UpdateControlStatus(m_bConnected, m_bSaving);
   int nOutNo = nSel + 1;
   AnalyzeStoredData(nOutNo);
}
void CSGMeasurementDlg::OnBnClickedButtonClearLog()