From 5777f968b7770edf668c1f0894a143f00c8ff265 Mon Sep 17 00:00:00 2001
From: Davide Mor <davide.mor@skywarder.eu>
Date: Tue, 23 Jul 2024 19:29:47 +0200
Subject: [PATCH] [Main] Added ADAController

---
 cmake/dependencies.cmake                      |   2 +-
 src/boards/Main/Actuators/Actuators.cpp       |  22 +-
 src/boards/Main/Actuators/Actuators.h         |   9 +-
 src/boards/Main/CanHandler/CanHandler.cpp     |  40 +-
 src/boards/Main/CanHandler/CanHandler.h       |   5 +
 src/boards/Main/Configs/ADAConfig.h           |  58 +++
 src/boards/Main/Configs/ActuatorsConfig.h     |  12 +-
 src/boards/Main/Configs/CanHandlerConfig.h    |   2 +-
 src/boards/Main/Configs/RadioConfig.h         |   4 +-
 src/boards/Main/Configs/SensorsConfig.h       |  16 +-
 src/boards/Main/Radio/Radio.cpp               |  16 +-
 src/boards/Main/Sensors/Sensors.cpp           |  44 +-
 src/boards/Main/Sensors/Sensors.h             |  25 +-
 .../ADAController/ADAController.cpp           | 416 ++++++++++++++++++
 .../ADAController/ADAController.h             |  78 ++++
 .../ADAController/ADAControllerData.h         |  77 ++++
 .../FlightModeManager/FlightModeManager.cpp   | 146 +++---
 .../FlightModeManager/FlightModeManager.h     |   8 +-
 .../FlightModeManager/FlightModeManagerData.h |  38 +-
 src/entrypoints/Main/main-entry.cpp           |  25 +-
 20 files changed, 840 insertions(+), 203 deletions(-)
 create mode 100644 src/boards/Main/Configs/ADAConfig.h
 create mode 100644 src/boards/Main/StateMachines/ADAController/ADAController.cpp
 create mode 100644 src/boards/Main/StateMachines/ADAController/ADAController.h
 create mode 100644 src/boards/Main/StateMachines/ADAController/ADAControllerData.h

diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index 643a21e53..79c336a49 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -32,7 +32,7 @@ set(MAIN_COMPUTER
     src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
     src/boards/Main/Actuators/Actuators.cpp
     # src/boards/Main/Sensors/RotatedIMU/RotatedIMU.cpp
-    # src/boards/Main/StateMachines/ADAController/ADAController.cpp
+    src/boards/Main/StateMachines/ADAController/ADAController.cpp
     src/boards/Main/PinHandler/PinHandler.cpp
     # src/boards/Main/AltitudeTrigger/AltitudeTrigger.cpp
     # src/boards/Main/StateMachines/ABKController/ABKController.cpp
diff --git a/src/boards/Main/Actuators/Actuators.cpp b/src/boards/Main/Actuators/Actuators.cpp
index 8da49bd11..edf8182e4 100644
--- a/src/boards/Main/Actuators/Actuators.cpp
+++ b/src/boards/Main/Actuators/Actuators.cpp
@@ -22,6 +22,7 @@
 
 #include "Actuators.h"
 
+#include <Main/CanHandler/CanHandler.h>
 #include <Main/Configs/ActuatorsConfig.h>
 #include <interfaces-impl/hwmapping.h>
 
@@ -64,7 +65,7 @@ bool Actuators::start()
     buzzerOff();
 
     uint8_t result = scheduler.addTask([this]() { updateBuzzer(); },
-                                       Config::Actuators::BUZZER_UPDATE_PERIOD,
+                                       Config::Actuators::BUZZER_UPDATE_RATE,
                                        TaskScheduler::Policy::RECOVER);
 
     if (result == 0)
@@ -74,7 +75,7 @@ bool Actuators::start()
     }
 
     result = scheduler.addTask([this]() { updateStatus(); },
-                               Config::Actuators::STATUS_UPDATE_PERIOD,
+                               Config::Actuators::STATUS_UPDATE_RATE,
                                TaskScheduler::Policy::RECOVER);
 
     if (result == 0)
@@ -119,6 +120,11 @@ bool Actuators::wiggleServo(ServosList servo)
     }
 }
 
+void Actuators::wiggleCanServo(ServosList servo)
+{
+    getModule<CanHandler>()->sendServoOpenCommand(servo, 1.0, 1000);
+}
+
 float Actuators::getServoPosition(ServosList servo)
 {
     Lock<FastMutex> lock{servosMutex};
@@ -147,24 +153,24 @@ void Actuators::setStatusOff() { statusOverflow = 0; }
 
 void Actuators::setStatusOk()
 {
-    statusOverflow = Config::Actuators::STATUS_OK_PERIOD;
+    statusOverflow = Config::Actuators::STATUS_OK_RATE;
 }
 
 void Actuators::setStatusErr()
 {
-    statusOverflow = Config::Actuators::STATUS_ERR_PERIOD;
+    statusOverflow = Config::Actuators::STATUS_ERR_RATE;
 }
 
 void Actuators::setBuzzerOff() { buzzerOverflow = 0; }
 
 void Actuators::setBuzzerArmed()
 {
-    buzzerOverflow = Config::Actuators::BUZZER_ARM_PERIOD;
+    buzzerOverflow = Config::Actuators::BUZZER_ARM_RATE;
 }
 
 void Actuators::setBuzzerLand()
 {
-    buzzerOverflow = Config::Actuators::BUZZER_LAND_PERIOD;
+    buzzerOverflow = Config::Actuators::BUZZER_LAND_RATE;
 }
 
 void Actuators::setCanServoOpen(ServosList servo, bool open)
@@ -184,10 +190,14 @@ void Actuators::camOn() { gpios::camEnable::high(); }
 
 void Actuators::camOff() { gpios::camEnable::low(); }
 
+bool Actuators::getCamState() { return gpios::camEnable::value() != 0; }
+
 void Actuators::cutterOn() { gpios::mainDeploy::high(); }
 
 void Actuators::cutterOff() { gpios::mainDeploy::low(); }
 
+bool Actuators::getCutterState() { return gpios::mainDeploy::value() != 0; }
+
 void Actuators::statusOn() { gpios::statusLed::high(); }
 
 void Actuators::statusOff() { gpios::statusLed::low(); }
diff --git a/src/boards/Main/Actuators/Actuators.h b/src/boards/Main/Actuators/Actuators.h
index 041f443d2..7b49d8b72 100644
--- a/src/boards/Main/Actuators/Actuators.h
+++ b/src/boards/Main/Actuators/Actuators.h
@@ -33,7 +33,10 @@
 namespace Main
 {
 
-class Actuators : public Boardcore::InjectableWithDeps<BoardScheduler>
+class CanHandler;
+
+class Actuators
+    : public Boardcore::InjectableWithDeps<BoardScheduler, CanHandler>
 {
 public:
     Actuators();
@@ -48,13 +51,16 @@ public:
     bool wiggleServo(ServosList servo);
     float getServoPosition(ServosList servo);
 
+    void wiggleCanServo(ServosList servo);
     bool isCanServoOpen(ServosList servo);
 
     void camOn();
     void camOff();
+    bool getCamState();
 
     void cutterOn();
     void cutterOff();
+    bool getCutterState();
 
     void setBuzzerOff();
     void setBuzzerArmed();
@@ -64,6 +70,7 @@ public:
     void setStatusErr();
     void setStatusOk();
 
+    // Methods for CanHandler
     void setCanServoOpen(ServosList servo, bool open);
 
 private:
diff --git a/src/boards/Main/CanHandler/CanHandler.cpp b/src/boards/Main/CanHandler/CanHandler.cpp
index 6b42dc47b..1dd201047 100644
--- a/src/boards/Main/CanHandler/CanHandler.cpp
+++ b/src/boards/Main/CanHandler/CanHandler.cpp
@@ -73,12 +73,12 @@ bool CanHandler::start()
                     TimestampTimer::getTimestamp(),
                     static_cast<int16_t>(stats.logNumber),
                     static_cast<uint8_t>(state),
-                    state == FlightModeManagerState::FMM_STATE_ARMED,
+                    state == FlightModeManagerState::ARMED,
                     false,  // TODO: HIL
                     stats.lastWriteError == 0,
                 });
         },
-        Config::CanHandler::STATUS_PERIOD);
+        Config::CanHandler::STATUS_RATE);
 
     if (result == 0)
     {
@@ -105,6 +105,25 @@ void CanHandler::sendEvent(Common::CanConfig::EventId event)
                           static_cast<uint8_t>(event));
 }
 
+void CanHandler::sendServoOpenCommand(ServosList servo, float maxAperture,
+                                      uint16_t openingTime)
+{
+
+    protocol.enqueueData(
+        static_cast<uint8_t>(CanConfig::Priority::CRITICAL),
+        static_cast<uint8_t>(CanConfig::PrimaryType::COMMAND),
+        static_cast<uint8_t>(CanConfig::Board::MAIN),
+        static_cast<uint8_t>(CanConfig::Board::BROADCAST),
+        static_cast<uint8_t>(servo),
+        ServoCommand{TimestampTimer::getTimestamp(), maxAperture, openingTime});
+}
+
+void CanHandler::sendServoCloseCommand(ServosList servo)
+{
+    // Closing a servo means opening it for 0s
+    sendServoOpenCommand(servo, 0.0f, 0);
+}
+
 CanHandler::CanStatus CanHandler::getCanStatus()
 {
     Lock<FastMutex> lock{statusMutex};
@@ -173,19 +192,20 @@ void CanHandler::handleSensor(const Boardcore::Canbus::CanMessage &msg)
             break;
         }
 
-        case CanConfig::SensorId::BOTTOM_TANK_PRESSURE:
-        {
-            CanPressureData data = pressureDataFromCanMessage(msg);
-            sdLogger.log(data);
-            sensors->setCanBottomTankPress(data);
-            break;
-        }
+            /* No longer exists
+            case CanConfig::SensorId::BOTTOM_TANK_PRESSURE:
+            {
+                CanPressureData data = pressureDataFromCanMessage(msg);
+                sdLogger.log(data);
+                sensors->setCanBottomTankPress(data);
+                break;
+            }*/
 
         case CanConfig::SensorId::TOP_TANK_PRESSURE:
         {
             CanPressureData data = pressureDataFromCanMessage(msg);
             sdLogger.log(data);
-            sensors->setCanTopTankPress(data);
+            sensors->setCanTopTankPress1(data);
             break;
         }
 
diff --git a/src/boards/Main/CanHandler/CanHandler.h b/src/boards/Main/CanHandler/CanHandler.h
index 23a40c84a..aa0f1eaf8 100644
--- a/src/boards/Main/CanHandler/CanHandler.h
+++ b/src/boards/Main/CanHandler/CanHandler.h
@@ -26,6 +26,7 @@
 #include <Main/Configs/CanHandlerConfig.h>
 #include <Main/Sensors/Sensors.h>
 #include <common/CanConfig.h>
+#include <common/Mavlink.h>
 #include <drivers/canbus/CanProtocol/CanProtocol.h>
 #include <utils/DependencyManager/DependencyManager.h>
 
@@ -95,6 +96,10 @@ public:
 
     void sendEvent(Common::CanConfig::EventId event);
 
+    void sendServoOpenCommand(ServosList servo, float maxAperture,
+                              uint16_t openingTime);
+    void sendServoCloseCommand(ServosList servo);
+
     CanStatus getCanStatus();
 
 private:
diff --git a/src/boards/Main/Configs/ADAConfig.h b/src/boards/Main/Configs/ADAConfig.h
new file mode 100644
index 000000000..a9437167c
--- /dev/null
+++ b/src/boards/Main/Configs/ADAConfig.h
@@ -0,0 +1,58 @@
+/* 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 <units/Frequency.h>
+
+#include <cstdint>
+
+namespace Main
+{
+
+namespace Config
+{
+
+namespace ADA
+{
+
+/* linter off */ using namespace Boardcore::Units::Frequency;
+
+constexpr Hertz SAMPLE_RATE         = 50_hz;
+constexpr float SAMPLE_RATE_SECONDS = 0.02;  // [s]
+
+constexpr int CALIBRATION_SAMPLES_COUNT       = 20;
+constexpr unsigned int CALIBRATION_SLEEP_TIME = 100;  // [ms]
+
+constexpr unsigned int SHADOW_MODE_TIMEOUT = 18000;  // [ms]
+
+constexpr float APOGEE_VERTICAL_SPEED_TARGET = 2.5;  // [m/s]
+constexpr unsigned int APOGEE_N_SAMPLES      = 5;
+
+constexpr float DEPLOYMENT_ALTITUDE_TARGET  = 350;  // [m]
+constexpr unsigned int DEPLOYMENT_N_SAMPLES = 5;
+
+}  // namespace ADA
+
+}  // namespace Config
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/Configs/ActuatorsConfig.h b/src/boards/Main/Configs/ActuatorsConfig.h
index 662e6cab3..9ee790b39 100644
--- a/src/boards/Main/Configs/ActuatorsConfig.h
+++ b/src/boards/Main/Configs/ActuatorsConfig.h
@@ -44,14 +44,14 @@ constexpr unsigned int EXP_MAX_PULSE = 2000;
 constexpr uint16_t BUZZER_FREQUENCY = 1000;
 constexpr float BUZZER_DUTY_CYCLE   = 0.5;
 
-constexpr Hertz BUZZER_UPDATE_PERIOD  = 20_hz;
-constexpr uint32_t BUZZER_ARM_PERIOD  = 10;  // 10 * 50ms = 500ms
-constexpr uint32_t BUZZER_LAND_PERIOD = 20;  // 20 * 50ms = 1000ms
+constexpr Hertz BUZZER_UPDATE_RATE  = 20_hz;
+constexpr uint32_t BUZZER_ARM_RATE  = 10;  // 10 * 50ms = 500ms
+constexpr uint32_t BUZZER_LAND_RATE = 20;  // 20 * 50ms = 1000ms
 
 // Status configs
-constexpr Hertz STATUS_UPDATE_PERIOD = 20_hz;
-constexpr uint32_t STATUS_OK_PERIOD  = 20;  // 20 * 50ms = 1000ms
-constexpr uint32_t STATUS_ERR_PERIOD = 2;   // 2 * 50ms = 10ms
+constexpr Hertz STATUS_UPDATE_RATE = 20_hz;
+constexpr uint32_t STATUS_OK_RATE  = 20;  // 20 * 50ms = 1000ms
+constexpr uint32_t STATUS_ERR_RATE = 2;   // 2 * 50ms = 10ms
 
 }  // namespace Actuators
 }  // namespace Config
diff --git a/src/boards/Main/Configs/CanHandlerConfig.h b/src/boards/Main/Configs/CanHandlerConfig.h
index 8ff3b0c1f..a8a5ba6b7 100644
--- a/src/boards/Main/Configs/CanHandlerConfig.h
+++ b/src/boards/Main/Configs/CanHandlerConfig.h
@@ -35,7 +35,7 @@ namespace CanHandler
 
 /* linter off */ using namespace std::chrono_literals;
 
-static constexpr std::chrono::nanoseconds STATUS_PERIOD  = 1000ms;
+static constexpr std::chrono::nanoseconds STATUS_RATE    = 1000ms;
 static constexpr std::chrono::nanoseconds STATUS_TIMEOUT = 1500ms;
 
 }  // namespace CanHandler
diff --git a/src/boards/Main/Configs/RadioConfig.h b/src/boards/Main/Configs/RadioConfig.h
index 976347546..8d847d440 100644
--- a/src/boards/Main/Configs/RadioConfig.h
+++ b/src/boards/Main/Configs/RadioConfig.h
@@ -46,8 +46,8 @@ constexpr uint8_t MAV_COMPONENT_ID = 96;
 
 constexpr unsigned int CIRCULAR_BUFFER_SIZE = 8;
 
-constexpr Hertz LOW_RATE_TELEMETRY_PERIOD  = 2_hz;
-constexpr Hertz HIGH_RATE_TELEMETRY_PERIOD = 4_hz;
+constexpr Hertz LOW_RATE_TELEMETRY  = 2_hz;
+constexpr Hertz HIGH_RATE_TELEMETRY = 4_hz;
 
 }  // namespace Radio
 
diff --git a/src/boards/Main/Configs/SensorsConfig.h b/src/boards/Main/Configs/SensorsConfig.h
index 9116bf41a..15e8fa985 100644
--- a/src/boards/Main/Configs/SensorsConfig.h
+++ b/src/boards/Main/Configs/SensorsConfig.h
@@ -46,7 +46,7 @@ namespace LPS22DF
 constexpr Boardcore::LPS22DF::AVG AVG = Boardcore::LPS22DF::AVG_4;
 constexpr Boardcore::LPS22DF::ODR ODR = Boardcore::LPS22DF::ODR_100;
 
-constexpr Hertz PERIOD = 50_hz;
+constexpr Hertz RATE   = 50_hz;
 constexpr bool ENABLED = true;
 }  // namespace LPS22DF
 
@@ -58,7 +58,7 @@ constexpr Boardcore::LPS28DFW::FullScaleRange FS = Boardcore::LPS28DFW::FS_1260;
 constexpr Boardcore::LPS28DFW::AVG AVG           = Boardcore::LPS28DFW::AVG_4;
 constexpr Boardcore::LPS28DFW::ODR ODR           = Boardcore::LPS28DFW::ODR_100;
 
-constexpr Hertz PERIOD = 50_hz;
+constexpr Hertz RATE   = 50_hz;
 constexpr bool ENABLED = true;
 }  // namespace LPS28DFW
 
@@ -71,7 +71,7 @@ constexpr Boardcore::H3LIS331DLDefs::OutputDataRate ODR =
 constexpr Boardcore::H3LIS331DLDefs::FullScaleRange FS =
     Boardcore::H3LIS331DLDefs::FullScaleRange::FS_100;
 
-constexpr Hertz PERIOD = 100_hz;
+constexpr Hertz RATE   = 100_hz;
 constexpr bool ENABLED = true;
 }  // namespace H3LIS331DL
 
@@ -82,7 +82,7 @@ namespace LIS2MDL
 constexpr Boardcore::LIS2MDL::ODR ODR = Boardcore::LIS2MDL::ODR_100_HZ;
 constexpr unsigned int TEMP_DIVIDER   = 5;
 
-constexpr Hertz PERIOD = 100_hz;
+constexpr Hertz RATE   = 100_hz;
 constexpr bool ENABLED = true;
 }  // namespace LIS2MDL
 
@@ -90,7 +90,7 @@ namespace UBXGPS
 {
 /* linter off */ using namespace Boardcore::Units::Frequency;
 
-constexpr Hertz PERIOD = 5_hz;
+constexpr Hertz RATE   = 5_hz;
 constexpr bool ENABLED = true;
 }  // namespace UBXGPS
 
@@ -112,7 +112,7 @@ constexpr Boardcore::LSM6DSRXConfig::GYR_ODR GYR_ODR =
 constexpr Boardcore::LSM6DSRXConfig::OPERATING_MODE GYR_OP_MODE =
     Boardcore::LSM6DSRXConfig::OPERATING_MODE::HIGH_PERFORMANCE;
 
-constexpr Hertz PERIOD = 50_hz;
+constexpr Hertz RATE   = 50_hz;
 constexpr bool ENABLED = true;
 }  // namespace LSM6DSRX
 
@@ -140,7 +140,7 @@ constexpr float STATIC_PRESSURE_1_SCALE = CHANNEL_0_SCALE;
 constexpr float STATIC_PRESSURE_2_SCALE = CHANNEL_1_SCALE;
 constexpr float DPL_BAY_PRESSURE_SCALE  = CHANNEL_2_SCALE;
 
-constexpr Hertz PERIOD = 100_hz;
+constexpr Hertz RATE   = 100_hz;
 constexpr bool ENABLED = true;
 }  // namespace ADS131M08
 
@@ -158,7 +158,7 @@ constexpr Boardcore::InternalADC::Channel CUTTER_SENSE_CH =
 constexpr float VBAT_SCALE     = 7500.0f / 2400.0f;
 constexpr float CAM_VBAT_SCALE = 7500.0f / 2400.0f;
 
-constexpr Hertz PERIOD = 10_hz;
+constexpr Hertz RATE   = 10_hz;
 constexpr bool ENABLED = true;
 }  // namespace InternalADC
 
diff --git a/src/boards/Main/Radio/Radio.cpp b/src/boards/Main/Radio/Radio.cpp
index 80fc39c2d..fc70db5b2 100644
--- a/src/boards/Main/Radio/Radio.cpp
+++ b/src/boards/Main/Radio/Radio.cpp
@@ -105,7 +105,7 @@ bool Radio::start()
             enqueueSystemTm(SystemTMList::MAV_FLIGHT_ID);
             flushPackets();
         },
-        Config::Radio::HIGH_RATE_TELEMETRY_PERIOD);
+        Config::Radio::HIGH_RATE_TELEMETRY);
 
     if (result == 0)
     {
@@ -116,7 +116,7 @@ bool Radio::start()
     // Low rate periodic telemetry
     result = scheduler.addTask([this]()
                                { enqueueSystemTm(SystemTMList::MAV_STATS_ID); },
-                               Config::Radio::LOW_RATE_TELEMETRY_PERIOD);
+                               Config::Radio::LOW_RATE_TELEMETRY);
 
     if (result == 0)
     {
@@ -227,7 +227,7 @@ void Radio::handleMessage(const mavlink_message_t& msg)
                 mavlink_msg_wiggle_servo_tc_get_servo_id(&msg));
 
             if (getModule<FlightModeManager>()->getState() ==
-                FlightModeManagerState::FMM_STATE_TEST_MODE)
+                FlightModeManagerState::TEST_MODE)
             {
                 // If the state is test mode, the wiggle is done
                 getModule<Actuators>()->wiggleServo(servoId);
@@ -556,11 +556,11 @@ bool Radio::enqueueSystemTm(uint8_t tmId)
             tm.timestamp = TimestampTimer::getTimestamp();
 
             // Sensors (either CAN or local)
-            tm.top_tank_pressure    = sensors->getTopTankPress().pressure;
-            tm.bottom_tank_pressure = -1.0f;  // TODO
-            tm.combustion_chamber_pressure = sensors->getCCPress().pressure;
-            tm.tank_temperature            = -1.0f;  // TODO
-            tm.battery_voltage = sensors->getMotorBatteryVoltage().voltage;
+            tm.top_tank_pressure    = sensors->getCanTopTankPress1().pressure;
+            tm.bottom_tank_pressure = -1.0f;  // Doesn't exist
+            tm.combustion_chamber_pressure = sensors->getCanCCPress().pressure;
+            tm.tank_temperature = sensors->getCanTankTemp().temperature;
+            tm.battery_voltage  = sensors->getCanMotorBatteryVoltage().voltage;
 
             // Valve states
             tm.main_valve_state =
diff --git a/src/boards/Main/Sensors/Sensors.cpp b/src/boards/Main/Sensors/Sensors.cpp
index c247058e1..3c02fff77 100644
--- a/src/boards/Main/Sensors/Sensors.cpp
+++ b/src/boards/Main/Sensors/Sensors.cpp
@@ -166,46 +166,46 @@ PressureData Sensors::getDplBayPressure()
     return dplBayPressure ? dplBayPressure->getLastSample() : PressureData{};
 }
 
-PressureData Sensors::getTopTankPress()
+PressureData Sensors::getCanTopTankPress1()
 {
     Lock<FastMutex> lock{canMutex};
-    return canTopTankPressure;
+    return canTopTankPressure1;
 }
 
-PressureData Sensors::getBottomTankPress()
+PressureData Sensors::getCanTopTankPress2()
 {
     Lock<FastMutex> lock{canMutex};
-    return canBottomTankPressure;
+    return canTopTankPressure2;
 }
 
-PressureData Sensors::getCCPress()
+PressureData Sensors::getCanCCPress()
 {
     Lock<FastMutex> lock{canMutex};
     return canCCPressure;
 }
 
-TemperatureData Sensors::getTankTemp()
+TemperatureData Sensors::getCanTankTemp()
 {
     Lock<FastMutex> lock{canMutex};
     return canTankTemperature;
 }
 
-VoltageData Sensors::getMotorBatteryVoltage()
+VoltageData Sensors::getCanMotorBatteryVoltage()
 {
     Lock<FastMutex> lock{canMutex};
     return canMotorBatteryVoltage;
 }
 
-void Sensors::setCanTopTankPress(Boardcore::PressureData data)
+void Sensors::setCanTopTankPress1(Boardcore::PressureData data)
 {
     Lock<FastMutex> lock{canMutex};
-    canTopTankPressure = data;
+    canTopTankPressure1 = data;
 }
 
-void Sensors::setCanBottomTankPress(Boardcore::PressureData data)
+void Sensors::setCanTopTankPress2(Boardcore::PressureData data)
 {
     Lock<FastMutex> lock{canMutex};
-    canBottomTankPressure = data;
+    canTopTankPressure2 = data;
 }
 
 void Sensors::setCanCCPress(Boardcore::PressureData data)
@@ -488,77 +488,77 @@ bool Sensors::sensorManagerInit()
 
     if (lps22df)
     {
-        SensorInfo info{"LPS22DF", Config::Sensors::LPS22DF::PERIOD,
+        SensorInfo info{"LPS22DF", Config::Sensors::LPS22DF::RATE,
                         [this]() { lps22dfCallback(); }};
         map.emplace(lps22df.get(), info);
     }
 
     if (lps28dfw)
     {
-        SensorInfo info{"LPS28DFW", Config::Sensors::LPS28DFW::PERIOD,
+        SensorInfo info{"LPS28DFW", Config::Sensors::LPS28DFW::RATE,
                         [this]() { lps28dfwCallback(); }};
         map.emplace(lps28dfw.get(), info);
     }
 
     if (h3lis331dl)
     {
-        SensorInfo info{"H3LIS331DL", Config::Sensors::H3LIS331DL::PERIOD,
+        SensorInfo info{"H3LIS331DL", Config::Sensors::H3LIS331DL::RATE,
                         [this]() { h3lis331dlCallback(); }};
         map.emplace(h3lis331dl.get(), info);
     }
 
     if (lis2mdl)
     {
-        SensorInfo info{"LIS2MDL", Config::Sensors::LIS2MDL::PERIOD,
+        SensorInfo info{"LIS2MDL", Config::Sensors::LIS2MDL::RATE,
                         [this]() { lis2mdlCallback(); }};
         map.emplace(lis2mdl.get(), info);
     }
 
     if (ubxgps)
     {
-        SensorInfo info{"UBXGPS", Config::Sensors::UBXGPS::PERIOD,
+        SensorInfo info{"UBXGPS", Config::Sensors::UBXGPS::RATE,
                         [this]() { ubxgpsCallback(); }};
         map.emplace(ubxgps.get(), info);
     }
 
     if (lsm6dsrx)
     {
-        SensorInfo info{"LSM6DSRX", Config::Sensors::LSM6DSRX::PERIOD,
+        SensorInfo info{"LSM6DSRX", Config::Sensors::LSM6DSRX::RATE,
                         [this]() { lsm6dsrxCallback(); }};
         map.emplace(lsm6dsrx.get(), info);
     }
 
     if (ads131m08)
     {
-        SensorInfo info{"ADS131M08", Config::Sensors::ADS131M08::PERIOD,
+        SensorInfo info{"ADS131M08", Config::Sensors::ADS131M08::RATE,
                         [this]() { ads131m08Callback(); }};
         map.emplace(ads131m08.get(), info);
     }
 
     if (staticPressure1)
     {
-        SensorInfo info{"StaticPressure1", Config::Sensors::ADS131M08::PERIOD,
+        SensorInfo info{"StaticPressure1", Config::Sensors::ADS131M08::RATE,
                         [this]() { staticPressure1Callback(); }};
         map.emplace(staticPressure1.get(), info);
     }
 
     if (staticPressure2)
     {
-        SensorInfo info{"StaticPressure2", Config::Sensors::ADS131M08::PERIOD,
+        SensorInfo info{"StaticPressure2", Config::Sensors::ADS131M08::RATE,
                         [this]() { staticPressure2Callback(); }};
         map.emplace(staticPressure2.get(), info);
     }
 
     if (dplBayPressure)
     {
-        SensorInfo info{"DplBayPressure", Config::Sensors::ADS131M08::PERIOD,
+        SensorInfo info{"DplBayPressure", Config::Sensors::ADS131M08::RATE,
                         [this]() { dplBayPressureCallback(); }};
         map.emplace(dplBayPressure.get(), info);
     }
 
     if (internalAdc)
     {
-        SensorInfo info{"InternalADC", Config::Sensors::InternalADC::PERIOD,
+        SensorInfo info{"InternalADC", Config::Sensors::InternalADC::RATE,
                         [this]() { internalAdcCallback(); }};
         map.emplace(internalAdc.get(), info);
     }
diff --git a/src/boards/Main/Sensors/Sensors.h b/src/boards/Main/Sensors/Sensors.h
index 5082dea0b..055af7eda 100644
--- a/src/boards/Main/Sensors/Sensors.h
+++ b/src/boards/Main/Sensors/Sensors.h
@@ -69,27 +69,28 @@ public:
     Boardcore::PressureData getStaticPressure2();
     Boardcore::PressureData getDplBayPressure();
 
-    Boardcore::PressureData getTopTankPress();
-    Boardcore::PressureData getBottomTankPress();
-    Boardcore::PressureData getCCPress();
-    Boardcore::TemperatureData getTankTemp();
-    Boardcore::VoltageData getMotorBatteryVoltage();
-
-    void setCanTopTankPress(Boardcore::PressureData data);
-    void setCanBottomTankPress(Boardcore::PressureData data);
+    Boardcore::PressureData getCanTopTankPress1();
+    Boardcore::PressureData getCanTopTankPress2();
+    Boardcore::PressureData getCanCCPress();
+    Boardcore::TemperatureData getCanTankTemp();
+    Boardcore::VoltageData getCanMotorBatteryVoltage();
+
+    std::vector<Boardcore::SensorInfo> getSensorInfos();
+
+    // Methods for CanHandler
+    void setCanTopTankPress1(Boardcore::PressureData data);
+    void setCanTopTankPress2(Boardcore::PressureData data);
     void setCanCCPress(Boardcore::PressureData data);
     void setCanTankTemp(Boardcore::TemperatureData data);
     void setCanMotorBatteryVoltage(Boardcore::VoltageData data);
 
-    std::vector<Boardcore::SensorInfo> getSensorInfos();
-
 protected:
     virtual bool postSensorCreationHook() { return true; }
 
     miosix::FastMutex canMutex;
     Boardcore::PressureData canCCPressure;
-    Boardcore::PressureData canBottomTankPressure;
-    Boardcore::PressureData canTopTankPressure;
+    Boardcore::PressureData canTopTankPressure1;
+    Boardcore::PressureData canTopTankPressure2;
     Boardcore::TemperatureData canTankTemperature;
     Boardcore::VoltageData canMotorBatteryVoltage;
 
diff --git a/src/boards/Main/StateMachines/ADAController/ADAController.cpp b/src/boards/Main/StateMachines/ADAController/ADAController.cpp
new file mode 100644
index 000000000..8e88d84de
--- /dev/null
+++ b/src/boards/Main/StateMachines/ADAController/ADAController.cpp
@@ -0,0 +1,416 @@
+/* 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 "ADAController.h"
+
+#include <Main/Configs/ADAConfig.h>
+#include <common/Events.h>
+#include <common/ReferenceConfig.h>
+#include <common/Topics.h>
+#include <events/EventBroker.h>
+#include <utils/AeroUtils/AeroUtils.h>
+
+using namespace Main;
+using namespace Boardcore;
+using namespace Common;
+using namespace miosix;
+
+// The default Kalman is empty, as calibrate will update it correctly
+const ADA::KalmanFilter::KalmanConfig DEFAULT_KALMAN_CONFIG{};
+
+ADA::KalmanFilter::KalmanConfig computeADAKalmanConfig(float refPressure)
+{
+    ADA::KalmanFilter::MatrixNN F;
+    ADA::KalmanFilter::MatrixPN H;
+    ADA::KalmanFilter::MatrixNN P;
+    ADA::KalmanFilter::MatrixNN Q;
+    ADA::KalmanFilter::MatrixPP R;
+    ADA::KalmanFilter::MatrixNM G;
+    ADA::KalmanFilter::CVectorN x;
+
+    // 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                                          },
+        {0.0, 0.0,                              1.0                                                                       }});
+
+    H = {1.0, 0.0, 0.0};
+
+    P = ADA::KalmanFilter::MatrixNN({
+        {1.0, 0.0, 0.0}, 
+        {0.0, 1.0, 0.0}, 
+        {0.0, 0.0, 1.0}});
+
+    Q << ADA::KalmanFilter::MatrixNN({
+        {30.0, 0.0,  0.0}, 
+        {0.0,  10.0, 0.0}, 
+        {0.0,  0.0,  2.5}});
+
+    R[0] = 4000.0;
+
+    G = ADA::KalmanFilter::MatrixNM::Zero();
+
+    x = ADA::KalmanFilter::CVectorN(refPressure, 0, 0);
+    // clang-format on
+
+    return {F, H, Q, R, P, G, x};
+}
+
+ADAController::ADAController()
+    : FSM{&ADAController::state_init, miosix::STACK_DEFAULT_FOR_PTHREAD,
+          Config::Scheduler::ADA_PRIORITY},
+      ada{DEFAULT_KALMAN_CONFIG}
+{
+    EventBroker::getInstance().subscribe(this, TOPIC_ADA);
+    EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
+
+    ada.setReferenceValues(ReferenceConfig::defaultReferenceValues);
+}
+
+bool ADAController::start()
+{
+    TaskScheduler& scheduler = getModule<BoardScheduler>()->getAdaScheduler();
+
+    uint8_t result =
+        scheduler.addTask([this]() { update(); }, Config::ADA::SAMPLE_RATE);
+
+    if (result == 0)
+    {
+        LOG_ERR(logger, "Failed to add ADA update task");
+        return false;
+    }
+
+    if (!FSM::start())
+    {
+        LOG_ERR(logger, "Failed to start ADA FSM");
+        return false;
+    }
+
+    return true;
+}
+
+ADAState ADAController::getADAState()
+{
+    Lock<FastMutex> lock{adaMutex};
+    return ada.getState();
+}
+
+void ADAController::update()
+{
+    PressureData baro = getModule<Sensors>()->getStaticPressure1();
+
+    // TODO(davide.mor): What about testing?
+    ADAControllerState curState = state;
+
+    // Lock ADA for the whole duration of the update
+    Lock<FastMutex> lock{adaMutex};
+
+    // First update the kalman
+    if (curState == ADAControllerState::ARMED ||
+        curState == ADAControllerState::SHADOW_MODE ||
+        curState == ADAControllerState::ACTIVE_ASCENT ||
+        curState == ADAControllerState::ACTIVE_DROGUE_DESCENT ||
+        curState == ADAControllerState::ACTIVE_TERMINAL_DESCENT)
+    {
+        // Perform baro correction
+        if (lastBaroTimestamp != baro.pressureTimestamp)
+        {
+            ada.update(baro.pressure);
+        }
+    }
+
+    // Then run detections
+    if (curState == ADAControllerState::SHADOW_MODE ||
+        curState == ADAControllerState::ACTIVE_ASCENT)
+    {
+        if (ada.getState().verticalSpeed <
+            Config::ADA::APOGEE_VERTICAL_SPEED_TARGET)
+        {
+            detectedApogees++;
+        }
+        else
+        {
+            // Apogees must be consecutive in order to be valid
+            detectedApogees = 0;
+        }
+
+        if (curState != ADAControllerState::SHADOW_MODE)
+        {
+            // DO NOT THROW EVENTS IN SHADOW_MODE!
+            if (detectedApogees > Config::ADA::APOGEE_N_SAMPLES)
+            {
+                EventBroker::getInstance().post(ADA_APOGEE_DETECTED, TOPIC_ADA);
+            }
+        }
+    }
+
+    if (curState == ADAControllerState::ACTIVE_DROGUE_DESCENT)
+    {
+        if (ada.getState().aglAltitude <
+            Config::ADA::DEPLOYMENT_ALTITUDE_TARGET)
+        {
+            detectedDeployments++;
+        }
+        else
+        {
+            detectedDeployments = 0;
+        }
+
+        if (detectedDeployments > Config::ADA::DEPLOYMENT_N_SAMPLES)
+        {
+            // TODO(davide.mor): Rename this event
+            EventBroker::getInstance().post(ALTITUDE_TRIGGER_ALTITUDE_REACHED,
+                                            TOPIC_ADA);
+        }
+    }
+
+    // Log this sample step
+    ADAControllerSampleData data = {TimestampTimer::getTimestamp(),
+                                    detectedApogees, detectedDeployments,
+                                    curState};
+    sdLogger.log(data);
+
+    lastBaroTimestamp = baro.pressureTimestamp;
+}
+
+void ADAController::calibrate()
+{
+    Stats baroStats;
+
+    for (int i = 0; i < Config::ADA::CALIBRATION_SAMPLES_COUNT; i++)
+    {
+        PressureData baro = getModule<Sensors>()->getStaticPressure1();
+        baroStats.add(baro.pressure);
+
+        Thread::sleep(Config::ADA::CALIBRATION_SLEEP_TIME);
+    }
+
+    Lock<FastMutex> lock{adaMutex};
+
+    ReferenceValues reference = ada.getReferenceValues();
+    reference.refPressure     = baroStats.getStats().mean;
+    reference.refAltitude     = Aeroutils::relAltitude(
+            reference.refPressure, reference.mslPressure, reference.mslTemperature);
+
+    ada.setReferenceValues(reference);
+    ada.setKalmanConfig(computeADAKalmanConfig(reference.refPressure));
+    ada.update(reference.refPressure);
+
+    EventBroker::getInstance().post(ADA_READY, TOPIC_ADA);
+}
+
+void ADAController::state_init(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::INIT);
+            break;
+        }
+        case ADA_CALIBRATE:
+        {
+            transition(&ADAController::state_calibrating);
+            break;
+        }
+    }
+}
+
+void ADAController::state_calibrating(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::CALIBRATING);
+
+            calibrate();
+            break;
+        }
+        case ADA_READY:
+        {
+            transition(&ADAController::state_ready);
+            break;
+        }
+    }
+}
+
+void ADAController::state_ready(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::READY);
+            break;
+        }
+        case ADA_CALIBRATE:
+        {
+            transition(&ADAController::state_calibrating);
+            break;
+        }
+        case FLIGHT_ARMED:
+        {
+            transition(&ADAController::state_armed);
+            break;
+        }
+    }
+}
+
+void ADAController::state_armed(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::ARMED);
+            break;
+        }
+        case FLIGHT_DISARMED:
+        {
+            transition(&ADAController::state_ready);
+            break;
+        }
+        case FLIGHT_LIFTOFF:
+        {
+            transition(&ADAController::state_shadow_mode);
+            break;
+        }
+    }
+}
+
+void ADAController::state_shadow_mode(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::SHADOW_MODE);
+
+            shadowModeTimeoutEvent = EventBroker::getInstance().postDelayed(
+                ADA_SHADOW_MODE_TIMEOUT, TOPIC_ADA,
+                Config::ADA::SHADOW_MODE_TIMEOUT);
+            break;
+        }
+
+        case EV_EXIT:
+        {
+            EventBroker::getInstance().removeDelayed(shadowModeTimeoutEvent);
+        }
+
+        case ADA_SHADOW_MODE_TIMEOUT:
+        {
+            transition(&ADAController::state_active_ascent);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&ADAController::state_end);
+            break;
+        }
+    }
+}
+
+void ADAController::state_active_ascent(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::ACTIVE_ASCENT);
+            break;
+        }
+
+        case FLIGHT_APOGEE_DETECTED:
+        {
+            transition(&ADAController::state_active_drogue_descent);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&ADAController::state_end);
+            break;
+        }
+    }
+}
+
+void ADAController::state_active_drogue_descent(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::ACTIVE_DROGUE_DESCENT);
+            break;
+        }
+
+        case FLIGHT_DPL_ALT_DETECTED:
+        {
+            transition(&ADAController::state_active_terminal_descent);
+            break;
+        }
+
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&ADAController::state_end);
+            break;
+        }
+    }
+}
+
+void ADAController::state_active_terminal_descent(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::ACTIVE_TERMINAL_DESCENT);
+            break;
+        }
+        case FLIGHT_LANDING_DETECTED:
+        {
+            transition(&ADAController::state_end);
+            break;
+        }
+    }
+}
+
+void ADAController::state_end(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            updateAndLogStatus(ADAControllerState::END);
+            break;
+        }
+    }
+}
+
+void ADAController::updateAndLogStatus(ADAControllerState state)
+{
+    this->state              = state;
+    ADAControllerStatus data = {TimestampTimer::getTimestamp(), state};
+    sdLogger.log(data);
+}
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/ADAController/ADAController.h b/src/boards/Main/StateMachines/ADAController/ADAController.h
new file mode 100644
index 000000000..5d559a858
--- /dev/null
+++ b/src/boards/Main/StateMachines/ADAController/ADAController.h
@@ -0,0 +1,78 @@
+/* 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/BoardScheduler.h>
+#include <Main/Sensors/Sensors.h>
+#include <Main/StateMachines/ADAController/ADAControllerData.h>
+#include <algorithms/ADA/ADA.h>
+#include <events/FSM.h>
+#include <utils/DependencyManager/DependencyManager.h>
+
+namespace Main
+{
+
+class ADAController
+    : public Boardcore::InjectableWithDeps<BoardScheduler, Sensors>,
+      public Boardcore::FSM<ADAController>
+{
+public:
+    ADAController();
+
+    [[nodiscard]] bool start() override;
+
+    Boardcore::ADAState getADAState();
+
+private:
+    void update();
+    void calibrate();
+
+    // FSM states
+    void state_init(const Boardcore::Event& event);
+    void state_calibrating(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_ascent(const Boardcore::Event& event);
+    void state_active_drogue_descent(const Boardcore::Event& event);
+    void state_active_terminal_descent(const Boardcore::Event& event);
+    void state_end(const Boardcore::Event& event);
+
+    void updateAndLogStatus(ADAControllerState state);
+
+    std::atomic<ADAControllerState> state{ADAControllerState::INIT};
+
+    Boardcore::Logger& sdLogger   = Boardcore::Logger::getInstance();
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("ada");
+
+    uint16_t shadowModeTimeoutEvent = 0;
+
+    miosix::FastMutex adaMutex;
+    Boardcore::ADA ada;
+
+    uint64_t lastBaroTimestamp       = 0;
+    unsigned int detectedApogees     = 0;
+    unsigned int detectedDeployments = 0;
+};
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/ADAController/ADAControllerData.h b/src/boards/Main/StateMachines/ADAController/ADAControllerData.h
new file mode 100644
index 000000000..94f1a6474
--- /dev/null
+++ b/src/boards/Main/StateMachines/ADAController/ADAControllerData.h
@@ -0,0 +1,77 @@
+/* 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 ADAControllerState : uint8_t
+{
+    INIT = 0,
+    CALIBRATING,
+    READY,
+    ARMED,
+    SHADOW_MODE,
+    ACTIVE_ASCENT,
+    ACTIVE_DROGUE_DESCENT,
+    ACTIVE_TERMINAL_DESCENT,
+    END
+};
+
+struct ADAControllerStatus
+{
+    uint64_t timestamp       = 0;
+    ADAControllerState state = ADAControllerState::INIT;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << static_cast<int>(state) << "\n";
+    }
+};
+
+struct ADAControllerSampleData
+{
+    uint64_t timestamp               = 0;
+    unsigned int detectedApogees     = 0;
+    unsigned int detectedDeployments = 0;
+    ADAControllerState state         = ADAControllerState::INIT;
+
+    static std::string header()
+    {
+        return "timestamp,detectedApogees,detectedDeployments,state\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << detectedApogees << "," << detectedDeployments
+           << "," << static_cast<int>(state) << "\n";
+    }
+};
+
+}  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
index e105d054f..00a979e92 100644
--- a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
+++ b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.cpp
@@ -43,73 +43,7 @@ FlightModeManager::FlightModeManager()
     EventBroker::getInstance().subscribe(this, TOPIC_NAS);
 }
 
-FlightModeManagerState FlightModeManager::getState()
-{
-    if (testState(&FlightModeManager::state_on_ground))
-    {
-        return FlightModeManagerState::FMM_STATE_ON_GROUND;
-    }
-    else if (testState(&FlightModeManager::state_init))
-    {
-        return FlightModeManagerState::FMM_STATE_INIT;
-    }
-    else if (testState(&FlightModeManager::state_init_error))
-    {
-        return FlightModeManagerState::FMM_STATE_INIT_ERROR;
-    }
-    else if (testState(&FlightModeManager::state_init_done))
-    {
-        return FlightModeManagerState::FMM_STATE_INIT_DONE;
-    }
-    else if (testState(&FlightModeManager::state_calibrate_sensors))
-    {
-        return FlightModeManagerState::FMM_STATE_CALIBRATE_SENSORS;
-    }
-    else if (testState(&FlightModeManager::state_calibrate_algorithms))
-    {
-        return FlightModeManagerState::FMM_STATE_CALIBRATE_ALGORITHMS;
-    }
-    else if (testState(&FlightModeManager::state_disarmed))
-    {
-        return FlightModeManagerState::FMM_STATE_DISARMED;
-    }
-    else if (testState(&FlightModeManager::state_test_mode))
-    {
-        return FlightModeManagerState::FMM_STATE_TEST_MODE;
-    }
-    else if (testState(&FlightModeManager::state_armed))
-    {
-        return FlightModeManagerState::FMM_STATE_ARMED;
-    }
-    else if (testState(&FlightModeManager::state_flying))
-    {
-        return FlightModeManagerState::FMM_STATE_FLYING;
-    }
-    else if (testState(&FlightModeManager::state_powered_ascent))
-    {
-        return FlightModeManagerState::FMM_STATE_POWERED_ASCENT;
-    }
-    else if (testState(&FlightModeManager::state_unpowered_ascent))
-    {
-        return FlightModeManagerState::FMM_STATE_UNPOWERED_ASCENT;
-    }
-    else if (testState(&FlightModeManager::state_drogue_descent))
-    {
-        return FlightModeManagerState::FMM_STATE_DROGUE_DESCENT;
-    }
-    else if (testState(&FlightModeManager::state_terminal_descent))
-    {
-        return FlightModeManagerState::FMM_STATE_TERMINAL_DESCENT;
-    }
-    else if (testState(&FlightModeManager::state_landed))
-    {
-        return FlightModeManagerState::FMM_STATE_LANDED;
-    }
-    else
-    {
-        return FlightModeManagerState::FMM_STATE_INVALID;
-    }
-}
+FlightModeManagerState FlightModeManager::getState() { return state; }
 
 State FlightModeManager::state_on_ground(const Event& event)
 {
@@ -117,7 +51,7 @@ State FlightModeManager::state_on_ground(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_ON_GROUND);
+            updateAndLogStatus(FlightModeManagerState::ON_GROUND);
             return HANDLED;
         }
         case EV_EXIT:
@@ -150,7 +84,7 @@ State FlightModeManager::state_init(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_INIT);
+            updateAndLogStatus(FlightModeManagerState::INIT);
             return HANDLED;
         }
         case EV_EXIT:
@@ -186,7 +120,7 @@ State FlightModeManager::state_init_error(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_INIT_ERROR);
+            updateAndLogStatus(FlightModeManagerState::INIT_ERROR);
             return HANDLED;
         }
         case EV_EXIT:
@@ -207,7 +141,7 @@ State FlightModeManager::state_init_error(const Event& event)
         }
         case TMTC_FORCE_INIT:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(CanConfig::EventId::FORCE_INIT);
             return transition(&FlightModeManager::state_init_done);
         }
         default:
@@ -223,7 +157,7 @@ State FlightModeManager::state_init_done(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_INIT_DONE);
+            updateAndLogStatus(FlightModeManagerState::INIT_DONE);
             EventBroker::getInstance().post(FMM_CALIBRATE, TOPIC_FMM);
             return HANDLED;
         }
@@ -256,7 +190,7 @@ State FlightModeManager::state_calibrate_sensors(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_CALIBRATE_SENSORS);
+            updateAndLogStatus(FlightModeManagerState::CALIBRATE_SENSORS);
             // TODO(davide.mor): Calibrate sensors
             Thread::sleep(2000);
             EventBroker::getInstance().post(FMM_SENSORS_CAL_DONE, TOPIC_FMM);
@@ -291,7 +225,7 @@ State FlightModeManager::state_calibrate_algorithms(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_CALIBRATE_ALGORITHMS);
+            updateAndLogStatus(FlightModeManagerState::CALIBRATE_ALGORITHMS);
 
             // Reset readiness status
             nasReady = false;
@@ -303,7 +237,6 @@ State FlightModeManager::state_calibrate_algorithms(const Event& event)
 
             // Quick hack to make the state machine go forward
             Thread::sleep(2000);
-            EventBroker::getInstance().post(NAS_READY, TOPIC_NAS);
             EventBroker::getInstance().post(ADA_READY, TOPIC_ADA);
 
             return HANDLED;
@@ -357,11 +290,13 @@ State FlightModeManager::state_disarmed(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_DISARMED);
+            updateAndLogStatus(FlightModeManagerState::DISARMED);
 
             getModule<Actuators>()->camOff();
             getModule<Actuators>()->setBuzzerOff();
 
+            EventBroker::getInstance().post(FLIGHT_DISARMED, TOPIC_FLIGHT);
+
             return HANDLED;
         }
         case EV_EXIT:
@@ -390,17 +325,18 @@ State FlightModeManager::state_disarmed(const Event& event)
         }
         case TMTC_CALIBRATE:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(CanConfig::EventId::CALIBRATE);
             return transition(&FlightModeManager::state_calibrate_sensors);
         }
         case TMTC_ENTER_TEST_MODE:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(
+                CanConfig::EventId::ENTER_TEST_MODE);
             return transition(&FlightModeManager::state_test_mode);
         }
         case TMTC_ARM:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(CanConfig::EventId::ARM);
             return transition(&FlightModeManager::state_armed);
         }
         default:
@@ -416,7 +352,7 @@ State FlightModeManager::state_test_mode(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_TEST_MODE);
+            updateAndLogStatus(FlightModeManagerState::TEST_MODE);
             // TODO(davide.mor): Start algorithms
             return HANDLED;
         }
@@ -449,7 +385,8 @@ State FlightModeManager::state_test_mode(const Event& event)
         }
         case TMTC_EXIT_TEST_MODE:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(
+                CanConfig::EventId::EXIT_TEST_MODE);
             return transition(&FlightModeManager::state_disarmed);
         }
         default:
@@ -465,14 +402,17 @@ State FlightModeManager::state_armed(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_ARMED);
+            updateAndLogStatus(FlightModeManagerState::ARMED);
 
             Logger::getInstance().stop();
             Logger::getInstance().start();
+            Logger::getInstance().resetStats();
 
             getModule<Actuators>()->camOn();
             getModule<Actuators>()->setBuzzerArmed();
 
+            EventBroker::getInstance().post(FLIGHT_ARMED, TOPIC_FLIGHT);
+
             return HANDLED;
         }
         case EV_EMPTY:
@@ -493,13 +433,13 @@ State FlightModeManager::state_armed(const Event& event)
         }
         case TMTC_DISARM:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(CanConfig::EventId::DISARM);
             return transition(&FlightModeManager::state_disarmed);
         }
         case TMTC_FORCE_LAUNCH:
         case FLIGHT_LAUNCH_PIN_DETACHED:
         {
-            // TODO(davide.mor): Also send this via CanBus
+            getModule<CanHandler>()->sendEvent(CanConfig::EventId::LIFTOFF);
             return transition(&FlightModeManager::state_flying);
         }
         default:
@@ -515,7 +455,7 @@ State FlightModeManager::state_flying(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_FLYING);
+            updateAndLogStatus(FlightModeManagerState::FLYING);
 
             getModule<Actuators>()->setBuzzerOff();
 
@@ -556,7 +496,9 @@ State FlightModeManager::state_powered_ascent(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_POWERED_ASCENT);
+            updateAndLogStatus(FlightModeManagerState::POWERED_ASCENT);
+
+            EventBroker::getInstance().post(FLIGHT_LIFTOFF, TOPIC_FLIGHT);
 
             // Safety engine shutdown
             engineShutdownEvent = EventBroker::getInstance().postDelayed(
@@ -581,8 +523,8 @@ State FlightModeManager::state_powered_ascent(const Event& event)
         case MEA_SHUTDOWN_DETECTED:
         case MOTOR_CLOSE_FEED_VALVE:
         {
-            // TODO(davide.mor): Actually shutdown the motor via CanBus
-
+            getModule<CanHandler>()->sendServoCloseCommand(
+                ServosList::MAIN_VALVE);
             return transition(&FlightModeManager::state_unpowered_ascent);
         }
         default:
@@ -598,7 +540,7 @@ State FlightModeManager::state_unpowered_ascent(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_UNPOWERED_ASCENT);
+            updateAndLogStatus(FlightModeManagerState::UNPOWERED_ASCENT);
 
             apogeeTimeoutEvent = EventBroker::getInstance().postDelayed(
                 TMTC_FORCE_APOGEE, TOPIC_TMTC,
@@ -621,9 +563,6 @@ State FlightModeManager::state_unpowered_ascent(const Event& event)
         case TMTC_FORCE_APOGEE:
         case ADA_APOGEE_DETECTED:
         {
-            // TODO(davide.mor): Also send this via CanBus
-            getModule<Actuators>()->openExpulsion();
-
             return transition(&FlightModeManager::state_drogue_descent);
         }
         default:
@@ -639,9 +578,19 @@ State FlightModeManager::state_drogue_descent(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_DROGUE_DESCENT);
+            updateAndLogStatus(FlightModeManagerState::DROGUE_DESCENT);
+
+            getModule<Actuators>()->openExpulsion();
+            getModule<CanHandler>()->sendEvent(
+                CanConfig::EventId::APOGEE_DETECTED);
 
-            // TODO(davide.mor): Perform venting?
+            EventBroker::getInstance().post(FLIGHT_APOGEE_DETECTED,
+                                            TOPIC_FLIGHT);
+
+            // TODO: We need a way to signal a full vent, the current setup
+            // doesn't allow to vent for more than 60s
+            getModule<CanHandler>()->sendServoOpenCommand(
+                ServosList::VENTING_VALVE, 1.0f, 60000);
 
             return HANDLED;
         }
@@ -675,7 +624,10 @@ State FlightModeManager::state_terminal_descent(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_TERMINAL_DESCENT);
+            updateAndLogStatus(FlightModeManagerState::TERMINAL_DESCENT);
+
+            EventBroker::getInstance().post(FLIGHT_DPL_ALT_DETECTED,
+                                            TOPIC_FLIGHT);
 
             // TODO(davide.mor): Actuate cutters?
 
@@ -710,8 +662,10 @@ State FlightModeManager::state_landed(const Event& event)
     {
         case EV_ENTRY:
         {
-            updateAndLogStatus(FMM_STATE_LANDED);
+            updateAndLogStatus(FlightModeManagerState::LANDED);
 
+            EventBroker::getInstance().post(FLIGHT_LANDING_DETECTED,
+                                            TOPIC_FLIGHT);
             Logger::getInstance().stop();
 
             getModule<Actuators>()->setBuzzerLand();
diff --git a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.h b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.h
index b25dda68a..6c4fd88d9 100644
--- a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.h
+++ b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManager.h
@@ -23,6 +23,7 @@
 #pragma once
 
 #include <Main/Actuators/Actuators.h>
+#include <Main/CanHandler/CanHandler.h>
 #include <events/EventBroker.h>
 #include <events/HSM.h>
 #include <utils/DependencyManager/DependencyManager.h>
@@ -32,8 +33,9 @@
 namespace Main
 {
 
-class FlightModeManager : public Boardcore::InjectableWithDeps<Actuators>,
-                          public Boardcore::HSM<FlightModeManager>
+class FlightModeManager
+    : public Boardcore::InjectableWithDeps<Actuators, CanHandler>,
+      public Boardcore::HSM<FlightModeManager>
 {
 public:
     FlightModeManager();
@@ -83,7 +85,7 @@ private:
     uint16_t engineShutdownEvent = 0;
     uint16_t apogeeTimeoutEvent  = 0;
 
-    std::atomic<FlightModeManagerState> state{FMM_STATE_INVALID};
+    std::atomic<FlightModeManagerState> state{FlightModeManagerState::INVALID};
 };
 
 }  // namespace Main
\ No newline at end of file
diff --git a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManagerData.h b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManagerData.h
index d275f7dc1..2f462a36c 100644
--- a/src/boards/Main/StateMachines/FlightModeManager/FlightModeManagerData.h
+++ b/src/boards/Main/StateMachines/FlightModeManager/FlightModeManagerData.h
@@ -25,31 +25,31 @@
 namespace Main
 {
 
-enum FlightModeManagerState : uint8_t
+enum class FlightModeManagerState : uint8_t
 {
-    FMM_STATE_ON_GROUND = 0,
-    FMM_STATE_INIT,
-    FMM_STATE_INIT_ERROR,
-    FMM_STATE_INIT_DONE,
-    FMM_STATE_CALIBRATE_SENSORS,
-    FMM_STATE_CALIBRATE_ALGORITHMS,
-    FMM_STATE_DISARMED,
-    FMM_STATE_TEST_MODE,
-    FMM_STATE_ARMED,
-    FMM_STATE_IGNITION,  // < Unused, kept for backward compatibility
-    FMM_STATE_FLYING,
-    FMM_STATE_POWERED_ASCENT,
-    FMM_STATE_UNPOWERED_ASCENT,
-    FMM_STATE_DROGUE_DESCENT,
-    FMM_STATE_TERMINAL_DESCENT,
-    FMM_STATE_LANDED,
-    FMM_STATE_INVALID
+    ON_GROUND = 0,
+    INIT,
+    INIT_ERROR,
+    INIT_DONE,
+    CALIBRATE_SENSORS,
+    CALIBRATE_ALGORITHMS,
+    DISARMED,
+    TEST_MODE,
+    ARMED,
+    IGNITION,  // < Unused, kept for backward compatibility
+    FLYING,
+    POWERED_ASCENT,
+    UNPOWERED_ASCENT,
+    DROGUE_DESCENT,
+    TERMINAL_DESCENT,
+    LANDED,
+    INVALID
 };
 
 struct FlightModeManagerStatus
 {
     uint64_t timestamp           = 0;
-    FlightModeManagerState state = FlightModeManagerState::FMM_STATE_INVALID;
+    FlightModeManagerState state = FlightModeManagerState::INVALID;
 
     static std::string header() { return "timestamp,state\n"; }
 
diff --git a/src/entrypoints/Main/main-entry.cpp b/src/entrypoints/Main/main-entry.cpp
index 334bcae20..c56cad48f 100644
--- a/src/entrypoints/Main/main-entry.cpp
+++ b/src/entrypoints/Main/main-entry.cpp
@@ -27,6 +27,7 @@
 #include <Main/PinHandler/PinHandler.h>
 #include <Main/Radio/Radio.h>
 #include <Main/Sensors/Sensors.h>
+#include <Main/StateMachines/ADAController/ADAController.h>
 #include <Main/StateMachines/FlightModeManager/FlightModeManager.h>
 #include <actuators/Servo/Servo.h>
 #include <drivers/timer/PWM.h>
@@ -56,16 +57,18 @@ int main()
     CanHandler *canHandler = new CanHandler();
     PinHandler *pinHandler = new PinHandler();
     FlightModeManager *fmm = new FlightModeManager();
+    ADAController *ada     = new ADAController();
 
     // Insert modules
-    bool initResult =
-        manager.insert<Buses>(buses) &&
-        manager.insert<BoardScheduler>(scheduler) &&
-        manager.insert<Sensors>(sensors) && manager.insert<Radio>(radio) &&
-        manager.insert<Actuators>(actuators) &&
-        manager.insert<CanHandler>(canHandler) &&
-        manager.insert<PinHandler>(pinHandler) &&
-        manager.insert<FlightModeManager>(fmm) && manager.inject();
+    bool initResult = manager.insert<Buses>(buses) &&
+                      manager.insert<BoardScheduler>(scheduler) &&
+                      manager.insert<Sensors>(sensors) &&
+                      manager.insert<Radio>(radio) &&
+                      manager.insert<Actuators>(actuators) &&
+                      manager.insert<CanHandler>(canHandler) &&
+                      manager.insert<PinHandler>(pinHandler) &&
+                      manager.insert<FlightModeManager>(fmm) &&
+                      manager.insert<ADAController>(ada) && manager.inject();
 
     manager.graphviz(std::cout);
 
@@ -135,6 +138,12 @@ int main()
         std::cout << "Error failed to start scheduler" << std::endl;
     }
 
+    if (!ada->start())
+    {
+        initResult = false;
+        std::cout << "Error failed to start ADAController" << std::endl;
+    }
+
     if (!fmm->start())
     {
         initResult = false;
-- 
GitLab