diff --git a/sysid/src/main/native/cpp/Util.cpp b/sysid/src/main/native/cpp/Util.cpp index a9acc537a7a..c4dc7803046 100644 --- a/sysid/src/main/native/cpp/Util.cpp +++ b/sysid/src/main/native/cpp/Util.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -23,6 +24,20 @@ void sysid::CreateTooltip(const char* text) { } } +void sysid::CreateErrorTooltip(const char* text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), + ICON_FA_TRIANGLE_EXCLAMATION); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", text); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + void sysid::CreateErrorPopup(bool& isError, std::string_view errorMessage) { if (isError) { ImGui::OpenPopup("Exception Caught!"); diff --git a/sysid/src/main/native/cpp/analysis/AnalysisManager.cpp b/sysid/src/main/native/cpp/analysis/AnalysisManager.cpp index 4e396410f8f..9a6d4427e47 100644 --- a/sysid/src/main/native/cpp/analysis/AnalysisManager.cpp +++ b/sysid/src/main/native/cpp/analysis/AnalysisManager.cpp @@ -192,37 +192,90 @@ AnalysisManager::FeedforwardGains AnalysisManager::CalculateFeedforward() { WPI_INFO(m_logger, "{}", "Calculating Gains"); // Calculate feedforward gains from the data. - const auto& ff = sysid::CalculateFeedforwardGains( - GetFilteredData(), m_data.mechanismType, false); - FeedforwardGains ffGains = {ff}; + const auto& analysisType = m_data.mechanismType; + const auto& ff = + sysid::CalculateFeedforwardGains(GetFilteredData(), analysisType, false); const auto& Ks = ff.coeffs[0]; + FeedforwardGain KsGain = { + .gain = Ks, .descriptor = "Voltage needed to overcome static friction."}; + if (Ks < 0) { + KsGain.isValidGain = false; + KsGain.errorMessage = fmt::format( + "Calculated Ks gain of: {0:.3f} is erroneous! Ks should be >= 0.", Ks); + } + const auto& Kv = ff.coeffs[1]; + FeedforwardGain KvGain = { + .gain = Kv, + .descriptor = + "Voltage needed to hold/cruise at a constant velocity while " + "overcoming the counter-electromotive force and any additional " + "friction."}; + if (Kv < 0) { + KvGain.isValidGain = false; + KvGain.errorMessage = fmt::format( + "Calculated Kv gain of: {0:.3f} is erroneous! Kv should be >= 0.", Kv); + } + const auto& Ka = ff.coeffs[2]; + FeedforwardGain KaGain = { + .gain = Ka, + .descriptor = + "Voltage needed to induce a given acceleration in the motor shaft."}; + if (Ka <= 0) { + KaGain.isValidGain = false; + KaGain.errorMessage = fmt::format( + "Calculated Ka gain of: {0:.3f} is erroneous! Ka should be > 0.", Ka); + } - if (Ka <= 0 || Kv < 0) { - throw InvalidDataError( - fmt::format("The calculated feedforward gains of kS: {}, Kv: {}, Ka: " - "{} are erroneous. Your Ka should be > 0 while your Kv and " - "Ks constants should both >= 0. Try adjusting the " - "filtering and trimming settings or collect better data.", - Ks, Kv, Ka)); + if (analysisType == analysis::kSimple) { + return FeedforwardGains{ + .olsResult = ff, .Ks = KsGain, .Kv = KvGain, .Ka = KaGain}; + } + + if (analysisType == analysis::kElevator || analysisType == analysis::kArm) { + const auto& Kg = ff.coeffs[3]; + FeedforwardGain KgGain = { + Kg, "Voltage needed to counteract the force of gravity."}; + if (Kg < 0) { + KgGain.isValidGain = false; + KgGain.errorMessage = fmt::format( + "Calculated Kg gain of: {0:.3f} is erroneous! Kg should be >= 0.", + Ka); + } + + // Elevator analysis only requires Kg + if (analysisType == analysis::kElevator) { + return FeedforwardGains{.olsResult = ff, + .Ks = KsGain, + .Kv = KvGain, + .Ka = KaGain, + .Kg = KgGain}; + } else { + // Arm analysis requires Kg and an angle offset + FeedforwardGain offset = { + .gain = ff.coeffs[4], + .descriptor = + "This is the angle offset which, when added to the angle " + "measurement, zeroes it out when the arm is horizontal. This is " + "needed for the arm feedforward to work."}; + return FeedforwardGains{ff, KsGain, KvGain, KaGain, KgGain, offset}; + } } - return ffGains; + return FeedforwardGains{.olsResult = ff}; } sysid::FeedbackGains AnalysisManager::CalculateFeedback( - std::vector ff) { - const auto& Kv = ff[1]; - const auto& Ka = ff[2]; + const FeedforwardGain& Kv, const FeedforwardGain& Ka) { FeedbackGains fb; if (m_settings.type == FeedbackControllerLoopType::kPosition) { - fb = sysid::CalculatePositionFeedbackGains(m_settings.preset, - m_settings.lqr, Kv, Ka); + fb = sysid::CalculatePositionFeedbackGains( + m_settings.preset, m_settings.lqr, Kv.gain, Ka.gain); } else { - fb = sysid::CalculateVelocityFeedbackGains(m_settings.preset, - m_settings.lqr, Kv, Ka); + fb = sysid::CalculateVelocityFeedbackGains( + m_settings.preset, m_settings.lqr, Kv.gain, Ka.gain); } return fb; diff --git a/sysid/src/main/native/cpp/view/Analyzer.cpp b/sysid/src/main/native/cpp/view/Analyzer.cpp index 467c0f611d0..63911887b27 100644 --- a/sysid/src/main/native/cpp/view/Analyzer.cpp +++ b/sysid/src/main/native/cpp/view/Analyzer.cpp @@ -48,10 +48,10 @@ Analyzer::Analyzer(glass::Storage& storage, wpi::Logger& logger) void Analyzer::UpdateFeedforwardGains() { WPI_INFO(m_logger, "{}", "Gain calc"); try { - const auto& [ff] = m_manager->CalculateFeedforward(); - m_ff = ff.coeffs; - m_accelRSquared = ff.rSquared; - m_accelRMSE = ff.rmse; + const auto& feedforwardGains = m_manager->CalculateFeedforward(); + m_feedforwardGains = feedforwardGains; + m_accelRSquared = feedforwardGains.olsResult.rSquared; + m_accelRMSE = feedforwardGains.olsResult.rmse; m_settings.preset.measurementDelay = m_settings.type == FeedbackControllerLoopType::kPosition ? m_manager->GetPositionDelay() @@ -80,16 +80,22 @@ void Analyzer::UpdateFeedforwardGains() { void Analyzer::UpdateFeedbackGains() { WPI_INFO(m_logger, "{}", "Updating feedback gains"); - if (m_ff[1] > 0 && m_ff[2] > 0) { - const auto& fb = m_manager->CalculateFeedback(m_ff); - m_timescale = units::second_t{m_ff[2] / m_ff[1]}; + + const auto& Kv = m_feedforwardGains.Kv; + const auto& Ka = m_feedforwardGains.Ka; + if (Kv.isValidGain && Ka.isValidGain) { + const auto& fb = m_manager->CalculateFeedback(Kv, Ka); + m_timescale = units::second_t{Ka.gain / Kv.gain}; + m_timescaleValid = true; m_Kp = fb.Kp; m_Kd = fb.Kd; + } else { + m_timescaleValid = false; } } -bool Analyzer::DisplayGain(const char* text, double* data, - bool readOnly = true) { +bool Analyzer::DisplayDouble(const char* text, double* data, + bool readOnly = true) { ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5); if (readOnly) { return ImGui::InputDouble(text, data, 0.0, 0.0, "%.5G", @@ -121,7 +127,7 @@ bool Analyzer::IsDataErrorState() { void Analyzer::ResetData() { m_plot.ResetData(); m_manager = std::make_unique(m_settings, m_logger); - m_ff = std::vector{1, 1, 1}; + m_feedforwardGains = AnalysisManager::FeedforwardGains{}; UpdateFeedbackGains(); } @@ -182,7 +188,8 @@ void Analyzer::ConfigParamsOnFileSelect() { // Estimate qp as 1/10 native distance unit m_settings.lqr.qp = 0.1; // Estimate qv as 1/4 * max velocity = 1/4 * (12V - kS) / kV - m_settings.lqr.qv = 0.25 * (12.0 - m_ff[0]) / m_ff[1]; + m_settings.lqr.qv = + 0.25 * (12.0 - m_feedforwardGains.Ks.gain) / m_feedforwardGains.Kv.gain; } void Analyzer::Display() { @@ -302,8 +309,9 @@ void Analyzer::PrepareGraphs() { AbortDataPrep(); m_dataThread = std::thread([&] { m_plot.SetData(m_manager->GetRawData(), m_manager->GetFilteredData(), - m_manager->GetUnit(), m_ff, m_manager->GetStartTimes(), - m_manager->GetAnalysisType(), m_abortDataPrep); + m_manager->GetUnit(), m_feedforwardGains, + m_manager->GetStartTimes(), m_manager->GetAnalysisType(), + m_abortDataPrep); }); UpdateFeedbackGains(); m_state = AnalyzerState::kNominalDisplay; @@ -355,28 +363,28 @@ void Analyzer::DisplayGraphs() { // If a JSON is selected if (m_state == AnalyzerState::kNominalDisplay) { - DisplayGain("Acceleration R²", &m_accelRSquared); + DisplayDouble("Acceleration R²", &m_accelRSquared); CreateTooltip( "The coefficient of determination of the OLS fit of acceleration " "versus velocity and voltage. Acceleration is extremely noisy, " "so this is generally quite small."); ImGui::SameLine(); - DisplayGain("Acceleration RMSE", &m_accelRMSE); + DisplayDouble("Acceleration RMSE", &m_accelRMSE); CreateTooltip( "The standard deviation of the residuals from the predicted " "acceleration." "This can be interpreted loosely as the mean measured disturbance " "from the \"ideal\" system equation."); - DisplayGain("Sim velocity R²", m_plot.GetSimRSquared()); + DisplayDouble("Sim velocity R²", m_plot.GetSimRSquared()); CreateTooltip( "The coefficient of determination the simulated velocity. " "Velocity is much less-noisy than acceleration, so this " "is pretty close to 1 for a decent fit."); ImGui::SameLine(); - DisplayGain("Sim velocity RMSE", m_plot.GetSimRMSE()); + DisplayDouble("Sim velocity RMSE", m_plot.GetSimRMSE()); CreateTooltip( "The standard deviation of the residuals from the simulated velocity " "predictions - essentially the size of the mean error of the " @@ -407,7 +415,7 @@ void Analyzer::AbortDataPrep() { void Analyzer::DisplayFeedforwardParameters(float beginX, float beginY) { // Increase spacing to not run into trackwidth in the normal analyzer view - constexpr double kHorizontalOffset = 0.9; + constexpr double kHorizontalOffset = 1.1; SetPosition(beginX, beginY, kHorizontalOffset, 0); bool displayAll = @@ -457,20 +465,20 @@ void Analyzer::DisplayFeedforwardParameters(float beginX, float beginY) { void Analyzer::CollectFeedforwardGains(float beginX, float beginY) { SetPosition(beginX, beginY, 0, 0); - if (DisplayGain("Kv", &m_ff[1], false)) { + if (DisplayDouble("Kv", &m_feedforwardGains.Kv.gain, false)) { UpdateFeedbackGains(); } SetPosition(beginX, beginY, 0, 1); - if (DisplayGain("Ka", &m_ff[2], false)) { + if (DisplayDouble("Ka", &m_feedforwardGains.Ka.gain, false)) { UpdateFeedbackGains(); } SetPosition(beginX, beginY, 0, 2); // Show Timescale ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4); - DisplayGain("Response Timescale (ms)", - reinterpret_cast(&m_timescale)); + DisplayDouble("Response Timescale (ms)", + reinterpret_cast(&m_timescale)); CreateTooltip( "The characteristic timescale of the system response in milliseconds. " "Both the control loop period and total signal delay should be " @@ -478,21 +486,39 @@ void Analyzer::CollectFeedforwardGains(float beginX, float beginY) { "system."); } +void Analyzer::DisplayFeedforwardGain(const char* text, + AnalysisManager::FeedforwardGain& ffGain, + bool readOnly = true) { + DisplayDouble(text, &ffGain.gain, readOnly); + if (!ffGain.isValidGain) { + // Display invalid gain message with warning and tooltip + CreateErrorTooltip(ffGain.errorMessage.c_str()); + } + + // Display descriptor message as tooltip, whether the gain is valid or not + CreateTooltip(ffGain.descriptor.c_str()); +} + void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) { SetPosition(beginX, beginY, 0, 0); - DisplayGain("Ks", &m_ff[0]); + DisplayFeedforwardGain("Ks", m_feedforwardGains.Ks); SetPosition(beginX, beginY, 0, 1); - DisplayGain("Kv", &m_ff[1]); + DisplayFeedforwardGain("Kv", m_feedforwardGains.Kv); SetPosition(beginX, beginY, 0, 2); - DisplayGain("Ka", &m_ff[2]); + DisplayFeedforwardGain("Ka", m_feedforwardGains.Ka); SetPosition(beginX, beginY, 0, 3); // Show Timescale ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4); - DisplayGain("Response Timescale (ms)", - reinterpret_cast(&m_timescale)); + DisplayDouble("Response Timescale (ms)", + reinterpret_cast(&m_timescale)); + if (!m_timescaleValid) { + CreateErrorTooltip( + "Response timescale calculation invalid. Ensure that calculated gains " + "are valid."); + } CreateTooltip( "The characteristic timescale of the system response in milliseconds. " "Both the control loop period and total signal delay should be " @@ -501,8 +527,8 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) { SetPosition(beginX, beginY, 0, 4); auto positionDelay = m_manager->GetPositionDelay(); - DisplayGain("Position Measurement Delay (ms)", - reinterpret_cast(&positionDelay)); + DisplayDouble("Position Measurement Delay (ms)", + reinterpret_cast(&positionDelay)); CreateTooltip( "The average elapsed time between the first application of " "voltage and the first detected change in mechanism position " @@ -512,8 +538,8 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) { SetPosition(beginX, beginY, 0, 5); auto velocityDelay = m_manager->GetVelocityDelay(); - DisplayGain("Velocity Measurement Delay (ms)", - reinterpret_cast(&velocityDelay)); + DisplayDouble("Velocity Measurement Delay (ms)", + reinterpret_cast(&velocityDelay)); CreateTooltip( "The average elapsed time between the first application of " "voltage and the maximum calculated mechanism acceleration " @@ -524,20 +550,20 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) { SetPosition(beginX, beginY, 0, 6); if (m_manager->GetAnalysisType() == analysis::kElevator) { - DisplayGain("Kg", &m_ff[3]); + DisplayFeedforwardGain("Kg", m_feedforwardGains.Kg); } else if (m_manager->GetAnalysisType() == analysis::kArm) { - DisplayGain("Kg", &m_ff[3]); + DisplayFeedforwardGain("Kg", m_feedforwardGains.Kg); double offset; auto unit = m_manager->GetUnit(); if (unit == "Radians") { - offset = m_ff[4]; + offset = m_feedforwardGains.offset.gain; } else if (unit == "Degrees") { - offset = m_ff[4] / std::numbers::pi * 180.0; + offset = m_feedforwardGains.offset.gain / std::numbers::pi * 180.0; } else if (unit == "Rotations") { - offset = m_ff[4] / (2 * std::numbers::pi); + offset = m_feedforwardGains.offset.gain / (2 * std::numbers::pi); } - DisplayGain( + DisplayDouble( fmt::format("Angle offset to horizontal ({})", GetAbbreviation(unit)) .c_str(), &offset); @@ -671,10 +697,10 @@ void Analyzer::DisplayFeedbackGains() { // Show Kp and Kd. float beginY = ImGui::GetCursorPosY(); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4); - DisplayGain("Kp", &m_Kp); + DisplayDouble("Kp", &m_Kp); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4); - DisplayGain("Kd", &m_Kd); + DisplayDouble("Kd", &m_Kd); // Come back to the starting y pos. ImGui::SetCursorPosY(beginY); @@ -686,8 +712,8 @@ void Analyzer::DisplayFeedbackGains() { } ImGui::SetCursorPosX(ImGui::GetFontSize() * 9); - if (DisplayGain(fmt::format("Max Position Error{}", unit).c_str(), - &m_settings.lqr.qp, false)) { + if (DisplayDouble(fmt::format("Max Position Error{}", unit).c_str(), + &m_settings.lqr.qp, false)) { if (m_settings.lqr.qp > 0) { UpdateFeedbackGains(); } @@ -700,14 +726,14 @@ void Analyzer::DisplayFeedbackGains() { } ImGui::SetCursorPosX(ImGui::GetFontSize() * 9); - if (DisplayGain(fmt::format("Max Velocity Error{}", unit).c_str(), - &m_settings.lqr.qv, false)) { + if (DisplayDouble(fmt::format("Max Velocity Error{}", unit).c_str(), + &m_settings.lqr.qv, false)) { if (m_settings.lqr.qv > 0) { UpdateFeedbackGains(); } } ImGui::SetCursorPosX(ImGui::GetFontSize() * 9); - if (DisplayGain("Max Control Effort (V)", &m_settings.lqr.r, false)) { + if (DisplayDouble("Max Control Effort (V)", &m_settings.lqr.r, false)) { if (m_settings.lqr.r > 0) { UpdateFeedbackGains(); } diff --git a/sysid/src/main/native/cpp/view/AnalyzerPlot.cpp b/sysid/src/main/native/cpp/view/AnalyzerPlot.cpp index d8afbaf06c0..6963f2c1b8f 100644 --- a/sysid/src/main/native/cpp/view/AnalyzerPlot.cpp +++ b/sysid/src/main/native/cpp/view/AnalyzerPlot.cpp @@ -127,16 +127,16 @@ void AnalyzerPlot::SetRawData(const Storage& data, std::string_view unit, void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData, std::string_view unit, - const std::vector& ffGains, + const AnalysisManager::FeedforwardGains& ffGains, const std::array& startTimes, AnalysisType type, std::atomic& abort) { double simSquaredErrorSum = 0; double squaredVariationSum = 0; int timeSeriesPoints = 0; - const auto& Ks = ffGains[0]; - const auto& Kv = ffGains[1]; - const auto& Ka = ffGains[2]; + const auto& Ks = ffGains.Ks.gain; + const auto& Kv = ffGains.Kv.gain; + const auto& Ka = ffGains.Ka.gain; auto& [slowForward, slowBackward, fastForward, fastBackward] = filteredData; auto& [rawSlowForward, rawSlowBackward, rawFastForward, rawFastBackward] = @@ -223,7 +223,7 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData, // Populate simulated time domain data if (type == analysis::kElevator) { - const auto& Kg = ffGains[3]; + const auto& Kg = ffGains.Kg.gain; m_quasistaticData.simData = PopulateTimeDomainSim( rawSlow, startTimes, fastStep, sysid::ElevatorSim{Ks, Kv, Ka, Kg}, &simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints); @@ -231,8 +231,8 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData, rawFast, startTimes, fastStep, sysid::ElevatorSim{Ks, Kv, Ka, Kg}, &simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints); } else if (type == analysis::kArm) { - const auto& Kg = ffGains[3]; - const auto& offset = ffGains[4]; + const auto& Kg = ffGains.Kg.gain; + const auto& offset = ffGains.offset.gain; m_quasistaticData.simData = PopulateTimeDomainSim( rawSlow, startTimes, fastStep, sysid::ArmSim{Ks, Kv, Ka, Kg, offset}, &simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints); @@ -288,10 +288,10 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData, std::copysign(Ks / Ka, slow[i].velocity); if (type == analysis::kElevator) { - const auto& Kg = ffGains[3]; + const auto& Kg = ffGains.Kg.gain; accelPortion -= Kg / Ka; } else if (type == analysis::kArm) { - const auto& Kg = ffGains[3]; + const auto& Kg = ffGains.Kg.gain; accelPortion -= Kg / Ka * slow[i].cos; } @@ -307,10 +307,10 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData, std::copysign(Ks / Ka, fast[i].velocity); if (type == analysis::kElevator) { - const auto& Kg = ffGains[3]; + const auto& Kg = ffGains.Kg.gain; accelPortion -= Kg / Ka; } else if (type == analysis::kArm) { - const auto& Kg = ffGains[3]; + const auto& Kg = ffGains.Kg.gain; accelPortion -= Kg / Ka * fast[i].cos; } diff --git a/sysid/src/main/native/include/sysid/Util.h b/sysid/src/main/native/include/sysid/Util.h index 1b8ede9c7d0..500601c9710 100644 --- a/sysid/src/main/native/include/sysid/Util.h +++ b/sysid/src/main/native/include/sysid/Util.h @@ -45,6 +45,14 @@ inline constexpr const char* kUnits[] = {"Meters", "Feet", "Inches", */ void CreateTooltip(const char* text); +/** + * Displays an error tooltip beside the widget that this method is called after + * with the provided text. + * + * @param text The text to show in the error tooltip. + */ +void CreateErrorTooltip(const char* text); + /** * Utility function to launch an error popup if an exception is detected. * diff --git a/sysid/src/main/native/include/sysid/analysis/AnalysisManager.h b/sysid/src/main/native/include/sysid/analysis/AnalysisManager.h index 5ad2c87494d..c115ccba462 100644 --- a/sysid/src/main/native/include/sysid/analysis/AnalysisManager.h +++ b/sysid/src/main/native/include/sysid/analysis/AnalysisManager.h @@ -76,14 +76,61 @@ class AnalysisManager { units::second_t stepTestDuration = 0_s; }; + struct FeedforwardGain { + /** + * The feedforward gain. + */ + double gain = 1; + + /** + * Descriptor attached to the feedforward gain. + */ + std::string descriptor = "Feedforward gain."; + + /** + * Whether the feedforward gain is valid. + */ + bool isValidGain = true; + + /** + * Error message attached to the feedforward gain. + */ + std::string errorMessage = "No error."; + }; + /** - * Stores feedforward. + * Stores feedforward gains. */ struct FeedforwardGains { /** - * Stores the Feedforward gains. + * Stores the raw OLSResult from analysis. + */ + OLSResult olsResult; + + /** + * The static gain Ks. + */ + FeedforwardGain Ks = {}; + + /** + * The velocity gain kV. + */ + FeedforwardGain Kv = {}; + + /** + * The acceleration gain kA. + */ + FeedforwardGain Ka = {}; + + /** + * The gravity gain Kg. + */ + FeedforwardGain Kg = {}; + + /** + * The offset (arm). */ - OLSResult ffGains; + FeedforwardGain offset = {}; }; /** @@ -177,7 +224,8 @@ class AnalysisManager { * @param ff The feedforward gains. * @return The calculated feedback gains. */ - FeedbackGains CalculateFeedback(std::vector ff); + FeedbackGains CalculateFeedback(const FeedforwardGain& Kv, + const FeedforwardGain& Ka); /** * Overrides the units in the JSON with the user-provided ones. diff --git a/sysid/src/main/native/include/sysid/view/Analyzer.h b/sysid/src/main/native/include/sysid/view/Analyzer.h index a3bbf91f689..e23df7e8239 100644 --- a/sysid/src/main/native/include/sysid/view/Analyzer.h +++ b/sysid/src/main/native/include/sysid/view/Analyzer.h @@ -182,9 +182,17 @@ class Analyzer : public glass::View { void UpdateFeedbackGains(); /** - * Handles logic of displaying a gain on ImGui + * Handles logic of displaying a double on ImGui. */ - bool DisplayGain(const char* text, double* data, bool readOnly); + bool DisplayDouble(const char* text, double* data, bool readOnly); + + /** + * Displays a Feedforward gain, including the gain itself along with its + * validity and message. + */ + void DisplayFeedforwardGain(const char* text, + AnalysisManager::FeedforwardGain& ffGain, + bool readOnly); /** * Handles errors when they pop up. @@ -210,12 +218,13 @@ class Analyzer : public glass::View { int m_selectedPreset = 0; // Feedforward and feedback gains. - std::vector m_ff; + AnalysisManager::FeedforwardGains m_feedforwardGains; double m_accelRSquared; double m_accelRMSE; double m_Kp; double m_Kd; units::millisecond_t m_timescale; + bool m_timescaleValid = false; // Units int m_selectedOverrideUnit = 0; diff --git a/sysid/src/main/native/include/sysid/view/AnalyzerPlot.h b/sysid/src/main/native/include/sysid/view/AnalyzerPlot.h index e5fbe8ca959..2c234ad7b8e 100644 --- a/sysid/src/main/native/include/sysid/view/AnalyzerPlot.h +++ b/sysid/src/main/native/include/sysid/view/AnalyzerPlot.h @@ -17,6 +17,7 @@ #include #include +#include "sysid/analysis/AnalysisManager.h" #include "sysid/analysis/AnalysisType.h" #include "sysid/analysis/Storage.h" @@ -44,15 +45,16 @@ class AnalyzerPlot { * @param rawData Raw data storage. * @param filteredData Filtered data storage. * @param unit Unit of the dataset - * @param ff List of feedforward gains (Ks, Kv, Ka, and optionally - * Kg). + * @param ff Feedforward gains (Ks, Kv, Ka, optionally + * Kg and offset). * @param startTimes Array of dataset start times. * @param type Type of analysis. * @param abort Aborts analysis early if set to true from another * thread. */ void SetData(const Storage& rawData, const Storage& filteredData, - std::string_view unit, const std::vector& ff, + std::string_view unit, + const AnalysisManager::FeedforwardGains& ff, const std::array& startTimes, AnalysisType type, std::atomic& abort);