diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index f4007eecdf2fe78ca1f5a9965191a26d466e8413..0a0905f194f5dad484d081d5f846a78577f50227 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -34,7 +34,7 @@ set(MAIN_COMPUTER
     src/boards/Main/StateMachines/ADAController/ADAController.cpp
     src/boards/Main/PinHandler/PinHandler.cpp
     # src/boards/Main/StateMachines/ABKController/ABKController.cpp
-    # src/boards/Main/StateMachines/MEAController/MEAController.cpp
+    src/boards/Main/StateMachines/MEAController/MEAController.cpp
     src/boards/Main/StatsRecorder/StatsRecorder.cpp
 )
 
diff --git a/src/boards/Main/BoardScheduler.h b/src/boards/Main/BoardScheduler.h
index 4ce9ade35c49a5adfa2d0c52072c546363768bef..11428599a702a4c921d51c74785ca71c7829253f 100644
--- a/src/boards/Main/BoardScheduler.h
+++ b/src/boards/Main/BoardScheduler.h
@@ -74,6 +74,8 @@ public:
 
     Boardcore::TaskScheduler &getNasScheduler() { return nas; }
 
+    Boardcore::TaskScheduler &getMeaScheduler() { return nas; }
+
     Boardcore::TaskScheduler &getAdaScheduler() { return ada; }
 
     Boardcore::TaskScheduler &getSensorsScheduler() { return sensors; }
diff --git a/src/boards/Main/Configs/ADAConfig.h b/src/boards/Main/Configs/ADAConfig.h
index a9437167ca965c0e8f600a03dcbdce29bafca104..648c5a0b38007e3e16b8dbf1ea211b29853c5208 100644
--- a/src/boards/Main/Configs/ADAConfig.h
+++ b/src/boards/Main/Configs/ADAConfig.h
@@ -37,8 +37,8 @@ namespace ADA
 
 /* linter off */ using namespace Boardcore::Units::Frequency;
 
-constexpr Hertz SAMPLE_RATE         = 50_hz;
-constexpr float SAMPLE_RATE_SECONDS = 0.02;  // [s]
+constexpr Hertz UPDATE_RATE         = 50_hz;
+constexpr float UPDATE_RATE_SECONDS = 0.02;  // [s]
 
 constexpr int CALIBRATION_SAMPLES_COUNT       = 20;
 constexpr unsigned int CALIBRATION_SLEEP_TIME = 100;  // [ms]
diff --git a/src/boards/Main/Configs/MEAConfig.h b/src/boards/Main/Configs/MEAConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..3fb6cfad5aed0ebccf4b30958a28598fd04c2549
--- /dev/null
+++ b/src/boards/Main/Configs/MEAConfig.h
@@ -0,0 +1,66 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <algorithms/MEA/MEA.h>
+#include <units/Frequency.h>
+
+namespace Main
+{
+
+namespace Config
+{
+
+namespace MEA
+{
+
+/* linter off */ using namespace Boardcore::Units::Frequency;
+
+constexpr Hertz UPDATE_RATE = 50_hz;
+
+constexpr unsigned int SHADOW_MODE_TIMEOUT = 4500;  // [ms]
+
+constexpr float SHUTDOWN_APOGEE_TARGET    = 3200;  // [m]
+constexpr unsigned int SHUTDOWN_N_SAMPLES = 5;
+
+constexpr float SENSOR_NOISE_VARIANCE       = 0.36f;
+constexpr float MODEL_NOISE_VARIANCE        = 0.1f;
+constexpr float DEFAULT_INITIAL_ROCKET_MASS = 35.5920f;
+
+// Pressure threshold after which the kalman is updated
+constexpr float CC_PRESSURE_THRESHOLD = 1.f;
+
+constexpr Boardcore::Aeroutils::AerodynamicCoeff AERO_COEFF = {
+    .n000 = 0.596535425207973f,
+    .n100 = -1.81429600946981f,
+    .n200 = 8.47683559348987f,
+    .n300 = -23.1759370919254f,
+    .n400 = 35.8276525337534f,
+    .n500 = -29.2336913633527f,
+    .n600 = 9.84223649075812f};
+
+}  // namespace MEA
+
+}  // namespace Config
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/Configs/NASConfig.h b/src/boards/Main/Configs/NASConfig.h
index f2802dd05cbccc9bbca5227250c134e384de166b..ace06075fabaea69f86753786481992e5fbc5135 100644
--- a/src/boards/Main/Configs/NASConfig.h
+++ b/src/boards/Main/Configs/NASConfig.h
@@ -37,13 +37,14 @@ namespace NAS
 
 /* linter off */ using namespace Boardcore::Units::Frequency;
 
-constexpr Hertz SAMPLE_RATE = 50_hz;
+constexpr Hertz UPDATE_RATE         = 50_hz;
+constexpr float UPDATE_RATE_SECONDS = 0.02;  // [s]
 
 constexpr int CALIBRATION_SAMPLES_COUNT       = 20;
 constexpr unsigned int CALIBRATION_SLEEP_TIME = 100;  // [ms]
 
 static const Boardcore::NASConfig CONFIG = {
-    .T              = 0.02,  // [s]
+    .T              = UPDATE_RATE_SECONDS,
     .SIGMA_BETA     = 0.0001,
     .SIGMA_W        = 0.3,
     .SIGMA_ACC      = 0.1,
@@ -65,6 +66,9 @@ static const Boardcore::NASConfig CONFIG = {
 // Only use one out of every 50 samples (1 Hz)
 constexpr int MAGNETOMETER_DECIMATE = 50;
 
+// Maximum allowed acceleration to correct with GPS
+constexpr float DISABLE_GPS_ACCELERATION = 34.0f;  // [m/s^2]
+
 // How much confidence (in m/s^2) to apply to the accelerometer to check if it
 // is 1g
 constexpr float ACCELERATION_1G_CONFIDENCE = 0.5;
diff --git a/src/boards/Main/Configs/SchedulerConfig.h b/src/boards/Main/Configs/SchedulerConfig.h
index f380166817feaacee862bbb6af0689f84fb99c02..8d3150dc5343025db42a1f2c5a9f9a005e050d7c 100644
--- a/src/boards/Main/Configs/SchedulerConfig.h
+++ b/src/boards/Main/Configs/SchedulerConfig.h
@@ -35,6 +35,8 @@ namespace Scheduler
 
 // Used for NAS related activities (state machines/scheduler)
 static const miosix::Priority NAS_PRIORITY = miosix::PRIORITY_MAX - 1;
+// Used for NAS related activities (state machines/scheduler)
+static const miosix::Priority MEA_PRIORITY = miosix::PRIORITY_MAX - 1;
 // Used for ADA related activities (state machines/scheduler)
 static const miosix::Priority ADA_PRIORITY = miosix::PRIORITY_MAX - 1;
 // Used for Sensors TaskScheduler
diff --git a/src/boards/Main/Radio/Radio.cpp b/src/boards/Main/Radio/Radio.cpp
index 4a0304c250a368f6035ebf842f50d1638506871a..1c71d6a5c8bf23f3d2f8d66453bc6c56a458d630 100644
--- a/src/boards/Main/Radio/Radio.cpp
+++ b/src/boards/Main/Radio/Radio.cpp
@@ -532,6 +532,7 @@ bool Radio::enqueueSystemTm(uint8_t tmId)
             Actuators* actuators = getModule<Actuators>();
             ADAController* ada   = getModule<ADAController>();
             NASController* nas   = getModule<NASController>();
+            MEAController* mea   = getModule<MEAController>();
 
             auto pressDigi   = sensors->getLPS22DFLastSample();
             auto imu         = sensors->getIMULastSample();
@@ -541,12 +542,13 @@ bool Radio::enqueueSystemTm(uint8_t tmId)
             auto pressDpl    = sensors->getDplBayPressureLastSample();
             auto adaState    = ada->getADAState();
             auto nasState    = nas->getNASState();
+            auto meaState    = mea->getMEAState();
 
             tm.timestamp = TimestampTimer::getTimestamp();
 
             tm.airspeed_pitot = -1.0f;  // TODO
-            tm.mea_mass       = -1.0f;  // TODO
-            tm.mea_apogee     = -1.0f;  // TODO
+            tm.mea_mass       = meaState.estimatedMass;
+            tm.mea_apogee     = meaState.estimatedApogee;
 
             // Sensors
             tm.pressure_digi   = pressDigi.pressure;
@@ -618,6 +620,7 @@ bool Radio::enqueueSystemTm(uint8_t tmId)
             FlightModeManager* fmm  = getModule<FlightModeManager>();
             ADAController* ada      = getModule<ADAController>();
             NASController* nas      = getModule<NASController>();
+            MEAController* mea      = getModule<MEAController>();
             Actuators* actuators    = getModule<Actuators>();
             StatsRecorder* recorder = getModule<StatsRecorder>();
 
@@ -664,7 +667,7 @@ bool Radio::enqueueSystemTm(uint8_t tmId)
             tm.fmm_state = static_cast<uint8_t>(fmm->getState());
             tm.abk_state = 255;  // TODO
             tm.nas_state = static_cast<uint8_t>(nas->getState());
-            tm.mea_state = 255;  // TODO
+            tm.mea_state = static_cast<uint8_t>(mea->getState());
 
             // Actuators
             tm.exp_angle =
diff --git a/src/boards/Main/Radio/Radio.h b/src/boards/Main/Radio/Radio.h
index 46fb7b9ab7c2b9217d2bafeb2ecdc6cd2187cf5a..e107dedc9cd2ff7b01a35dd8fd281c94730a66e3 100644
--- a/src/boards/Main/Radio/Radio.h
+++ b/src/boards/Main/Radio/Radio.h
@@ -31,6 +31,7 @@
 #include <Main/Sensors/Sensors.h>
 #include <Main/StateMachines/ADAController/ADAController.h>
 #include <Main/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Main/StateMachines/MEAController/MEAController.h>
 #include <Main/StateMachines/NASController/NASController.h>
 #include <Main/StatsRecorder/StatsRecorder.h>
 #include <common/Mavlink.h>
@@ -45,10 +46,10 @@ using MavDriver = Boardcore::MavlinkDriver<Boardcore::SX1278Fsk::MTU,
                                            Config::Radio::MAV_OUT_QUEUE_SIZE,
                                            Config::Radio::MAV_MAX_LENGTH>;
 
-class Radio
-    : public Boardcore::InjectableWithDeps<
-          Buses, BoardScheduler, Actuators, PinHandler, CanHandler, Sensors,
-          FlightModeManager, ADAController, NASController, StatsRecorder>
+class Radio : public Boardcore::InjectableWithDeps<
+                  Buses, BoardScheduler, Actuators, PinHandler, CanHandler,
+                  Sensors, FlightModeManager, ADAController, NASController,
+                  MEAController, StatsRecorder>
 {
 public:
     Radio() {}
diff --git a/src/boards/Main/StateMachines/ADAController/ADAController.cpp b/src/boards/Main/StateMachines/ADAController/ADAController.cpp
index f116bd29dc1a2bcb6eeb64780f1b02c606189c1d..1c781cfecadfa8395cf658723422dda29157e790 100644
--- a/src/boards/Main/StateMachines/ADAController/ADAController.cpp
+++ b/src/boards/Main/StateMachines/ADAController/ADAController.cpp
@@ -49,8 +49,8 @@ ADA::KalmanFilter::KalmanConfig computeADAKalmanConfig(float refPressure)
 
     // clang-format off
     F  = ADA::KalmanFilter::MatrixNN({
-        {1.0, Config::ADA::SAMPLE_RATE_SECONDS, 0.5f * Config::ADA::SAMPLE_RATE_SECONDS * Config::ADA::SAMPLE_RATE_SECONDS},
-        {0.0, 1.0,                              Config::ADA::SAMPLE_RATE_SECONDS                                          },
+        {1.0, Config::ADA::UPDATE_RATE_SECONDS, 0.5f * Config::ADA::UPDATE_RATE_SECONDS * Config::ADA::UPDATE_RATE_SECONDS},
+        {0.0, 1.0,                              Config::ADA::UPDATE_RATE_SECONDS                                          },
         {0.0, 0.0,                              1.0                                                                       }});
 
     H = {1.0, 0.0, 0.0};
@@ -91,7 +91,7 @@ bool ADAController::start()
     TaskScheduler& scheduler = getModule<BoardScheduler>()->getAdaScheduler();
 
     uint8_t result =
-        scheduler.addTask([this]() { update(); }, Config::ADA::SAMPLE_RATE);
+        scheduler.addTask([this]() { update(); }, Config::ADA::UPDATE_RATE);
 
     if (result == 0)
     {
diff --git a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
index a3cd2a16fa13916ed77505a2cb1a76eed4792cb6..35212d12a9782b0d0a9f751a4e95e2e87eb9f5cb 100644
--- a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
+++ b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
@@ -41,6 +41,7 @@ FlightModeManager::FlightModeManager()
     EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
     EventBroker::getInstance().subscribe(this, TOPIC_ADA);
     EventBroker::getInstance().subscribe(this, TOPIC_NAS);
+    EventBroker::getInstance().subscribe(this, TOPIC_MEA);
 }
 
 FlightModeManagerState FlightModeManager::getState() { return state; }
@@ -358,6 +359,7 @@ State FlightModeManager::state_test_mode(const Event& event)
 
             EventBroker::getInstance().post(ADA_FORCE_START, TOPIC_ADA);
             EventBroker::getInstance().post(NAS_FORCE_START, TOPIC_NAS);
+            EventBroker::getInstance().post(MEA_FORCE_START, TOPIC_MEA);
             getModule<Sensors>()->resetMagCalibrator();
             getModule<Sensors>()->enableMagCalibrator();
             return HANDLED;
@@ -366,6 +368,7 @@ State FlightModeManager::state_test_mode(const Event& event)
         {
             EventBroker::getInstance().post(ADA_FORCE_STOP, TOPIC_ADA);
             EventBroker::getInstance().post(NAS_FORCE_STOP, TOPIC_NAS);
+            EventBroker::getInstance().post(MEA_FORCE_STOP, TOPIC_MEA);
             getModule<Sensors>()->disableMagCalibrator();
             return HANDLED;
         }
diff --git a/src/boards/Main/StateMachines/MEAController/MEAController.cpp b/src/boards/Main/StateMachines/MEAController/MEAController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40ea0458736f7dbf56b6d1627bd891afaf18e366
--- /dev/null
+++ b/src/boards/Main/StateMachines/MEAController/MEAController.cpp
@@ -0,0 +1,378 @@
+/* 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "MEAController.h"
+
+#include <Main/Configs/MEAConfig.h>
+#include <Main/Configs/SchedulerConfig.h>
+#include <common/Events.h>
+#include <common/Topics.h>
+#include <events/EventBroker.h>
+
+using namespace Main;
+using namespace Boardcore;
+using namespace Common;
+using namespace miosix;
+using namespace Eigen;
+
+MEA::Config computeMEAConfig()
+{
+    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>::Identity() * Config::MEA::MODEL_NOISE_VARIANCE;
+    config.G = Matrix<float, 3, 1>{{4}, {0}, {0}};
+    
+    config.baroH = {1.780138883879285,-1.625379384370081,0.f};
+    config.baroR = Config::MEA::SENSOR_NOISE_VARIANCE;
+
+    config.P           = Matrix<float, 3, 3>::Zero();
+    config.initialMass = Config::MEA::DEFAULT_INITIAL_ROCKET_MASS;
+    
+    // For now, never trigger accel correction
+    config.accelThresh = 999999.0f; 
+    config.speedThresh = 999999.0f; 
+
+    // TODO: Update these
+    config.Kt    = -1.0f;
+    config.alpha = -1.0f;
+    config.c     = -1.0f;
+
+    config.coeffs = Config::MEA::AERO_COEFF;
+    
+    // Rockets diameter
+    // TODO: De-hardcode this
+    float d = 0.15f; 
+    config.crossSection = Constants::PI * (d / 2) * (d / 2);
+    
+    // TODO: Update these
+    config.ae = -1.0f;
+    config.p0 = -1.0f;
+    // clang-format on
+
+    return config;
+}
+
+MEAController::MEAController()
+    : FSM{&MEAController::state_init, miosix::STACK_DEFAULT_FOR_PTHREAD,
+          Config::Scheduler::MEA_PRIORITY},
+      mea{computeMEAConfig()}
+{
+    EventBroker::getInstance().subscribe(this, TOPIC_MEA);
+    EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
+}
+
+bool MEAController::start()
+{
+    TaskScheduler& scheduler = getModule<BoardScheduler>()->getMeaScheduler();
+
+    uint8_t result =
+        scheduler.addTask([this]() { update(); }, Config::MEA::UPDATE_RATE);
+
+    if (result == 0)
+    {
+        LOG_ERR(logger, "Failed to add MEA update task");
+        return false;
+    }
+
+    if (!FSM::start())
+    {
+        LOG_ERR(logger, "Failed to start MEA FSM");
+        return false;
+    }
+
+    return true;
+}
+
+MEAState MEAController::getMEAState()
+{
+    Lock<FastMutex> lock{meaMutex};
+    return mea.getState();
+}
+
+MEAControllerState MEAController::getState() { return state; }
+
+void MEAController::update()
+{
+    MEAControllerState curState = state;
+
+    // Lock MEA for the whole duration of the update
+    Lock<FastMutex> lock{meaMutex};
+
+    if (curState == MEAControllerState::ARMED ||
+        curState == MEAControllerState::SHADOW_MODE ||
+        curState == MEAControllerState::ACTIVE ||
+        curState == MEAControllerState::ACTIVE_UNPOWERED)
+    {
+        // Perform updates only during this phases
+
+        PressureData baro = getModule<Sensors>()->getCanCCPressLastSample();
+        IMUData imu       = getModule<Sensors>()->getIMULastSample();
+        NASState nas      = getModule<NASController>()->getNASState();
+        ReferenceValues reference =
+            getModule<NASController>()->getReferenceValues();
+
+        // TODO: Is this even correct?
+        float aperture =
+            getModule<Actuators>()->isCanServoOpen(ServosList::MAIN_VALVE)
+                ? 1.0f
+                : 0.0f;
+
+        if (baro.pressure > Config::MEA::CC_PRESSURE_THRESHOLD)
+        {
+            MEA::Step step{aperture};
+
+            if (baro.pressureTimestamp > lastBaroTimestamp)
+            {
+                step.withCCPressure(baro);
+            }
+
+            if (imu.accelerationTimestamp > lastAccTimestamp)
+            {
+                step.withAcceleration(imu);
+            }
+
+            if (nas.timestamp > lastNasTimestamp)
+            {
+                step.withSpeedAndAlt(-nas.vd, reference.refAltitude - nas.d);
+            }
+
+            mea.update(step);
+
+            MEAState state = mea.getState();
+
+            // Run detections
+            if (curState == MEAControllerState::SHADOW_MODE ||
+                curState == MEAControllerState::ACTIVE ||
+                curState == MEAControllerState::ACTIVE_UNPOWERED)
+            {
+                if (state.estimatedApogee > Config::MEA::SHUTDOWN_APOGEE_TARGET)
+                {
+                    detectedShutdowns++;
+                }
+                else
+                {
+                    detectedShutdowns = 0;
+                }
+
+                if (curState != MEAControllerState::SHADOW_MODE &&
+                    curState == MEAControllerState::ACTIVE_UNPOWERED)
+                {
+                    // DO NOT THROW EVENTS IN SHADOW_MODE!
+                    if (detectedShutdowns > Config::MEA::SHUTDOWN_N_SAMPLES)
+                    {
+                        EventBroker::getInstance().post(MEA_SHUTDOWN_DETECTED,
+                                                        TOPIC_MEA);
+                    }
+                }
+            }
+
+            lastBaroTimestamp = baro.pressureTimestamp;
+            lastAccTimestamp  = imu.accelerationTimestamp;
+            lastNasTimestamp  = nas.timestamp;
+
+            sdLogger.log(state);
+        }
+    }
+}
+
+void MEAController::state_init(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::INIT);
+
+            // Immediately transition to ready
+            transition(&MEAController::state_ready);
+            break;
+        }
+    }
+}
+
+void MEAController::state_ready(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::READY);
+            break;
+        }
+
+        case MEA_FORCE_START:
+        case FLIGHT_ARMED:
+        {
+            transition(&MEAController::state_armed);
+            break;
+        }
+    }
+}
+
+void MEAController::state_armed(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::ARMED);
+            break;
+        }
+
+        case MEA_FORCE_STOP:
+        case FLIGHT_DISARMED:
+        {
+            transition(&MEAController::state_ready);
+            break;
+        }
+
+        case FLIGHT_LIFTOFF:
+        {
+            transition(&MEAController::state_shadow_mode);
+            break;
+        }
+    }
+}
+
+void MEAController::state_shadow_mode(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::SHADOW_MODE);
+
+            shadowModeTimeoutEvent = EventBroker::getInstance().postDelayed(
+                MEA_SHADOW_MODE_TIMEOUT, TOPIC_MEA,
+                Config::MEA::SHADOW_MODE_TIMEOUT);
+            break;
+        }
+
+        case EV_EXIT:
+        {
+            EventBroker::getInstance().removeDelayed(shadowModeTimeoutEvent);
+            break;
+        }
+
+        case MEA_FORCE_STOP:
+        {
+            transition(&MEAController::state_ready);
+            break;
+        }
+
+        case MEA_SHADOW_MODE_TIMEOUT:
+        {
+            transition(&MEAController::state_active);
+            break;
+        }
+
+        case FLIGHT_APOGEE_DETECTED:
+        {
+            transition(&MEAController::state_active_unpowered);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&MEAController::state_end);
+            break;
+        }
+    }
+}
+
+void MEAController::state_active(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::ACTIVE);
+            break;
+        }
+
+        case MEA_FORCE_STOP:
+        {
+            transition(&MEAController::state_ready);
+            break;
+        }
+
+        case FLIGHT_APOGEE_DETECTED:
+        {
+            transition(&MEAController::state_active_unpowered);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&MEAController::state_end);
+            break;
+        }
+    }
+}
+
+void MEAController::state_active_unpowered(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::ACTIVE_UNPOWERED);
+            break;
+        }
+
+        case MEA_FORCE_STOP:
+        {
+            transition(&MEAController::state_ready);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&MEAController::state_end);
+            break;
+        }
+    }
+}
+
+void MEAController::state_end(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(MEAControllerState::END);
+            break;
+        }
+    }
+}
+
+void MEAController::updateAndLogStatus(MEAControllerState state)
+{
+    this->state              = state;
+    MEAControllerStatus data = {TimestampTimer::getTimestamp(), state};
+    sdLogger.log(data);
+}
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/MEAController/MEAController.h b/src/boards/Main/StateMachines/MEAController/MEAController.h
new file mode 100644
index 0000000000000000000000000000000000000000..54500cd2147d49facb08ed597e790ab23a5674f4
--- /dev/null
+++ b/src/boards/Main/StateMachines/MEAController/MEAController.h
@@ -0,0 +1,83 @@
+/* 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Main/Actuators/Actuators.h>
+#include <Main/BoardScheduler.h>
+#include <Main/Sensors/Sensors.h>
+#include <Main/StateMachines/MEAController/MEAController.h>
+#include <Main/StateMachines/MEAController/MEAControllerData.h>
+#include <Main/StateMachines/NASController/NASController.h>
+#include <algorithms/MEA/MEA.h>
+#include <diagnostic/PrintLogger.h>
+#include <events/FSM.h>
+#include <utils/DependencyManager/DependencyManager.h>
+
+namespace Main
+{
+
+class MEAController
+    : public Boardcore::FSM<MEAController>,
+      public Boardcore::InjectableWithDeps<BoardScheduler, Actuators, Sensors,
+                                           NASController>
+{
+public:
+    MEAController();
+
+    [[nodiscard]] bool start() override;
+
+    Boardcore::MEAState getMEAState();
+
+    MEAControllerState getState();
+
+private:
+    void update();
+
+    // FSM states
+    void state_init(const Boardcore::Event& event);
+    void state_ready(const Boardcore::Event& event);
+    void state_armed(const Boardcore::Event& event);
+    void state_shadow_mode(const Boardcore::Event& event);
+    void state_active(const Boardcore::Event& event);
+    void state_active_unpowered(const Boardcore::Event& event);
+    void state_end(const Boardcore::Event& event);
+
+    void updateAndLogStatus(MEAControllerState state);
+
+    std::atomic<MEAControllerState> state{MEAControllerState::INIT};
+
+    Boardcore::Logger& sdLogger   = Boardcore::Logger::getInstance();
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("mea");
+
+    uint16_t shadowModeTimeoutEvent = 0;
+
+    miosix::FastMutex meaMutex;
+    Boardcore::MEA mea;
+
+    uint64_t lastAccTimestamp      = 0;
+    uint64_t lastBaroTimestamp     = 0;
+    uint64_t lastNasTimestamp      = 0;
+    unsigned int detectedShutdowns = 0;
+};
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/MEAController/MEAControllerData.h b/src/boards/Main/StateMachines/MEAController/MEAControllerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..b60e8e1ffcd9b6fa0d1f9e3b53b72026e5e494ce
--- /dev/null
+++ b/src/boards/Main/StateMachines/MEAController/MEAControllerData.h
@@ -0,0 +1,56 @@
+/* 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
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <ostream>
+#include <string>
+
+namespace Main
+{
+
+enum class MEAControllerState : uint8_t
+{
+    INIT = 0,
+    READY,
+    ARMED,
+    SHADOW_MODE,
+    ACTIVE,
+    ACTIVE_UNPOWERED,
+    END
+};
+
+struct MEAControllerStatus
+{
+    uint64_t timestamp       = 0;
+    MEAControllerState state = MEAControllerState::INIT;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << static_cast<int>(state) << "\n";
+    }
+};
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/NASController/NASController.cpp b/src/boards/Main/StateMachines/NASController/NASController.cpp
index 6361f0548bce9c9b8c23761182813d800bb88f39..a50d4b7ba84e16c2bb0f2007b80c0870b229564b 100644
--- a/src/boards/Main/StateMachines/NASController/NASController.cpp
+++ b/src/boards/Main/StateMachines/NASController/NASController.cpp
@@ -66,7 +66,7 @@ bool NASController::start()
     TaskScheduler& scheduler = getModule<BoardScheduler>()->getNasScheduler();
 
     size_t result =
-        scheduler.addTask([this]() { update(); }, Config::NAS::SAMPLE_RATE);
+        scheduler.addTask([this]() { update(); }, Config::NAS::UPDATE_RATE);
 
     if (result == 0)
     {
@@ -111,6 +111,10 @@ void NASController::update()
         UBXGPSData gps    = sensors->getUBXGPSLastSample();
         PressureData baro = sensors->getAtmosPressureLastSample();
 
+        // Calculate acceleration
+        Vector3f acc    = static_cast<AccelerometerData>(imu);
+        float accLength = acc.norm();
+
         // Perform initial NAS prediction
         // TODO: What about stale data?
         nas.predictGyro(imu);
@@ -128,7 +132,8 @@ void NASController::update()
             magDecimateCount++;
         }
 
-        if (lastGpsTimestamp < gps.gpsTimestamp && gps.fix == 3)
+        if (lastGpsTimestamp < gps.gpsTimestamp && gps.fix == 3 &&
+            accLength < Config::NAS::DISABLE_GPS_ACCELERATION)
         {
             nas.correctGPS(gps);
         }
@@ -147,9 +152,6 @@ void NASController::update()
         }
 
         // Check if the accelerometer is measuring 1g
-        Vector3f acc    = static_cast<AccelerometerData>(imu);
-        float accLength = acc.norm();
-
         if (accLength <
                 (Constants::g + Config::NAS::ACCELERATION_1G_CONFIDENCE / 2) &&
             accLength >
diff --git a/src/boards/Main/StateMachines/NASController/NASController.h b/src/boards/Main/StateMachines/NASController/NASController.h
index 6b2f32fe365a52a4744347171679e938788bdfea..042c8f927a8e30035b9a5953646eed295d265585 100644
--- a/src/boards/Main/StateMachines/NASController/NASController.h
+++ b/src/boards/Main/StateMachines/NASController/NASController.h
@@ -65,7 +65,7 @@ private:
     std::atomic<NASControllerState> state{NASControllerState::INIT};
 
     Boardcore::Logger& sdLogger   = Boardcore::Logger::getInstance();
-    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("ada");
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("nas");
 
     miosix::FastMutex nasMutex;
     Boardcore::NAS nas;
diff --git a/src/entrypoints/Main/main-entry.cpp b/src/entrypoints/Main/main-entry.cpp
index afbe0e75c52fb4eee1b23854c1dde0353c6b17cb..db4900fe5aba361704eab00801887567a1a5e6f0 100644
--- a/src/entrypoints/Main/main-entry.cpp
+++ b/src/entrypoints/Main/main-entry.cpp
@@ -29,6 +29,7 @@
 #include <Main/Sensors/Sensors.h>
 #include <Main/StateMachines/ADAController/ADAController.h>
 #include <Main/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Main/StateMachines/MEAController/MEAController.h>
 #include <Main/StateMachines/NASController/NASController.h>
 #include <Main/StatsRecorder/StatsRecorder.h>
 #include <actuators/Servo/Servo.h>
@@ -61,6 +62,7 @@ int main()
     FlightModeManager *fmm  = new FlightModeManager();
     ADAController *ada      = new ADAController();
     NASController *nas      = new NASController();
+    MEAController *mea      = new MEAController();
     StatsRecorder *recorder = new StatsRecorder();
 
     // Insert modules
@@ -74,6 +76,7 @@ int main()
         manager.insert<FlightModeManager>(fmm) &&
         manager.insert<ADAController>(ada) &&
         manager.insert<NASController>(nas) &&
+        manager.insert<MEAController>(mea) &&
         manager.insert<StatsRecorder>(recorder) && manager.inject();
 
     manager.graphviz(std::cout);
@@ -156,6 +159,12 @@ int main()
         std::cout << "Error failed to start NASController" << std::endl;
     }
 
+    if (!mea->start())
+    {
+        initResult = false;
+        std::cout << "Error failed to start MEAController" << std::endl;
+    }
+
     if (!fmm->start())
     {
         initResult = false;