| | |
| | | #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 |
| | |
| | | // 定时器相关宏定义 |
| | | #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 |
| | | { |
| | |
| | | , 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) |
| | |
| | | , m_nTrayIconID(0) |
| | | , m_bTrayIconCreated(FALSE) |
| | | , m_bExitingFromTray(FALSE) |
| | | , m_nAutoStart(TRUE) |
| | | { |
| | | m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); |
| | | } |
| | |
| | | Shell_NotifyIcon(NIM_DELETE, &m_trayIconData); |
| | | m_bTrayIconCreated = FALSE; |
| | | } |
| | | |
| | | m_plcListener.Stop(); |
| | | |
| | | // 保存配置文件 |
| | | SaveConfig(GetConfigPath()); |
| | | |
| | | DestroyWindow(); |
| | | CDialogEx::OnClose(); |
| | |
| | | } |
| | | } |
| | | |
| | | 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) { |
| | |
| | | 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; |
| | |
| | | |
| | | 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; }); |
| | | |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | |
| | | 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; |
| | | } |
| | |
| | | return false; |
| | | } |
| | | |
| | | float CSGMeasurementDlg::AnalyzeStoredData(int nOutNo) |
| | | double CSGMeasurementDlg::AnalyzeStoredData(int nOutNo) |
| | | { |
| | | MEASURE_FUNC_START(); |
| | | |
| | | UpdateData(TRUE); |
| | | |
| | | if (m_nUseTrigger) { |
| | | UpdateControlStatus(m_bConnected, m_bSaving); |
| | | AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | if (!m_bConnected) { |
| | | AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | if (m_bSaving) { |
| | | AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | if (nOutNo < 1 || nOutNo > 4) { |
| | | AppendLogLineRichStyled(_T("输出端口编号无效,必须在 1 到 4 之间。"), LOG_COLOR_ERROR); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | if (m_nSavePointCount < 0) { |
| | | AppendLogLineRichStyled(_T("数据点数必须大于 0。"), LOG_COLOR_ERROR); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | clock_t startClock = clock(); // 记录开始时间 |
| | | |
| | | std::vector<float> vecBuffer(m_nSavePointCount, 0.0f); |
| | | int nReceived = 0; |
| | |
| | | CString strError; |
| | | strError.Format(_T("读取 OUT%d 数据失败,错误码:%#X"), nOutNo, nRet); |
| | | AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); |
| | | return -1.0f; |
| | | return DBL_MAX; |
| | | } |
| | | |
| | | vecBuffer.resize(nReceived); |
| | |
| | | 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 -1.0f; |
| | | 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, vecGlass1, vecStable1, m_nFixedCount, m_fMaxDelta); |
| | | bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2, vecStable2, m_nFixedCount, m_fMaxDelta); |
| | | 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) { |
| | |
| | | CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset); |
| | | } |
| | | |
| | | clock_t endClock = clock(); // 记录结束时间 |
| | | double dElapsedMs = 1000.0 * (endClock - startClock) / CLOCKS_PER_SEC; |
| | | |
| | | CString strElapsed; |
| | | strElapsed.Format(_T("AnalyzeStoredData 执行耗时:%.1f ms"), dElapsedMs); |
| | | AppendLogLineRichStyled(strElapsed, LOG_COLOR_SUCCESS); |
| | | MEASURE_FUNC_END(); |
| | | |
| | | return fOffset; |
| | | } |
| | |
| | | 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) |
| | |
| | | // 初始化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); |
| | | |
| | |
| | | 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(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 图标 |
| | | 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); |
| | | } |
| | |
| | | |
| | | // 文本 |
| | | CString str; |
| | | if (id == ID_TRAY_OPEN_DIR) { |
| | | str = _T("打开目录"); |
| | | } |
| | | |
| | | if (id == ID_TRAY_RESTORE) { |
| | | str = _T("恢复界面"); |
| | | } |
| | |
| | | // 右键点击弹出菜单 |
| | | 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); |
| | | |
| | |
| | | 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() |
| | | { |
| | | ShowWindow(SW_SHOW); // 恢复窗口 |