diff --git a/sysid/src/main/native/cpp/App.cpp b/sysid/src/main/native/cpp/App.cpp index 6d1b9285a3..178a0f726c 100644 --- a/sysid/src/main/native/cpp/App.cpp +++ b/sysid/src/main/native/cpp/App.cpp @@ -106,8 +106,10 @@ void Application(std::string_view saveDir) { auto analyzer = std::make_unique(storage, gLogger); logLoader->unload.connect([ds = dataSelector.get()] { ds->Reset(); }); - dataSelector->testdata = [_analyzer = analyzer.get()](auto data) { + dataSelector->testdata = [_analyzer = analyzer.get(), + ds = dataSelector.get()](auto data) { _analyzer->m_data = data; + _analyzer->SetMissingTests(ds->m_missingTests); _analyzer->AnalyzeData(); }; diff --git a/sysid/src/main/native/cpp/view/Analyzer.cpp b/sysid/src/main/native/cpp/view/Analyzer.cpp index 03b39171b2..726042586d 100644 --- a/sysid/src/main/native/cpp/view/Analyzer.cpp +++ b/sysid/src/main/native/cpp/view/Analyzer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -251,6 +252,13 @@ void Analyzer::Display() { } break; } + case AnalyzerState::kMissingTestsError: { + CreateErrorPopup(m_errorPopup, m_exception); + if (!m_errorPopup) { + m_state = AnalyzerState::kWaitingForData; + } + break; + } case AnalyzerState::kGeneralDataError: case AnalyzerState::kTestDurationError: case AnalyzerState::kVelocityThresholdError: { @@ -269,6 +277,9 @@ void Analyzer::Display() { void Analyzer::PrepareData() { WPI_INFO(m_logger, "{}", "Preparing data"); try { + if (m_missingTests.size() > 0) { + throw sysid::MissingTestsError{m_missingTests}; + } m_manager->PrepareData(); UpdateFeedforwardGains(); UpdateFeedbackGains(); @@ -281,6 +292,9 @@ void Analyzer::PrepareData() { } catch (const sysid::NoDynamicDataError& e) { m_state = AnalyzerState::kTestDurationError; HandleError(e.what()); + } catch (const sysid::MissingTestsError& e) { + m_state = AnalyzerState::kMissingTestsError; + HandleError(e.what()); } catch (const AnalysisManager::FileReadingError& e) { m_state = AnalyzerState::kFileError; HandleError(e.what()); @@ -324,6 +338,10 @@ void Analyzer::HandleError(std::string_view msg) { PrepareRawGraphs(); } +void Analyzer::SetMissingTests(const std::vector& missingTests) { + m_missingTests = missingTests; +} + void Analyzer::DisplayGraphs() { ImGui::SetNextWindowPos(ImVec2{kDiagnosticPlotWindowPos}, ImGuiCond_FirstUseEver); diff --git a/sysid/src/main/native/cpp/view/DataSelector.cpp b/sysid/src/main/native/cpp/view/DataSelector.cpp index 3409bf6ee3..f3c320c643 100644 --- a/sysid/src/main/native/cpp/view/DataSelector.cpp +++ b/sysid/src/main/native/cpp/view/DataSelector.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -111,6 +112,7 @@ void DataSelector::Display() { continue; } WPI_INFO(m_logger, "Loaded test state {}", it2->first); + m_executedTests.insert(it2->first); ++it2; } if (it->second.empty()) { @@ -132,6 +134,15 @@ void DataSelector::Display() { return; } + if (m_executedTests.size() < 4 && !m_testCountValidated) { + for (auto test : kValidTests) { + if (!m_executedTests.contains(test)) { + m_missingTests.push_back(test); + m_testCountValidated = true; + } + } + } + #if 0 // Test filtering if (ImGui::BeginCombo("Test", m_selectedTest.c_str())) { diff --git a/sysid/src/main/native/include/sysid/analysis/FilteringUtils.h b/sysid/src/main/native/include/sysid/analysis/FilteringUtils.h index 67266ae647..3948cdd121 100644 --- a/sysid/src/main/native/include/sysid/analysis/FilteringUtils.h +++ b/sysid/src/main/native/include/sysid/analysis/FilteringUtils.h @@ -4,15 +4,16 @@ #pragma once -#include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -68,6 +69,25 @@ class NoQuasistaticDataError : public std::exception { } }; +/** + * Exception for not all tests being present. + */ +class MissingTestsError : public std::exception { + public: + explicit MissingTestsError(std::vector MissingTests) + : missingTests(std::move(MissingTests)) { + errorString = fmt::format( + "The following tests were not detected: {}. Make sure to perform all " + "four tests as described in the SysId documentation.", + fmt::join(missingTests, ", ")); + } + const char* what() const noexcept override { return errorString.c_str(); } + + private: + std::vector missingTests; + std::string errorString; +}; + /** * Exception for Dynamic Data being completely removed. */ diff --git a/sysid/src/main/native/include/sysid/view/Analyzer.h b/sysid/src/main/native/include/sysid/view/Analyzer.h index bb7763f26f..4b6e258341 100644 --- a/sysid/src/main/native/include/sysid/view/Analyzer.h +++ b/sysid/src/main/native/include/sysid/view/Analyzer.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,7 @@ class Analyzer : public glass::View { kVelocityThresholdError, kTestDurationError, kGeneralDataError, + kMissingTestsError, kFileError }; /** @@ -91,6 +93,11 @@ class Analyzer : public glass::View { */ void AnalyzeData(); + /** + * Used by DataSelector to import any missing tests. + */ + void SetMissingTests(const std::vector& missingTests); + private: /** * Kills the data preparation thread @@ -199,6 +206,7 @@ class Analyzer : public glass::View { // Stores the exception message. std::string m_exception; + std::vector m_missingTests; bool m_calcDefaults = false; diff --git a/sysid/src/main/native/include/sysid/view/DataSelector.h b/sysid/src/main/native/include/sysid/view/DataSelector.h index 71732a7ed7..e387837595 100644 --- a/sysid/src/main/native/include/sysid/view/DataSelector.h +++ b/sysid/src/main/native/include/sysid/view/DataSelector.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ class DataSelector : public glass::View { * Called when new test data is loaded. */ std::function testdata; + std::vector m_missingTests; private: wpi::Logger& m_logger; @@ -74,6 +76,11 @@ class DataSelector : public glass::View { int m_selectedAnalysis = 0; std::future m_testdataFuture; std::vector m_testdataStats; + std::set kValidTests = {"quasistatic-forward", + "quasistatic-reverse", "dynamic-forward", + "dynamic-reverse"}; + std::set m_executedTests; + bool m_testCountValidated = false; static Tests LoadTests(const glass::DataLogReaderEntry& testStateEntry); TestData BuildTestData();