From 6b99e0f419db56f572367b8d73aeddd4ab5d9bcb Mon Sep 17 00:00:00 2001
From: Davide Mor <davide.mor@skywarder.eu>
Date: Sat, 17 Aug 2024 22:54:50 +0200
Subject: [PATCH] [MEA] Updated with new version
---
src/shared/algorithms/MEA/MEA.cpp | 174 ++++++++++++++++++++---
src/shared/algorithms/MEA/MEA.h | 137 +++++++++++++-----
src/shared/algorithms/MEA/MEAData.h | 23 +--
src/shared/utils/AeroUtils/AeroUtils.cpp | 7 +
src/shared/utils/AeroUtils/AeroUtils.h | 14 ++
src/tests/catch/test-MEA.cpp | 60 ++++++--
6 files changed, 336 insertions(+), 79 deletions(-)
diff --git a/src/shared/algorithms/MEA/MEA.cpp b/src/shared/algorithms/MEA/MEA.cpp
index eeedf74c0..399ee03ee 100644
--- a/src/shared/algorithms/MEA/MEA.cpp
+++ b/src/shared/algorithms/MEA/MEA.cpp
@@ -1,5 +1,5 @@
-/* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Matteo Pignataro
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Mor
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,42 +24,170 @@
#include <drivers/timer/TimestampTimer.h>
-namespace Boardcore
+using namespace Boardcore;
+using namespace Eigen;
+
+MEA::Step::Step(float mainValveOpen) : mainValveOpen{mainValveOpen} {}
+
+void MEA::Step::withCCPressure(PressureData ccPressure)
{
+ withCCPressure(ccPressure.pressure);
+}
-MEA::MEA(const KalmanFilter::KalmanConfig kalmanConfig)
- : filter(kalmanConfig), state()
+void MEA::Step::withCCPressure(float ccPressure)
{
- updateState();
+ this->ccPressure = ccPressure;
+ hasCCPressure = true;
}
-void MEA::update(const float feedValvePosition, const float pressure)
+void MEA::Step::withAcceleration(AccelerometerData acceleration)
{
- // Update the Kalman filter
- filter.predictWithControl(KalmanFilter::CVectorM{feedValvePosition});
- filter.correct(KalmanFilter::CVectorP{pressure});
+ withAcceleration(acceleration);
+}
- // Compute the derived values for useful data
+void MEA::Step::withAcceleration(Eigen::Vector<float, 3> acceleration)
+{
+ this->acceleration = acceleration;
+ hasAcceleration = true;
+}
+
+void MEA::Step::withSpeedAndAlt(float verticalSpeed, float mslAltitude)
+{
+ this->verticalSpeed = verticalSpeed;
+ this->mslAltitude = mslAltitude;
+ hasSpeedAndAlt = true;
+}
+
+MEA::MEA(const Config &config)
+ : F{config.F}, Q{config.Q}, G{config.G}, baroH{config.baroH},
+ baroR{config.baroR}, P{config.P}, x{0, 0, config.initialMass},
+ accelThresh{config.accelThresh}, speedThresh{config.speedThresh},
+ Kt{config.Kt}, alpha{config.alpha}, c{config.c}, coeffs{config.coeffs},
+ crossSection{config.crossSection}, ae{config.ae}, p0{config.p0}
+{
+}
+
+void MEA::update(const Step &step)
+{
+ // First run the prediction step
+ predict(step);
+
+ // Compute applied force/mach/CD/rho
+ computeForce(step);
+
+ // Run cc pressure correction
+ correctBaro(step);
+
+ // Run accelerometer correction
+ correctAccel(step);
+
+ // Run apogee prediction
+ computeApogee(step);
+
+ // Finally update state
updateState();
}
MEAState MEA::getState() { return state; }
-void MEA::setKalmanConfig(KalmanFilter::KalmanConfig config)
+void MEA::predict(const Step &step)
{
- filter.setConfig(config);
+ x = F * x + G * step.mainValveOpen;
+ P = F * P * F.transpose() + Q;
}
-void MEA::updateState()
+void MEA::computeForce(const Step &step)
+{
+ if (!step.hasSpeedAndAlt)
+ return;
+
+ // NOTE: Here we assume that verticalSpeed roughly equals the total speed,
+ // so that we don't depend on N/E speed components, which can be quite
+ // unreliable in case of no GPS fix
+ mach = Aeroutils::computeMach(-step.mslAltitude, step.verticalSpeed,
+ Constants::MSL_TEMPERATURE);
+
+ cd = Aeroutils::computeCd(coeffs, mach);
+ rho = Aeroutils::computeRho(-step.mslAltitude, Constants::MSL_TEMPERATURE);
+
+ // Dynamic pressure
+ q = 0.5f * rho * (step.verticalSpeed * step.verticalSpeed);
+
+ // Aerodynamic force component
+ force = q * crossSection * cd;
+
+ if (step.mslAltitude > 800)
+ {
+ // At high altitudes we need to compensate for low external pressure
+
+ // External pressure
+ float p = Aeroutils::relPressure(step.mslAltitude);
+ force -= (p0 - p) * ae;
+ }
+}
+
+void MEA::correctBaro(const Step &step)
{
- const auto filterState = filter.getState();
- const auto filterOutput = filter.getOutput();
-
- state.timestamp = TimestampTimer::getTimestamp();
- state.correctedPressure = filterOutput(0);
- state.x0 = filterState(0);
- state.x1 = filterState(1);
- state.x2 = filterState(2);
+ if (!step.hasCCPressure)
+ return;
+
+ float S = baroH * P * baroH.transpose() + baroR;
+ Matrix<float, 3, 1> K = (P * baroH.transpose()) / S;
+
+ P = (Matrix<float, 3, 3>::Identity() - K * baroH) * P;
+ x = x + K * (step.ccPressure - baroH * x);
}
-} // namespace Boardcore
+void MEA::correctAccel(const Step &step)
+{
+ if (!step.hasAcceleration || !step.hasSpeedAndAlt)
+ return;
+
+ // Do not correct at low speed/acceleration
+ if (step.acceleration.norm() < accelThresh ||
+ step.verticalSpeed < speedThresh)
+ return;
+
+ float y = (Kt * (float)(baroH * x) - force) / x(2);
+
+ Matrix<float, 1, 3> accelH =
+ Vector<float, 3>{Kt * baroH(0) / x(2), Kt * baroH(1) / x(2),
+ -(Kt * (float)(baroH * x) - force) / (x(2) * x(2))}
+ .transpose();
+
+ float accelR = alpha * q + c;
+
+ float S = accelH * P * accelH.transpose() + accelR;
+ Matrix<float, 3, 1> K = (P * accelH.transpose()) / S;
+
+ P = (Matrix<float, 3, 3>::Identity() - K * accelH) * P;
+ x = x + K * (step.acceleration.x() - y);
+}
+
+void MEA::computeApogee(const Step &step)
+{
+ if (!step.hasSpeedAndAlt)
+ return;
+
+ float mass = x(2);
+
+ // TODO: Matlab uses CD_correction_shutDown, should we?
+ // Holy fuck, massive formula, split this for the love of god
+ apogee = step.mslAltitude +
+ 1 / (2 * ((0.5f * rho * cd * crossSection) / mass)) *
+ log1p(1 + (step.verticalSpeed * step.verticalSpeed *
+ ((0.5f * rho * cd * crossSection) / mass)) /
+ Constants::g);
+}
+
+void MEA::updateState()
+{
+ state.timestamp = TimestampTimer::getTimestamp();
+
+ state.x0 = x(0);
+ state.x1 = x(1);
+
+ state.estimatedMass = x(2);
+ state.estimatedPressure = baroH * x;
+ state.estimatedApogee = apogee;
+}
\ No newline at end of file
diff --git a/src/shared/algorithms/MEA/MEA.h b/src/shared/algorithms/MEA/MEA.h
index d69bc5bb0..56467767a 100644
--- a/src/shared/algorithms/MEA/MEA.h
+++ b/src/shared/algorithms/MEA/MEA.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Matteo Pignataro
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Mor
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,53 +22,124 @@
#pragma once
-#include <algorithms/Kalman/Kalman.h>
+#include <algorithms/MEA/MEAData.h>
+#include <sensors/SensorData.h>
+#include <utils/AeroUtils/AeroUtils.h>
-#include "MEAData.h"
+#include <Eigen/Dense>
namespace Boardcore
{
-/**
- * @brief MEA stands for Mass Estimation Algorithm.
- *
- * It represents a kalman filter which by performing a prediction with the
- * current feed valve state and a correction with the pressure in combustion
- * chamber, estimates the remaining mass of the rocket.
- */
class MEA
{
public:
- using KalmanFilter = Kalman<float, 3, 1, 1>;
+ struct Config
+ {
+ Eigen::Matrix<float, 3, 3> F; //< State propagation matrix
+ Eigen::Matrix<float, 3, 3> Q; //< Model variance matrix
+ Eigen::Vector<float, 3> G; //< Input vector
- explicit MEA(const KalmanFilter::KalmanConfig kalmanConfig);
+ Eigen::Vector<float, 3> baroH; //< Barometer output matrix
+ float baroR; //< Barometer measurement variance
- /**
- * @brief Update the Kalman filter.
- *
- * @param feedValvePosition Position reference of the feed valve [0-1]
- * @param pressure Measured pressure inside the combustion chamber [Pa]
- */
- void update(const float feedValvePosition, const float pressure);
+ Eigen::Matrix<float, 3, 3> P; //< Error covariance matrix
- /**
- * @brief Returns the MEA data including the estimated mass.
- */
- MEAState getState();
+ float initialMass; //< Initial mass of the rocket
+
+ float accelThresh; //< Minimum required acceleration to trigger accel
+ // correction.
+ float speedThresh; //< Minumum required speed to trigger accel
+ // correction.
+
+ float Kt; //< TODO: What is this?
+ float alpha; //< TODO: What is this?
+ float c; //< TODO: What is this?
+
+ Aeroutils::AerodynamicCoeff coeffs; //< Aerodynamic coefficients.
+ float crossSection; //< Cross section of the rocket.
+
+ float ae; //< TODO: What is this?
+ float p0; //< Pressure at nozzle exit
+ };
+
+ struct Step
+ {
+ float mainValveOpen =
+ 0.0f; //< How open is the main valve? (between 0-1)
+
+ float ccPressure =
+ 0.0f; //< What is the current combustion chamber pressure? [bar]
+ bool hasCCPressure = false; //< Is ccPressure valid?
+
+ Eigen::Vector<float, 3> acceleration =
+ Eigen::Vector<float, 3>::Zero(); //< Acceleration [m/s^2]
+ bool hasAcceleration = false; //< Is acceleration valid?
+
+ float verticalSpeed =
+ 0.0f; //< Vertical component of speed [up positive] [m/s]
+ float mslAltitude = 0.0f; //< Mean sea level altitude [m]
+ bool hasSpeedAndAlt =
+ false; //< Are verticalSpeed and mslAltitude valid?
+
+ explicit Step(float mainValveOpen);
+
+ void withCCPressure(PressureData ccPressure);
+ void withCCPressure(float ccPressure);
+
+ void withAcceleration(AccelerometerData acceleration);
+ void withAcceleration(Eigen::Vector<float, 3> acceleration);
- /**
- * @brief Sets the Kalman filter configuration.
- */
- void setKalmanConfig(KalmanFilter::KalmanConfig config);
+ void withSpeedAndAlt(float verticalSpeed, float mslAltitude);
+ };
+
+ explicit MEA(const Config &config);
+
+ void update(const Step &step);
+
+ MEAState getState();
private:
- /**
- * @brief Computes useful data from the kalman current state.
- */
+ void predict(const Step &step);
+ void computeForce(const Step &step);
+ void correctBaro(const Step &step);
+ void correctAccel(const Step &step);
+ void computeApogee(const Step &step);
void updateState();
- KalmanFilter filter;
+ Eigen::Matrix<float, 3, 3> F; //< State propagation matrix
+ Eigen::Matrix<float, 3, 3> Q; //< Model variance matrix
+
+ Eigen::Vector<float, 3> G; //< Input Vector
+
+ Eigen::Matrix<float, 1, 3> baroH;
+ float baroR;
+
+ Eigen::Matrix<float, 3, 3> P; //< Error covariance matrix
+ Eigen::Vector<float, 3> x; //< State vector
+
+ float mach = 0.0f; //< Latest computed mach
+ float cd = 0.0f; //< Latest computed CD
+ float rho = 0.0f; //< Latest computed rho
+ float q = 0.0f; //< Latest computed dynamic pressure
+ float force = 0.0f; //< Latest computed force
+
+ float apogee = 0.0f; //< Latest computed apogee
+
+ float accelThresh;
+ float speedThresh;
+
+ float Kt;
+ float alpha;
+ float c;
+
+ Aeroutils::AerodynamicCoeff coeffs;
+ float crossSection;
+
+ float ae;
+ float p0;
+
MEAState state;
};
-} // namespace Boardcore
+} // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/algorithms/MEA/MEAData.h b/src/shared/algorithms/MEA/MEAData.h
index 1a3dc7086..6164a8b7a 100644
--- a/src/shared/algorithms/MEA/MEAData.h
+++ b/src/shared/algorithms/MEA/MEAData.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Matteo Pignataro
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Mor
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,7 @@
#pragma once
+#include <cstdint>
#include <ostream>
namespace Boardcore
@@ -30,21 +31,25 @@ namespace Boardcore
struct MEAState
{
uint64_t timestamp;
- float correctedPressure;
+
+ float estimatedPressure;
+ float estimatedMass;
+ float estimatedApogee;
+
float x0;
float x1;
- float x2;
static std::string header()
{
- return "timestamp,correctedPressure,x0,x1,x2\n";
+ return "timestamp,estimatedPressure,estimatedMass,estimatedApogee,x0,"
+ "x1\n";
}
- void print(std::ostream& os) const
+ void print(std::ostream &os) const
{
- os << timestamp << "," << correctedPressure << "," << x0 << "," << x1
- << "," << x2 << "\n";
+ os << timestamp << "," << estimatedPressure << "," << estimatedMass
+ << "," << estimatedApogee << "," << x0 << "," << x1 << "\n";
}
};
-} // namespace Boardcore
+} // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/utils/AeroUtils/AeroUtils.cpp b/src/shared/utils/AeroUtils/AeroUtils.cpp
index 1ec63ecab..b1592df1e 100644
--- a/src/shared/utils/AeroUtils/AeroUtils.cpp
+++ b/src/shared/utils/AeroUtils/AeroUtils.cpp
@@ -119,6 +119,13 @@ float computePitotAirspeed(float pressureTotal, float pressureStatic, float d,
return M * c;
}
+float computeCd(const AerodynamicCoeff& coeff, float mach)
+{
+ return coeff.n000 + coeff.n100 * mach + coeff.n200 * powf(mach, 2) +
+ coeff.n300 * powf(mach, 3) + coeff.n400 * powf(mach, 4) +
+ coeff.n500 * powf(mach, 5) + coeff.n600 * powf(mach, 6);
+}
+
} // namespace Aeroutils
} // namespace Boardcore
diff --git a/src/shared/utils/AeroUtils/AeroUtils.h b/src/shared/utils/AeroUtils/AeroUtils.h
index 96223d359..cc4a91d45 100644
--- a/src/shared/utils/AeroUtils/AeroUtils.h
+++ b/src/shared/utils/AeroUtils/AeroUtils.h
@@ -231,6 +231,20 @@ float computePitotMach(float pressureTotal, float pressureStatic);
float computePitotAirspeed(float pressureTotal, float pressureStatic, float d,
float t0);
+struct AerodynamicCoeff
+{
+ float n000, n100, n200, n300, n400, n500, n600;
+};
+
+/**
+ * @brief Computes the CD from aerodynamic coefficients and mach.
+ *
+ * @param coeff Aerodynamic coefficients.
+ * @param mach Mach value.
+ * @return The computed CD.
+ */
+float computeCd(const AerodynamicCoeff& coeff, float mach);
+
} // namespace Aeroutils
} // namespace Boardcore
diff --git a/src/tests/catch/test-MEA.cpp b/src/tests/catch/test-MEA.cpp
index 6a4d64a68..257a52cf0 100644
--- a/src/tests/catch/test-MEA.cpp
+++ b/src/tests/catch/test-MEA.cpp
@@ -30,6 +30,7 @@
using namespace Boardcore;
using namespace Eigen;
+/*
constexpr float sensorNoiseVariance = 0.36f;
constexpr float modelNoiseVariance = 0.1f;
constexpr float initialRocketMass = 35.01f;
@@ -40,17 +41,43 @@ MEA::KalmanFilter::KalmanConfig getMEAKalmanConfig()
// clang-format off
config.F = MEA::KalmanFilter::MatrixNN({
- {1.435871191228868, -0.469001276508780, 0.f},
+ {1.435871191228868, -0.469001276508780, 0.f},
{1.f, 0.f, 0.f},
{-0.002045309260755, 0.001867496708935, 1.f}});
-
+
config.H = {1.780138883879285,-1.625379384370081,0.f};
config.P = MEA::KalmanFilter::MatrixNN::Zero();
- config.Q = modelNoiseVariance * MEA::KalmanFilter::CVectorN({1, 1, 1}).asDiagonal();
- config.R[0] = sensorNoiseVariance;
- config.G = MEA::KalmanFilter::MatrixNM{{4}, {0}, {0}};
- config.x = {0, 0, initialRocketMass};
+ config.Q = modelNoiseVariance * MEA::KalmanFilter::CVectorN({1, 1,
+1}).asDiagonal(); config.R[0] = sensorNoiseVariance; config.G =
+MEA::KalmanFilter::MatrixNM{{4}, {0}, {0}}; config.x = {0, 0,
+initialRocketMass};
+ // clang-format on
+
+ return config;
+}
+*/
+
+MEA::Config getMEAConfig()
+{
+ MEA::Config config;
+
+ // clang-format off
+ config.F = Matrix<float, 3, 3>({
+ {1.435871191228868, -0.469001276508780, 0.f},
+ {1.f, 0.f, 0.f},
+ {-0.002045309260755, 0.001867496708935, 1.f}});
+ config.Q = Matrix<float, 3, 3>({
+ {0.1f, 0.0f, 0.0f},
+ {0.0f, 0.1f, 0.0f},
+ {0.0f, 0.0f, 0.1f}});
+ config.G = Matrix<float, 3, 1>{{4}, {0}, {0}};
+
+ config.baroH = {1.780138883879285,-1.625379384370081,0.f};
+ config.baroR = 0.36f;
+
+ config.P = Matrix<float, 3, 3>::Zero();
+ config.initialMass = 35.01f;
// clang-format on
return config;
@@ -58,31 +85,36 @@ MEA::KalmanFilter::KalmanConfig getMEAKalmanConfig()
TEST_CASE("MEA Update Test")
{
- MEA mea(getMEAKalmanConfig());
+ MEA mea(getMEAConfig());
MEAState state;
- std::cout << "pressure,estimatedPressure,estimatedMass,command"
- << std::endl;
+ std::cout << MEAState::header();
for (unsigned i = 1; i < PRESSURE.size(); i++)
{
// Update the kalman
- mea.update(COMMAND[i - 1], PRESSURE[i]);
+ MEA::Step step{COMMAND[i - 1]};
+ step.withCCPressure(PRESSURE[i]);
+
+ mea.update(step);
// Get the results
state = mea.getState();
- if (state.x2 != Approx(ESTIMATED_MASS[i]).epsilon(0.01))
+ state.print(std::cout);
+
+ if (state.estimatedMass != Approx(ESTIMATED_MASS[i]).epsilon(0.01))
{
FAIL("The estimated mass differs from the correct one ["
- << i << "]: " << state.x2 << " != " << ESTIMATED_MASS[i]);
+ << i << "]: " << state.estimatedMass
+ << " != " << ESTIMATED_MASS[i]);
}
- if (state.correctedPressure !=
+ if (state.estimatedPressure !=
Approx(ESTIMATED_PRESSURE[i]).epsilon(0.01))
{
FAIL("The estimated pressure differs from the correct one ["
- << i << "]: " << state.correctedPressure
+ << i << "]: " << state.estimatedPressure
<< " != " << ESTIMATED_PRESSURE[i]);
}
}
--
GitLab