| SourceCode/Bond/Servo/CPageProdOverview.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/CPanelProduction.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/CPanelProduction.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/ProductionStats.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/ProductionStats.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
SourceCode/Bond/Servo/CPageProdOverview.cpp
@@ -5,11 +5,12 @@ #include "Servo.h" #include "CPageProdOverview.h" #include "afxdialogex.h" #include "CPanelProduction.h" namespace { constexpr UINT_PTR kTimerRefreshId = 2001; constexpr UINT kTimerRefreshIntervalMs = 5000; constexpr UINT kTimerRefreshIntervalMs = 10000; } IMPLEMENT_DYNAMIC(CPageProdOverview, CDialogEx) @@ -129,12 +130,36 @@ void CPageProdOverview::RefreshData() { auto* pPanel = dynamic_cast<CPanelProduction*>(GetParent()); if (pPanel == nullptr) { pPanel = dynamic_cast<CPanelProduction*>(GetParent() ? GetParent()->GetParent() : nullptr); } if (pPanel == nullptr) { m_labelDayOut.setText(_T("--")); m_labelNightOut.setText(_T("--")); m_labelDayTakt.setText(_T("--")); m_labelNightTakt.setText(_T("--")); return; } ProductionShiftSummary day; ProductionShiftSummary night; if (!pPanel->TryGetDayNightSummaries(day, night)) { m_labelDayOut.setText(_T("--")); m_labelNightOut.setText(_T("--")); m_labelDayTakt.setText(_T("--")); m_labelNightTakt.setText(_T("--")); return; } CString text; text.Format(_T("%d"), 123); text.Format(_T("%lld"), day.output.pairsTotal); m_labelDayOut.setText(text); text.Format(_T("%d"), 1235); text.Format(_T("%lld"), night.output.pairsTotal); m_labelNightOut.setText(text); m_labelDayTakt.setText(_T("1236")); m_labelNightTakt.setText(_T("1238")); text.Format(_T("%.1fs"), day.output.avgTaktSeconds); m_labelDayTakt.setText(text); text.Format(_T("%.1fs"), night.output.avgTaktSeconds); m_labelNightTakt.setText(text); } SourceCode/Bond/Servo/CPanelProduction.cpp
@@ -156,11 +156,12 @@ } } BOOL CPanelProduction::TryGetShiftSummary(ProductionShiftSummary& outSummary) BOOL CPanelProduction::TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight) { CSingleLock lock(&m_csShiftSummary, TRUE); if (!m_bShiftSummaryValid) return FALSE; outSummary = m_shiftSummary; outDay = m_daySummary; outNight = m_nightSummary; return TRUE; } @@ -197,10 +198,12 @@ for (;;) { if (self->m_evStopStats.Lock(intervalMs)) break; ProductionShiftSummary summary; if (ProductionStats::ComputeCurrentShiftSummary(theApp.m_model.m_configuration, summary)) { ProductionShiftSummary daySummary; ProductionShiftSummary nightSummary; if (ProductionStats::ComputeDayNightSummaries(theApp.m_model.m_configuration, daySummary, nightSummary)) { CSingleLock lock(&self->m_csShiftSummary, TRUE); self->m_shiftSummary = std::move(summary); self->m_daySummary = std::move(daySummary); self->m_nightSummary = std::move(nightSummary); self->m_bShiftSummaryValid = TRUE; } } @@ -211,12 +214,5 @@ void CPanelProduction::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 if (nIDEvent == 1) { ProductionShiftSummary outSummary; if (TryGetShiftSummary(outSummary)) { TRACE("OnTimer outSummary.output.pairsPass:%d\n", outSummary.output.pairsPass); } } CDialogEx::OnTimer(nIDEvent); } SourceCode/Bond/Servo/CPanelProduction.h
@@ -24,8 +24,9 @@ HWND m_hPlaceholder; CAccordionWnd* m_pAccordionWnd; // Production shift summary (updated by background thread) ProductionShiftSummary m_shiftSummary; // Production shift summaries (updated by background thread) ProductionShiftSummary m_daySummary; ProductionShiftSummary m_nightSummary; BOOL m_bShiftSummaryValid; CCriticalSection m_csShiftSummary; CWinThread* m_pStatsThread; @@ -49,8 +50,8 @@ afx_msg void OnVLineMoveX(NMHDR* nmhdr, LRESULT* result); afx_msg void OnBnClickedButtonClose(); // Thread-safe snapshot for UI timer display BOOL TryGetShiftSummary(ProductionShiftSummary& outSummary); // Thread-safe snapshots for UI timer display BOOL TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight); private: static UINT AFX_CDECL StatsThreadProc(LPVOID pParam); SourceCode/Bond/Servo/ProductionStats.cpp
@@ -88,7 +88,9 @@ } const char* sql = "SELECT class_id, buddy_id, aoi_result " "SELECT class_id, buddy_id, aoi_result, " "IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), '')," "IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end, 'localtime'), '') " "FROM glass_log " "WHERE t_end IS NOT NULL AND t_end >= ? AND t_end < ?;"; @@ -105,6 +107,7 @@ bool hasPass = false; bool hasFail = false; bool hasNo = false; long long maxTaktSeconds = -1; }; std::unordered_map<std::string, PairAgg> pairs; @@ -114,6 +117,8 @@ const char* classId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); const char* buddyId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)); const int aoi = sqlite3_column_int(stmt, 2); const char* sStart = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3)); const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4)); const std::string a = classId ? classId : ""; const std::string b = buddyId ? buddyId : ""; @@ -130,6 +135,12 @@ if (aoi == 1) agg.hasPass = true; else if (aoi == 2) agg.hasFail = true; else agg.hasNo = true; std::chrono::system_clock::time_point tpStart{}, tpEnd{}; if (TryParseLocalTime(sStart ? sStart : "", tpStart) && TryParseLocalTime(sEnd ? sEnd : "", tpEnd) && tpEnd > tpStart) { const auto secs = std::chrono::duration_cast<std::chrono::seconds>(tpEnd - tpStart).count(); if (secs > agg.maxTaktSeconds) agg.maxTaktSeconds = secs; } } else if (rc == SQLITE_DONE) { break; @@ -143,14 +154,23 @@ sqlite3_close(db); out.pairsTotal = static_cast<long long>(pairs.size()); long long sumTakt = 0; long long cntTakt = 0; for (const auto& kv : pairs) { const auto& agg = kv.second; if (agg.hasFail) out.pairsFail++; else if (agg.hasPass) out.pairsPass++; else out.pairsNoResult++; if (agg.maxTaktSeconds >= 0) { sumTakt += agg.maxTaktSeconds; cntTakt += 1; } } const long long denom = out.pairsPass + out.pairsFail; out.yield = (denom > 0) ? (static_cast<double>(out.pairsPass) / static_cast<double>(denom)) : 0.0; out.taktSamplePairs = cntTakt; out.avgTaktSeconds = (cntTakt > 0) ? (static_cast<double>(sumTakt) / static_cast<double>(cntTakt)) : 0.0; } static void ComputeAlarmSummaryFromDb( @@ -359,6 +379,7 @@ LOGI("<ProductionStats>Shift=%s, [%s ~ %s]", shiftName, s.window.startLocal.c_str(), s.window.endLocal.c_str()); LOGI("<ProductionStats>Output(pairs): total=%lld, pass=%lld, fail=%lld, no_result=%lld, yield=%.2f%%", s.output.pairsTotal, s.output.pairsPass, s.output.pairsFail, s.output.pairsNoResult, s.output.yield * 100.0); LOGI("<ProductionStats>Takt: avg=%.1fs, samples=%lld", s.output.avgTaktSeconds, s.output.taktSamplePairs); LOGI("<ProductionStats>Alarms: triggered=%d, overlapping=%d, downtime=%.1f min", s.alarms.alarmsTriggered, s.alarms.alarmsOverlapping, s.alarms.downtimeMinutes); if (!s.alarms.bySeverity.empty()) { @@ -375,3 +396,43 @@ LOGI("%s", oss.str().c_str()); } } bool ProductionStats::ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight) { ProductionShiftSummary cur; if (!ComputeCurrentShiftSummary(config, cur)) return false; // Determine previous adjacent window for the other shift. ProductionShiftSummary other = cur; if (cur.window.type == ProductionShiftType::Day) { other.window.type = ProductionShiftType::Night; other.window.end = cur.window.start; other.window.start = cur.window.start - (cur.window.end - cur.window.start); } else { other.window.type = ProductionShiftType::Day; other.window.end = cur.window.start; other.window.start = cur.window.start - (cur.window.end - cur.window.start); } other.window.startLocal = FormatLocal(other.window.start); other.window.endLocal = FormatLocal(other.window.end); other.window.startUtcIso = FormatUtcIso(other.window.start); other.window.endUtcIso = FormatUtcIso(other.window.end); other.output = ProductionOutputSummary{}; other.alarms = ProductionAlarmSummary{}; other.transfers = ProductionTransferSummary{}; ComputeOutputFromProcessDb(other.window, other.output); ComputeAlarmSummaryFromDb(other.window, other.alarms); ComputeTransferSummaryFromDb(other.window, other.transfers); if (cur.window.type == ProductionShiftType::Day) { outDay = std::move(cur); outNight = std::move(other); } else { outNight = std::move(cur); outDay = std::move(other); } return true; } SourceCode/Bond/Servo/ProductionStats.h
@@ -27,6 +27,10 @@ long long pairsFail = 0; long long pairsNoResult = 0; double yield = 0.0; // pairsPass / (pairsPass + pairsFail), 0 if denom==0 // Average takt time derived from glass_log.t_start/t_end (per pair, seconds) double avgTaktSeconds = 0.0; long long taktSamplePairs = 0; }; struct ProductionAlarmSummary { @@ -54,5 +58,9 @@ static bool GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow); static bool ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary); static void LogCurrentShiftSummary(CConfiguration& config); }; // Computes "current shift" and its adjacent other shift, so UI can always show Day+Night numbers. // - If current is Day: day=current day shift, night=previous night shift. // - If current is Night: night=current night shift, day=previous day shift. static bool ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight); };