diff --git a/sysid/src/main/native/cpp/view/DataSelector.cpp b/sysid/src/main/native/cpp/view/DataSelector.cpp index 9f36605c834..cfa5ef666d1 100644 --- a/sysid/src/main/native/cpp/view/DataSelector.cpp +++ b/sysid/src/main/native/cpp/view/DataSelector.cpp @@ -10,8 +10,16 @@ #include #include +#include "sysid/Util.h" +#include "sysid/analysis/AnalysisType.h" +#include "sysid/analysis/Storage.h" +#include "wpi/DataLogReader.h" + using namespace sysid; +static constexpr const char* kAnalysisTypes[] = {"Drivetrain", "Elevator", + "Arm", "Simple"}; + static bool EmitEntryTarget(const char* name, bool isString, const glass::DataLogReaderEntry** entry) { if (*entry) { @@ -36,15 +44,51 @@ static bool EmitEntryTarget(const char* name, bool isString, } void DataSelector::Display() { + using namespace std::chrono_literals; + + // building test data is modal (due to async access) + if (m_testdataFuture.valid()) { + if (m_testdataFuture.wait_for(0s) == std::future_status::ready) { + TestData data = m_testdataFuture.get(); + for (auto&& motordata : data.motorData) { + m_testdataStats.emplace_back( + fmt::format("Test State: {}", motordata.first())); + int i = 0; + for (auto&& run : motordata.second.runs) { + m_testdataStats.emplace_back(fmt::format( + " Run {} samples: {} Volt {} Pos {} Vel", ++i, + run.voltage.size(), run.position.size(), run.velocity.size())); + } + } + if (testdata) { + testdata(std::move(data)); + } + } + ImGui::Text("Loading data..."); + return; + } + + if (!m_testdataStats.empty()) { + for (auto&& line : m_testdataStats) { + ImGui::TextUnformatted(line.c_str()); + } + if (ImGui::Button("Ok")) { + m_testdataStats.clear(); + } + return; + } + if (EmitEntryTarget("Test State", true, &m_testStateEntry)) { - LoadTests(*m_testStateEntry); + m_testsFuture = + std::async(std::launch::async, [testStateEntry = m_testStateEntry] { + return LoadTests(*testStateEntry); + }); } if (!m_testStateEntry) { return; } - using namespace std::chrono_literals; if (m_testsFuture.valid() && m_testsFuture.wait_for(0s) == std::future_status::ready) { m_tests = m_testsFuture.get(); @@ -69,69 +113,122 @@ void DataSelector::Display() { ImGui::EndCombo(); } + ImGui::Combo("Analysis Type", &m_selectedAnalysis, kAnalysisTypes, + IM_ARRAYSIZE(kAnalysisTypes)); + // DND targets EmitEntryTarget("Velocity", false, &m_velocityEntry); EmitEntryTarget("Position", false, &m_positionEntry); EmitEntryTarget("Voltage", false, &m_voltageEntry); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 7); + ImGui::Combo("Units", &m_selectedUnit, kUnits, IM_ARRAYSIZE(kUnits)); + if (!m_selectedTest.empty() && m_velocityEntry && m_positionEntry && m_voltageEntry) { if (ImGui::Button("Load")) { + m_testdataFuture = + std::async(std::launch::async, [this] { return BuildTestData(); }); } } } void DataSelector::Reset() { - m_tests = {}; + m_testsFuture = {}; + m_tests.clear(); m_selectedTest.clear(); m_testStateEntry = nullptr; m_velocityEntry = nullptr; m_positionEntry = nullptr; m_voltageEntry = nullptr; + m_testdataFuture = {}; } -void DataSelector::LoadTests(const glass::DataLogReaderEntry& testStateEntry) { - m_testsFuture = std::async(std::launch::async, [&] { - Tests rv; - for (auto&& range : testStateEntry.ranges) { - std::string_view prevState; - Runs* curRuns = nullptr; - wpi::log::DataLogReader::iterator lastStart = range.begin(); - for (auto it = range.begin(), end = range.end(); it != end; ++it) { - std::string_view testState; - if (it->GetEntry() != testStateEntry.entry || - !it->GetString(&testState)) { - continue; - } +DataSelector::Tests DataSelector::LoadTests( + const glass::DataLogReaderEntry& testStateEntry) { + Tests tests; + for (auto&& range : testStateEntry.ranges) { + std::string_view prevState; + Runs* curRuns = nullptr; + wpi::log::DataLogReader::iterator lastStart = range.begin(); + for (auto it = range.begin(), end = range.end(); it != end; ++it) { + std::string_view testState; + if (it->GetEntry() != testStateEntry.entry || + !it->GetString(&testState)) { + continue; + } - if (testState == "none") { - continue; - } + if (testState == "none") { + continue; + } - auto [testName, direction] = wpi::rsplit(testState, '-'); + auto [testName, direction] = wpi::rsplit(testState, '-'); - // track runs as iterator ranges of the same test - if (curRuns && testState != prevState) { - curRuns->emplace_back(lastStart, it); - lastStart = it; - } - auto testIt = rv.find(testName); - if (testIt == rv.end()) { - testIt = rv.emplace(std::string{testName}, State{}).first; - } - auto stateIt = testIt->second.find(testState); - if (stateIt == testIt->second.end()) { - stateIt = - testIt->second.emplace(std::string{testState}, Runs{}).first; - } - curRuns = &stateIt->second; - prevState = testState; + // track runs as iterator ranges of the same test + if (curRuns && testState != prevState) { + curRuns->emplace_back(lastStart, it); + lastStart = it; + } + auto testIt = tests.find(testName); + if (testIt == tests.end()) { + testIt = tests.emplace(std::string{testName}, State{}).first; } + auto stateIt = testIt->second.find(testState); + if (stateIt == testIt->second.end()) { + stateIt = testIt->second.emplace(std::string{testState}, Runs{}).first; + } + curRuns = &stateIt->second; + prevState = testState; + } - if (curRuns) { - curRuns->emplace_back(lastStart, range.end()); + if (curRuns) { + curRuns->emplace_back(lastStart, range.end()); + } + } + return tests; +} + +template +static void AddSample(std::vector>& samples, + const wpi::log::DataLogRecord& record, bool isDouble) { + if (isDouble) { + double val; + if (record.GetDouble(&val)) { + samples.emplace_back(units::second_t{record.GetTimestamp() * 1.0e-6}, + T{val}); + } + } else { + float val; + if (record.GetFloat(&val)) { + samples.emplace_back(units::second_t{record.GetTimestamp() * 1.0e-6}, + T{static_cast(val)}); + } + } +} + +TestData DataSelector::BuildTestData() { + TestData data; + data.distanceUnit = kUnits[m_selectedUnit]; + data.mechanismType = analysis::FromName(kAnalysisTypes[m_selectedAnalysis]); + bool voltageDouble = m_voltageEntry->type == "double"; + bool positionDouble = m_positionEntry->type == "double"; + bool velocityDouble = m_velocityEntry->type == "double"; + + for (auto&& state : m_tests[m_selectedTest]) { + auto& motorData = data.motorData[state.first]; + for (auto&& range : state.second) { + auto& run = motorData.runs.emplace_back(); + for (auto&& record : range) { + if (record.GetEntry() == m_voltageEntry->entry) { + AddSample(run.voltage, record, voltageDouble); + } else if (record.GetEntry() == m_positionEntry->entry) { + AddSample(run.position, record, positionDouble); + } else if (record.GetEntry() == m_velocityEntry->entry) { + AddSample(run.velocity, record, velocityDouble); + } } } - return rv; - }); + } + + return data; } diff --git a/sysid/src/main/native/include/sysid/view/DataSelector.h b/sysid/src/main/native/include/sysid/view/DataSelector.h index a903667c508..fac3e6784d5 100644 --- a/sysid/src/main/native/include/sysid/view/DataSelector.h +++ b/sysid/src/main/native/include/sysid/view/DataSelector.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -13,6 +14,8 @@ #include #include +#include "sysid/analysis/Storage.h" + namespace glass { class DataLogReaderEntry; class Storage; @@ -47,19 +50,29 @@ class DataSelector : public glass::View { */ void Reset(); - private: - void LoadTests(const glass::DataLogReaderEntry& testStateEntry); + /** + * Called when new test data is loaded. + */ + std::function testdata; + private: // wpi::Logger& m_logger; using Runs = std::vector; using State = std::map>; // full name using Tests = std::map>; // e.g. "dynamic" std::future m_testsFuture; - std::string m_selectedTest; Tests m_tests; + std::string m_selectedTest; const glass::DataLogReaderEntry* m_testStateEntry = nullptr; const glass::DataLogReaderEntry* m_velocityEntry = nullptr; const glass::DataLogReaderEntry* m_positionEntry = nullptr; const glass::DataLogReaderEntry* m_voltageEntry = nullptr; + int m_selectedUnit = 0; + int m_selectedAnalysis = 0; + std::future m_testdataFuture; + std::vector m_testdataStats; + + static Tests LoadTests(const glass::DataLogReaderEntry& testStateEntry); + TestData BuildTestData(); }; } // namespace sysid diff --git a/sysid/src/main/native/include/sysid/view/LogLoader.h b/sysid/src/main/native/include/sysid/view/LogLoader.h index 91ce72ae3de..5e685d71fa7 100644 --- a/sysid/src/main/native/include/sysid/view/LogLoader.h +++ b/sysid/src/main/native/include/sysid/view/LogLoader.h @@ -47,7 +47,8 @@ class LogLoader : public glass::View { void Display() override; /** - * Signal called when the current file is unloaded (invalidates any LogEntry*). + * Signal called when the current file is unloaded (invalidates any + * LogEntry*). */ wpi::sig::Signal<> unload; diff --git a/sysid/src/main/native/include/sysid/view/UILayout.h b/sysid/src/main/native/include/sysid/view/UILayout.h index 2699cd86a1e..b8587e19541 100644 --- a/sysid/src/main/native/include/sysid/view/UILayout.h +++ b/sysid/src/main/native/include/sysid/view/UILayout.h @@ -63,7 +63,7 @@ inline constexpr Vector2d kLeftColSize{ // Left column contents inline constexpr Vector2d kLogLoaderWindowPos = kLeftColPos; -inline constexpr Vector2d kLogLoaderWindowSize{kLeftColSize.x, 550}; +inline constexpr Vector2d kLogLoaderWindowSize{kLeftColSize.x, 500}; inline constexpr Vector2d kDataSelectorWindowPos = kLogLoaderWindowPos + Vector2d{0, kLogLoaderWindowSize.y + kWindowGap}; inline constexpr Vector2d kDataSelectorWindowSize{