diff --git a/CMakeLists.txt b/CMakeLists.txt
index 239e46323be079d1091eab9766450382a3565995..65a2c2f39dcc2afed86092df03fcbe3658d809c1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,16 @@ target_include_directories(main-entry-euroc PRIVATE ${OBSW_INCLUDE_DIRS})
 target_compile_definitions(main-entry-euroc PRIVATE EUROC)
 sbs_target(main-entry-euroc stm32f767zi_skyward_death_stack_v4)
 
+add_executable(payload-entry-roccaraso src/entrypoints/Payload/payload-entry.cpp ${PAYLOAD_COMPUTER})
+target_include_directories(payload-entry-roccaraso PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(payload-entry-roccaraso PRIVATE ROCCARASO)
+sbs_target(payload-entry-roccaraso stm32f767zi_skyward_death_stack_v4)
+
+add_executable(payload-entry-euroc src/entrypoints/Payload/payload-entry.cpp ${PAYLOAD_COMPUTER})
+target_include_directories(payload-entry-euroc PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(payload-entry-euroc PRIVATE EUROC)
+sbs_target(payload-entry-euroc stm32f767zi_skyward_death_stack_v4)
+
 add_executable(motor-entry src/entrypoints/Motor/motor-entry.cpp ${MOTOR_SOURCES})
 target_include_directories(motor-entry PRIVATE ${OBSW_INCLUDE_DIRS})
 sbs_target(motor-entry stm32f767zi_gemini_motor)
@@ -63,7 +73,3 @@ sbs_target(rig-entry stm32f429zi_skyward_rig)
 add_executable(con_rig-entry src/entrypoints/con_RIG/con_rig-entry.cpp ${CON_RIG_COMPUTER})
 target_include_directories(con_rig-entry PRIVATE ${OBSW_INCLUDE_DIRS})
 sbs_target(con_rig-entry stm32f429zi_stm32f4discovery)
-
-#-----------------------------------------------------------------------------#
-#                               Test entrypoints                              #
-#-----------------------------------------------------------------------------#
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index da4dc6d1f4720e9ad6e5cbb1ccb6dd009373b949..65860823f9cd53c58948c5d92b194c6c28dc06c5 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -25,7 +25,7 @@ set(OBSW_INCLUDE_DIRS
     src/hardware_in_the_loop
 )
 
-set(HIL 
+set(HIL
     src/hardware_in_the_loop/HIL/HILFlightPhasesManager.cpp
     src/hardware_in_the_loop/HIL/HILTransceiver.cpp
 )
@@ -73,3 +73,25 @@ set(CON_RIG_COMPUTER
     src/boards/con_RIG/Radio/Radio.cpp
     src/boards/con_RIG/BoardScheduler.cpp
 )
+
+set(PAYLOAD_COMPUTER
+    src/boards/Payload/Actuators/Actuators.cpp
+    src/boards/Payload/CanHandler/CanHandler.cpp
+    src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.cpp
+    src/boards/Payload/Sensors/Sensors.cpp
+    src/boards/Payload/BoardScheduler.cpp
+    src/boards/Payload/PinHandler/PinHandler.cpp
+    src/boards/Payload/Radio/Radio.cpp
+    src/boards/Payload/TMRepository/TMRepository.cpp
+    src/boards/Payload/StateMachines/NASController/NASController.cpp
+    src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.cpp
+    src/boards/Payload/StateMachines/WingController/WingController.cpp
+    src/boards/Payload/AltitudeTrigger/AltitudeTrigger.cpp
+    src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.cpp
+    src/boards/Payload/Wing/AutomaticWingAlgorithm.cpp
+    src/boards/Payload/Wing/Guidance/EarlyManeuverGuidanceAlgorithm.cpp
+    src/boards/Payload/Wing/FileWingAlgorithm.cpp
+    src/boards/Payload/Wing/WingAlgorithm.cpp
+    src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.cpp
+    src/boards/Payload/WindEstimationScheme/WindEstimation.cpp
+)
diff --git a/src/boards/Payload/Actuators/Actuators.cpp b/src/boards/Payload/Actuators/Actuators.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..759aef675a7d6b20a281ce92ed3cab0563e7e28a
--- /dev/null
+++ b/src/boards/Payload/Actuators/Actuators.cpp
@@ -0,0 +1,268 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Federico Lolli
+ *
+ * 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 "Actuators.h"
+
+#include <Payload/BoardScheduler.h>
+#include <Payload/Configs/ActuatorsConfigs.h>
+#include <interfaces-impl/hwmapping.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Payload::ActuatorsConfigs;
+
+namespace Payload
+{
+
+Actuators::Actuators(Boardcore::TaskScheduler* sched) : scheduler(sched)
+{
+    leftServo  = new Servo(SERVO_1_TIMER, SERVO_1_PWM_CH, LEFT_SERVO_MIN_PULSE,
+                           LEFT_SERVO_MAX_PULSE);
+    rightServo = new Servo(SERVO_2_TIMER, SERVO_2_PWM_CH, RIGHT_SERVO_MIN_PULSE,
+                           RIGHT_SERVO_MAX_PULSE);
+    buzzer     = new PWM(BUZZER_TIMER, BUZZER_FREQUENCY);
+    buzzer->setDutyCycle(BUZZER_CHANNEL, BUZZER_DUTY_CYCLE);
+    camOff();
+}
+
+bool Actuators::start()
+{
+    // Servos
+    enableServo(PARAFOIL_LEFT_SERVO);
+    setServo(PARAFOIL_LEFT_SERVO, 0);
+    enableServo(PARAFOIL_RIGHT_SERVO);
+    setServo(PARAFOIL_RIGHT_SERVO, 0);
+
+    // Signaling Devices configurations
+
+    return scheduler->addTask([&]() { updateBuzzer(); }, BUZZER_UPDATE_PERIOD,
+                              TaskScheduler::Policy::RECOVER) != 0;
+}
+
+bool Actuators::setServo(ServosList servoId, float percentage)
+{
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            leftServo->setPosition(percentage);
+            Logger::getInstance().log(leftServo->getState());
+            break;
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            rightServo->setPosition(percentage);
+            Logger::getInstance().log(rightServo->getState());
+            break;
+        }
+        default:
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool Actuators::setServoAngle(ServosList servoId, float angle)
+{
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            leftServo->setPosition(angle / LEFT_SERVO_ROTATION);
+            Logger::getInstance().log(leftServo->getState());
+            break;
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            rightServo->setPosition(angle / RIGHT_SERVO_ROTATION);
+            Logger::getInstance().log(rightServo->getState());
+            break;
+        }
+        default:
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool Actuators::wiggleServo(ServosList servoId)
+{
+
+    if (!setServo(servoId, 1))
+    {
+        return false;
+    }
+    Thread::sleep(1000);
+    return setServo(servoId, 0);
+}
+
+bool Actuators::enableServo(ServosList servoId)
+{
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            leftServo->enable();
+            break;
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            rightServo->enable();
+            break;
+        }
+        default:
+            return false;
+    }
+
+    return true;
+}
+
+bool Actuators::disableServo(ServosList servoId)
+{
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            leftServo->disable();
+            break;
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            rightServo->disable();
+            break;
+        }
+        default:
+            return false;
+    }
+
+    return true;
+}
+
+float Actuators::getServoPosition(ServosList servoId)
+{
+
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            return leftServo->getPosition();
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            return rightServo->getPosition();
+        }
+        default:
+            return 0;
+    }
+
+    return 0;
+}
+
+float Actuators::getServoAngle(ServosList servoId)
+{
+    switch (servoId)
+    {
+        case PARAFOIL_LEFT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> ll(leftServoMutex);
+            return leftServo->getPosition() * LEFT_SERVO_ROTATION;
+        }
+        case PARAFOIL_RIGHT_SERVO:
+        {
+            miosix::Lock<miosix::FastMutex> lr(rightServoMutex);
+            return rightServo->getPosition() * RIGHT_SERVO_ROTATION;
+        }
+        default:
+            return 0;
+    }
+
+    return 0;
+}
+
+void Actuators::cuttersOn() { gpios::cut_trigger::high(); }
+
+void Actuators::cuttersOff() { gpios::cut_trigger::low(); }
+
+void Actuators::camOn() { gpios::camera_enable::high(); }
+
+void Actuators::camOff() { gpios::camera_enable::low(); }
+
+void Actuators::buzzerArmed()
+{
+    miosix::Lock<miosix::FastMutex> l(rocketSignalingStateMutex);
+    // Set the counter with respect to the update function period
+    buzzerCounterOverflow = ROCKET_SS_ARMED_PERIOD / BUZZER_UPDATE_PERIOD;
+}
+
+void Actuators::buzzerLanded()
+{
+    miosix::Lock<miosix::FastMutex> l(rocketSignalingStateMutex);
+    buzzerCounterOverflow = ROCKET_SS_LAND_PERIOD / BUZZER_UPDATE_PERIOD;
+}
+
+void Actuators::buzzerOff()
+{
+    miosix::Lock<miosix::FastMutex> l(rocketSignalingStateMutex);
+    buzzerCounterOverflow = 0;
+}
+
+void Actuators::updateBuzzer()
+{
+    miosix::Lock<miosix::FastMutex> l(rocketSignalingStateMutex);
+    if (buzzerCounterOverflow == 0)
+    {
+        // The buzzer is deactivated thus the channel is disabled
+        buzzer->disableChannel(BUZZER_CHANNEL);
+    }
+    else
+    {
+        if (buzzerCounter >= buzzerCounterOverflow)
+        {
+            // Enable the channel for this period
+            buzzer->enableChannel(BUZZER_CHANNEL);
+            buzzerCounter = 0;
+        }
+        else
+        {
+            buzzer->disableChannel(BUZZER_CHANNEL);
+            buzzerCounter++;
+        }
+    }
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Actuators/Actuators.h b/src/boards/Payload/Actuators/Actuators.h
new file mode 100644
index 0000000000000000000000000000000000000000..db2169c17d79ed7648f0ae1b9150e690740ecd19
--- /dev/null
+++ b/src/boards/Payload/Actuators/Actuators.h
@@ -0,0 +1,138 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Federico Lolli
+ *
+ * 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 <actuators/Servo/Servo.h>
+#include <common/Mavlink.h>
+#include <interfaces/gpio.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+
+struct Actuators : public Boardcore::Module
+{
+    explicit Actuators(Boardcore::TaskScheduler* sched);
+
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Moves the specified servo to the given position.
+     *
+     * @param servoId Servo to move.
+     * @param percentage Angle to set [0-1].
+     * @return True if the the angle was set.
+     */
+    bool setServo(ServosList servoId, float percentage);
+
+    /**
+     * @brief Moves the specified servo to the given position.
+     *
+     * @param servoId Servo to move.
+     * @param angle Angle to set [degree].
+     * @return True if the the angle was set.
+     */
+    bool setServoAngle(ServosList servoId, float angle);
+
+    /**
+     * @brief Wiggles the servo for few seconds.
+     *
+     * @param servoId Servo to move.
+     * @return true
+     * @return false
+     */
+    bool wiggleServo(ServosList servoId);
+
+    /**
+     * @brief Enables the specified servo.
+     *
+     * @param servoId Servo to enable.
+     * @return True if the servo was enabled.
+     * @return False if the servoId is invalid.
+     */
+    bool enableServo(ServosList servoId);
+
+    /**
+     * @brief Disables the specified servo.
+     *
+     * @param servoId Servo to disable.
+     * @return True if the servo was disabled.
+     * @return False if the servoId is invalid.
+     */
+    bool disableServo(ServosList servoId);
+
+    /**
+     * @brief Returns the current position of the specified servo.
+     *
+     * @param servoId Servo to read.
+     * @return float current Servo position in range [0-1], 0 if the servoId is
+     * invalid.
+     */
+    float getServoPosition(ServosList servoId);
+
+    /**
+     * @brief Returns the current angle of the specified servo.
+     *
+     * @param servoId Servo to read.
+     * @return float current Servo angle in range [0-180], 0 if the servoId is
+     * invalid.
+     */
+    float getServoAngle(ServosList servoId);
+
+    void cuttersOn();
+    void cuttersOff();
+
+    void camOn();
+    void camOff();
+
+    void buzzerArmed();
+    void buzzerError();
+    void buzzerLanded();
+    void buzzerOff();
+
+private:
+    void toggleLed();
+    /**
+     * @brief Automatic called method to update the buzzer status
+     */
+    void updateBuzzer();
+
+    Boardcore::TaskScheduler* scheduler = nullptr;
+    Boardcore::Servo* leftServo         = nullptr;
+    Boardcore::Servo* rightServo        = nullptr;
+    Boardcore::PWM* buzzer              = nullptr;
+
+    // mutexes
+    miosix::FastMutex leftServoMutex;
+    miosix::FastMutex rightServoMutex;
+    miosix::FastMutex rocketSignalingStateMutex;
+
+    // Counter that enables and disables the buzzer
+    uint32_t buzzerCounter = 0;
+    // Upper limit of the buzzer counter
+    uint32_t buzzerCounterOverflow = 0;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.cpp b/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6398f6e51ee398d55ca657035461a5f32a3cdddc
--- /dev/null
+++ b/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.cpp
@@ -0,0 +1,101 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 "AltitudeTrigger.h"
+
+#include <Payload/BoardScheduler.h>
+#include <Payload/Configs/WingConfig.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+
+using namespace Boardcore;
+using namespace Common;
+
+namespace Payload
+{
+
+AltitudeTrigger::AltitudeTrigger(TaskScheduler *sched)
+    : running(false), confidence(0),
+      deploymentAltitude(WingConfig::ALTITUDE_TRIGGER_DEPLOYMENT_ALTITUDE),
+      scheduler(sched)
+{
+}
+
+bool AltitudeTrigger::start()
+{
+    return (scheduler->addTask(std::bind(&AltitudeTrigger::update, this),
+                               WingConfig::ALTITUDE_TRIGGER_PERIOD) != 0);
+}
+
+void AltitudeTrigger::enable()
+{
+    miosix::Lock<miosix::FastMutex> l(mutex);
+    confidence = 0;
+    running    = true;
+}
+
+void AltitudeTrigger::setDeploymentAltitude(float altitude)
+{
+    miosix::Lock<miosix::FastMutex> l(mutex);
+    deploymentAltitude = altitude;
+}
+
+void AltitudeTrigger::disable()
+{
+    miosix::Lock<miosix::FastMutex> l(mutex);
+    running = false;
+}
+
+bool AltitudeTrigger::isActive()
+{
+    miosix::Lock<miosix::FastMutex> l(mutex);
+    return running;
+}
+
+void AltitudeTrigger::update()
+{
+    miosix::Lock<miosix::FastMutex> l(mutex);
+    if (running)
+    {
+        // We multiply by -1 to have a positive height
+        float height =
+            -ModuleManager::getInstance().get<NASController>()->getNasState().d;
+
+        if (height < WingConfig::ALTITUDE_TRIGGER_DEPLOYMENT_ALTITUDE)
+        {
+            confidence++;
+        }
+        else
+        {
+            confidence = 0;
+        }
+        if (confidence >= WingConfig ::ALTITUDE_TRIGGER_CONFIDENCE)
+        {
+            confidence = 0;
+            EventBroker::getInstance().post(ALTITUDE_TRIGGER_ALTITUDE_REACHED,
+                                            TOPIC_FLIGHT);
+            running = false;
+        }
+    }
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.h b/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.h
new file mode 100644
index 0000000000000000000000000000000000000000..b0d2731ac9ecbcdc19ca2cd5f0ae9edd156735e1
--- /dev/null
+++ b/src/boards/Payload/AltitudeTrigger/AltitudeTrigger.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/BoardScheduler.h>
+
+#include <atomic>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+
+class AltitudeTrigger : public Boardcore::Module
+{
+public:
+    explicit AltitudeTrigger(Boardcore::TaskScheduler *sched);
+
+    /**
+     * @brief Adds the update() task to the task scheduler.
+     */
+    bool start();
+
+    /**
+     * @brief Enable the AltitudeTrigger.
+     */
+    void enable();
+
+    /**
+     * @brief Disable the AltitudeTrigger.
+     */
+    void disable();
+
+    /**
+     * @return The status of the AltitudeTrigger
+     */
+    bool isActive();
+
+    /**
+     * @return Set the altitude of the AltitudeTrigger
+     */
+    void setDeploymentAltitude(float altitude);
+
+private:
+    // Update method that posts a FLIGHT_WING_ALT_PASSED when the correct
+    // altitude is reached
+    void update();
+
+    bool running;
+
+    // Number of times that the algorithm detects to be below the fixed
+    // altitude
+    int confidence;
+
+    float deploymentAltitude;
+
+    miosix::FastMutex mutex;
+
+    Boardcore::TaskScheduler *scheduler = nullptr;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/BoardScheduler.cpp b/src/boards/Payload/BoardScheduler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..10eafa5f52b0d11473470ae239165890a3795661
--- /dev/null
+++ b/src/boards/Payload/BoardScheduler.cpp
@@ -0,0 +1,66 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro
+ *
+ * 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 "BoardScheduler.h"
+
+using namespace Boardcore;
+
+namespace Payload
+{
+
+BoardScheduler::BoardScheduler()
+{
+    scheduler1 = new TaskScheduler(miosix::PRIORITY_MAX - 4);
+    scheduler2 = new TaskScheduler(miosix::PRIORITY_MAX - 3);
+    scheduler3 = new TaskScheduler(miosix::PRIORITY_MAX - 2);
+    scheduler4 = new TaskScheduler(miosix::PRIORITY_MAX - 1);
+}
+
+TaskScheduler* BoardScheduler::getScheduler(miosix::Priority priority)
+{
+    switch (priority.get())
+    {
+        case miosix::PRIORITY_MAX:
+            return scheduler4;
+        case miosix::PRIORITY_MAX - 1:
+            return scheduler3;
+        case miosix::PRIORITY_MAX - 2:
+            return scheduler2;
+        case miosix::MAIN_PRIORITY:
+            return scheduler1;
+        default:
+            return scheduler1;
+    }
+}
+
+bool BoardScheduler::start()
+{
+    return scheduler1->start() && scheduler2->start() && scheduler3->start() &&
+           scheduler4->start();
+}
+
+bool BoardScheduler::isStarted()
+{
+    return scheduler1->isRunning() && scheduler2->isRunning() &&
+           scheduler3->isRunning() && scheduler4->isRunning();
+}
+}  // namespace Payload
\ No newline at end of file
diff --git a/src/boards/Payload/BoardScheduler.h b/src/boards/Payload/BoardScheduler.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a09bccc34b1508ff13df578ce9e8a9440badf43
--- /dev/null
+++ b/src/boards/Payload/BoardScheduler.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro
+ *
+ * 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 <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+/**
+ * @brief Class that wraps the 4 main task schedulers of the entire OBSW.
+ * There is a task scheduler for every miosix priority
+ */
+class BoardScheduler : public Boardcore::Module
+{
+public:
+    BoardScheduler();
+
+    /**
+     * @brief Get the Scheduler object relative to the requested priority
+     *
+     * @param priority The task scheduler priority
+     * @return Boardcore::TaskScheduler& Reference to the requested task
+     * scheduler.
+     * @note Min priority scheduler is returned in case of non valid priority.
+     */
+    Boardcore::TaskScheduler* getScheduler(miosix::Priority priority);
+
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Returns if all the schedulers are up and running
+     */
+    bool isStarted();
+
+private:
+    Boardcore::TaskScheduler* scheduler1;
+    Boardcore::TaskScheduler* scheduler2;
+    Boardcore::TaskScheduler* scheduler3;
+    Boardcore::TaskScheduler* scheduler4;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Buses.h b/src/boards/Payload/Buses.h
new file mode 100644
index 0000000000000000000000000000000000000000..d4f49813721bcd92a32e125a8547633c4bd8cf01
--- /dev/null
+++ b/src/boards/Payload/Buses.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <drivers/i2c/I2C.h>
+#include <drivers/spi/SPIBus.h>
+#include <drivers/usart/USART.h>
+#include <interfaces-impl/hwmapping.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+namespace Payload
+{
+class Buses : public Boardcore::Module
+{
+public:
+    Boardcore::SPIBus spi1;
+    Boardcore::SPIBus spi3;
+    Boardcore::SPIBus spi4;
+    Boardcore::SPIBus spi6;
+
+    Boardcore::I2C i2c1;
+
+    Boardcore::USART usart1;
+    Boardcore::USART usart2;
+    Boardcore::USART uart4;
+
+    Buses()
+        : spi1(SPI1), spi3(SPI3), spi4(SPI4), spi6(SPI6),
+          i2c1(I2C1, miosix::interfaces::i2c1::scl::getPin(),
+               miosix::interfaces::i2c1::sda::getPin()),
+          usart1(USART1, 115200), usart2(USART2, 115200), uart4(UART4, 115200)
+    {
+    }
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/CanHandler/CanHandler.cpp b/src/boards/Payload/CanHandler/CanHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e9ba14e44aba062f39f77f000624f9084b2ac35c
--- /dev/null
+++ b/src/boards/Payload/CanHandler/CanHandler.cpp
@@ -0,0 +1,149 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Federico Mandelli, Alberto Nidasio
+ *
+ * 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 "CanHandler.h"
+
+#include <Payload/Configs/CanHandlerConfig.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+
+#include <functional>
+
+using namespace std;
+using namespace placeholders;
+using namespace Boardcore;
+using namespace Canbus;
+using namespace Common;
+using namespace CanConfig;
+using namespace Payload::CanHandlerConfig;
+
+namespace Payload
+{
+
+CanHandler::CanHandler(TaskScheduler *sched) : scheduler(sched)
+{
+    CanbusDriver::AutoBitTiming bitTiming;
+    bitTiming.baudRate    = BAUD_RATE;
+    bitTiming.samplePoint = SAMPLE_POINT;
+    CanbusDriver::CanbusConfig config;
+    driver = new CanbusDriver(CAN2, config, bitTiming);
+
+    protocol =
+        new CanProtocol(driver, bind(&CanHandler::handleCanMessage, this, _1),
+                        miosix::PRIORITY_MAX - 1);
+
+    // Accept messages only from the main and RIG board
+    protocol->addFilter(static_cast<uint8_t>(Board::MAIN),
+                        static_cast<uint8_t>(Board::BROADCAST));
+    protocol->addFilter(static_cast<uint8_t>(Board::RIG),
+                        static_cast<uint8_t>(Board::BROADCAST));
+    driver->init();
+}
+
+bool CanHandler::start()
+{
+    bool result;
+    // Add a task to periodically send the pitot data
+    result = scheduler->addTask(  // sensor template
+                 [&]()
+                 {
+                     protocol->enqueueData(
+                         static_cast<uint8_t>(Priority::MEDIUM),
+                         static_cast<uint8_t>(PrimaryType::SENSORS),
+                         static_cast<uint8_t>(Board::PAYLOAD),
+                         static_cast<uint8_t>(Board::BROADCAST),
+                         static_cast<uint8_t>(SensorId::PITOT),
+                         ModuleManager::getInstance()
+                             .get<Sensors>()
+                             ->getPitotLastSample());
+                 },
+                 PITOT_TRANSMISSION_PERIOD) != 0;
+
+    result =
+        result &&
+        scheduler->addTask(  // status
+            [&]()
+            {
+                FlightModeManagerState state = ModuleManager::getInstance()
+                                                   .get<FlightModeManager>()
+                                                   ->getStatus()
+                                                   .state;
+                protocol->enqueueSimplePacket(
+                    static_cast<uint8_t>(Priority::MEDIUM),
+                    static_cast<uint8_t>(PrimaryType::STATUS),
+                    static_cast<uint8_t>(Board::PAYLOAD),
+                    static_cast<uint8_t>(Board::BROADCAST),
+                    static_cast<uint8_t>(state),
+                    ((state == FlightModeManagerState::ARMED) ? 0x01 : 0x00));
+            },
+            STATUS_TRANSMISSION_PERIOD) != 0;
+    return protocol->start() && result;
+}
+
+bool CanHandler::isStarted() { return protocol->isStarted(); }
+
+void CanHandler::sendEvent(EventId event)
+{
+    protocol->enqueueEvent(static_cast<uint8_t>(Priority::CRITICAL),
+                           static_cast<uint8_t>(PrimaryType::EVENTS),
+                           static_cast<uint8_t>(Board::PAYLOAD),
+                           static_cast<uint8_t>(Board::BROADCAST),
+                           static_cast<uint8_t>(event));
+}
+
+void CanHandler::handleCanMessage(const CanMessage &msg)
+{
+    PrimaryType msgType = static_cast<PrimaryType>(msg.getPrimaryType());
+
+    switch (msgType)
+    {
+        case PrimaryType::EVENTS:
+        {
+            handleCanEvent(msg);
+            break;
+        }
+        default:
+        {
+            LOG_WARN(logger, "Received unsupported message type: type={}",
+                     msgType);
+            break;
+        }
+    }
+}
+
+void CanHandler::handleCanEvent(const Boardcore::Canbus::CanMessage &msg)
+{
+    EventId eventId = static_cast<EventId>(msg.getSecondaryType());
+
+    auto it = eventToEvent.find(eventId);
+
+    if (it != eventToEvent.end())
+    {
+        EventBroker::getInstance().post(it->second, TOPIC_CAN);
+    }
+    else
+        LOG_WARN(logger, "Received unsupported event: id={}", eventId);
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/CanHandler/CanHandler.h b/src/boards/Payload/CanHandler/CanHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..2f040b4e05bdaf1b7cf1793dc231c790e59d87f0
--- /dev/null
+++ b/src/boards/Payload/CanHandler/CanHandler.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Federico Mandelli, Alberto Nidasio
+ *
+ * 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 <Payload/BoardScheduler.h>
+#include <common/CanConfig.h>
+#include <drivers/canbus/CanProtocol/CanProtocol.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+/**
+ * @class CanHandler
+ *
+ * @brief Payload implementation of CanProtocol with methods for ease of use
+ *
+ */
+class CanHandler : public Boardcore::Module
+{
+
+public:
+    /**
+     * @brief Creates the protocol and the driver and adds the filters.
+     *
+     * @warning With Payload motherboard CanDriver initialization could be
+     * blocking
+     */
+    explicit CanHandler(Boardcore::TaskScheduler *sched);
+
+    /**
+     * @brief Starts the protocol and adds the periodic messages to the task
+     * scheduler.
+     *
+     * @return true if the protocol started correctly and the tasks were
+     * successfully inserted
+     */
+    bool start();
+
+    /**
+     * @return true if the protocol is started
+     */
+    bool isStarted();
+
+    /**
+     * @brief Compile the the ID of the message and send the event trough
+     * CanProtocol
+     */
+    void sendEvent(Common::CanConfig::EventId event);
+
+private:
+    /**
+     * @brief Function called by CanProtocol when a new message is received
+     */
+    void handleCanMessage(const Boardcore::Canbus::CanMessage &msg);
+
+    /**
+     * @brief Converts the received CanEvent in Event and post it on TOPIC_CAN
+     */
+    void handleCanEvent(const Boardcore::Canbus::CanMessage &msg);
+
+    Boardcore::Canbus::CanbusDriver *driver;
+    Boardcore::Canbus::CanProtocol *protocol;
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("canhandler");
+
+    Boardcore::TaskScheduler *scheduler = nullptr;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/ActuatorsConfigs.h b/src/boards/Payload/Configs/ActuatorsConfigs.h
new file mode 100644
index 0000000000000000000000000000000000000000..015793dc403fb1a6d6c98039f1fb171e0e0bb7cb
--- /dev/null
+++ b/src/boards/Payload/Configs/ActuatorsConfigs.h
@@ -0,0 +1,68 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Federico Lolli
+ *
+ * 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 <drivers/timer/PWM.h>
+#include <drivers/timer/TimerUtils.h>
+
+namespace Payload
+{
+
+namespace ActuatorsConfigs
+{
+
+// Left servo
+static TIM_TypeDef* const SERVO_1_TIMER = TIM3;
+constexpr Boardcore::TimerUtils::Channel SERVO_1_PWM_CH =
+    Boardcore::TimerUtils::Channel::CHANNEL_1;
+
+constexpr float LEFT_SERVO_ROTATION  = 120;  // [deg]
+constexpr float LEFT_SERVO_MIN_PULSE = 900;  // [us]
+constexpr float LEFT_SERVO_MAX_PULSE =
+    LEFT_SERVO_MIN_PULSE + 10 * LEFT_SERVO_ROTATION;  // [us]
+
+// Right servo
+static TIM_TypeDef* const SERVO_2_TIMER = TIM3;
+constexpr Boardcore::TimerUtils::Channel SERVO_2_PWM_CH =
+    Boardcore::TimerUtils::Channel::CHANNEL_2;
+
+constexpr float RIGHT_SERVO_ROTATION  = 120;   // [deg]
+constexpr float RIGHT_SERVO_MIN_PULSE = 2100;  // [us]
+constexpr float RIGHT_SERVO_MAX_PULSE =
+    RIGHT_SERVO_MIN_PULSE - 10 * RIGHT_SERVO_ROTATION;  // [us]
+
+// Buzzer configs
+static TIM_TypeDef* const BUZZER_TIMER = TIM1;
+constexpr Boardcore::TimerUtils::Channel BUZZER_CHANNEL =
+    Boardcore::TimerUtils::Channel::CHANNEL_1;
+constexpr uint32_t BUZZER_FREQUENCY = 1000;
+constexpr float BUZZER_DUTY_CYCLE   = 0.5;
+
+constexpr uint32_t BUZZER_UPDATE_PERIOD = 100;  // [ms]
+
+constexpr uint32_t ROCKET_SS_ARMED_PERIOD = 500;
+constexpr uint32_t ROCKET_SS_LAND_PERIOD  = 1000;  // [ms]
+
+}  // namespace ActuatorsConfigs
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/CanHandlerConfig.h b/src/boards/Payload/Configs/CanHandlerConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..c3ffa58ecf598a14a200ddb6caa846bd59d069fc
--- /dev/null
+++ b/src/boards/Payload/Configs/CanHandlerConfig.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+
+namespace Payload
+{
+
+namespace CanHandlerConfig
+{
+
+constexpr unsigned int PITOT_TRANSMISSION_PERIOD  = 50;    // ms
+constexpr unsigned int STATUS_TRANSMISSION_PERIOD = 1000;  // ms
+
+}  // namespace CanHandlerConfig
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/FlightModeManagerConfig.h b/src/boards/Payload/Configs/FlightModeManagerConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d09f70c850b4d443550ab7e79539a767e542a7f
--- /dev/null
+++ b/src/boards/Payload/Configs/FlightModeManagerConfig.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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
+
+namespace Payload
+{
+constexpr unsigned int MISSION_TIMEOUT = 15 * 60 * 1000;  // [ms]
+constexpr unsigned int APOGEE_TIMEOUT  = 40 * 1000;       // [ms]
+constexpr unsigned int LOGGING_DELAY   = 30 * 1000;       // [ms]
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/FlightStatsRecorderConfig.h b/src/boards/Payload/Configs/FlightStatsRecorderConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab003f917f89fa54dd28a893eec2ac57b5fc09a3
--- /dev/null
+++ b/src/boards/Payload/Configs/FlightStatsRecorderConfig.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <stdint.h>
+
+namespace Payload
+{
+namespace FlightStatsRecorderConfig
+{
+constexpr uint32_t FSR_UPDATE_PERIOD = 100;  // [ms]
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/NASConfig.h b/src/boards/Payload/Configs/NASConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..d6b38e0856479c61358a139133e54234c59119ae
--- /dev/null
+++ b/src/boards/Payload/Configs/NASConfig.h
@@ -0,0 +1,67 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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/NAS/NASConfig.h>
+#include <algorithms/ReferenceValues.h>
+#include <common/ReferenceConfig.h>
+
+namespace Payload
+{
+
+namespace NASConfig
+{
+
+constexpr uint32_t UPDATE_PERIOD = 20;  // 50 hz
+
+constexpr int CALIBRATION_SAMPLES_COUNT = 20;
+constexpr int CALIBRATION_SLEEP_TIME    = 100;  // [ms]
+
+constexpr int ACCELERATION_THRESHOLD_SAMPLE = 50;
+
+constexpr float ACCELERATION_THRESHOLD = 2;  // [m/s^2]
+
+static const Boardcore::NASConfig config = {
+    UPDATE_PERIOD / 1000.0,          // T
+    0.0001f,                         // SIGMA_BETA
+    0.3f,                            // SIGMA_W
+    0.1f,                            // SIGMA_ACC
+    0.1f,                            // SIGMA_MAG
+    10.0f,                           // SIGMA_GPS
+    4.3f,                            // SIGMA_BAR
+    10.0f,                           // SIGMA_POS
+    10.0f,                           // SIGMA_VEL
+    10.0f,                           // SIGMA_PITOT
+    1.0f,                            // P_POS
+    10.0f,                           // P_POS_VERTICAL
+    1.0f,                            // P_VEL
+    10.0f,                           // P_VEL_VERTICAL
+    0.01f,                           // P_ATT
+    0.01f,                           // P_BIAS
+    6.0f,                            // SATS_NUM
+    Common::ReferenceConfig::nedMag  // NED_MAG
+};
+
+}  // namespace NASConfig
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/PinHandlerConfig.h b/src/boards/Payload/Configs/PinHandlerConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..d02c99e07c1334b573aa286c9aae5bf599f3d134
--- /dev/null
+++ b/src/boards/Payload/Configs/PinHandlerConfig.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio
+ *
+ * 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 <utils/PinObserver/PinObserver.h>
+
+namespace Payload
+{
+
+constexpr unsigned int NC_DETACH_PIN_THRESHOLD = 20;
+constexpr Boardcore::PinTransition NC_DETACH_PIN_TRIGGER =
+    Boardcore::PinTransition::RISING_EDGE;
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/RadioConfig.h b/src/boards/Payload/Configs/RadioConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..ade3dd7f1d92fcbacd065d309dbe554bb7a4afcd
--- /dev/null
+++ b/src/boards/Payload/Configs/RadioConfig.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <common/Mavlink.h>
+
+namespace Payload
+{
+namespace RadioConfig
+{
+// Mavlink driver template parameters
+constexpr uint32_t RADIO_PKT_LENGTH     = 255;
+constexpr uint32_t RADIO_OUT_QUEUE_SIZE = 20;
+constexpr uint32_t RADIO_MAV_MSG_LENGTH = MAVLINK_MAX_DIALECT_PAYLOAD_SIZE;
+
+constexpr uint32_t RADIO_PERIODIC_TELEMETRY_PERIOD = 200;
+
+constexpr uint16_t RADIO_SLEEP_AFTER_SEND = 50;
+constexpr size_t RADIO_OUT_BUFFER_MAX_AGE = 10;
+
+constexpr uint8_t MAV_SYSTEM_ID = 171;
+constexpr uint8_t MAV_COMP_ID   = 96;
+
+constexpr uint32_t MAVLINK_QUEUE_SIZE = 10;
+
+}  // namespace RadioConfig
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/SensorsConfig.h b/src/boards/Payload/Configs/SensorsConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..df790a830e160764b2b262a64da431bcab27f13d
--- /dev/null
+++ b/src/boards/Payload/Configs/SensorsConfig.h
@@ -0,0 +1,124 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <sensors/ADS131M08/ADS131M08.h>
+#include <sensors/H3LIS331DL/H3LIS331DL.h>
+#include <sensors/LIS2MDL/LIS2MDL.h>
+#include <sensors/LPS22DF/LPS22DF.h>
+#include <sensors/LPS28DFW/LPS28DFW.h>
+#include <sensors/LSM6DSRX/LSM6DSRX.h>
+#include <sensors/UBXGPS/UBXGPSSpi.h>
+
+namespace Payload
+{
+namespace SensorsConfig
+{
+constexpr Boardcore::LPS22DF::AVG LPS22DF_AVG = Boardcore::LPS22DF::AVG_4;
+constexpr Boardcore::LPS22DF::ODR LPS22DF_ODR = Boardcore::LPS22DF::ODR_100;
+
+constexpr Boardcore::LPS28DFW::AVG LPS28DFW_AVG = Boardcore::LPS28DFW::AVG_4;
+constexpr Boardcore::LPS28DFW::ODR LPS28DFW_ODR = Boardcore::LPS28DFW::ODR_100;
+constexpr Boardcore::LPS28DFW::FullScaleRange LPS28DFW_FSR =
+    Boardcore::LPS28DFW::FS_1260;
+
+constexpr Boardcore::H3LIS331DLDefs::OutputDataRate H3LIS331DL_ODR =
+    Boardcore::H3LIS331DLDefs::OutputDataRate::ODR_400;
+constexpr Boardcore::H3LIS331DLDefs::BlockDataUpdate H3LIS331DL_BDU =
+    Boardcore::H3LIS331DLDefs::BlockDataUpdate::BDU_CONTINUOS_UPDATE;
+constexpr Boardcore::H3LIS331DLDefs::FullScaleRange H3LIS331DL_FSR =
+    Boardcore::H3LIS331DLDefs::FullScaleRange::FS_100;
+
+constexpr Boardcore::LIS2MDL::OperativeMode LIS2MDL_OPERATIVE_MODE =
+    Boardcore::LIS2MDL::MD_CONTINUOUS;
+constexpr Boardcore::LIS2MDL::ODR LIS2MDL_ODR = Boardcore::LIS2MDL::ODR_100_HZ;
+constexpr unsigned int LIS2MDL_TEMPERATURE_DIVIDER = 5;
+
+constexpr uint8_t UBXGPS_SAMPLE_RATE = 5;
+
+constexpr Boardcore::LSM6DSRXConfig::BDU LSM6DSRX_BDU =
+    Boardcore::LSM6DSRXConfig::BDU::CONTINUOUS_UPDATE;
+
+constexpr Boardcore::LSM6DSRXConfig::ACC_FULLSCALE LSM6DSRX_ACC_FS =
+    Boardcore::LSM6DSRXConfig::ACC_FULLSCALE::G16;
+constexpr Boardcore::LSM6DSRXConfig::ACC_ODR LSM6DSRX_ACC_ODR =
+    Boardcore::LSM6DSRXConfig::ACC_ODR::HZ_416;
+constexpr Boardcore::LSM6DSRXConfig::OPERATING_MODE LSM6DSRX_OPERATING_MODE =
+    Boardcore::LSM6DSRXConfig::OPERATING_MODE::NORMAL;
+
+constexpr Boardcore::LSM6DSRXConfig::GYR_FULLSCALE LSM6DSRX_GYR_FS =
+    Boardcore::LSM6DSRXConfig::GYR_FULLSCALE::DPS_4000;
+constexpr Boardcore::LSM6DSRXConfig::GYR_ODR LSM6DSRX_GYR_ODR =
+    Boardcore::LSM6DSRXConfig::GYR_ODR::HZ_416;
+
+constexpr Boardcore::LSM6DSRXConfig::FIFO_MODE LSM6DSRX_FIFO_MODE =
+    Boardcore::LSM6DSRXConfig::FIFO_MODE::CONTINUOUS;
+constexpr Boardcore::LSM6DSRXConfig::FIFO_TIMESTAMP_DECIMATION
+    LSM6DSRX_FIFO_TIMESTAMP_DECIMATION =
+        Boardcore::LSM6DSRXConfig::FIFO_TIMESTAMP_DECIMATION::DEC_1;
+constexpr Boardcore::LSM6DSRXConfig::FIFO_TEMPERATURE_BDR
+    LSM6DSRX_FIFO_TEMPERATURE_BDR =
+        Boardcore::LSM6DSRXConfig::FIFO_TEMPERATURE_BDR::DISABLED;
+
+constexpr Boardcore::ADS131M08Defs::OversamplingRatio
+    ADS131M08_OVERSAMPLING_RATIO =
+        Boardcore::ADS131M08Defs::OversamplingRatio::OSR_8192;
+constexpr bool ADS131M08_GLOBAL_CHOP_MODE = true;
+
+constexpr uint32_t LPS22DF_PERIOD         = 20;  // [ms] 50Hz
+constexpr uint32_t LPS28DFW_PERIOD        = 20;  // [ms] 50Hz
+constexpr uint32_t H3LIS331DL_PERIOD      = 10;  // [ms] 100Hz
+constexpr uint32_t LIS2MDL_PERIOD         = 10;  // [ms] 100Hz
+constexpr uint32_t UBXGPS_PERIOD          = 1000 / UBXGPS_SAMPLE_RATE;  // [ms]
+constexpr uint32_t LSM6DSRX_PERIOD        = 20;  // [ms] 50Hz
+constexpr uint32_t ADS131M08_PERIOD       = 10;  // [ms] 100Hz
+constexpr uint32_t IMU_PERIOD             = 20;  // [ms] Fake processed IMU 50Hz
+constexpr uint32_t MAG_CALIBRATION_PERIOD = 100;  // [ms] 10Hz
+
+// ADC sensors configs
+constexpr float ADC_VOLTAGE_RANGE = 1.2f;  // [V] Voltage reading range
+constexpr Boardcore::ADS131M08Defs::Channel BATTERY_VOLTAGE_CHANNEL =
+    Boardcore::ADS131M08Defs::Channel::CHANNEL_1;
+constexpr Boardcore::ADS131M08Defs::Channel DYNAMIC_PRESSURE_CHANNEL =
+    Boardcore::ADS131M08Defs::Channel::CHANNEL_2;
+constexpr Boardcore::ADS131M08Defs::Channel STATIC_PRESSURE_CHANNEL =
+    Boardcore::ADS131M08Defs::Channel::CHANNEL_4;
+constexpr Boardcore::ADS131M08Defs::Channel CURRENT_CHANNEL =
+    Boardcore::ADS131M08Defs::Channel::CHANNEL_5;
+constexpr Boardcore::ADS131M08Defs::Channel CAM_BATTERY_VOLTAGE_CHANNEL =
+    Boardcore::ADS131M08Defs::Channel::CHANNEL_7;
+// ADC Voltage divider
+constexpr float BATTERY_VOLTAGE_CONVERSION_FACTOR =
+    (20.f / 2.4f) +
+    1;  // 20 kOhm resistor and 2.4 kOhm resistor for voltage divider
+constexpr float CURRENT_CONVERSION_FACTOR =
+    (20.f / 4.f) / (12.f / (12.f + 33.f));
+constexpr float CURRENT_OFFSET = 0.133333333f;  // V in ADC
+
+constexpr unsigned int NUMBER_OF_SENSORS = 8;
+
+// Calibration samples
+constexpr unsigned int CALIBRATION_SAMPLES = 20;
+constexpr unsigned int CALIBRATION_PERIOD  = 100;
+
+}  // namespace SensorsConfig
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/VerticalVelocityConfig.h b/src/boards/Payload/Configs/VerticalVelocityConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..28e36e0701311f5c037e7501747c1bbff4fe3b79
--- /dev/null
+++ b/src/boards/Payload/Configs/VerticalVelocityConfig.h
@@ -0,0 +1,37 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Raul Radu
+ *
+ * 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
+
+namespace Payload
+{
+
+namespace FailSafe
+{
+
+constexpr int FAILSAFE_VERTICAL_VELOCITY_TRIGGER_PERIOD     = 10;  // [ms]
+constexpr float FAILSAFE_VERTICAL_VELOCITY_THRESHOLD        = 12;  // [m/s]
+constexpr int FAILSAFE_VERTICAL_VELOCITY_TRIGGER_CONFIDENCE = 30;
+
+}  // namespace FailSafe
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/WESConfig.h b/src/boards/Payload/Configs/WESConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..36382803a9472568c8e9bfdf195847383cf561a5
--- /dev/null
+++ b/src/boards/Payload/Configs/WESConfig.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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 <miosix.h>
+
+namespace Payload
+{
+
+namespace WESConfig
+{
+
+constexpr uint32_t WES_CALIBRATION_TIMEOUT =
+    5 * 1000;  // time needed for the first loop [ms]
+
+constexpr int WES_CALIBRATION_SAMPLE_NUMBER =
+    20;  // number to sample to take in the first loop
+constexpr uint32_t WES_CALIBRATION_UPDATE_PERIOD =
+    WES_CALIBRATION_TIMEOUT / WES_CALIBRATION_SAMPLE_NUMBER;
+constexpr uint32_t WES_PREDICTION_UPDATE_PERIOD =
+    100;  // update period of WES[ms]
+
+}  // namespace WESConfig
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Configs/WingConfig.h b/src/boards/Payload/Configs/WingConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f8dbd72192a23106363a84d0a2061ac8db5176e9
--- /dev/null
+++ b/src/boards/Payload/Configs/WingConfig.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli
+ *
+ * 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
+
+namespace Payload
+{
+
+namespace WingConfig
+{
+// Algorithm configuration
+
+#if defined(EUROC)
+constexpr float DEFAULT_TARGET_LAT = 39.389733;
+constexpr float DEFAULT_TARGET_LON = -8.288992;
+#elif defined(ROCCARASO)
+constexpr float DEFAULT_TARGET_LAT = 41.809216;
+constexpr float DEFAULT_TARGET_LON = 14.055310;
+#else  // Milan
+constexpr float DEFAULT_TARGET_LAT = 41.809216;
+constexpr float DEFAULT_TARGET_LON = 14.055310;
+#endif
+
+constexpr int WING_UPDATE_PERIOD = 1000;  // [ms]
+
+constexpr float PI_CONTROLLER_SATURATION_MAX_LIMIT = 2.09439;
+constexpr float PI_CONTROLLER_SATURATION_MIN_LIMIT = -2.09439;
+
+constexpr int GUIDANCE_CONFIDENCE                = 15;
+constexpr int GUIDANCE_M1_ALTITUDE_THRESHOLD     = 250;  //[m]
+constexpr int GUIDANCE_M2_ALTITUDE_THRESHOLD     = 150;  //[m]
+constexpr int GUIDANCE_TARGET_ALTITUDE_THRESHOLD = 50;   //[m]
+
+// TODO check this parameter preflight
+constexpr float KP                                   = 0.4;   //[m]
+constexpr float KI                                   = 0.08;  //[m]
+constexpr float ALTITUDE_TRIGGER_DEPLOYMENT_ALTITUDE = 300;   // [meters]
+
+constexpr int ALTITUDE_TRIGGER_CONFIDENCE = 10;   // [number of sample]
+constexpr int ALTITUDE_TRIGGER_PERIOD     = 100;  //[ms]
+}  // namespace WingConfig
+
+}  // namespace Payload
diff --git a/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.cpp b/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..faf62811c45892dd27aac1dcf5d63833f2db9f2b
--- /dev/null
+++ b/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.cpp
@@ -0,0 +1,177 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Configs/FlightStatsRecorderConfig.h>
+#include <Payload/FlightStatsRecorder/FlightStatsRecorder.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+
+#include <Eigen/Core>
+
+using namespace Boardcore;
+using namespace Payload::FlightStatsRecorderConfig;
+using namespace Eigen;
+using namespace miosix;
+
+namespace Payload
+{
+FlightStatsRecorder::FlightStatsRecorder(TaskScheduler* sched)
+    : scheduler(sched)
+{
+    // Init the data structure to avoid UB
+    stats.max_airspeed_pitot = 0;
+    stats.apogee_alt         = 0;
+    stats.apogee_lat         = 0;
+    stats.apogee_lon         = 0;
+    stats.apogee_ts          = 0;
+    stats.cpu_load           = 0;
+    stats.dpl_max_acc        = 0;
+    stats.dpl_ts             = 0;
+    stats.free_heap          = 0;
+    stats.liftoff_max_acc    = 0;
+    stats.liftoff_max_acc_ts = 0;
+    stats.max_z_speed        = 0;
+    stats.max_z_speed_ts     = 0;
+    stats.min_pressure       = 0;
+    stats.max_speed_altitude = 0;
+}
+
+bool FlightStatsRecorder::start()
+{
+    return scheduler->addTask([&]() { update(); }, FSR_UPDATE_PERIOD,
+                              TaskScheduler::Policy::RECOVER);
+}
+
+void FlightStatsRecorder::update()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // FMM state
+    FlightModeManagerState flightState =
+        modules.get<FlightModeManager>()->getStatus().state;
+
+    // Data gathering
+    Sensors* sensor           = modules.get<Sensors>();
+    AccelerometerData accData = sensor->getIMULastSample();
+    PressureData baroData     = sensor->getStaticPressureLastSample();
+    PitotData pitotData       = sensor->getPitotLastSample();
+    GPSData gpsData           = sensor->getGPSLastSample();
+    NASState nasData          = modules.get<NASController>()->getNasState();
+
+    // Store the apogee ts
+    uint64_t previousApogee = stats.apogee_ts;
+
+    // Update the data
+    updateAcc(flightState, accData);
+    updateBaro(flightState, baroData);
+    updatePitot(flightState, pitotData);
+    updateNAS(flightState, nasData);
+
+    // If the apogee ts is different update the GPS apogee coordinates
+    if (previousApogee != stats.apogee_ts)
+    {
+        Lock<FastMutex> l(mutex);
+        // minus to get positive altitude
+        stats.apogee_alt = -nasData.d;
+        stats.apogee_lat = gpsData.latitude;
+        stats.apogee_lon = gpsData.longitude;
+    }
+}
+
+mavlink_payload_stats_tm_t FlightStatsRecorder::getStats()
+{
+    Lock<FastMutex> l(mutex);
+    return stats;
+}
+
+void FlightStatsRecorder::updateAcc(FlightModeManagerState flightState,
+                                    AccelerometerData data)
+{
+    Vector3f accelerations = static_cast<Vector3f>(data);
+    float norm             = accelerations.norm();
+
+    if (flightState == FlightModeManagerState::ASCENDING)
+    {
+        Lock<FastMutex> l(mutex);
+
+        stats.liftoff_max_acc = std::max(stats.liftoff_max_acc, norm);
+        bool changed          = stats.liftoff_max_acc == norm;
+
+        // In case of a change update the timestamp
+        stats.liftoff_max_acc_ts =
+            changed ? data.accelerationTimestamp : stats.liftoff_max_acc_ts;
+    }
+    else if (flightState >= FlightModeManagerState::DROGUE_DESCENT)
+    {
+        Lock<FastMutex> l(mutex);
+
+        stats.dpl_max_acc = std::max(stats.dpl_max_acc, norm);
+
+        // Change the timestamp of DPL if not already done
+        stats.dpl_ts =
+            stats.dpl_ts == 0 ? data.accelerationTimestamp : stats.dpl_ts;
+    }
+}
+
+void FlightStatsRecorder::updateBaro(FlightModeManagerState flightState,
+                                     PressureData data)
+{
+    Lock<FastMutex> l(mutex);
+
+    if (stats.min_pressure == 0)
+    {
+        stats.min_pressure = data.pressure;
+    }
+
+    // Update the min
+    stats.min_pressure = std::min(stats.min_pressure, data.pressure);
+}
+
+void FlightStatsRecorder::updatePitot(FlightModeManagerState flightState,
+                                      PitotData data)
+{
+    Lock<FastMutex> l(mutex);
+
+    stats.max_airspeed_pitot =
+        std::max(stats.max_airspeed_pitot, data.airspeed);
+}
+
+void FlightStatsRecorder::updateNAS(FlightModeManagerState flightState,
+                                    NASState state)
+{
+    if (flightState == FlightModeManagerState::ASCENDING)
+    {
+        Lock<FastMutex> l(mutex);
+
+        stats.max_z_speed = std::max(stats.max_z_speed, -state.vd);
+        bool changed      = stats.max_z_speed == -state.vd;
+
+        // Change the max speed altitude and timestamp if the max speed changed
+        stats.max_speed_altitude =
+            changed ? -state.d : stats.max_speed_altitude;
+
+        stats.max_z_speed_ts = changed ? state.timestamp : stats.max_z_speed_ts;
+    }
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.h b/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.h
new file mode 100644
index 0000000000000000000000000000000000000000..1470c416862cc738f3d757390203a669e77d5074
--- /dev/null
+++ b/src/boards/Payload/FlightStatsRecorder/FlightStatsRecorder.h
@@ -0,0 +1,80 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <algorithms/ADA/ADAData.h>
+#include <algorithms/NAS/NASState.h>
+#include <common/Mavlink.h>
+#include <scheduler/TaskScheduler.h>
+#include <sensors/SensorData.h>
+#include <sensors/analog/Pitot/PitotData.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+class FlightStatsRecorder : public Boardcore::Module
+{
+public:
+    FlightStatsRecorder(Boardcore::TaskScheduler* sched);
+
+    /**
+     * @brief Adds a task to the scheduler to update the stats
+     */
+    bool start();
+
+    /**
+     * @brief Gets the packet already populated
+     */
+    mavlink_payload_stats_tm_t getStats();
+
+private:
+    /**
+     * @brief Update method that gathers all the info to update the data
+     * structure
+     */
+    void update();
+
+    // Update methods
+    void updateAcc(FlightModeManagerState flightState,
+                   Boardcore::AccelerometerData data);
+    void updateBaro(FlightModeManagerState flightState,
+                    Boardcore::PressureData data);
+    void updateDPLPressure(FlightModeManagerState flightState,
+                           Boardcore::PressureData data);
+    void updatePitot(FlightModeManagerState flightState,
+                     Boardcore::PitotData data);
+    void updateNAS(FlightModeManagerState flightState,
+                   Boardcore::NASState data);
+
+    // Update scheduler to update all the data
+    Boardcore::TaskScheduler* scheduler = nullptr;
+
+    // Data structure
+    mavlink_payload_stats_tm_t stats;
+
+    // Update mutex
+    miosix::FastMutex mutex;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/PinHandler/PinHandler.cpp b/src/boards/Payload/PinHandler/PinHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4005f105beded8ce6cb1a0cacda21029020bba26
--- /dev/null
+++ b/src/boards/Payload/PinHandler/PinHandler.cpp
@@ -0,0 +1,75 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio, Federico Mandelli
+ *
+ * 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 "PinHandler.h"
+
+#include <Payload/Configs/PinHandlerConfig.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+#include <interfaces-impl/hwmapping.h>
+#include <miosix.h>
+
+#include <functional>
+
+using namespace std::placeholders;
+using namespace miosix;
+using namespace Boardcore;
+using namespace Common;
+
+// TODO change hwmapping for Euroc this is need to have the right name
+using nosecone_detach = gpios::liftoff_detach;
+
+namespace Payload
+{
+
+void PinHandler::onExpulsionPinTransition(PinTransition transition)
+{
+    if (transition == NC_DETACH_PIN_TRIGGER)
+        EventBroker::getInstance().post(FLIGHT_NC_DETACHED, TOPIC_FLIGHT);
+}
+
+bool PinHandler::start()
+{
+    running = PinObserver::getInstance().start();
+    return running;
+}
+
+bool PinHandler::isStarted() { return running; }
+
+std::map<PinsList, PinData> PinHandler::getPinsData()
+{
+    std::map<PinsList, PinData> data;
+
+    data[PinsList::NOSECONE_PIN] =
+        PinObserver::getInstance().getPinData(nosecone_detach::getPin());
+
+    return data;
+}
+
+PinHandler::PinHandler() : running(false)
+{
+    PinObserver::getInstance().registerPinCallback(
+        nosecone_detach::getPin(),
+        std::bind(&PinHandler::onExpulsionPinTransition, this, _1),
+        NC_DETACH_PIN_THRESHOLD);
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/PinHandler/PinHandler.h b/src/boards/Payload/PinHandler/PinHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..45dcff2c9fd1b27c766b43120161051ce5b06f0a
--- /dev/null
+++ b/src/boards/Payload/PinHandler/PinHandler.h
@@ -0,0 +1,66 @@
+/* Copyright (c) 2019-2023 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio, Federico Mandelli
+ *
+ * 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 <common/Mavlink.h>
+#include <diagnostic/PrintLogger.h>
+#include <utils/PinObserver/PinObserver.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+
+/**
+ * @brief This class contains the handlers for the detach pins on the rocket.
+ *
+ * It uses Boardcore's PinObserver to bind these functions to the GPIO pins.
+ * The handlers post an event on the EventBroker.
+ */
+class PinHandler : public Boardcore::Module
+{
+public:
+    PinHandler();
+
+    bool start();
+
+    bool isStarted();
+
+    /**
+     * @brief Called when the deployment servo actuation is detected via the
+     * optical sensor.
+     */
+    void onExpulsionPinTransition(Boardcore::PinTransition transition);
+
+    /**
+     * @brief Returns a vector with all the pins data.
+     */
+    std::map<PinsList, Boardcore::PinData> getPinsData();
+
+private:
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("pinhandler");
+
+    std::atomic<bool> running;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Radio/Radio.cpp b/src/boards/Payload/Radio/Radio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc84d7db797f658d561075c1f89e6967b8794f88
--- /dev/null
+++ b/src/boards/Payload/Radio/Radio.cpp
@@ -0,0 +1,517 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Actuators/Actuators.h>
+#include <Payload/AltitudeTrigger/AltitudeTrigger.h>
+#include <Payload/Buses.h>
+#include <Payload/Radio/Radio.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/StateMachines/WingController/WingController.h>
+#include <Payload/TMRepository/TMRepository.h>
+#include <common/Events.h>
+#include <common/Topics.h>
+#include <drivers/interrupt/external_interrupts.h>
+#include <events/EventBroker.h>
+#include <radio/SX1278/SX1278Frontends.h>
+
+#include <memory>
+
+using namespace Boardcore;
+using namespace Common;
+
+#define SX1278_IRQ_DIO0 EXTI3_IRQHandlerImpl
+#define SX1278_IRQ_DIO1 EXTI4_IRQHandlerImpl
+#define SX1278_IRQ_DIO3 EXTI5_IRQHandlerImpl
+
+void __attribute__((used)) SX1278_IRQ_DIO0()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<Payload::Radio>()->transceiver)
+    {
+        modules.get<Payload::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+
+void __attribute__((used)) SX1278_IRQ_DIO1()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<Payload::Radio>()->transceiver)
+    {
+        modules.get<Payload::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+
+void __attribute__((used)) SX1278_IRQ_DIO3()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<Payload::Radio>()->transceiver)
+    {
+        modules.get<Payload::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+namespace Payload
+{
+
+Radio::Radio(TaskScheduler* sched) : scheduler(sched) {}
+
+bool Radio::start()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    // Config the transceiver
+    SX1278Fsk::Config config = {
+        .freq_rf    = 868000000,
+        .freq_dev   = 50000,
+        .bitrate    = 48000,
+        .rx_bw      = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+        .afc_bw     = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+        .ocp        = 120,
+        .power      = 13,
+        .shaping    = Boardcore::SX1278Fsk::Config::Shaping::GAUSSIAN_BT_1_0,
+        .dc_free    = Boardcore::SX1278Fsk::Config::DcFree::WHITENING,
+        .enable_crc = false};
+
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<Skyward433Frontend>();
+
+    transceiver = new SX1278Fsk(
+        modules.get<Buses>()->spi6, miosix::radio::cs::getPin(),
+        miosix::radio::dio0::getPin(), miosix::radio::dio1::getPin(),
+        miosix::radio::dio3::getPin(), SPI::ClockDivider::DIV_128,
+        std::move(frontend));
+
+    // Config the radio
+    SX1278Fsk::Error error = transceiver->init(config);
+
+    // Add periodic telemetry send task
+    uint8_t result =
+        scheduler->addTask([=]() { this->sendPeriodicMessage(); },
+                           RadioConfig::RADIO_PERIODIC_TELEMETRY_PERIOD,
+                           TaskScheduler::Policy::RECOVER);
+    result *= scheduler->addTask(
+        [&]()
+        {
+            this->enqueueMsg(
+                modules.get<TMRepository>()->packSystemTm(MAV_STATS_ID, 0, 0));
+        },
+        RadioConfig::RADIO_PERIODIC_TELEMETRY_PERIOD * 2,
+        TaskScheduler::Policy::RECOVER);
+
+    // Config mavDriver
+    mavDriver = new MavDriver(
+        transceiver,
+        [=](MavDriver*, const mavlink_message_t& msg)
+        { this->handleMavlinkMessage(msg); },
+        RadioConfig::RADIO_SLEEP_AFTER_SEND,
+        RadioConfig::RADIO_OUT_BUFFER_MAX_AGE);
+
+    // Check radio failure
+    if (error != SX1278Fsk::Error::NONE)
+    {
+        return false;
+    }
+
+    // Start the mavlink driver thread
+    return mavDriver->start() && result != 0;
+}
+
+void Radio::sendAck(const mavlink_message_t& msg)
+{
+    mavlink_message_t ackMsg;
+    mavlink_msg_ack_tm_pack(RadioConfig::MAV_SYSTEM_ID,
+                            RadioConfig::MAV_COMP_ID, &ackMsg, msg.msgid,
+                            msg.seq);
+    enqueueMsg(ackMsg);
+}
+
+void Radio::sendNack(const mavlink_message_t& msg)
+{
+    mavlink_message_t nackMsg;
+    mavlink_msg_nack_tm_pack(RadioConfig::MAV_SYSTEM_ID,
+                             RadioConfig::MAV_COMP_ID, &nackMsg, msg.msgid,
+                             msg.seq);
+    enqueueMsg(nackMsg);
+}
+
+void Radio::logStatus() { Logger::getInstance().log(mavDriver->getStatus()); }
+
+bool Radio::isStarted()
+{
+    return mavDriver->isStarted() && scheduler->isRunning();
+}
+
+void Radio::handleMavlinkMessage(const mavlink_message_t& msg)
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    switch (msg.msgid)
+    {
+        case MAVLINK_MSG_ID_PING_TC:
+        {
+            // Do nothing, just add the ack to the queue
+            break;
+        }
+        case MAVLINK_MSG_ID_COMMAND_TC:
+        {
+            // Let the handle command reply to the message
+            return handleCommand(msg);
+        }
+        case MAVLINK_MSG_ID_SYSTEM_TM_REQUEST_TC:
+        {
+            SystemTMList tmId = static_cast<SystemTMList>(
+                mavlink_msg_system_tm_request_tc_get_tm_id(&msg));
+
+            if (tmId == SystemTMList::MAV_SENSORS_STATE_ID)
+            {
+                mavlink_message_t msg;
+                mavlink_sensor_state_tm_t tm;
+                auto sensorsState = ModuleManager::getInstance()
+                                        .get<Sensors>()
+                                        ->getSensorInfo();
+                for (SensorInfo i : sensorsState)
+                {
+                    strcpy(tm.sensor_name, i.id.c_str());
+                    tm.state = 0;
+                    if (i.isEnabled)
+                    {
+                        tm.state += 1;
+                    }
+                    if (i.isInitialized)
+                    {
+                        tm.state += 2;
+                    }
+                    mavlink_msg_sensor_state_tm_encode(
+                        RadioConfig::MAV_SYSTEM_ID, RadioConfig::MAV_COMP_ID,
+                        &msg, &tm);
+                    enqueueMsg(msg);
+                }
+            }
+            else
+            {
+
+                // Add to the queue the respose
+                mavlink_message_t response =
+                    modules.get<TMRepository>()->packSystemTm(tmId, msg.msgid,
+                                                              msg.seq);
+
+                // Add the response to the queue
+                enqueueMsg(response);
+
+                // Check if the TM repo answered with a NACK. If so the function
+                // must return to avoid sending a default ack
+                if (response.msgid == MAVLINK_MSG_ID_NACK_TM)
+                {
+                    return;
+                }
+            }
+            break;
+        }
+        case MAVLINK_MSG_ID_SENSOR_TM_REQUEST_TC:
+        {
+            SensorsTMList tmId = static_cast<SensorsTMList>(
+                mavlink_msg_sensor_tm_request_tc_get_sensor_name(&msg));
+
+            // Add to the queue the respose
+            mavlink_message_t response =
+                modules.get<TMRepository>()->packSensorsTm(tmId, msg.msgid,
+                                                           msg.seq);
+
+            // Add the response to the queue
+            enqueueMsg(response);
+
+            // Check if the TM repo answered with a NACK. If so the function
+            // must return to avoid sending a default ack
+            if (response.msgid == MAVLINK_MSG_ID_NACK_TM)
+            {
+                return;
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_SERVO_TM_REQUEST_TC:
+        {
+            ServosList servoId = static_cast<ServosList>(
+                mavlink_msg_servo_tm_request_tc_get_servo_id(&msg));
+
+            // Add to the queue the respose
+            mavlink_message_t response =
+                modules.get<TMRepository>()->packServoTm(servoId, msg.msgid,
+                                                         msg.seq);
+
+            // Add the response to the queue
+            mavDriver->enqueueMsg(response);
+
+            // Check if the TM repo answered with a NACK. If so the function
+            // must return to avoid sending a default ack
+            if (response.msgid == MAVLINK_MSG_ID_NACK_TM)
+            {
+                return;
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_SERVO_ANGLE_TC:
+        {
+            ServosList servoId = static_cast<ServosList>(
+                mavlink_msg_set_servo_angle_tc_get_servo_id(&msg));
+            float angle = mavlink_msg_set_servo_angle_tc_get_angle(&msg);
+
+            // Move the servo, if it fails send a nack
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                    FlightModeManagerState::TEST_MODE ||
+                !modules.get<Actuators>()->setServoAngle(servoId, angle))
+            {
+                return sendNack(msg);
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_WIGGLE_SERVO_TC:
+        {
+            ServosList servoId = static_cast<ServosList>(
+                mavlink_msg_wiggle_servo_tc_get_servo_id(&msg));
+
+            // Send nack if the FMM is not in test mode
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                    FlightModeManagerState::TEST_MODE ||
+                !modules.get<Actuators>()->wiggleServo(servoId))
+            {
+                return sendNack(msg);
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_RESET_SERVO_TC:
+        {
+            ServosList servoId = static_cast<ServosList>(
+                mavlink_msg_reset_servo_tc_get_servo_id(&msg));
+
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                    FlightModeManagerState::TEST_MODE ||
+                !modules.get<Actuators>()->setServo(servoId, 0))
+            {
+                return sendNack(msg);
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_REFERENCE_ALTITUDE_TC:
+        {
+            float altitude =
+                mavlink_msg_set_reference_altitude_tc_get_ref_altitude(&msg);
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<NASController>()->setReferenceAltitude(altitude);
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_REFERENCE_TEMPERATURE_TC:
+        {
+            float temperature =
+                mavlink_msg_set_reference_temperature_tc_get_ref_temp(&msg);
+
+            temperature += 273.15;
+
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<NASController>()->setReferenceAltitude(temperature);
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_DEPLOYMENT_ALTITUDE_TC:
+        {
+            float altitude =
+                mavlink_msg_set_deployment_altitude_tc_get_dpl_altitude(&msg);
+
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<AltitudeTrigger>()->setDeploymentAltitude(altitude);
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_ORIENTATION_TC:
+        {
+            float yaw   = mavlink_msg_set_orientation_tc_get_yaw(&msg);
+            float pitch = mavlink_msg_set_orientation_tc_get_pitch(&msg);
+            float roll  = mavlink_msg_set_orientation_tc_get_roll(&msg);
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<NASController>()->setOrientation(yaw, pitch, roll);
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_COORDINATES_TC:
+        {
+            float latitude = mavlink_msg_set_coordinates_tc_get_latitude(&msg);
+            float longitude =
+                mavlink_msg_set_coordinates_tc_get_longitude(&msg);
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<NASController>()->setCoordinates(
+                Eigen::Vector2f(latitude, longitude));
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_TARGET_COORDINATES_TC:
+        {
+            float latitude = mavlink_msg_set_coordinates_tc_get_latitude(&msg);
+            float longitude =
+                mavlink_msg_set_coordinates_tc_get_longitude(&msg);
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+            modules.get<WingController>()->setTargetPosition(
+                Eigen::Vector2f(latitude, longitude));
+            break;
+        }
+        case MAVLINK_MSG_ID_SET_ALGORITHM_TC:
+        {
+            u_int8_t algoID =
+                mavlink_msg_set_algorithm_tc_get_algorithm_number(&msg);
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                    FlightModeManagerState::TEST_MODE ||
+                !modules.get<WingController>()->selectAlgorithm(algoID))
+            {
+                return sendNack(msg);
+            }
+
+            break;
+        }
+        case MAVLINK_MSG_ID_RAW_EVENT_TC:
+        {
+            uint8_t topicId = mavlink_msg_raw_event_tc_get_topic_id(&msg);
+            uint8_t eventId = mavlink_msg_raw_event_tc_get_event_id(&msg);
+
+            // Send the event only if the flight mode manager is in test mode
+            if (modules.get<FlightModeManager>()->getStatus().state !=
+                FlightModeManagerState::TEST_MODE)
+            {
+                return sendNack(msg);
+            }
+
+            EventBroker::getInstance().post(topicId, eventId);
+            break;
+        }
+    }
+
+    // At the end send the ack message
+    sendAck(msg);
+}
+
+void Radio::handleCommand(const mavlink_message_t& msg)
+{
+    MavCommandList commandId = static_cast<MavCommandList>(
+        mavlink_msg_command_tc_get_command_id(&msg));
+
+    static const std::map<MavCommandList, Events> commandToEvent{
+        {MAV_CMD_ARM, TMTC_ARM},
+        {MAV_CMD_DISARM, TMTC_DISARM},
+        {MAV_CMD_CALIBRATE, TMTC_CALIBRATE},
+        {MAV_CMD_FORCE_INIT, TMTC_FORCE_INIT},
+        {MAV_CMD_FORCE_LAUNCH, TMTC_FORCE_LAUNCH},
+        {MAV_CMD_FORCE_LANDING, TMTC_FORCE_LANDING},
+        {MAV_CMD_FORCE_APOGEE, TMTC_FORCE_APOGEE},
+        {MAV_CMD_FORCE_EXPULSION, TMTC_FORCE_EXPULSION},
+        {MAV_CMD_FORCE_DEPLOYMENT, TMTC_FORCE_DEPLOYMENT},
+        {MAV_CMD_START_LOGGING, TMTC_START_LOGGING},
+        {MAV_CMD_STOP_LOGGING, TMTC_STOP_LOGGING},
+        {MAV_CMD_FORCE_REBOOT, TMTC_RESET_BOARD},
+        {MAV_CMD_ENTER_TEST_MODE, TMTC_ENTER_TEST_MODE},
+        {MAV_CMD_EXIT_TEST_MODE, TMTC_EXIT_TEST_MODE},
+        {MAV_CMD_START_RECORDING, TMTC_START_RECORDING},
+        {MAV_CMD_STOP_RECORDING, TMTC_STOP_RECORDING},
+    };
+    switch (commandId)
+    {
+        case MAV_CMD_SAVE_CALIBRATION:
+        {
+            // Save the sensor calibration and adopt it
+            ModuleManager::getInstance().get<Sensors>()->writeMagCalibration();
+            break;
+        }
+        default:
+        {
+            // If the command is not a particular one, look for it inside the
+            // map
+            auto it = commandToEvent.find(commandId);
+
+            if (it != commandToEvent.end())
+            {
+                EventBroker::getInstance().post(it->second, TOPIC_TMTC);
+            }
+            else
+            {
+                return sendNack(msg);
+            }
+        }
+    }
+    // Acknowledge the message
+    sendAck(msg);
+}
+
+void Radio::sendPeriodicMessage()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Send all the queue messages
+    {
+        Lock<FastMutex> lock(queueMutex);
+
+        for (uint8_t i = 0; i < messageQueueIndex; i++)
+        {
+            mavDriver->enqueueMsg(messageQueue[i]);
+        }
+
+        // Reset the index
+        messageQueueIndex = 0;
+    }
+
+    mavDriver->enqueueMsg(
+        modules.get<TMRepository>()->packSystemTm(MAV_FLIGHT_ID, 0, 0));
+}
+
+void Radio::enqueueMsg(const mavlink_message_t& msg)
+{
+    {
+        Lock<FastMutex> lock(queueMutex);
+
+        // Insert the message inside the queue only if there is enough space
+        if (messageQueueIndex < RadioConfig::MAVLINK_QUEUE_SIZE)
+        {
+            messageQueue[messageQueueIndex] = msg;
+            messageQueueIndex++;
+        }
+    }
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/Radio/Radio.h b/src/boards/Payload/Radio/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..4aa33b844b5eeffb2c7e2e1785f1c9c5532a2ddc
--- /dev/null
+++ b/src/boards/Payload/Radio/Radio.h
@@ -0,0 +1,102 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Configs/RadioConfig.h>
+#include <common/Mavlink.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+#include <radio/SX1278/SX1278Fsk.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+using MavDriver = Boardcore::MavlinkDriver<Boardcore::SX1278Fsk::MTU,
+                                           RadioConfig::RADIO_OUT_QUEUE_SIZE,
+                                           RadioConfig::RADIO_MAV_MSG_LENGTH>;
+
+class Radio : public Boardcore::Module
+{
+public:
+    Radio(Boardcore::TaskScheduler* sched);
+
+    /**
+     * @brief Starts the MavlinkDriver
+     */
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Sends via radio an acknowledge message about the parameter passed
+     * message
+     */
+    void sendAck(const mavlink_message_t& msg);
+
+    /**
+     * @brief Sends via radio an non-acknowledge message about the parameter
+     * passed message
+     */
+    void sendNack(const mavlink_message_t& msg);
+
+    /**
+     * @brief Saves the MavlinkDriver and transceiver status
+     */
+    void logStatus();
+
+    /**
+     * @brief Returns if the radio module is correctly started
+     */
+    bool isStarted();
+
+    Boardcore::SX1278Fsk* transceiver = nullptr;
+    MavDriver* mavDriver              = nullptr;
+
+private:
+    /**
+     * @brief Called by the MavlinkDriver when a message is received
+     */
+    void handleMavlinkMessage(const mavlink_message_t& msg);
+
+    /**
+     * @brief Called by the handleMavlinkMessage to handle a command message
+     */
+    void handleCommand(const mavlink_message_t& msg);
+
+    /**
+     * @brief Sends the periodic telemetry
+     */
+    void sendPeriodicMessage();
+
+    /**
+     * @brief Inserts the mavlink message into the queue
+     */
+    void enqueueMsg(const mavlink_message_t& msg);
+
+    // Messages queue
+    mavlink_message_t messageQueue[RadioConfig::MAVLINK_QUEUE_SIZE];
+    uint32_t messageQueueIndex = 0;
+    miosix::FastMutex queueMutex;
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("Radio");
+    Boardcore::TaskScheduler* scheduler = nullptr;
+};
+}  // namespace Payload
\ No newline at end of file
diff --git a/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.cpp b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb1b56949d717d3b2a8c1c67ced98a799d70f150
--- /dev/null
+++ b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.cpp
@@ -0,0 +1,103 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Sensors/RotatedIMU/RotatedIMU.h>
+
+using namespace Eigen;
+using namespace Boardcore;
+
+namespace Payload
+{
+
+RotatedIMU::RotatedIMU(std::function<AccelerometerData()> accSampleFunction,
+                       std::function<MagnetometerData()> magSampleFunction,
+                       std::function<GyroscopeData()> gyroSampleFunction)
+    : accSample(accSampleFunction), magSample(magSampleFunction),
+      gyroSample(gyroSampleFunction), accT(Matrix3f::Identity()),
+      magT(Matrix3f::Identity()), gyroT(Matrix3f::Identity())
+{
+}
+
+void RotatedIMU::addAccTransformation(const Matrix3f& t) { accT = t * accT; }
+void RotatedIMU::addMagTransformation(const Matrix3f& t) { magT = t * magT; }
+void RotatedIMU::addGyroTransformation(const Matrix3f& t) { gyroT = t * gyroT; }
+
+void RotatedIMU::resetTransformations()
+{
+    accT  = Matrix3f::Identity();
+    magT  = Matrix3f::Identity();
+    gyroT = Matrix3f::Identity();
+}
+
+RotatedIMUData RotatedIMU::sampleImpl()
+{
+    Vector3f accResult  = accT * static_cast<Vector3f>(accSample());
+    Vector3f magResult  = magT * static_cast<Vector3f>(magSample());
+    Vector3f gyroResult = gyroT * static_cast<Vector3f>(gyroSample());
+
+    return {AccelerometerData{accResult}, GyroscopeData{gyroResult},
+            MagnetometerData{magResult}};
+}
+
+// Static functions
+Matrix3f RotatedIMU::rotateAroundX(float angle)
+{
+    Matrix3f rotation;
+    angle = angle * EIGEN_PI / 180.f;
+
+    // clang-format off
+    rotation = Matrix3f{{1,     0,           0},
+                        {0,     cosf(angle), -sinf(angle)},
+                        {0,     sinf(angle), cosf(angle)}};
+    // clang-format on
+
+    return rotation;
+}
+
+Matrix3f RotatedIMU::rotateAroundY(float angle)
+{
+    Matrix3f rotation;
+    angle = angle * EIGEN_PI / 180.f;
+
+    // clang-format off
+    rotation = Matrix3f{{cosf(angle),   0,  sinf(angle)},
+                        {0,             1,  0},
+                        {-sinf(angle),  0,  cosf(angle)}};
+    // clang-format on
+
+    return rotation;
+}
+
+Matrix3f RotatedIMU::rotateAroundZ(float angle)
+{
+    Matrix3f rotation;
+    angle = angle * EIGEN_PI / 180.f;
+
+    // clang-format off
+    rotation = Matrix3f{{cosf(angle),   -sinf(angle),   0},
+                        {sinf(angle),   cosf(angle),    0},
+                        {0,             0,              1}};
+    // clang-format on
+
+    return rotation;
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.h b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.h
new file mode 100644
index 0000000000000000000000000000000000000000..d939e8e54a8bb8c417dff47fe89f93a70f60304d
--- /dev/null
+++ b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.h
@@ -0,0 +1,120 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Sensors/RotatedIMU/RotatedIMUData.h>
+#include <sensors/Sensor.h>
+
+#include <Eigen/Eigen>
+#include <functional>
+
+namespace Payload
+{
+/**
+ * @brief Creates a "fake" sensor which allows the user
+ * to transform the values with transformation matrices.
+ * The default configuration (thus the reset one)
+ * comprehends identity matrices as transformation ones.
+ *
+ * @warning This is NOT a thread safe class
+ */
+class RotatedIMU : public Boardcore::Sensor<RotatedIMUData>
+{
+public:
+    /**
+     * @brief Construct a new Rotated IMU object
+     *
+     * @param accSampleFunction Function to call to retrieve an accelerometer
+     * sample
+     * @param magSampleFunction Function to call to retrieve a magnetometer
+     * sample
+     * @param gyroSampleFunction Function to call to retrieve a gyroscope sample
+     */
+    RotatedIMU(
+        std::function<Boardcore::AccelerometerData()> accSampleFunction = []()
+        { return Boardcore::AccelerometerData{}; },
+        std::function<Boardcore::MagnetometerData()> magSampleFunction = []()
+        { return Boardcore::MagnetometerData{}; },
+        std::function<Boardcore::GyroscopeData()> gyroSampleFunction = []()
+        { return Boardcore::GyroscopeData{}; });
+
+    /**
+     * @brief Multiplies the current accelerometer transformation
+     * @param transformation Transformation matrix to be multiplied to the
+     * current one
+     */
+    void addAccTransformation(const Eigen::Matrix3f& t);
+
+    /**
+     * @brief Multiplies the current magnetometer transformation
+     * @param transformation Transformation matrix to be multiplied to the
+     * current one
+     */
+    void addMagTransformation(const Eigen::Matrix3f& t);
+
+    /**
+     * @brief Multiplies the current gyroscope transformation
+     * @param transformation Transformation matrix to be multiplied to the
+     * current one
+     */
+    void addGyroTransformation(const Eigen::Matrix3f& t);
+
+    /**
+     * @brief Resets all the transformations to the original (Identity) ones
+     */
+    void resetTransformations();
+
+    bool init() override { return true; }
+    bool selfTest() override { return true; }
+
+    /**
+     * @brief Creates a rotation matrix around the X axis
+     * @param angle Angle in degrees
+     */
+    static Eigen::Matrix3f rotateAroundX(float angle);
+
+    /**
+     * @brief Creates a rotation matrix around the Y axis
+     * @param angle Angle in degrees
+     */
+    static Eigen::Matrix3f rotateAroundY(float angle);
+
+    /**
+     * @brief Creates a rotation matrix around the Z axis
+     * @param angle Angle in degrees
+     */
+    static Eigen::Matrix3f rotateAroundZ(float angle);
+
+private:
+    RotatedIMUData sampleImpl() override;
+
+    // Functions to sample the under neath sensors
+    std::function<Boardcore::AccelerometerData()> accSample;
+    std::function<Boardcore::MagnetometerData()> magSample;
+    std::function<Boardcore::GyroscopeData()> gyroSample;
+
+    // Transformation matrices
+    Eigen::Matrix3f accT;
+    Eigen::Matrix3f magT;
+    Eigen::Matrix3f gyroT;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Sensors/RotatedIMU/RotatedIMUData.h b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMUData.h
new file mode 100644
index 0000000000000000000000000000000000000000..455d1ae206102886a1d8d222d9c75332d123b181
--- /dev/null
+++ b/src/boards/Payload/Sensors/RotatedIMU/RotatedIMUData.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <sensors/SensorData.h>
+
+struct RotatedIMUData : public Boardcore::AccelerometerData,
+                        public Boardcore::MagnetometerData,
+                        public Boardcore::GyroscopeData
+{
+    RotatedIMUData()
+        : AccelerometerData{0, 0.0, 0.0, 0.0},
+          MagnetometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0}
+
+    {
+    }
+
+    RotatedIMUData(AccelerometerData acc, GyroscopeData gyr,
+                   MagnetometerData mag)
+        : AccelerometerData(acc), MagnetometerData(mag), GyroscopeData(gyr)
+    {
+    }
+
+    static std::string header()
+    {
+        return "accelerationTimestamp,accelerationX,accelerationY,"
+               "accelerationZ,angularSpeedTimestamp,angularSpeedX,"
+               "angularSpeedY,angularSpeedZ,magneticFieldTimestamp,"
+               "magneticFieldX,magneticFieldY,magneticFieldZ\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accelerationTimestamp << "," << accelerationX << ","
+           << accelerationY << "," << accelerationZ << ","
+           << angularSpeedTimestamp << "," << angularSpeedX << ","
+           << angularSpeedY << "," << angularSpeedZ << ","
+           << magneticFieldTimestamp << "," << magneticFieldX << ","
+           << magneticFieldY << "," << magneticFieldZ << "\n";
+    }
+};
diff --git a/src/boards/Payload/Sensors/SensorData.h b/src/boards/Payload/Sensors/SensorData.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ea2399f7475cba2dc532c5298021dec61baa3fc
--- /dev/null
+++ b/src/boards/Payload/Sensors/SensorData.h
@@ -0,0 +1,97 @@
+
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <sensors/LPS28DFW/LPS28DFWData.h>
+namespace Payload
+{
+struct LPS28DFW_1Data : Boardcore::LPS28DFWData
+{
+    explicit LPS28DFW_1Data(const Boardcore::LPS28DFWData& data)
+        : Boardcore::LPS28DFWData(data)
+    {
+    }
+
+    LPS28DFW_1Data() {}
+
+    static std::string header()
+    {
+        return "pressureTimestamp,pressure,temperatureTimestamp,temperature\n ";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << pressureTimestamp << "," << pressure << ","
+           << temperatureTimestamp << "," << temperature << "\n";
+    }
+};
+
+struct LPS28DFW_2Data : Boardcore::LPS28DFWData
+{
+    explicit LPS28DFW_2Data(const Boardcore::LPS28DFWData& data)
+        : Boardcore::LPS28DFWData(data)
+    {
+    }
+
+    LPS28DFW_2Data() {}
+
+    static std::string header()
+    {
+        return "pressureTimestamp,pressure,temperatureTimestamp,temperature\n ";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << pressureTimestamp << "," << pressure << ","
+           << temperatureTimestamp << "," << temperature << "\n";
+    }
+};
+
+struct SensorsCalibrationParameter
+{
+    uint64_t timestamp;
+    float referencePressure;
+    float offsetStatic;
+    float offsetDynamic;
+
+    SensorsCalibrationParameter(uint64_t timestamp, float referencePressure,
+                                float offsetStatic, float offsetDynamic)
+        : timestamp(timestamp), referencePressure(referencePressure),
+          offsetStatic(offsetStatic), offsetDynamic(offsetDynamic)
+    {
+    }
+
+    SensorsCalibrationParameter() : SensorsCalibrationParameter(0, 0, 0, 0) {}
+
+    static std::string header()
+    {
+        return "timestamp,referencePressure,offsetStatic,offsetDynamic";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << referencePressure << "," << offsetStatic
+           << "," << offsetDynamic << "\n";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Sensors/Sensors.cpp b/src/boards/Payload/Sensors/Sensors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9adc0129ea5630df9a125cab42f63d989be24a33
--- /dev/null
+++ b/src/boards/Payload/Sensors/Sensors.cpp
@@ -0,0 +1,687 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 "Sensors.h"
+
+#include <Payload/Buses.h>
+#include <Payload/Configs/SensorsConfig.h>
+#include <common/ReferenceConfig.h>
+#include <interfaces-impl/hwmapping.h>
+
+using namespace Boardcore;
+using namespace std;
+using namespace Payload::SensorsConfig;
+namespace Payload
+{
+LPS22DFData Sensors::getLPS22DFLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return lps22df != nullptr ? lps22df->getLastSample() : LPS22DFData{};
+}
+LPS28DFWData Sensors::getLPS28DFW_1LastSample()
+{
+    miosix::PauseKernelLock lock;
+    return lps28dfw_1 != nullptr ? lps28dfw_1->getLastSample() : LPS28DFWData{};
+}
+LPS28DFWData Sensors::getLPS28DFW_2LastSample()
+{
+    miosix::PauseKernelLock lock;
+    return lps28dfw_2 != nullptr ? lps28dfw_2->getLastSample() : LPS28DFWData{};
+}
+H3LIS331DLData Sensors::getH3LIS331DLLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return h3lis331dl != nullptr ? h3lis331dl->getLastSample()
+                                 : H3LIS331DLData{};
+}
+LIS2MDLData Sensors::getLIS2MDLLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return lis2mdl != nullptr ? lis2mdl->getLastSample() : LIS2MDLData{};
+}
+UBXGPSData Sensors::getGPSLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return ubxgps != nullptr ? ubxgps->getLastSample() : UBXGPSData{};
+}
+LSM6DSRXData Sensors::getLSM6DSRXLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return lsm6dsrx != nullptr ? lsm6dsrx->getLastSample() : LSM6DSRXData{};
+}
+ADS131M08Data Sensors::getADS131M08LastSample()
+{
+    miosix::PauseKernelLock lock;
+    return ads131m08 != nullptr ? ads131m08->getLastSample() : ADS131M08Data{};
+}
+
+HSCMRNN015PAData Sensors::getStaticPressureLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return staticPressure != nullptr ? staticPressure->getLastSample()
+                                     : HSCMRNN015PAData{};
+}
+
+SSCMRNN030PAData Sensors::getDynamicPressureLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return dynamicPressure != nullptr ? dynamicPressure->getLastSample()
+                                      : SSCMRNN030PAData{};
+}
+PitotData Sensors::getPitotLastSample()
+{
+    miosix::PauseKernelLock lock;
+    return pitot != nullptr ? pitot->getLastSample() : PitotData{};
+}
+
+// Processed Getters
+BatteryVoltageSensorData Sensors::getBatteryVoltageLastSample()
+{
+    // Do not need to pause the kernel, the last sample getter is already
+    // protected
+    ADS131M08Data sample = getADS131M08LastSample();
+    BatteryVoltageSensorData data;
+
+    // Populate the data
+    data.voltageTimestamp = sample.timestamp;
+    data.channelId        = static_cast<uint8_t>(BATTERY_VOLTAGE_CHANNEL);
+    data.voltage          = sample.getVoltage(BATTERY_VOLTAGE_CHANNEL).voltage;
+    data.batVoltage       = sample.getVoltage(BATTERY_VOLTAGE_CHANNEL).voltage *
+                      BATTERY_VOLTAGE_CONVERSION_FACTOR;
+    return data;
+}
+
+BatteryVoltageSensorData Sensors::getCamBatteryVoltageLastSample()
+{
+    // Do not need to pause the kernel, the last sample getter is already
+    // protected
+    ADS131M08Data sample = getADS131M08LastSample();
+    BatteryVoltageSensorData data;
+
+    // Populate the data
+    data.voltageTimestamp = sample.timestamp;
+    data.channelId        = static_cast<uint8_t>(CAM_BATTERY_VOLTAGE_CHANNEL);
+    data.voltage    = sample.getVoltage(CAM_BATTERY_VOLTAGE_CHANNEL).voltage;
+    data.batVoltage = sample.getVoltage(CAM_BATTERY_VOLTAGE_CHANNEL).voltage *
+                      BATTERY_VOLTAGE_CONVERSION_FACTOR;
+    return data;
+}
+
+CurrentData Sensors::getCurrentLastSample()
+{
+    // Do not need to pause the kernel, the last sample getter is already
+    // protected
+    ADS131M08Data sample = getADS131M08LastSample();
+    CurrentData data;
+
+    // Populate the data
+    data.currentTimestamp = sample.timestamp;
+    data.current =
+        (sample.getVoltage(CURRENT_CHANNEL).voltage - CURRENT_OFFSET) *
+        CURRENT_CONVERSION_FACTOR;
+    return data;
+}
+
+RotatedIMUData Sensors::getIMULastSample()
+{
+    miosix::PauseKernelLock lock;
+    return imu != nullptr ? imu->getLastSample() : RotatedIMUData{};
+}
+
+MagnetometerData Sensors::getCalibratedMagnetometerLastSample()
+{
+    // Do not need to pause the kernel, the last sample getter is already
+    // protected
+    MagnetometerData lastSample = getLIS2MDLLastSample();
+    MagnetometerData result;
+
+    // Correct the result and copy the timestamp
+    {
+        miosix::Lock<FastMutex> l(calibrationMutex);
+        result =
+            static_cast<MagnetometerData>(magCalibration.correct(lastSample));
+    }
+
+    result.magneticFieldTimestamp = lastSample.magneticFieldTimestamp;
+    return result;
+}
+
+Sensors::Sensors(TaskScheduler* sched) : scheduler(sched), sensorsCounter(0) {}
+
+bool Sensors::start()
+{
+    // Read the magnetometer calibration from predefined file
+    magCalibration.fromFile("magCalibration.csv");
+    // Init all the sensors
+    lps22dfInit();
+    lps28dfw_1Init();
+    lps28dfw_2Init();
+    h3lis331dlInit();
+    lis2mdlInit();
+    ubxgpsInit();
+    lsm6dsrxInit();
+    ads131m08Init();
+    staticPressureInit();
+    dynamicPressureInit();
+    pitotInit();
+    imuInit();
+
+    // Add the magnetometer calibration to the scheduler
+    size_t result = scheduler->addTask(
+        [&]()
+        {
+            // Gather the last sample data
+            MagnetometerData lastSample = getLIS2MDLLastSample();
+
+            // Feed the data to the calibrator inside a protected area.
+            // Contention is not high and the use of a mutex is suitable to
+            // avoid pausing the kernel for this calibration operation
+            {
+                miosix::Lock<FastMutex> l(calibrationMutex);
+                magCalibrator.feed(lastSample);
+            }
+        },
+        MAG_CALIBRATION_PERIOD);
+
+    // Create sensor manager with populated map and configured scheduler
+    manager = new SensorManager(sensorMap, scheduler);
+    return manager->start() && result != 0;
+}
+
+void Sensors::stop() { manager->stop(); }
+
+bool Sensors::isStarted()
+{
+    return manager->areAllSensorsInitialized() && scheduler->isRunning();
+}
+
+void Sensors::calibrate()
+{
+    // Create the stats to calibrate the barometers
+    Stats lps28dfw1Stats;
+    Stats lps28dfw2Stats;
+    Stats staticPressureStats;
+    Stats dynamicPressureStats;
+
+    // Add N samples to the stats
+    for (unsigned int i = 0; i < SensorsConfig::CALIBRATION_SAMPLES; i++)
+    {
+        lps28dfw1Stats.add(getLPS28DFW_1LastSample().pressure);
+        lps28dfw2Stats.add(getLPS28DFW_2LastSample().pressure);
+        staticPressureStats.add(getStaticPressureLastSample().pressure);
+        dynamicPressureStats.add(getDynamicPressureLastSample().pressure);
+
+        // Delay for the expected period
+        miosix::Thread::sleep(SensorsConfig::CALIBRATION_PERIOD);
+    }
+
+    // Compute the difference between the mean value from LPS28DFW
+    float reference =
+        (lps28dfw1Stats.getStats().mean + lps28dfw2Stats.getStats().mean) / 2.f;
+
+    staticPressure->updateOffset(staticPressureStats.getStats().mean -
+                                 reference);
+    dynamicPressure->updateOffset(dynamicPressureStats.getStats().mean -
+                                  reference);
+
+    // Log the offsets
+    SensorsCalibrationParameter cal{};
+    cal.timestamp         = TimestampTimer::getTimestamp();
+    cal.offsetStatic      = staticPressureStats.getStats().mean - reference;
+    cal.offsetDynamic     = dynamicPressureStats.getStats().mean - reference;
+    cal.referencePressure = reference;
+
+    Logger::getInstance().log(cal);
+}
+
+bool Sensors::writeMagCalibration()
+{
+    // Compute the calibration result in protected area
+    {
+        miosix::Lock<FastMutex> l(calibrationMutex);
+        SixParametersCorrector cal = magCalibrator.computeResult();
+
+        // Check result validity
+        if (!isnan(cal.getb()[0]) && !isnan(cal.getb()[1]) &&
+            !isnan(cal.getb()[2]) && !isnan(cal.getA()[0]) &&
+            !isnan(cal.getA()[1]) && !isnan(cal.getA()[2]))
+        {
+            magCalibration = cal;
+
+            // Save the calibration to the calibration file
+            return magCalibration.toFile("magCalibration.csv");
+        }
+    }
+    return false;
+}
+
+void Sensors::lps22dfInit()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Get the correct SPI configuration
+    SPIBusConfig config = LPS22DF::getDefaultSPIConfig();
+    config.clockDivider = SPI::ClockDivider::DIV_16;
+
+    // Configure the device
+    LPS22DF::Config sensorConfig;
+    sensorConfig.avg = LPS22DF_AVG;
+    sensorConfig.odr = LPS22DF_ODR;
+
+    // Create sensor instance with configured parameters
+    lps22df = new LPS22DF(modules.get<Buses>()->spi3,
+                          miosix::sensors::LPS22DF::cs::getPin(), config,
+                          sensorConfig);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("LPS22DF", LPS22DF_PERIOD,
+                    bind(&Sensors::lps22dfCallback, this));
+    sensorMap.emplace(make_pair(lps22df, info));
+
+    // used for the sensor state
+    auto lps22Status =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(lps22df); });
+    sensorsInit[sensorsCounter] = lps22Status;
+    sensorsCounter++;
+}
+void Sensors::lps28dfw_1Init()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Configure the sensor
+    LPS28DFW::SensorConfig config{false, LPS28DFW_FSR, LPS28DFW_AVG,
+                                  LPS28DFW_ODR, false};
+
+    // Create sensor instance with configured parameters
+    lps28dfw_1 = new LPS28DFW(modules.get<Buses>()->i2c1, config);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("LPS28DFW_1", LPS28DFW_PERIOD,
+                    bind(&Sensors::lps28dfw_1Callback, this));
+    sensorMap.emplace(make_pair(lps28dfw_1, info));
+
+    // used for the sensor state
+    auto lps28_1Status =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(lps28dfw_1); });
+    sensorsInit[sensorsCounter] = lps28_1Status;
+    sensorsCounter++;
+}
+void Sensors::lps28dfw_2Init()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Configure the sensor
+    LPS28DFW::SensorConfig config{true, LPS28DFW_FSR, LPS28DFW_AVG,
+                                  LPS28DFW_ODR, false};
+
+    // Create sensor instance with configured parameters
+    lps28dfw_2 = new LPS28DFW(modules.get<Buses>()->i2c1, config);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("LPS28DFW_2", LPS28DFW_PERIOD,
+                    bind(&Sensors::lps28dfw_2Callback, this));
+    sensorMap.emplace(make_pair(lps28dfw_2, info));
+
+    // used for the sensor state
+    auto lps28_2Status =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(lps28dfw_2); });
+    sensorsInit[sensorsCounter] = lps28_2Status;
+    sensorsCounter++;
+}
+void Sensors::h3lis331dlInit()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Get the correct SPI configuration
+    SPIBusConfig config = H3LIS331DL::getDefaultSPIConfig();
+    config.clockDivider = SPI::ClockDivider::DIV_16;
+
+    // Create sensor instance with configured parameters
+    h3lis331dl = new H3LIS331DL(
+        modules.get<Buses>()->spi3, miosix::sensors::H3LIS331DL::cs::getPin(),
+        config, H3LIS331DL_ODR, H3LIS331DL_BDU, H3LIS331DL_FSR);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("H3LIS331DL", H3LIS331DL_PERIOD,
+                    bind(&Sensors::h3lis331dlCallback, this));
+    sensorMap.emplace(make_pair(h3lis331dl, info));
+
+    // used for the sensor state
+    auto h3lis331Status =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(h3lis331dl); });
+    sensorsInit[sensorsCounter] = h3lis331Status;
+    sensorsCounter++;
+}
+void Sensors::lis2mdlInit()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Get the correct SPI configuration
+    SPIBusConfig config = LIS2MDL::getDefaultSPIConfig();
+    config.clockDivider = SPI::ClockDivider::DIV_16;
+
+    // Configure the sensor
+    LIS2MDL::Config sensorConfig;
+    sensorConfig.deviceMode         = LIS2MDL_OPERATIVE_MODE;
+    sensorConfig.odr                = LIS2MDL_ODR;
+    sensorConfig.temperatureDivider = LIS2MDL_TEMPERATURE_DIVIDER;
+
+    // Create sensor instance with configured parameters
+    lis2mdl = new LIS2MDL(modules.get<Buses>()->spi3,
+                          miosix::sensors::LIS2MDL::cs::getPin(), config,
+                          sensorConfig);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("LIS2MDL", LIS2MDL_PERIOD,
+                    bind(&Sensors::lis2mdlCallback, this));
+    sensorMap.emplace(make_pair(lis2mdl, info));
+
+    // used for the sensor state
+    auto lis2mdlStatus =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(lis2mdl); });
+    sensorsInit[sensorsCounter] = lis2mdlStatus;
+    sensorsCounter++;
+}
+
+void Sensors::ubxgpsInit()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Get the correct SPI configuration
+    SPIBusConfig config = UBXGPSSpi::getDefaultSPIConfig();
+    config.clockDivider = SPI::ClockDivider::DIV_64;
+
+    // Create sensor instance with configured parameters
+    ubxgps = new UBXGPSSpi(modules.get<Buses>()->spi4,
+                           miosix::sensors::GPS::cs::getPin(), config, 5);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("UBXGPS", UBXGPS_PERIOD,
+                    bind(&Sensors::ubxgpsCallback, this));
+    sensorMap.emplace(make_pair(ubxgps, info));
+
+    // used for the sensor state
+    auto ubxStatus =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(ubxgps); });
+    sensorsInit[sensorsCounter] = ubxStatus;
+    sensorsCounter++;
+}
+
+void Sensors::lsm6dsrxInit()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Configure the SPI
+    SPIBusConfig config;
+    config.clockDivider = SPI::ClockDivider::DIV_32;
+    config.mode         = SPI::Mode::MODE_0;
+
+    // Configure the sensor
+    LSM6DSRXConfig sensorConfig;
+    sensorConfig.bdu = LSM6DSRX_BDU;
+
+    // Accelerometer
+    sensorConfig.fsAcc     = LSM6DSRX_ACC_FS;
+    sensorConfig.odrAcc    = LSM6DSRX_ACC_ODR;
+    sensorConfig.opModeAcc = LSM6DSRX_OPERATING_MODE;
+
+    // Gyroscope
+    sensorConfig.fsGyr     = LSM6DSRX_GYR_FS;
+    sensorConfig.odrGyr    = LSM6DSRX_GYR_ODR;
+    sensorConfig.opModeGyr = LSM6DSRX_OPERATING_MODE;
+
+    // Fifo
+    sensorConfig.fifoMode                = LSM6DSRX_FIFO_MODE;
+    sensorConfig.fifoTimestampDecimation = LSM6DSRX_FIFO_TIMESTAMP_DECIMATION;
+    sensorConfig.fifoTemperatureBdr      = LSM6DSRX_FIFO_TEMPERATURE_BDR;
+
+    // Create sensor instance with configured parameters
+    lsm6dsrx = new LSM6DSRX(modules.get<Buses>()->spi1,
+                            miosix::sensors::LSM6DSRX::cs::getPin(), config,
+                            sensorConfig);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("LSM6DSRX", LSM6DSRX_PERIOD,
+                    bind(&Sensors::lsm6dsrxCallback, this));
+    sensorMap.emplace(make_pair(lsm6dsrx, info));
+
+    // used for the sensor state
+    auto lsm6dsrxStatus =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(lsm6dsrx); });
+    sensorsInit[sensorsCounter] = lsm6dsrxStatus;
+    sensorsCounter++;
+}
+
+void Sensors::ads131m08Init()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Configure the SPI
+    SPIBusConfig config;
+    config.clockDivider = SPI::ClockDivider::DIV_32;
+
+    // Configure the device
+    ADS131M08::Config sensorConfig;
+    sensorConfig.oversamplingRatio     = ADS131M08_OVERSAMPLING_RATIO;
+    sensorConfig.globalChopModeEnabled = ADS131M08_GLOBAL_CHOP_MODE;
+
+    // Create the sensor instance with configured parameters
+    ads131m08 = new ADS131M08(modules.get<Buses>()->spi4,
+                              miosix::sensors::ADS131::cs::getPin(), config,
+                              sensorConfig);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("ADS131M08", ADS131M08_PERIOD,
+                    bind(&Sensors::ads131m08Callback, this));
+    sensorMap.emplace(make_pair(ads131m08, info));
+
+    // used for the sensor state
+    auto ads131m08Status =
+        ([&]() -> SensorInfo { return manager->getSensorInfo(ads131m08); });
+    sensorsInit[sensorsCounter] = ads131m08Status;
+    sensorsCounter++;
+}
+
+void Sensors::staticPressureInit()
+{
+    // create lambda function to read the voltage
+    auto readVoltage = (
+        [&]() -> ADCData
+        {
+             ADS131M08Data sample = ads131m08->getLastSample();
+           return sample.getVoltage(
+               STATIC_PRESSURE_CHANNEL);
+        });
+
+    staticPressure = new HSCMRNN015PA(readVoltage, ADC_VOLTAGE_RANGE);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("StaticPressure", ADS131M08_PERIOD,
+                    bind(&Sensors::staticPressureCallback, this));
+    sensorMap.emplace(make_pair(staticPressure, info));
+}
+
+void Sensors::dynamicPressureInit()
+{
+    // create lambda function to read the voltage
+    auto readVoltage = (
+        [&]() -> ADCData
+        {
+             auto sample = ads131m08->getLastSample();
+             return sample.getVoltage(
+                DYNAMIC_PRESSURE_CHANNEL);
+        });
+
+    dynamicPressure = new SSCMRNN030PA(readVoltage, ADC_VOLTAGE_RANGE);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("TotalPressure", ADS131M08_PERIOD,
+                    bind(&Sensors::dynamicPressureCallback, this));
+    sensorMap.emplace(make_pair(dynamicPressure, info));
+}
+
+void Sensors::pitotInit()
+{
+    // create lambda function to read the pressure
+    function<float()> getDynamicPressure(
+        [&]() { return dynamicPressure->getLastSample().pressure; });
+    function<float()> getStaticPressure(
+        [&]() { return staticPressure->getLastSample().pressure; });
+
+    pitot = new Pitot(getDynamicPressure, getStaticPressure);
+    pitot->setReferenceValues(Common::ReferenceConfig::defaultReferenceValues);
+
+    // Emplace the sensor inside the map
+    SensorInfo info("Pitot", ADS131M08_PERIOD,
+                    bind(&Sensors::pitotCallback, this));
+    sensorMap.emplace(make_pair(pitot, info));
+}
+
+void Sensors::pitotSetReferenceAltitude(float altitude)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    ReferenceValues reference = pitot->getReferenceValues();
+    reference.refAltitude     = altitude;
+    pitot->setReferenceValues(reference);
+}
+
+void Sensors::pitotSetReferenceTemperature(float temperature)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    ReferenceValues reference = pitot->getReferenceValues();
+    reference.refTemperature  = temperature + 273.15f;
+    pitot->setReferenceValues(reference);
+}
+
+std::array<SensorInfo, 8> Sensors::getSensorInfo()
+{
+    std::array<SensorInfo, 8> sensorState;
+    for (size_t i = 0; i < sensorsInit.size(); i++)
+    {
+        sensorState[i] = sensorsInit[i]();
+    }
+    return sensorState;
+}
+
+void Sensors::imuInit()
+{
+    // Register the IMU as the fake sensor, passing as parameters the
+    // methods to retrieve real data. The sensor is not synchronized, but
+    // the sampling thread is always the same.
+    imu = new RotatedIMU(
+        bind(&LSM6DSRX::getLastSample, lsm6dsrx),
+        bind(&Sensors::getCalibratedMagnetometerLastSample, this),
+        bind(&LSM6DSRX::getLastSample, lsm6dsrx));
+
+    // Invert the Y axis on the magnetometer
+    Eigen::Matrix3f m{{1, 0, 0}, {0, -1, 0}, {0, 0, 1}};
+    imu->addMagTransformation(m);
+
+    // Emplace the sensor inside the map (TODO CHANGE PERIOD INTO NON MAGIC)
+    SensorInfo info("RotatedIMU", IMU_PERIOD,
+                    bind(&Sensors::imuCallback, this));
+    sensorMap.emplace(make_pair(imu, info));
+}
+
+void Sensors::lps22dfCallback()
+{
+    LPS22DFData lastSample = lps22df->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::lps28dfw_1Callback()
+{
+    LPS28DFW_1Data lastSample =
+        static_cast<LPS28DFW_1Data>(lps28dfw_1->getLastSample());
+
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::lps28dfw_2Callback()
+{
+    LPS28DFW_2Data lastSample =
+        static_cast<LPS28DFW_2Data>(lps28dfw_2->getLastSample());
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::h3lis331dlCallback()
+{
+    H3LIS331DLData lastSample = h3lis331dl->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::lis2mdlCallback()
+{
+    LIS2MDLData lastSample = lis2mdl->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::ubxgpsCallback()
+{
+    UBXGPSData lastSample = ubxgps->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::lsm6dsrxCallback()
+{
+    LSM6DSRXData lastSample = lsm6dsrx->getLastSample();
+
+    auto& fifo        = lsm6dsrx->getLastFifo();
+    uint16_t fifoSize = lsm6dsrx->getLastFifoSize();
+
+    // For every instance inside the fifo log the sample
+    for (uint16_t i = 0; i < fifoSize; i++)
+    {
+        Logger::getInstance().log(fifo.at(i));
+    }
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::ads131m08Callback()
+{
+    ADS131M08Data lastSample = ads131m08->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+
+void Sensors::staticPressureCallback()
+{
+    HSCMRNN015PAData lastSample = staticPressure->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+
+void Sensors::dynamicPressureCallback()
+{
+    SSCMRNN030PAData lastSample = dynamicPressure->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+
+void Sensors::pitotCallback()
+{
+    PitotData lastSample = pitot->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+void Sensors::imuCallback()
+{
+    RotatedIMUData lastSample = imu->getLastSample();
+    Logger::getInstance().log(lastSample);
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Sensors/Sensors.h b/src/boards/Payload/Sensors/Sensors.h
new file mode 100644
index 0000000000000000000000000000000000000000..faeee0b846d2c6b90b85ad3772128a35ce119fe8
--- /dev/null
+++ b/src/boards/Payload/Sensors/Sensors.h
@@ -0,0 +1,180 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Configs/SensorsConfig.h>
+#include <Payload/Sensors/RotatedIMU/RotatedIMU.h>
+#include <sensors/ADS131M08/ADS131M08.h>
+#include <sensors/H3LIS331DL/H3LIS331DL.h>
+#include <sensors/LIS2MDL/LIS2MDL.h>
+#include <sensors/LPS22DF/LPS22DF.h>
+#include <sensors/LPS28DFW/LPS28DFW.h>
+#include <sensors/LSM6DSRX/LSM6DSRX.h>
+#include <sensors/SensorData.h>
+#include <sensors/SensorManager.h>
+#include <sensors/UBXGPS/UBXGPSSpi.h>
+#include <sensors/analog/BatteryVoltageSensorData.h>
+#include <sensors/analog/Pitot/Pitot.h>
+#include <sensors/analog/Pitot/PitotData.h>
+#include <sensors/analog/pressure/honeywell/HSCMRNN015PA.h>
+#include <sensors/analog/pressure/honeywell/SSCMRNN030PA.h>
+#include <sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "SensorData.h"
+
+namespace Payload
+{
+
+class Sensors : public Boardcore::Module
+{
+public:
+    explicit Sensors(Boardcore::TaskScheduler* sched);
+
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Stops the sensor manager
+     * @warning Stops the passed scheduler
+     */
+    void stop();
+
+    /**
+     * @brief Returns if all the sensors are started successfully
+     */
+    bool isStarted();
+
+    /**
+     * @brief Calibrates the sensors with an offset
+     */
+    void calibrate();
+
+    /**
+     * @brief Takes the result of the live magnetometer calibration and applies
+     * it to the current calibration + writes it in the csv file
+     *
+     * @return true if the write was successful
+     */
+    bool writeMagCalibration();
+
+    // Sensor getters
+    Boardcore::LPS22DFData getLPS22DFLastSample();
+    Boardcore::LPS28DFWData getLPS28DFW_1LastSample();
+    Boardcore::LPS28DFWData getLPS28DFW_2LastSample();
+    Boardcore::H3LIS331DLData getH3LIS331DLLastSample();
+    Boardcore::LIS2MDLData getLIS2MDLLastSample();
+    Boardcore::UBXGPSData getGPSLastSample();
+    Boardcore::LSM6DSRXData getLSM6DSRXLastSample();
+    Boardcore::ADS131M08Data getADS131M08LastSample();
+
+    // Processed getters
+    Boardcore::BatteryVoltageSensorData getBatteryVoltageLastSample();
+    Boardcore::BatteryVoltageSensorData getCamBatteryVoltageLastSample();
+    Boardcore::CurrentData getCurrentLastSample();
+    RotatedIMUData getIMULastSample();
+    Boardcore::MagnetometerData getCalibratedMagnetometerLastSample();
+    Boardcore::HSCMRNN015PAData getStaticPressureLastSample();
+    Boardcore::SSCMRNN030PAData getDynamicPressureLastSample();
+    Boardcore::PitotData getPitotLastSample();
+
+    void pitotSetReferenceAltitude(float altitude);
+    void pitotSetReferenceTemperature(float temperature);
+
+    std::array<Boardcore::SensorInfo, 8> getSensorInfo();
+
+private:
+    // Init and callbacks methods
+    void lps22dfInit();
+    void lps22dfCallback();
+
+    void lps28dfw_1Init();
+    void lps28dfw_1Callback();
+
+    void lps28dfw_2Init();
+    void lps28dfw_2Callback();
+
+    void h3lis331dlInit();
+    void h3lis331dlCallback();
+
+    void lis2mdlInit();
+    void lis2mdlCallback();
+
+    void ubxgpsInit();
+    void ubxgpsCallback();
+
+    void lsm6dsrxInit();
+    void lsm6dsrxCallback();
+
+    void ads131m08Init();
+    void ads131m08Callback();
+
+    void staticPressureInit();
+    void staticPressureCallback();
+
+    void dynamicPressureInit();
+    void dynamicPressureCallback();
+
+    void pitotInit();
+    void pitotCallback();
+
+    void imuInit();
+    void imuCallback();
+
+    // Sensors instances
+    Boardcore::LPS22DF* lps22df       = nullptr;
+    Boardcore::LPS28DFW* lps28dfw_1   = nullptr;
+    Boardcore::LPS28DFW* lps28dfw_2   = nullptr;
+    Boardcore::H3LIS331DL* h3lis331dl = nullptr;
+    Boardcore::LIS2MDL* lis2mdl       = nullptr;
+    Boardcore::UBXGPSSpi* ubxgps      = nullptr;
+    Boardcore::LSM6DSRX* lsm6dsrx     = nullptr;
+    Boardcore::ADS131M08* ads131m08   = nullptr;
+
+    // Fake processed sensors
+    RotatedIMU* imu                          = nullptr;
+    Boardcore::HSCMRNN015PA* staticPressure  = nullptr;
+    Boardcore::SSCMRNN030PA* dynamicPressure = nullptr;
+    Boardcore::Pitot* pitot                  = nullptr;
+
+    // Magnetometer live calibration
+    Boardcore::SoftAndHardIronCalibration magCalibrator;
+    Boardcore::SixParametersCorrector magCalibration;
+    miosix::FastMutex calibrationMutex;
+
+    // Sensor manager
+    Boardcore::SensorManager* manager = nullptr;
+    Boardcore::SensorManager::SensorMap_t sensorMap;
+    Boardcore::TaskScheduler* scheduler = nullptr;
+
+    uint8_t sensorsCounter;
+
+    // SD logger
+    Boardcore::Logger& SDlogger = Boardcore::Logger::getInstance();
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("Sensors");
+
+    std::array<std::function<Boardcore::SensorInfo()>,
+               SensorsConfig::NUMBER_OF_SENSORS>
+        sensorsInit;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.cpp b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5d45e037da2874d25629acceae10a7d3b5a9190
--- /dev/null
+++ b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.cpp
@@ -0,0 +1,668 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Federico Mandelli
+ *
+ * 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 "FlightModeManager.h"
+
+#include <Payload/Actuators/Actuators.h>
+#include <Payload/AltitudeTrigger/AltitudeTrigger.h>
+#include <Payload/CanHandler/CanHandler.h>
+#include <Payload/Configs/FlightModeManagerConfig.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h>
+#include <common/Events.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <events/EventBroker.h>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Common;
+
+namespace Payload
+{
+FlightModeManager::FlightModeManager()
+    : HSM(&FlightModeManager::state_on_ground)
+{
+    EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
+    EventBroker::getInstance().subscribe(this, TOPIC_FMM);
+    EventBroker::getInstance().subscribe(this, TOPIC_TMTC);
+    EventBroker::getInstance().subscribe(this, TOPIC_CAN);
+    EventBroker::getInstance().subscribe(this, TOPIC_NAS);
+}
+
+FlightModeManager::~FlightModeManager()
+{
+    EventBroker::getInstance().unsubscribe(this);
+}
+
+FlightModeManagerStatus FlightModeManager::getStatus()
+{
+    PauseKernelLock lock;
+    return status;
+}
+
+State FlightModeManager::state_on_ground(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_top);
+        }
+        case EV_INIT:
+        {
+            return transition(&FlightModeManager::state_init);
+        }
+        case TMTC_START_LOGGING:
+        {
+            Logger::getInstance().start();
+            return HANDLED;
+        }
+        case TMTC_STOP_LOGGING:
+        {
+            Logger::getInstance().stop();
+            return HANDLED;
+        }
+        case TMTC_RESET_BOARD:
+        {
+            reboot();
+            return HANDLED;
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_init(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::INIT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case FMM_INIT_OK:
+        {
+            return transition(&FlightModeManager::state_init_done);
+        }
+        case FMM_INIT_ERROR:
+        {
+            return transition(&FlightModeManager::state_init_error);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_init_error(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::INIT_ERROR);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case CAN_FORCE_INIT:
+        case TMTC_FORCE_INIT:
+        {
+            if (event != CAN_FORCE_INIT)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::FORCE_INIT);
+            }
+            return transition(&FlightModeManager::state_init_done);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_init_done(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            // We turn off the led if we are coming from init_error
+            logStatus(FlightModeManagerState::INIT_DONE);
+            EventBroker::getInstance().post(FMM_CALIBRATE, TOPIC_FMM);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case FMM_CALIBRATE:
+        {
+            return transition(&FlightModeManager::state_sensors_calibration);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_sensors_calibration(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::SENSORS_CALIBRATION);
+            ModuleManager::getInstance().get<Sensors>()->calibrate();
+            EventBroker::getInstance().post(FMM_ALGOS_CALIBRATE, TOPIC_FMM);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case FMM_ALGOS_CALIBRATE:
+        {
+            return transition(&FlightModeManager::state_algos_calibration);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_algos_calibration(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::ALGOS_CALIBRATION);
+            // EventBroker::getInstance().post(FMM_ALGOS_CALIBRATE, TOPIC_FMM);
+            EventBroker::getInstance().post(NAS_CALIBRATE, TOPIC_NAS);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case NAS_READY:
+        {
+            EventBroker::getInstance().post(FMM_READY, TOPIC_FMM);
+            return transition(&FlightModeManager::state_disarmed);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_disarmed(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::DISARMED);
+            // Stop eventual logging
+            Logger::getInstance().stop();
+            ModuleManager::getInstance().get<Actuators>()->buzzerOff();
+            ModuleManager::getInstance().get<Actuators>()->camOff();
+            EventBroker::getInstance().post(FLIGHT_DISARMED, TOPIC_FLIGHT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case CAN_ARM:
+        case TMTC_ARM:
+        {
+            if (event != CAN_ARM)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::ARM);
+            }
+            return transition(&FlightModeManager::state_armed);
+        }
+        case CAN_ENTER_TEST_MODE:
+        case TMTC_ENTER_TEST_MODE:
+        {
+            if (event != CAN_ENTER_TEST_MODE)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::ENTER_TEST_MODE);
+            }
+            return transition(&FlightModeManager::state_test_mode);
+        }
+        case CAN_CALIBRATE:
+        case TMTC_CALIBRATE:
+        {
+            if (event != CAN_CALIBRATE)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::CALIBRATE);
+            }
+            return transition(&FlightModeManager::state_sensors_calibration);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_test_mode(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            Logger::getInstance().start();
+            logStatus(FlightModeManagerState::TEST_MODE);
+
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            ModuleManager::getInstance().get<Actuators>()->camOff();
+            Logger::getInstance().stop();
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_on_ground);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case TMTC_START_RECORDING:
+        {
+            ModuleManager::getInstance().get<Actuators>()->camOn();
+            return HANDLED;
+        }
+        case TMTC_STOP_RECORDING:
+        {
+            ModuleManager::getInstance().get<Actuators>()->camOff();
+            return HANDLED;
+        }
+        case TMTC_RESET_BOARD:
+        {
+            Logger::getInstance().stop();
+            reboot();
+            return HANDLED;
+        }
+        case CAN_EXIT_TEST_MODE:
+        case TMTC_EXIT_TEST_MODE:
+        {
+            Logger::getInstance().stop();
+            if (event != CAN_EXIT_TEST_MODE)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::EXIT_TEST_MODE);
+            }
+            return transition(&FlightModeManager::state_disarmed);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_armed(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::ARMED);
+            Logger::getInstance().start();
+
+            // Starts signaling devices and camera
+            ModuleManager::getInstance().get<Actuators>()->buzzerArmed();
+            ModuleManager::getInstance().get<Actuators>()->camOn();
+            // Post event
+            EventBroker::getInstance().post(FLIGHT_ARMED, TOPIC_FLIGHT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            Logger::getInstance().stop();
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_top);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case CAN_DISARM:
+        case TMTC_DISARM:
+        {
+            EventBroker::getInstance().post(NAS_FORCE_STOP, TOPIC_NAS);
+            if (event != CAN_DISARM)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::DISARM);
+            }
+            return transition(&FlightModeManager::state_disarmed);
+        }
+        case CAN_LIFTOFF:
+        case TMTC_FORCE_LAUNCH:
+        {
+            if (event != CAN_LIFTOFF)
+            {
+                ModuleManager::getInstance().get<CanHandler>()->sendEvent(
+                    CanConfig::EventId::LIFTOFF);
+            }
+            return transition(&FlightModeManager::state_flying);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_flying(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            EventBroker::getInstance().postDelayed(
+                FLIGHT_MISSION_TIMEOUT, TOPIC_FLIGHT, MISSION_TIMEOUT);
+            EventBroker::getInstance().postDelayed(
+                FLIGHT_NC_DETACHED, TOPIC_FLIGHT, APOGEE_TIMEOUT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_top);
+        }
+        case EV_INIT:
+        {
+            return transition(&FlightModeManager::state_ascending);
+        }
+        case TMTC_FORCE_LANDING:
+        case FLIGHT_MISSION_TIMEOUT:
+        {
+            return transition(&FlightModeManager::state_landed);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_ascending(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::ASCENDING);
+            EventBroker::getInstance().post(FLIGHT_LIFTOFF, TOPIC_FLIGHT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_flying);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case CAN_APOGEE_DETECTED:
+        case FLIGHT_NC_DETACHED:
+        case TMTC_FORCE_EXPULSION:
+        {
+            return transition(&FlightModeManager::state_drogue_descent);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_drogue_descent(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::DROGUE_DESCENT);
+
+            ModuleManager::getInstance().get<AltitudeTrigger>()->enable();
+
+            // ModuleManager::getInstance()
+            //     .get<VerticalVelocityTrigger>()
+            //     ->enable();
+
+            EventBroker::getInstance().post(FLIGHT_DROGUE_DESCENT,
+                                            TOPIC_FLIGHT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_flying);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case ALTITUDE_TRIGGER_ALTITUDE_REACHED:
+        case TMTC_FORCE_DEPLOYMENT:
+        {
+            ModuleManager::getInstance().get<AltitudeTrigger>()->disable();
+            ModuleManager::getInstance()
+                .get<VerticalVelocityTrigger>()
+                ->disable();
+            return transition(&FlightModeManager::state_wing_descent);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_wing_descent(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            EventBroker::getInstance().post(FLIGHT_WING_DESCENT, TOPIC_FLIGHT);
+
+            logStatus(FlightModeManagerState::WING_DESCENT);
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_flying);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State FlightModeManager::state_landed(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(FlightModeManagerState::LANDED);
+
+            // Turns off signaling devices
+            ModuleManager::getInstance().get<Actuators>()->buzzerLanded();
+            ModuleManager::getInstance().get<Actuators>()->camOff();
+            ModuleManager::getInstance().get<Actuators>()->disableServo(
+                PARAFOIL_LEFT_SERVO);
+            ModuleManager::getInstance().get<Actuators>()->disableServo(
+                PARAFOIL_RIGHT_SERVO);
+            // Sends events
+            EventBroker::getInstance().post(FLIGHT_LANDING_DETECTED,
+                                            TOPIC_FLIGHT);
+            EventBroker::getInstance().postDelayed(FMM_STOP_LOGGING, TOPIC_FMM,
+                                                   LOGGING_DELAY);
+
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&FlightModeManager::state_top);
+        }
+        case EV_INIT:
+        {
+            return HANDLED;
+        }
+        case FMM_STOP_LOGGING:
+        {
+            Logger::getInstance().stop();
+            return HANDLED;
+        }
+        case TMTC_RESET_BOARD:
+        {
+            Logger::getInstance().stop();
+            reboot();
+            return HANDLED;
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+void FlightModeManager::logStatus(FlightModeManagerState state)
+{
+    status.timestamp = TimestampTimer::getTimestamp();
+    status.state     = state;
+
+    Logger::getInstance().log(status);
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.h b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..a801add576b79bc001e95b3710e36163e25d0203
--- /dev/null
+++ b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManager.h
@@ -0,0 +1,126 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Federico Mandelli
+ *
+ * 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 <diagnostic/PrintLogger.h>
+#include <events/HSM.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "FlightModeManagerData.h"
+
+namespace Payload
+{
+class FlightModeManager : public Boardcore::HSM<FlightModeManager>,
+                          public Boardcore::Module
+{
+public:
+    FlightModeManager();
+    ~FlightModeManager();
+
+    bool startModule() { return start(); }
+    FlightModeManagerStatus getStatus();
+
+    /**
+     * @brief Super state for when the payload is on ground.
+     */
+    // Super state for when the payload is on ground.
+    Boardcore::State state_on_ground(const Boardcore::Event& event);
+
+    /**
+     * @brief Super state for when the payload is on ground.
+     */
+    Boardcore::State state_init(const Boardcore::Event& event);
+
+    /**
+     * @brief State in which the init has failed
+     */
+    Boardcore::State state_init_error(const Boardcore::Event& event);
+
+    /**
+     * @brief State in which the init is done and a calibration event is
+     * thrown
+     */
+    Boardcore::State state_init_done(const Boardcore::Event& event);
+
+    /**
+     * @brief Calibration of all sensors.
+     */
+    Boardcore::State state_sensors_calibration(const Boardcore::Event& event);
+
+    /**
+     * @brief Calibration of all algorithms.
+     */
+    Boardcore::State state_algos_calibration(const Boardcore::Event& event);
+
+    /**
+     * @brief State in which the electronics is ready to be armed
+     */
+    Boardcore::State state_disarmed(const Boardcore::Event& event);
+
+    /**
+     * @brief The rocket will accept specific telecommands otherwise considered
+     * risky.
+     */
+    Boardcore::State state_test_mode(const Boardcore::Event& event);
+
+    /**
+     * @brief Super state in which the algorithms start to run (NAS) and the
+     * electronics is ready to fly
+     */
+    Boardcore::State state_armed(const Boardcore::Event& event);
+
+    /**
+     * @brief Super state for when the payload is in the air.
+     */
+    Boardcore::State state_flying(const Boardcore::Event& event);
+
+    /**
+     * @brief Ascending phase of the trajectory.
+     */
+    Boardcore::State state_ascending(const Boardcore::Event& event);
+
+    /**
+     * @brief State in which the apogee/expulsion has occurred
+     */
+    Boardcore::State state_drogue_descent(const Boardcore::Event& event);
+
+    /**
+     * @brief State in which the parafoil wing is opened and starts guiding
+     * itself
+     */
+    Boardcore::State state_wing_descent(const Boardcore::Event& event);
+
+    /**
+     * @brief The rocket ended the flight and closes the log.
+     */
+    Boardcore::State state_landed(const Boardcore::Event& event);
+
+private:
+    void logStatus(FlightModeManagerState state);
+
+    FlightModeManagerStatus status;
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("fmm");
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManagerData.h b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManagerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c40d9540195ab6ba158a9b6adc34a8d61864c6d
--- /dev/null
+++ b/src/boards/Payload/StateMachines/FlightModeManager/FlightModeManagerData.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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 <stdint.h>
+
+#include <iostream>
+#include <string>
+
+namespace Payload
+{
+
+enum class FlightModeManagerState : uint8_t
+{
+    INVALID = 0,
+    INIT,
+    INIT_ERROR,
+    INIT_DONE,
+    SENSORS_CALIBRATION,
+    ALGOS_CALIBRATION,
+    DISARMED,
+    TEST_MODE,
+    ARMED,
+    ASCENDING,
+    DROGUE_DESCENT,
+    WING_DESCENT,
+    LANDED,
+};
+
+struct FlightModeManagerStatus
+{
+    uint64_t timestamp           = 0;
+    FlightModeManagerState state = FlightModeManagerState::INVALID;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)state << "\n";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/NASController/NASController.cpp b/src/boards/Payload/StateMachines/NASController/NASController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41503d305065a1d9cc25ddae3b5507ecb69b2457
--- /dev/null
+++ b/src/boards/Payload/StateMachines/NASController/NASController.cpp
@@ -0,0 +1,370 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Configs/NASConfig.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <common/Events.h>
+#include <common/ReferenceConfig.h>
+#include <events/EventBroker.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+using namespace Boardcore;
+using namespace Eigen;
+using namespace std;
+using namespace Common;
+namespace Payload
+{
+NASController::NASController(TaskScheduler* sched)
+    : FSM(&NASController::state_idle), nas(NASConfig::config), scheduler(sched)
+{
+    // Subscribe the class to the topics
+    EventBroker::getInstance().subscribe(this, TOPIC_NAS);
+    EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
+
+    // Setup the NAS
+    Matrix<float, 13, 1> x = Matrix<float, 13, 1>::Zero();
+
+    // Create the initial quaternion
+    Vector4f q = SkyQuaternion::eul2quat({0, 0, 0});
+
+    // Set the initial quaternion inside the matrix
+    x(6) = q(0);
+    x(7) = q(1);
+    x(8) = q(2);
+    x(9) = q(3);
+
+    // Set the NAS x matrix
+    nas.setX(x);
+
+    // Set the referenced values for the correct place on earth
+    nas.setReferenceValues(ReferenceConfig::defaultReferenceValues);
+}
+
+bool NASController::start()
+{
+    // Add the task to the scheduler
+    size_t result = scheduler->addTask(bind(&NASController::update, this),
+                                       NASConfig::UPDATE_PERIOD,
+                                       TaskScheduler::Policy::RECOVER);
+
+    return ActiveObject::start() && result != 0;
+}
+
+void NASController::update()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Update the NAS state only if the FSM is active
+    if (this->testState(&NASController::state_active))
+    {
+        // Get the IMU data
+        RotatedIMUData imuData = modules.get<Sensors>()->getIMULastSample();
+        UBXGPSData gpsData     = modules.get<Sensors>()->getGPSLastSample();
+
+        HSCMRNN015PAData baroData =
+            modules.get<Sensors>()->getStaticPressureLastSample();
+
+        // NAS prediction
+        nas.predictGyro(imuData);
+        nas.predictAcc(imuData);
+
+        // NAS correction
+        nas.correctMag(imuData);
+        nas.correctGPS(gpsData);
+        nas.correctBaro(baroData.pressure);
+        // Correct with accelerometer if the acceleration is in specs
+        Vector3f acceleration  = static_cast<AccelerometerData>(imuData);
+        float accelerationNorm = acceleration.norm();
+        if (accelerationValid)
+        {
+            nas.correctAcc(imuData);
+        }
+        if ((accelerationNorm <
+                 (9.8 + (NASConfig::ACCELERATION_THRESHOLD) / 2) &&
+             accelerationNorm >
+                 (9.8 - (NASConfig::ACCELERATION_THRESHOLD) / 2)))
+        {
+            if (!accelerationValid)
+            {
+                accSampleAfterSpike++;
+            }
+        }
+        else
+        {
+            accelerationValid   = false;
+            accSampleAfterSpike = 0;
+        }
+        if (accSampleAfterSpike > NASConfig::ACCELERATION_THRESHOLD_SAMPLE)
+        {
+            accSampleAfterSpike = 0;
+            accelerationValid   = true;
+        }
+
+        Logger::getInstance().log(nas.getState());
+    }
+}
+
+void NASController::calibrate()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    Vector3f acceleration  = Vector3f::Zero();
+    Vector3f magneticField = Vector3f::Zero();
+    Stats pressure;
+
+    for (int i = 0; i < NASConfig::CALIBRATION_SAMPLES_COUNT; i++)
+    {
+        // IMU
+        LSM6DSRXData imuData = modules.get<Sensors>()->getLSM6DSRXLastSample();
+        acceleration += Vector3f(imuData.accelerationX, imuData.accelerationY,
+                                 imuData.accelerationZ);
+
+        // Magnetometer
+        LIS2MDLData magData = modules.get<Sensors>()->getLIS2MDLLastSample();
+        magneticField +=
+            Vector3f(magData.magneticFieldX, magData.magneticFieldY,
+                     magData.magneticFieldZ);
+
+        // Static pressure barometer
+        HSCMRNN015PAData barometerData =
+            modules.get<Sensors>()->getStaticPressureLastSample();
+        pressure.add(barometerData.pressure);
+
+        miosix::Thread::sleep(NASConfig::CALIBRATION_SLEEP_TIME);
+    }
+
+    // Normalize the data
+    acceleration /= NASConfig::CALIBRATION_SAMPLES_COUNT;
+    magneticField /= NASConfig::CALIBRATION_SAMPLES_COUNT;
+    acceleration.normalize();
+    magneticField.normalize();
+
+    // Use the triad algorithm to estimate the initial orientation
+    StateInitializer state;
+    state.triad(acceleration, magneticField, ReferenceConfig::nedMag);
+
+    // Set the pressure reference using an already existing reference values
+    ReferenceValues reference = nas.getReferenceValues();
+    reference.refPressure     = pressure.getStats().mean;
+
+    // Set reference altitude using barometric measure
+    reference.refAltitude = Aeroutils::relAltitude(pressure.getStats().mean);
+
+    // If in this moment the GPS has fix i use that position as starting
+    UBXGPSData gps = modules.get<Sensors>()->getGPSLastSample();
+    if (gps.fix != 0)
+    {
+        // We don't set the altitude with the GPS because of not precise
+        // measurements
+        reference.refLatitude  = gps.latitude;
+        reference.refLongitude = gps.longitude;
+    }
+
+    // Update the algorithm reference values
+    {
+        miosix::PauseKernelLock lock;
+        nas.setX(state.getInitX());
+        nas.setReferenceValues(reference);
+    }
+
+    EventBroker::getInstance().post(NAS_READY, TOPIC_NAS);
+}
+
+void NASController::setCoordinates(Vector2f position)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    ReferenceValues reference = nas.getReferenceValues();
+    reference.refLatitude     = position[0];
+    reference.refLongitude    = position[1];
+    nas.setReferenceValues(reference);
+}
+
+void NASController::setOrientation(float yaw, float pitch, float roll)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    Matrix<float, 13, 1> x = nas.getX();
+
+    // Set quaternions
+    Vector4f q = SkyQuaternion::eul2quat({yaw, pitch, roll});
+    x(6)       = q(0);
+    x(7)       = q(1);
+    x(8)       = q(2);
+    x(9)       = q(3);
+
+    nas.setX(x);
+}
+
+void NASController::setReferenceAltitude(float altitude)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    ReferenceValues reference = nas.getReferenceValues();
+    reference.refAltitude     = altitude;
+    nas.setReferenceValues(reference);
+}
+
+void NASController::setReferenceTemperature(float temperature)
+{
+    // Need to pause the kernel because the only invocation comes from the radio
+    // which is a separate thread
+    miosix::PauseKernelLock l;
+
+    ReferenceValues reference = nas.getReferenceValues();
+
+    // The temperature is in degrees, converted in kelvin
+    reference.refTemperature = temperature + 273.15f;
+    nas.setReferenceValues(reference);
+}
+
+void NASController::setReferenceValues(const ReferenceValues reference)
+{
+    // Need to pause the kernel
+    miosix::PauseKernelLock l;
+    nas.setReferenceValues(reference);
+}
+
+NASControllerStatus NASController::getStatus()
+{
+    // Need to pause the kernel
+    miosix::PauseKernelLock l;
+    return status;
+}
+
+NASState NASController::getNasState()
+{
+    // Need to pause the kernel
+    miosix::PauseKernelLock l;
+    return nas.getState();
+}
+
+ReferenceValues NASController::getReferenceValues()
+{
+    // Need to pause the kernel
+    miosix::PauseKernelLock l;
+    return nas.getReferenceValues();
+}
+
+void NASController::state_idle(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return logStatus(NASControllerState::IDLE);
+        }
+        case NAS_CALIBRATE:
+        {
+            return transition(&NASController::state_calibrating);
+        }
+    }
+}
+
+void NASController::state_calibrating(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+
+            // Calibrate the NAS
+            calibrate();
+            return logStatus(NASControllerState::CALIBRATING);
+        }
+        case NAS_READY:
+        {
+            return transition(&NASController::state_ready);
+        }
+    }
+}
+
+void NASController::state_ready(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return logStatus(NASControllerState::READY);
+        }
+        case NAS_CALIBRATE:
+        {
+            return transition(&NASController::state_calibrating);
+        }
+        case NAS_FORCE_START:
+        case FLIGHT_ARMED:
+        {
+            return transition(&NASController::state_active);
+        }
+    }
+}
+
+void NASController::state_active(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return logStatus(NASControllerState::ACTIVE);
+        }
+        case FLIGHT_LANDING_DETECTED:
+        case FLIGHT_MISSION_TIMEOUT:
+        {
+            return transition(&NASController::state_end);
+        }
+        case NAS_FORCE_STOP:
+        case FLIGHT_DISARMED:
+        {
+            return transition(&NASController::state_ready);
+        }
+    }
+}
+
+void NASController::state_end(const Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return logStatus(NASControllerState::END);
+        }
+    }
+}
+
+void NASController::logStatus(NASControllerState state)
+{
+    // Update the current FSM state
+    status.timestamp = TimestampTimer::getTimestamp();
+    status.state     = state;
+
+    // Log the status
+    Logger::getInstance().log(status);
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/NASController/NASController.h b/src/boards/Payload/StateMachines/NASController/NASController.h
new file mode 100644
index 0000000000000000000000000000000000000000..73a256714815349a9b293bb41457f5a98212efb9
--- /dev/null
+++ b/src/boards/Payload/StateMachines/NASController/NASController.h
@@ -0,0 +1,99 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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/NAS/NAS.h>
+#include <diagnostic/PrintLogger.h>
+#include <events/FSM.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "NASControllerData.h"
+
+namespace Payload
+{
+class NASController : public Boardcore::FSM<NASController>,
+                      public Boardcore::Module
+{
+public:
+    NASController(Boardcore::TaskScheduler* sched);
+
+    /**
+     * @brief Starts the FSM thread and adds the update function into the
+     * scheduler
+     */
+    bool start() override;
+
+    // NAS FSM called methods
+    void calibrate();
+
+    // NAS setters
+    void setCoordinates(Eigen::Vector2f position);
+    void setOrientation(float yaw, float pitch, float roll);
+    void setReferenceAltitude(float altitude);
+    void setReferenceTemperature(float temperature);
+    void setReferenceValues(const Boardcore::ReferenceValues reference);
+
+    // NAS Getters
+    NASControllerStatus getStatus();
+    Boardcore::NASState getNasState();
+    Boardcore::ReferenceValues getReferenceValues();
+
+    // FSM states
+    void state_idle(const Boardcore::Event& event);
+    void state_calibrating(const Boardcore::Event& event);
+    void state_ready(const Boardcore::Event& event);
+    void state_active(const Boardcore::Event& event);
+    void state_end(const Boardcore::Event& event);
+
+private:
+    /**
+     * @brief Update the NAS estimation
+     */
+    void update();
+
+    /**
+     * @brief Logs the NAS status updating the FSM state
+     * @param state The current FSM state
+     */
+    void logStatus(NASControllerState state);
+
+    // Controller state machine status
+    NASControllerStatus status;
+    Boardcore::NAS nas;
+
+    // Scheduler to be used for update function
+    Boardcore::TaskScheduler* scheduler;
+
+    // User set (or triac set) initial orientation
+    Eigen::Vector3f initialOrientation;
+
+    // Parameters used to decide wether to correct with accelerometer after a
+    // spike is detected
+    bool accelerationValid       = true;
+    u_int8_t accSampleAfterSpike = 0;
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("NAS");
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/NASController/NASControllerData.h b/src/boards/Payload/StateMachines/NASController/NASControllerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..782c231b6660710de706b85b589d6d802d14bf33
--- /dev/null
+++ b/src/boards/Payload/StateMachines/NASController/NASControllerData.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <stdint.h>
+
+#include <iostream>
+#include <string>
+
+namespace Payload
+{
+
+enum class NASControllerState : uint8_t
+{
+    UNINIT = 0,
+    IDLE,
+    CALIBRATING,
+    READY,
+    ACTIVE,
+    END
+};
+
+struct NASControllerStatus
+{
+    long long timestamp      = 0;
+    NASControllerState state = NASControllerState::UNINIT;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)state << "\n";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/WingController/WingController.cpp b/src/boards/Payload/StateMachines/WingController/WingController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..33a64f24aaf0617aa5b7566e903019e42a292449
--- /dev/null
+++ b/src/boards/Payload/StateMachines/WingController/WingController.cpp
@@ -0,0 +1,472 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli, Radu Raul
+ *
+ * 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 "WingController.h"
+
+#include <Payload/Actuators/Actuators.h>
+#include <Payload/BoardScheduler.h>
+#include <Payload/Configs/WESConfig.h>
+#include <Payload/Configs/WingConfig.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/WindEstimationScheme/WindEstimation.h>
+#include <Payload/Wing/AutomaticWingAlgorithm.h>
+#include <Payload/Wing/FileWingAlgorithm.h>
+#include <Payload/Wing/WingAlgorithm.h>
+#include <Payload/Wing/WingAlgorithmData.h>
+#include <Payload/Wing/WingTargetPositionData.h>
+#include <common/Events.h>
+#include <diagnostic/PrintLogger.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <events/EventBroker.h>
+
+using namespace Boardcore;
+using namespace Payload::WingConfig;
+using namespace Payload::WESConfig;
+using namespace Common;
+using namespace miosix;
+
+namespace Payload
+{
+
+WingController::WingController(TaskScheduler* sched)
+    : HSM(&WingController::state_idle), running(false), selectedAlgorithm(0),
+      scheduler(sched)
+{
+
+    EventBroker::getInstance().subscribe(this, TOPIC_FLIGHT);
+    EventBroker::getInstance().subscribe(this, TOPIC_DPL);
+    this->targetPositionGEO = {DEFAULT_TARGET_LAT, DEFAULT_TARGET_LON};
+}
+
+bool WingController::start()
+{
+    return scheduler->addTask(std::bind(&WingController::update, this),
+                              WING_UPDATE_PERIOD) &&
+           addAlgorithms() && HSM::start();
+}
+
+WingController::~WingController()
+{
+    EventBroker::getInstance().unsubscribe(this);
+}
+
+WingControllerStatus WingController::getStatus()
+{
+    miosix::Lock<miosix::FastMutex> s(statusMutex);
+    return status;
+}
+
+State WingController::state_idle(const Boardcore::Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(WingControllerState::IDLE);
+            return HANDLED;
+        }
+        case FLIGHT_WING_DESCENT:
+        {
+            ModuleManager::getInstance().get<Actuators>()->cuttersOn();
+            return transition(&WingController::state_flying);
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&WingController::state_top);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State WingController::state_flying(const Event& event)
+{
+
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&WingController::state_top);
+        }
+        case EV_INIT:
+        {
+            return transition(&WingController::state_calibration);
+        }
+        case FLIGHT_LANDING_DETECTED:
+        {
+            return transition(&WingController::state_on_ground);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State WingController::state_calibration(const Boardcore::Event& event)
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    switch (event)
+    {
+        case EV_ENTRY:  // starts twirling and calibration wes
+        {
+            logStatus(WingControllerState::CALIBRATION);
+            modules.get<Actuators>()->setServo(ServosList::PARAFOIL_LEFT_SERVO,
+                                               1);
+            modules.get<Actuators>()->setServo(ServosList::PARAFOIL_RIGHT_SERVO,
+                                               0);
+            EventBroker::getInstance().postDelayed(DPL_WES_CAL_DONE, TOPIC_DPL,
+                                                   WES_CALIBRATION_TIMEOUT);
+            modules.get<WindEstimation>()
+                ->startWindEstimationSchemeCalibration();
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&WingController::state_flying);
+        }
+        case DPL_WES_CAL_DONE:
+        {
+            reset();
+            // Turn off the cutters
+            ModuleManager::getInstance().get<Actuators>()->cuttersOff();
+            return transition(&WingController::state_controlled_descent);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+State WingController::state_controlled_descent(const Boardcore::Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:  // start automatic algorithm
+        {
+            logStatus(WingControllerState::ALGORITHM_CONTROLLED);
+            selectAlgorithm(0);
+            setEarlyManeuverPoints(
+                convertTargetPositionToNED(targetPositionGEO),
+                {ModuleManager::getInstance()
+                     .get<NASController>()
+                     ->getNasState()
+                     .n,
+                 ModuleManager::getInstance()
+                     .get<NASController>()
+                     ->getNasState()
+                     .e});
+            startAlgorithm();
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&WingController::state_flying);
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+State WingController::state_on_ground(const Boardcore::Event& event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(WingControllerState::ON_GROUND);
+
+            ModuleManager::getInstance()
+                .get<WindEstimation>()
+                ->stopWindEstimationScheme();
+            ModuleManager::getInstance()
+                .get<WindEstimation>()
+                ->stopWindEstimationSchemeCalibration();
+            stopAlgorithm();
+            return HANDLED;
+        }
+        case EV_EXIT:
+        {
+            return HANDLED;
+        }
+        case EV_EMPTY:
+        {
+            return tranSuper(&WingController::state_top);
+        }
+        default:
+        {
+            return UNHANDLED;
+        }
+    }
+}
+
+bool WingController::addAlgorithms()
+{
+    WingAlgorithm* algorithm;
+    WingAlgorithmData step;
+
+    bool result;
+    // We add an AutomaticWingAlgorithm and a FileWingAlgorithm
+
+    algorithm = new AutomaticWingAlgorithm(KP, KI, PARAFOIL_LEFT_SERVO,
+                                           PARAFOIL_RIGHT_SERVO, emGuidance);
+    // Ensure that the servos are correct
+    algorithm->setServo(PARAFOIL_LEFT_SERVO, PARAFOIL_RIGHT_SERVO);
+
+    // Init the algorithm
+    result = algorithm->init();
+
+    // Add the algorithm to the vector
+    algorithms.push_back(algorithm);
+
+    algorithm = new WingAlgorithm(PARAFOIL_LEFT_SERVO, PARAFOIL_RIGHT_SERVO);
+
+    for (int i = 0; i < 3; i++)
+    {
+        step.servo1Angle = 120;
+        step.servo2Angle = 0;
+        step.timestamp   = 0 + i * 40000000;
+        algorithm->addStep(step);
+        step.servo1Angle = 0;
+        step.servo2Angle = 0;
+        step.timestamp += 10000000 + i * 40000000;
+        algorithm->addStep(step);
+        step.servo1Angle = 0;
+        step.servo2Angle = 120;
+        step.timestamp += 10000000 + i * 40000000;
+        algorithm->addStep(step);
+        step.servo1Angle = 0;
+        step.servo2Angle = 0;
+        step.timestamp += 10000000 + i * 40000000;
+        algorithm->addStep(step);
+    }
+
+    // Ensure that the servos are correct
+    algorithm->setServo(PARAFOIL_LEFT_SERVO, PARAFOIL_RIGHT_SERVO);
+
+    // Init the algorithm
+    result = result && algorithm->init();
+
+    // Add the algorithm to the vector
+    algorithms.push_back(algorithm);
+
+    selectAlgorithm(0);
+
+    return result;
+}
+
+bool WingController::selectAlgorithm(unsigned int index)
+{
+    bool success = false;
+    //  We change the selected algorithm only if we are in IDLE
+    if (getStatus().state == WingControllerState::IDLE)
+    {
+        stopAlgorithm();
+        if (index < algorithms.size())
+        {
+            LOG_INFO(logger, "Algorithm {:1} selected", index);
+            selectedAlgorithm = index;
+            success           = true;
+        }
+        else
+        {
+            // Select the 0 algorithm
+            selectedAlgorithm = 0;
+            success           = false;
+        }
+    }
+    return success;
+}
+
+void WingController::startAlgorithm()
+{
+    // If the selected algorithm is valid --> also the
+    // algorithms array is not empty i start the whole thing
+    if (selectedAlgorithm < algorithms.size())
+    {
+        running = true;
+
+        // Begin the selected algorithm
+        algorithms[selectedAlgorithm]->begin();
+
+        LOG_INFO(logger, "Wing algorithm started");
+    }
+}
+
+void WingController::stopAlgorithm()
+{
+    if (running)
+    {
+        // Set running to false
+        running = false;
+        // Stop the algorithm if selected
+        if (selectedAlgorithm < algorithms.size())
+        {
+            algorithms[selectedAlgorithm]->end();
+            reset();
+        }
+    }
+}
+
+void WingController::update()
+{
+    if (running)
+    {
+        algorithms[selectedAlgorithm]->step();
+    }
+}
+
+void WingController::flare()
+{
+    // Set the servo position to flare (pull the two ropes as skydiving people
+    // do)
+
+    ModuleManager::getInstance().get<Actuators>()->setServo(PARAFOIL_LEFT_SERVO,
+                                                            1);
+    ModuleManager::getInstance().get<Actuators>()->setServo(
+        PARAFOIL_RIGHT_SERVO, 1);
+}
+
+void WingController::reset()
+{
+    // Set the servo position to reset
+    ModuleManager::getInstance().get<Actuators>()->setServo(PARAFOIL_LEFT_SERVO,
+                                                            0);
+    ModuleManager::getInstance().get<Actuators>()->setServo(
+        PARAFOIL_RIGHT_SERVO, 0);
+}
+
+void WingController::setTargetPosition(Eigen::Vector2f targetGEO)
+{
+    if (ModuleManager::getInstance().get<NASController>()->getStatus().state !=
+        NASControllerState::READY)
+    {
+        this->targetPositionGEO = targetGEO;
+        setEarlyManeuverPoints(
+            convertTargetPositionToNED(targetPositionGEO),
+            {ModuleManager::getInstance().get<NASController>()->getNasState().n,
+             ModuleManager::getInstance()
+                 .get<NASController>()
+                 ->getNasState()
+                 .e});
+    }
+}
+
+void WingController::setEarlyManeuverPoints(Eigen::Vector2f targetNED,
+                                            Eigen::Vector2f currentPosNED)
+{
+
+    Eigen::Vector2f targetOffsetNED = targetNED - currentPosNED;
+
+    Eigen::Vector2f norm_point = targetOffsetNED / targetOffsetNED.norm();
+
+    float psi0 = atan2(norm_point[1], norm_point[0]);
+
+    float distFromCenterline = 20;  // the distance that the M1 and M2 points
+                                    // must have from the center line
+
+    // Calculate the angle between the lines <NED Origin, target> and <NED
+    // Origin, M1> This angle is the same for M2 since is symmetric to M1
+    // relatively to the center line
+    float psiMan = atan2(distFromCenterline, targetOffsetNED.norm());
+
+    float maneuverPointsMagnitude = distFromCenterline / sin(psiMan);
+    float m2Angle                 = psi0 + psiMan;
+    float m1Angle                 = psi0 - psiMan;
+
+    Eigen::Vector2f emcPosition =
+        targetOffsetNED * 1.2 +
+        currentPosNED;  // EMC is calculated as target * 1.2
+
+    Eigen::Vector2f m1Position =
+        Eigen::Vector2f(cos(m1Angle), sin(m1Angle)) * maneuverPointsMagnitude +
+        currentPosNED;
+
+    Eigen::Vector2f m2Position =
+        Eigen::Vector2f(cos(m2Angle), sin(m2Angle)) * maneuverPointsMagnitude +
+        currentPosNED;
+
+    emGuidance.setPoints(targetNED, emcPosition, m1Position, m2Position);
+    WingTargetPositionData data;
+
+    data.receivedLat = targetPositionGEO[0];
+    data.receivedLon = targetPositionGEO[1];
+
+    data.targetN = targetNED[0];
+    data.targetE = targetNED[1];
+
+    data.emcN = emcPosition[0];
+    data.emcE = emcPosition[1];
+
+    data.m1N = m1Position[0];
+    data.m1E = m1Position[1];
+
+    data.m2N = m2Position[0];
+    data.m2E = m2Position[1];
+
+    // Log the received position
+    Logger::getInstance().log(data);
+}
+
+void WingController::logStatus(WingControllerState state)
+{
+    miosix::Lock<miosix::FastMutex> s(statusMutex);
+    status.timestamp = TimestampTimer::getTimestamp();
+    status.state     = state;
+
+    Logger::getInstance().log(status);
+}
+
+Eigen::Vector2f WingController::convertTargetPositionToNED(
+    Eigen::Vector2f targetGEO)
+{
+    return Aeroutils::geodetic2NED(targetGEO, {ModuleManager::getInstance()
+                                                   .get<NASController>()
+                                                   ->getReferenceValues()
+                                                   .refLatitude,
+                                               ModuleManager::getInstance()
+                                                   .get<NASController>()
+                                                   ->getReferenceValues()
+                                                   .refLongitude});
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/WingController/WingController.h b/src/boards/Payload/StateMachines/WingController/WingController.h
new file mode 100644
index 0000000000000000000000000000000000000000..1fb84ee6638df0990638911734ca37c7d6f9b8c1
--- /dev/null
+++ b/src/boards/Payload/StateMachines/WingController/WingController.h
@@ -0,0 +1,182 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h>
+#include <Payload/Wing/WingAlgorithm.h>
+#include <events/HSM.h>
+
+#include <Eigen/Core>
+#include <atomic>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "WingControllerData.h"
+
+/**
+ * @brief This class allows the user to select the wing algorithm
+ * that has to be used during the tests. It also registers his
+ * dedicated function in the task scheduler in order to be
+ * executed every fixed period and to update the two servos position
+ * depending on the selected algorithm.
+ *
+ * Use case example:
+ * controller = new WingController(scheduler);
+ *
+ * controller.addAlgorithm("filename");
+ * OR
+ * controller.addAlgorithm(algorithm);
+ *
+ * controller.selectAlgorithm(index);
+ *
+ * controller.start();
+ * controller.stop();  //If you want to abort the execution
+ * controller.start(); //If you want to start again from the beginning
+ */
+
+namespace Payload
+{
+class WingController : public Boardcore::HSM<WingController>,
+                       public Boardcore::Module
+
+{
+
+public:
+    Boardcore::State state_idle(const Boardcore::Event& event);
+    Boardcore::State state_flying(const Boardcore::Event& event);
+    Boardcore::State state_calibration(const Boardcore::Event& event);
+    Boardcore::State state_controlled_descent(const Boardcore::Event& event);
+    Boardcore::State state_on_ground(const Boardcore::Event& event);
+
+    /**
+     * @brief Destroy the Wing Controller object.
+     */
+    ~WingController();
+
+    /**
+     * @brief Method to set the target position.
+     */
+    void setTargetPosition(Eigen::Vector2f target);
+
+    /**
+     * @brief Selects the algorithm if present.
+     *
+     * @param index The algorithms vector index
+     *
+     * @return true if the selection was successful
+     */
+    bool selectAlgorithm(unsigned int index);
+
+    WingControllerStatus getStatus();
+
+    /**
+     * @brief Construct a new Wing Controller object
+     */
+    explicit WingController(Boardcore::TaskScheduler* sched);
+
+    bool start() override;
+
+private:
+    /**
+     * @brief Method to add the algorithm in the list
+     *
+     * @param algorithm The algorithm with
+     * all already done (e.g. steps already registered)
+     */
+    bool addAlgorithms();
+
+    /**
+     * @brief target position getter
+     */
+    Eigen::Vector2f convertTargetPositionToNED(Eigen::Vector2f targetGEO);
+
+    /**
+     * @brief set points needed by the Guidance
+     */
+    void setEarlyManeuverPoints(Eigen::Vector2f targetNED,
+                                Eigen::Vector2f currentPosNED);
+
+    void logStatus(WingControllerState state);
+
+    WingControllerStatus status;
+
+    miosix::FastMutex statusMutex;
+
+    /**
+     * @brief Target position
+     */
+    Eigen::Vector2f targetPositionGEO;
+
+    /**
+     * @brief List of loaded algorithms (from SD or not)
+     */
+    std::vector<WingAlgorithm*> algorithms;
+
+    /**
+     * @brief PrintLogger
+     */
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("PayloadTest");
+
+    /**
+     * @brief Internal running state
+     */
+    std::atomic<bool> running;
+    /**
+     * @brief This attribute is modified by the mavlink radio section.
+     * The user using the Ground Station can select the pre-enumerated algorithm
+     * to execute
+     */
+    std::atomic<size_t> selectedAlgorithm;
+
+    /**
+     * @brief Instance of the Early Maneuver Guidance Algorithm used by
+     * AutomaticWingAlgorithm
+     */
+    EarlyManeuversGuidanceAlgorithm emGuidance;
+
+    /**
+     * @brief  starts the selected algorithm
+     */
+    void startAlgorithm();
+
+    /**
+     * @brief Sets the internal state to stop and
+     * stops the selected algorithm
+     */
+    void stopAlgorithm();
+
+    /**
+     * @brief Stops any on going algorithm and flares the wing
+     */
+    void flare();
+
+    /**
+     * @brief Resets the servos in their initial position
+     */
+    void reset();
+
+    void update();
+
+    Boardcore::TaskScheduler* scheduler = nullptr;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/StateMachines/WingController/WingControllerData.h b/src/boards/Payload/StateMachines/WingController/WingControllerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..82e444561b10465ed3c179b058b9198c5de7cf9a
--- /dev/null
+++ b/src/boards/Payload/StateMachines/WingController/WingControllerData.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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 <stdint.h>
+
+#include <iostream>
+#include <string>
+
+namespace Payload
+{
+
+enum class WingControllerState : uint8_t
+{
+    UNINIT = 0,
+    IDLE,
+    CALIBRATION,
+    ALGORITHM_CONTROLLED,
+    ON_GROUND,
+    END
+};
+
+struct WingControllerStatus
+{
+    long long timestamp       = 0;
+    WingControllerState state = WingControllerState::UNINIT;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)state << "\n";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/TMRepository/TMRepository.cpp b/src/boards/Payload/TMRepository/TMRepository.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62eec8f4f034178cc1705e1d7e37989f05304eeb
--- /dev/null
+++ b/src/boards/Payload/TMRepository/TMRepository.cpp
@@ -0,0 +1,616 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Actuators/Actuators.h>
+#include <Payload/BoardScheduler.h>
+#include <Payload/Configs/RadioConfig.h>
+#include <Payload/FlightStatsRecorder/FlightStatsRecorder.h>
+#include <Payload/PinHandler/PinHandler.h>
+#include <Payload/Radio/Radio.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/StateMachines/WingController/WingController.h>
+#include <Payload/TMRepository/TMRepository.h>
+#include <Payload/WindEstimationScheme/WindEstimation.h>
+#include <diagnostic/CpuMeter/CpuMeter.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <events/EventBroker.h>
+#include <interfaces-impl/hwmapping.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace miosix;
+using namespace Boardcore;
+
+namespace Payload
+{
+mavlink_message_t TMRepository::packSystemTm(SystemTMList tmId, uint8_t msgId,
+                                             uint8_t seq)
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    mavlink_message_t msg;
+
+    switch (tmId)
+    {
+        case SystemTMList::MAV_SYS_ID:
+        {
+            mavlink_sys_tm_t tm;
+
+            tm.timestamp       = TimestampTimer::getTimestamp();
+            tm.logger          = Logger::getInstance().isStarted();
+            tm.board_scheduler = modules.get<BoardScheduler>()->isStarted();
+            tm.event_broker    = EventBroker::getInstance().isRunning();
+            tm.radio           = modules.get<Radio>()->isStarted();
+            tm.sensors         = modules.get<Sensors>()->isStarted();
+            tm.pin_observer    = modules.get<PinHandler>()->isStarted();
+
+            mavlink_msg_sys_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SystemTMList::MAV_LOGGER_ID:
+        {
+            mavlink_logger_tm_t tm;
+
+            // Get the logger stats
+            LoggerStats stats = Logger::getInstance().getStats();
+
+            tm.timestamp          = stats.timestamp;
+            tm.average_write_time = stats.averageWriteTime;
+            tm.buffers_filled     = stats.buffersFilled;
+            tm.buffers_written    = stats.buffersWritten;
+            tm.dropped_samples    = stats.droppedSamples;
+            tm.last_write_error   = stats.lastWriteError;
+            tm.log_number         = stats.logNumber;
+            tm.max_write_time     = stats.maxWriteTime;
+            tm.queued_samples     = stats.queuedSamples;
+            tm.too_large_samples  = stats.tooLargeSamples;
+            tm.writes_failed      = stats.writesFailed;
+
+            mavlink_msg_logger_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                         RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SystemTMList::MAV_MAVLINK_STATS:
+        {
+            mavlink_mavlink_stats_tm_t tm;
+
+            // Get the mavlink stats
+            MavlinkStatus stats = modules.get<Radio>()->mavDriver->getStatus();
+
+            tm.timestamp               = stats.timestamp;
+            tm.n_send_queue            = stats.nSendQueue;
+            tm.max_send_queue          = stats.maxSendQueue;
+            tm.n_send_errors           = stats.nSendErrors;
+            tm.msg_received            = stats.mavStats.msg_received;
+            tm.buffer_overrun          = stats.mavStats.buffer_overrun;
+            tm.parse_error             = stats.mavStats.parse_error;
+            tm.parse_state             = stats.mavStats.parse_state;
+            tm.packet_idx              = stats.mavStats.packet_idx;
+            tm.current_rx_seq          = stats.mavStats.current_rx_seq;
+            tm.current_tx_seq          = stats.mavStats.current_tx_seq;
+            tm.packet_rx_success_count = stats.mavStats.packet_rx_success_count;
+            tm.packet_rx_drop_count    = stats.mavStats.packet_rx_drop_count;
+
+            mavlink_msg_mavlink_stats_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                                RadioConfig::MAV_COMP_ID, &msg,
+                                                &tm);
+            break;
+        }
+        case SystemTMList::MAV_NAS_ID:
+        {
+            mavlink_nas_tm_t tm;
+
+            // Get the current NAS state
+            NASState state = modules.get<NASController>()->getNasState();
+            ReferenceValues ref =
+                modules.get<NASController>()->getReferenceValues();
+
+            tm.timestamp = state.timestamp;
+            tm.state     = static_cast<uint8_t>(
+                modules.get<NASController>()->getStatus().state);
+            tm.nas_n           = state.n;
+            tm.nas_e           = state.e;
+            tm.nas_d           = state.d;
+            tm.nas_vn          = state.vn;
+            tm.nas_ve          = state.ve;
+            tm.nas_vd          = state.vd;
+            tm.nas_qx          = state.qx;
+            tm.nas_qy          = state.qy;
+            tm.nas_qz          = state.qz;
+            tm.nas_qw          = state.qw;
+            tm.nas_bias_x      = state.bx;
+            tm.nas_bias_y      = state.by;
+            tm.nas_bias_z      = state.bz;
+            tm.ref_pressure    = ref.refPressure;
+            tm.ref_temperature = ref.refTemperature;
+            tm.ref_latitude    = ref.refLatitude;
+            tm.ref_longitude   = ref.refLongitude;
+
+            mavlink_msg_nas_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SystemTMList::MAV_FLIGHT_ID:
+        {
+            mavlink_payload_flight_tm_t tm;
+
+            tm.timestamp = TimestampTimer::getTimestamp();
+
+            // Last samples
+            LPS28DFWData lps28dfw1 =
+                modules.get<Sensors>()->getLPS28DFW_1LastSample();
+            RotatedIMUData imu = modules.get<Sensors>()->getIMULastSample();
+            UBXGPSData gps     = modules.get<Sensors>()->getGPSLastSample();
+            Eigen::Vector2f wind =
+                modules.get<WindEstimation>()->getWindEstimationScheme();
+
+            // NAS state
+            NASState nasState = modules.get<NASController>()->getNasState();
+
+            tm.nas_state = static_cast<uint8_t>(
+                modules.get<NASController>()->getStatus().state);
+            tm.fmm_state = static_cast<uint8_t>(
+                modules.get<FlightModeManager>()->getStatus().state);
+            tm.wes_state = static_cast<uint8_t>(
+                modules.get<WingController>()->getStatus().state);
+
+            tm.wes_n = wind[0];
+            tm.wes_e = wind[1];
+
+            tm.pressure_digi = lps28dfw1.pressure;
+            tm.pressure_static =
+                modules.get<Sensors>()->getStaticPressureLastSample().pressure;
+            // Pitot
+            tm.airspeed_pitot =
+                modules.get<Sensors>()->getPitotLastSample().airspeed;
+
+            // Altitude agl
+            tm.altitude_agl = -nasState.d;
+
+            // IMU
+            tm.acc_x  = imu.accelerationX;
+            tm.acc_y  = imu.accelerationY;
+            tm.acc_z  = imu.accelerationZ;
+            tm.gyro_x = imu.angularSpeedX;
+            tm.gyro_y = imu.angularSpeedY;
+            tm.gyro_z = imu.angularSpeedZ;
+
+            // Magnetometer
+            tm.mag_x = imu.magneticFieldX;
+            tm.mag_y = imu.magneticFieldY;
+            tm.mag_z = imu.magneticFieldZ;
+
+            // GPS
+            tm.gps_fix = gps.fix;
+            tm.gps_lat = gps.latitude;
+            tm.gps_lon = gps.longitude;
+            tm.gps_alt = gps.height;
+
+            // NAS
+            tm.nas_n      = nasState.n;
+            tm.nas_e      = nasState.e;
+            tm.nas_d      = nasState.d;
+            tm.nas_vn     = nasState.vn;
+            tm.nas_ve     = nasState.ve;
+            tm.nas_vd     = nasState.vd;
+            tm.nas_qx     = nasState.qx;
+            tm.nas_qy     = nasState.qy;
+            tm.nas_qz     = nasState.qz;
+            tm.nas_qw     = nasState.qw;
+            tm.nas_bias_x = nasState.bx;
+            tm.nas_bias_y = nasState.by;
+            tm.nas_bias_z = nasState.bz;
+            // Servos
+            tm.left_servo_angle = modules.get<Actuators>()->getServoAngle(
+                ServosList::PARAFOIL_LEFT_SERVO);
+            tm.right_servo_angle = modules.get<Actuators>()->getServoAngle(
+                ServosList::PARAFOIL_RIGHT_SERVO);
+
+            tm.pin_nosecone = modules.get<PinHandler>()
+                                  ->getPinsData()[PinsList::NOSECONE_PIN]
+                                  .lastState;
+
+            tm.cutter_presence =
+                static_cast<uint8_t>(miosix::gpios::cut_sense::value());
+
+            tm.battery_voltage = modules.get<Sensors>()
+                                     ->getBatteryVoltageLastSample()
+                                     .batVoltage;
+            tm.current_consumption =
+                modules.get<Sensors>()->getCurrentLastSample().current;
+            tm.temperature  = lps28dfw1.temperature;
+            tm.logger_error = Logger::getInstance().getStats().lastWriteError;
+
+            tm.cam_battery_voltage = modules.get<Sensors>()
+                                         ->getCamBatteryVoltageLastSample()
+                                         .batVoltage;
+
+            mavlink_msg_payload_flight_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                                 RadioConfig::MAV_COMP_ID, &msg,
+                                                 &tm);
+
+            break;
+        }
+        case SystemTMList::MAV_STATS_ID:
+        {
+            mavlink_payload_stats_tm_t tm =
+                modules.get<FlightStatsRecorder>()->getStats();
+            tm.cpu_load  = CpuMeter::getCpuStats().mean;
+            tm.free_heap = CpuMeter::getCpuStats().freeHeap;
+
+            mavlink_msg_payload_stats_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                                RadioConfig::MAV_COMP_ID, &msg,
+                                                &tm);
+            break;
+        }
+        case SystemTMList::MAV_FSM_ID:
+        {
+            mavlink_fsm_tm_t tm;
+
+            tm.timestamp = TimestampTimer::getTimestamp();
+            tm.abk_state = 0;
+            tm.ada_state = 0;
+            tm.dpl_state = static_cast<uint8_t>(
+                modules.get<WingController>()->getStatus().state);
+            tm.fmm_state = static_cast<uint8_t>(
+                modules.get<FlightModeManager>()->getStatus().state);
+            tm.nas_state = static_cast<uint8_t>(
+                modules.get<NASController>()->getStatus().state);
+            tm.wes_state = 0;
+
+            mavlink_msg_fsm_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SystemTMList::MAV_PIN_OBS_ID:
+        {
+            mavlink_pin_tm_t tm;
+
+            tm.timestamp = TimestampTimer::getTimestamp();
+            tm.last_change_timestamp =
+                modules.get<PinHandler>()
+                    ->getPinsData()[PinsList::NOSECONE_PIN]
+                    .lastStateTimestamp; /*<  Last change timestamp of pin*/
+            tm.pin_id =
+                PinsList::NOSECONE_PIN; /*<  A member of the PinsList enum*/
+            tm.changes_counter =
+                modules.get<PinHandler>()
+                    ->getPinsData()[PinsList::NOSECONE_PIN]
+                    .changesCount; /*<  Number of changes of pin*/
+            tm.current_state = modules.get<PinHandler>()
+                                   ->getPinsData()[PinsList::NOSECONE_PIN]
+                                   .lastState; /*<  Current state of pin*/
+
+            mavlink_msg_pin_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        default:
+        {
+            // Send by default the nack message
+            mavlink_nack_tm_t nack;
+
+            nack.recv_msgid = msgId;
+            nack.seq_ack    = seq;
+
+            LOG_ERR(logger, "Unknown message id: {}", tmId);
+            mavlink_msg_nack_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                       RadioConfig::MAV_COMP_ID, &msg, &nack);
+
+            break;
+        }
+    }
+    return msg;
+}
+mavlink_message_t TMRepository::packSensorsTm(SensorsTMList sensorId,
+                                              uint8_t msgId, uint8_t seq)
+{
+    mavlink_message_t msg;
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    switch (sensorId)
+    {
+        case SensorsTMList::MAV_GPS_ID:
+        {
+            mavlink_gps_tm_t tm;
+
+            UBXGPSData gpsData = modules.get<Sensors>()->getGPSLastSample();
+
+            tm.timestamp = gpsData.gpsTimestamp;
+            strcpy(tm.sensor_name, "UBXGPS");
+            tm.fix          = gpsData.fix;
+            tm.height       = gpsData.height;
+            tm.latitude     = gpsData.latitude;
+            tm.longitude    = gpsData.longitude;
+            tm.n_satellites = gpsData.satellites;
+            tm.speed        = gpsData.speed;
+            tm.track        = gpsData.track;
+            tm.vel_down     = gpsData.velocityDown;
+            tm.vel_east     = gpsData.velocityEast;
+            tm.vel_north    = gpsData.velocityNorth;
+
+            mavlink_msg_gps_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+
+        case SensorsTMList::MAV_STATIC_PRESS_ID:
+        {
+            mavlink_pressure_tm_t tm;
+
+            HSCMRNN015PAData pressureData =
+                modules.get<Sensors>()->getStaticPressureLastSample();
+
+            tm.timestamp = pressureData.pressureTimestamp;
+            strcpy(tm.sensor_name, "STATIC_PRESSURE");
+            tm.pressure = pressureData.pressure;
+
+            mavlink_msg_pressure_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                           RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SensorsTMList::MAV_DPL_PRESS_ID:
+        {
+            mavlink_pressure_tm_t tm;
+
+            SSCMRNN030PAData pressureData =
+                modules.get<Sensors>()->getDynamicPressureLastSample();
+
+            tm.timestamp = pressureData.pressureTimestamp;
+            strcpy(tm.sensor_name, "DYNAMIC_PRESSURE");
+            tm.pressure = pressureData.pressure;
+
+            mavlink_msg_pressure_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                           RadioConfig::MAV_COMP_ID, &msg, &tm);
+
+            break;
+        }
+        case SensorsTMList::MAV_PITOT_PRESS_ID:
+        {
+            mavlink_pressure_tm_t tm;
+
+            SSCMRNN030PAData pitot =
+                modules.get<Sensors>()->getDynamicPressureLastSample();
+
+            tm.timestamp = pitot.pressureTimestamp;
+            tm.pressure  = pitot.pressure;
+            strcpy(tm.sensor_name, "PITOT_PRESSURE");
+
+            mavlink_msg_pressure_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                           RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_LIS2MDL_ID:
+        {
+            mavlink_imu_tm_t tm;
+
+            LIS2MDLData mag = modules.get<Sensors>()->getLIS2MDLLastSample();
+
+            tm.acc_x     = 0;
+            tm.acc_y     = 0;
+            tm.acc_z     = 0;
+            tm.gyro_x    = 0;
+            tm.gyro_y    = 0;
+            tm.gyro_z    = 0;
+            tm.mag_x     = mag.magneticFieldX;
+            tm.mag_y     = mag.magneticFieldY;
+            tm.mag_z     = mag.magneticFieldZ;
+            tm.timestamp = mag.magneticFieldTimestamp;
+            strcpy(tm.sensor_name, "LIS2MD");
+
+            mavlink_msg_imu_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_LSM6DSRX_ID:
+        {
+            mavlink_imu_tm_t tm;
+
+            LSM6DSRXData imu = modules.get<Sensors>()->getLSM6DSRXLastSample();
+
+            tm.acc_x  = imu.accelerationX;
+            tm.acc_y  = imu.accelerationY;
+            tm.acc_z  = imu.accelerationZ;
+            tm.gyro_x = imu.angularSpeedX;
+            tm.gyro_y = imu.angularSpeedY;
+            tm.gyro_z = imu.angularSpeedZ;
+            tm.mag_x  = 0;
+            tm.mag_y  = 0;
+            tm.mag_z  = 0;
+
+            tm.timestamp = imu.accelerationTimestamp;
+            strcpy(tm.sensor_name, "LSM6DSRX");
+
+            mavlink_msg_imu_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_H3LIS331DL_ID:
+        {
+            mavlink_imu_tm_t tm;
+
+            H3LIS331DLData imu =
+                modules.get<Sensors>()->getH3LIS331DLLastSample();
+
+            tm.acc_x  = imu.accelerationX;
+            tm.acc_y  = imu.accelerationY;
+            tm.acc_z  = imu.accelerationZ;
+            tm.gyro_x = 0;
+            tm.gyro_y = 0;
+            tm.gyro_z = 0;
+            tm.mag_x  = 0;
+            tm.mag_y  = 0;
+            tm.mag_z  = 0;
+
+            tm.timestamp = imu.accelerationTimestamp;
+            strcpy(tm.sensor_name, "H3LIS331DL");
+
+            mavlink_msg_imu_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_LPS22DF_ID:
+        {
+            mavlink_pressure_tm_t tm;
+
+            LPS22DFData pressure =
+                modules.get<Sensors>()->getLPS22DFLastSample();
+
+            tm.timestamp = pressure.pressureTimestamp;
+            tm.pressure  = pressure.pressure;
+            strcpy(tm.sensor_name, "LPS22DF");
+
+            mavlink_msg_pressure_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                           RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_LPS28DFW_ID:
+        {
+            mavlink_pressure_tm_t tm;
+            LPS28DFWData pressure1 =
+                modules.get<Sensors>()->getLPS28DFW_1LastSample();
+
+            tm.timestamp = pressure1.pressureTimestamp;
+            tm.pressure  = pressure1.pressure;
+            strcpy(tm.sensor_name, "LPS28DFW");
+
+            mavlink_msg_pressure_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                           RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_CURRENT_SENSE_ID:
+        {
+            mavlink_current_tm_t tm;
+            CurrentData current =
+                modules.get<Sensors>()->getCurrentLastSample();
+            tm.current = current.current;
+            strcpy(tm.sensor_name, "CURRENT");
+            tm.timestamp = current.currentTimestamp;
+
+            mavlink_msg_current_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                          RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_BATTERY_VOLTAGE_ID:
+        {
+            mavlink_voltage_tm_t tm;
+            BatteryVoltageSensorData voltage =
+                modules.get<Sensors>()->getBatteryVoltageLastSample();
+            tm.voltage = voltage.batVoltage;
+            strcpy(tm.sensor_name, "BATTERY");
+            tm.timestamp = voltage.voltageTimestamp;
+            mavlink_msg_voltage_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                          RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        case SensorsTMList::MAV_ADS_ID:
+        {
+            mavlink_adc_tm_t tm;
+            ADS131M08Data voltage =
+                modules.get<Sensors>()->getADS131M08LastSample();
+            tm.channel_0 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_0).voltage;
+
+            tm.channel_1 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_1).voltage;
+
+            tm.channel_2 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_2).voltage;
+
+            tm.channel_3 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_3).voltage;
+
+            tm.channel_4 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_4).voltage;
+
+            tm.channel_5 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_5).voltage;
+
+            tm.channel_6 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_6).voltage;
+
+            tm.channel_7 =
+                voltage.getVoltage(ADS131M08Defs::Channel::CHANNEL_7).voltage;
+
+            strcpy(tm.sensor_name, "ADS131M08");
+            tm.timestamp = voltage.timestamp;
+            mavlink_msg_adc_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                      RadioConfig::MAV_COMP_ID, &msg, &tm);
+            break;
+        }
+        default:
+        {
+            mavlink_nack_tm_t nack;
+
+            nack.recv_msgid = msgId;
+            nack.seq_ack    = seq;
+
+            LOG_DEBUG(logger, "Unknown telemetry id: {}", sensorId);
+            mavlink_msg_nack_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                       RadioConfig::MAV_COMP_ID, &msg, &nack);
+            break;
+        }
+    }
+
+    return msg;
+}
+
+mavlink_message_t TMRepository::packServoTm(ServosList servoId, uint8_t msgId,
+                                            uint8_t seq)
+{
+    mavlink_message_t msg;
+
+    if (servoId == PARAFOIL_LEFT_SERVO || servoId == PARAFOIL_RIGHT_SERVO)
+    {
+        mavlink_servo_tm_t tm;
+
+        tm.servo_id = servoId;
+        tm.servo_position =
+            ModuleManager::getInstance().get<Actuators>()->getServoAngle(
+                servoId);
+
+        mavlink_msg_servo_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                    RadioConfig::MAV_COMP_ID, &msg, &tm);
+    }
+    else
+    {
+        mavlink_nack_tm_t nack;
+
+        nack.recv_msgid = msgId;
+        nack.seq_ack    = seq;
+
+        mavlink_msg_nack_tm_encode(RadioConfig::MAV_SYSTEM_ID,
+                                   RadioConfig::MAV_COMP_ID, &msg, &nack);
+    }
+
+    return msg;
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/TMRepository/TMRepository.h b/src/boards/Payload/TMRepository/TMRepository.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d2285c9c1c451511e03e593d4df7181811a1d29
--- /dev/null
+++ b/src/boards/Payload/TMRepository/TMRepository.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <common/Mavlink.h>
+#include <diagnostic/PrintLogger.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+class TMRepository : public Boardcore::Module
+{
+public:
+    inline TMRepository() {}
+
+    // Packs the telemetry at system level (E.g. logger telemetry..)
+    mavlink_message_t packSystemTm(SystemTMList tmId, uint8_t msgId,
+                                   uint8_t seq);
+
+    // Packs the telemetry at sensors level (E.g. pressure sensors..)
+    mavlink_message_t packSensorsTm(SensorsTMList sensorId, uint8_t msgId,
+                                    uint8_t seq);
+
+    // Packs the telemetry about servo positions states
+    mavlink_message_t packServoTm(ServosList servoId, uint8_t msgId,
+                                  uint8_t seq);
+
+private:
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("TMRepository");
+};
+}  // namespace Payload
\ No newline at end of file
diff --git a/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.cpp b/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..538d58a45b8441582492659d9f91faf28fe5217f
--- /dev/null
+++ b/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.cpp
@@ -0,0 +1,87 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Raul Radu
+ *
+ * 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 <Payload/BoardScheduler.h>
+#include <Payload/Configs/VerticalVelocityConfig.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+
+#include <functional>
+
+using namespace std;
+using namespace Boardcore;
+using namespace Common;
+
+namespace Payload
+{
+
+VerticalVelocityTrigger::VerticalVelocityTrigger(TaskScheduler* sched)
+    : running(false), confidence(0), scheduler(sched)
+{
+}
+
+bool VerticalVelocityTrigger::start()
+{
+    return scheduler->addTask(
+        bind(&VerticalVelocityTrigger::update, this),
+        FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_PERIOD);
+}
+
+void VerticalVelocityTrigger::enable()
+{
+    confidence = 0;
+    running    = true;
+}
+
+void VerticalVelocityTrigger::disable() { running = false; }
+
+bool VerticalVelocityTrigger::isActive() { return running; }
+
+void VerticalVelocityTrigger::update()
+{
+    if (running)
+    {
+        float verticalVelocity = -ModuleManager::getInstance()
+                                      .get<NASController>()
+                                      ->getNasState()
+                                      .vd;
+        if (verticalVelocity < FailSafe::FAILSAFE_VERTICAL_VELOCITY_THRESHOLD)
+        {
+            confidence++;
+        }
+        else
+        {
+            confidence = 0;
+        }
+        if (confidence >=
+            FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_CONFIDENCE)
+        {
+            confidence = 0;
+            EventBroker::getInstance().post(ALTITUDE_TRIGGER_ALTITUDE_REACHED,
+                                            TOPIC_FLIGHT);
+            running = false;
+        }
+    }
+}
+}  // namespace Payload
diff --git a/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h b/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h
new file mode 100644
index 0000000000000000000000000000000000000000..84f721f35a6e05efdff43de2da985f81ed8de9da
--- /dev/null
+++ b/src/boards/Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Raul Radu
+ *
+ * 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 <Payload/BoardScheduler.h>
+
+#include <atomic>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Payload
+{
+
+/**
+ * This class is used by the FailSafe logic to react to
+ * the premature opening of the parafoil by the payload
+ * Required modules:
+ * - Payload::BoardScheduler
+ * - Payload::NASController
+ */
+class VerticalVelocityTrigger : public Boardcore::Module
+{
+public:
+    /**
+     * Default constructor for VerticalVelocityTrigger
+     * Sets the trigger to disabled by default
+     */
+    VerticalVelocityTrigger(Boardcore::TaskScheduler* sched);
+
+    /**
+     * Starts the module by inserting it in the BoardScheduler
+     */
+    bool start();
+
+    /**
+     * Enables the Vertical Velocity Trigger
+     */
+    void enable();
+
+    /**
+     * Disables the Vertical Velocity Trigger
+     */
+    void disable();
+
+    /**
+     * Checks if the trigger is enabled
+     * @return true if the trigger is enabled
+     * @return false if the trigger is not enabled
+     */
+    bool isActive();
+
+private:
+    /**
+     * This method is called every
+     * FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_PERIOD
+     * and reads the state from the NAS and when the velocity is below
+     * the threshold for some confidence level it posts the event
+     * FLIGHT_FAILSAFE_TRIGGERED in topic TOPIC_FLIGHT
+     */
+    void update();
+
+    std::atomic<bool> running;
+
+    // Number of times that the algorithm detects to be slower than the velocity
+    // threshold
+    std::atomic<int> confidence;
+
+    Boardcore::TaskScheduler* scheduler;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/WindEstimationScheme/WindEstimation.cpp b/src/boards/Payload/WindEstimationScheme/WindEstimation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6cc50cc7baec7997e6986e7119837eee200cde4
--- /dev/null
+++ b/src/boards/Payload/WindEstimationScheme/WindEstimation.cpp
@@ -0,0 +1,215 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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 "WindEstimation.h"
+
+#include <Payload/BoardScheduler.h>
+#include <Payload/Configs/WESConfig.h>
+#include <Payload/Sensors/Sensors.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Payload::WESConfig;
+using namespace Boardcore;
+using namespace Common;
+using namespace miosix;
+
+namespace Payload
+{
+
+WindEstimation::WindEstimation(TaskScheduler* sched)
+    : running(false), calRunning(false), scheduler(sched)
+{
+    funv << 1.0f, 0.0f, 0.0f, 1.0f;  // cppcheck-suppress constStatement
+}
+
+bool WindEstimation::start()
+{
+    scheduler->addTask(
+        std::bind(&WindEstimation::windEstimationSchemeCalibration, this),
+        WES_CALIBRATION_UPDATE_PERIOD);
+
+    // Register the WES task
+    scheduler->addTask(std::bind(&WindEstimation::windEstimationScheme, this),
+                       WES_PREDICTION_UPDATE_PERIOD);
+
+    return true;
+}
+
+WindEstimation::~WindEstimation()
+{
+    stopWindEstimationSchemeCalibration();
+    stopWindEstimationScheme();
+}
+
+bool WindEstimation::getStatus() { return running; }
+
+void WindEstimation::startWindEstimationSchemeCalibration()
+{
+    if (!calRunning && !running)
+    {
+        calRunning = true;
+        nSampleCal = 0;
+        vx         = 0;
+        vy         = 0;
+        v2         = 0;
+        LOG_INFO(logger, "WindEstimationCalibration started");
+    }
+}
+
+void WindEstimation::stopWindEstimationSchemeCalibration()
+{
+    calRunning = false;
+    LOG_INFO(logger, "WindEstimationSchemeCalibration stopped");
+    // assign value to the array only if running != false;
+    if (!running)
+    {
+        miosix::Lock<FastMutex> l(mutex);
+        wind = windCalibration;
+    }
+    windLogger.vn = windCalibration[0];
+    windLogger.ve = windCalibration[1];
+    startWindEstimationScheme();
+    logStatus();
+}
+
+void WindEstimation::windEstimationSchemeCalibration()
+{
+
+    if (calRunning)
+    {
+        auto gpsData =
+            ModuleManager::getInstance().get<Sensors>()->getGPSLastSample();
+        if (gpsData.fix != 0)
+        {
+            if (nSampleCal < WES_CALIBRATION_SAMPLE_NUMBER)
+            {
+                float gpsN = 0, gpsE = 0;
+
+                gpsN                             = gpsData.velocityNorth;
+                gpsE                             = gpsData.velocityEast;
+                calibrationMatrix(nSampleCal, 0) = gpsN;
+                calibrationMatrix(nSampleCal, 1) = gpsE;
+                calibrationV2(nSampleCal)        = gpsN * gpsN + gpsE * gpsE;
+
+                vx += gpsN;
+                vy += gpsE;
+                v2 += gpsN * gpsN + gpsE * gpsE;
+                nSampleCal++;
+            }
+            else
+            {
+                vx = vx / nSampleCal;
+                vy = vy / nSampleCal;
+                v2 = v2 / nSampleCal;
+                for (int i = 0; i < nSampleCal; i++)
+                {
+                    calibrationMatrix(i, 0) = calibrationMatrix(i, 0) - vx;
+                    calibrationMatrix(i, 1) = calibrationMatrix(i, 1) - vy;
+                    calibrationV2(i)        = 0.5f * (calibrationV2(i) - v2);
+                }
+                Eigen::MatrixXf calibrationMatrixT =
+                    calibrationMatrix.transpose();
+                windCalibration =
+                    (calibrationMatrixT * calibrationMatrix)
+                        .ldlt()
+                        .solve(calibrationMatrixT * calibrationV2);
+                stopWindEstimationSchemeCalibration();
+            }
+        }
+    }
+}
+
+void WindEstimation::startWindEstimationScheme()
+{
+    if (!running)
+    {
+        running = true;
+        // nSample has to be reset after the calibration
+        nSample = nSampleCal;
+        LOG_INFO(logger, "WindEstimationScheme started");
+    }
+}
+
+void WindEstimation::stopWindEstimationScheme()
+{
+    if (running)
+    {
+        running = false;
+        LOG_INFO(logger, "WindEstimationScheme ended");
+    }
+}
+
+void WindEstimation::windEstimationScheme()
+{
+    if (running)
+    {
+        auto gpsData =
+            ModuleManager::getInstance().get<Sensors>()->getGPSLastSample();
+        if (gpsData.fix != 0)
+        {
+            Eigen::Vector2f phi;
+            Eigen::Matrix<float, 1, 2> phiT;
+            Eigen::Vector2f temp;
+            float y, gpsN = 0, gpsE = 0;
+            gpsN = gpsData.velocityNorth;
+            gpsE = gpsData.velocityEast;
+            // update avg
+            nSample++;
+            vx = (vx * nSample + gpsN) / (nSample + 1);
+            vy = (vy * nSample + gpsE) / (nSample + 1);
+            v2 = (v2 * nSample + (gpsN * gpsN + gpsE * gpsE)) / (nSample + 1);
+            phi(0) = gpsN - vx;
+            phi(1) = gpsE - vy;
+            y      = 0.5f * ((gpsN * gpsN + gpsE * gpsE) - v2);
+
+            phiT = phi.transpose();
+            funv =
+                (funv - (funv * phi * phiT * funv) / (1 + (phiT * funv * phi)));
+            temp = (0.5 * (funv + funv.transpose()) * phi) *
+                   (y - phiT * getWindEstimationScheme());
+            {
+                miosix::Lock<FastMutex> l(mutex);
+                wind          = wind + temp;
+                windLogger.vn = wind[0];
+                windLogger.ve = wind[1];
+            }
+            logStatus();
+        }
+    }
+}
+
+Eigen::Vector2f WindEstimation::getWindEstimationScheme()
+{
+    miosix::Lock<FastMutex> l(mutex);
+    return wind;
+}
+
+void WindEstimation::logStatus()
+{
+    windLogger.timestamp = TimestampTimer::getTimestamp();
+    Logger::getInstance().log(windLogger);
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/WindEstimationScheme/WindEstimation.h b/src/boards/Payload/WindEstimationScheme/WindEstimation.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3c5e0148ab938b66afcc26fda9ccd412842b31e
--- /dev/null
+++ b/src/boards/Payload/WindEstimationScheme/WindEstimation.h
@@ -0,0 +1,130 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Federico Mandelli
+ *
+ * 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 <Payload/BoardScheduler.h>
+#include <Payload/Configs/WESConfig.h>
+#include <diagnostic/PrintLogger.h>
+#include <logger/Logger.h>
+
+#include <Eigen/Core>
+#include <atomic>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "WindEstimationData.h"
+namespace Payload
+{
+
+/**
+ * @brief This class implements the wind prediction algorithm, the first part is
+ * the initial setup, and then the continuos algoritms runs;
+ */
+class WindEstimation : public Boardcore::Module
+{
+public:
+    /**
+     * @brief Construct a new Wing Controller object
+     */
+    explicit WindEstimation(Boardcore::TaskScheduler* sched);
+
+    /**
+     * @brief Destroy the Wing Controller object.
+     */
+    ~WindEstimation();
+
+    bool start();
+
+    void startWindEstimationSchemeCalibration();
+
+    void stopWindEstimationSchemeCalibration();
+
+    void startWindEstimationScheme();
+
+    void stopWindEstimationScheme();
+
+    bool getStatus();
+
+    Eigen::Vector2f getWindEstimationScheme();
+
+private:
+    /**
+     * @brief Creates the windCalibration matrix with the starting prediction
+     * value
+     */
+    void windEstimationSchemeCalibration();
+
+    /**
+     * @brief Updates the wind matrix with the updated wind prediction values
+     */
+    void windEstimationScheme();
+
+    /**
+     * @brief Logs the prediction
+     */
+    void logStatus();
+
+    /**
+     * @brief Parameters needed for calibration
+     */
+    Eigen::Vector2f windCalibration{0.0f, 0.0f};
+    uint8_t nSampleCal = 0;
+    Eigen::Matrix<float, WESConfig::WES_CALIBRATION_SAMPLE_NUMBER, 2>
+        calibrationMatrix;
+    Eigen::Vector<float, WESConfig::WES_CALIBRATION_SAMPLE_NUMBER>
+        calibrationV2;
+    float vx = 0;
+    float vy = 0;
+    float v2 = 0;
+
+    /**
+     * @brief Parameters needed for recursive
+     */
+    Eigen::Vector2f wind{0.0f, 0.0f};
+    uint8_t nSample = 0;
+    Eigen::Matrix2f funv;
+
+    /**
+     * @brief Mutex
+     */
+    miosix::FastMutex mutex;
+
+    /**
+     * @brief PrintLogger
+     */
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("PayloadTest");
+
+    /**
+     * @brief Logging struct
+     */
+    WindLogging windLogger{0, 0, 0};
+
+    /**
+     * @brief Internal running state
+     */
+    std::atomic<bool> running;
+    std::atomic<bool> calRunning;
+
+    Boardcore::TaskScheduler* scheduler = nullptr;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/WindEstimationScheme/WindEstimationData.h b/src/boards/Payload/WindEstimationScheme/WindEstimationData.h
new file mode 100644
index 0000000000000000000000000000000000000000..49d674fc96df107474c7b001e301530e1879770e
--- /dev/null
+++ b/src/boards/Payload/WindEstimationScheme/WindEstimationData.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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 <stdint.h>
+
+#include <iostream>
+#include <string>
+
+namespace Payload
+{
+
+struct WindLogging
+{
+    long long timestamp = 0;
+    float vn = 0, ve = 0;
+
+    static std::string header() { return "timestamp,vn,ve\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << vn << "," << ve << "\n ";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/AutomaticWingAlgorithm.cpp b/src/boards/Payload/Wing/AutomaticWingAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eab89c3e4bea83a43ce9af65a76b91b5473272ef
--- /dev/null
+++ b/src/boards/Payload/Wing/AutomaticWingAlgorithm.cpp
@@ -0,0 +1,181 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Radu Raul
+ *
+ * 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 "AutomaticWingAlgorithm.h"
+
+#include <Payload/Configs/WingConfig.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/StateMachines/WingController/WingController.h>
+#include <Payload/WindEstimationScheme/WindEstimation.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <math.h>
+#include <utils/AeroUtils/AeroUtils.h>
+#include <utils/Constants.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Boardcore;
+using namespace Eigen;
+using namespace Payload::WingConfig;
+
+namespace Payload
+{
+AutomaticWingAlgorithm::AutomaticWingAlgorithm(float Kp, float Ki,
+                                               ServosList servo1,
+                                               ServosList servo2,
+                                               GuidanceAlgorithm& guidance)
+    : WingAlgorithm(servo1, servo2), guidance(guidance)
+{
+    controller = new PIController(Kp, Ki, WING_UPDATE_PERIOD / 1000.0f,
+                                  PI_CONTROLLER_SATURATION_MIN_LIMIT,
+                                  PI_CONTROLLER_SATURATION_MAX_LIMIT);
+}
+
+AutomaticWingAlgorithm::~AutomaticWingAlgorithm() { delete (controller); }
+
+void AutomaticWingAlgorithm::step()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    if (modules.get<Sensors>()->getGPSLastSample().fix != 0)
+    {
+        // The PI calculated result
+        float result = algorithmStep(
+            modules.get<NASController>()->getNasState(),
+            modules.get<WindEstimation>()->getWindEstimationScheme());
+
+        // Actuate the result
+        if (result > 0)
+        {
+            // Activate the servo1 and reset servo2
+            modules.get<Actuators>()->setServoAngle(servo1, result);
+            modules.get<Actuators>()->setServoAngle(servo2, 0);
+        }
+        else
+        {
+            // Activate the servo2 and reset servo1
+            modules.get<Actuators>()->setServoAngle(servo1, 0);
+            modules.get<Actuators>()->setServoAngle(servo2, result * -1);
+        }
+
+        // Log the servo positions
+        {
+            miosix::Lock<FastMutex> l(mutex);
+
+            data.timestamp = TimestampTimer::getTimestamp();
+            data.servo1Angle =
+                modules.get<Actuators>()->getServoPosition(servo1);
+            data.servo2Angle =
+                modules.get<Actuators>()->getServoPosition(servo2);
+            SDlogger->log(data);
+        }
+    }
+    else
+    {
+        // If we loose fix we set both servo at 0
+        modules.get<Actuators>()->setServoAngle(servo1, 0);
+        modules.get<Actuators>()->setServoAngle(servo2, 0);
+    }
+}
+
+float AutomaticWingAlgorithm::algorithmStep(NASState state, Vector2f windNED)
+{
+    float result;
+    // For some algorithms the third component is needed!
+    Vector3f currentPosition(state.n, state.e, state.d);
+
+    Vector2f heading;  // used for logging purposes
+
+    float targetAngle = guidance.calculateTargetAngle(currentPosition, heading);
+
+    Vector2f relativeVelocity(state.vn - windNED[0], state.ve - windNED[1]);
+
+    // Compute the angle of the current velocity
+    float velocityAngle;
+
+    // In case of a 0 north velocity i force the angle to 90
+    if (relativeVelocity[0] == 0 && relativeVelocity[1] == 0)
+    {
+        velocityAngle = 0;
+    }
+    else if (relativeVelocity[1] == 0)
+    {
+        velocityAngle = (relativeVelocity[0] > 0 ? 1 : -1) * Constants::PI / 2;
+    }
+    else
+    {
+        velocityAngle = atan2(relativeVelocity[1], relativeVelocity[0]);
+    }
+
+    // Compute the angle difference
+    float error = angleDiff(targetAngle, velocityAngle);
+
+    // Call the PI with the just calculated error. The result is in RADIANS,
+    // if positive we activate one servo, if negative the other
+    result = controller->update(error);
+
+    // Convert the result from radians back to degrees
+    result = result * (180.f / Constants::PI);
+
+    // Flip the servo orientation
+    result *= -1;
+
+    // Logs the outputs
+    {
+        miosix::Lock<FastMutex> l(mutex);
+        data.targetX       = heading[0];
+        data.targetY       = heading[1];
+        data.targetAngle   = targetAngle;
+        data.velocityAngle = velocityAngle;
+        data.error         = error;
+        data.pidOutput     = result;
+    }
+
+    return result;
+}
+
+float AutomaticWingAlgorithm::angleDiff(float a, float b)
+{
+    float diff = a - b;
+
+    // Angle difference
+    if (diff < -Constants::PI || Constants::PI < diff)
+    {
+        diff += Constants::PI;
+        bool positiveInput = diff > 0;
+
+        diff = diff - floor(diff / (2 * Constants::PI)) * (2 * Constants::PI);
+
+        // diff = fmod(diff, 2 * Constants::PI);
+        if (diff == 0 && positiveInput)
+        {
+            diff = 2 * Constants::PI;
+        }
+
+        diff -= Constants::PI;
+    }
+
+    return diff;
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/AutomaticWingAlgorithm.h b/src/boards/Payload/Wing/AutomaticWingAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..ffc00ec599c1834da9f6259d9a809f3a54ebf8df
--- /dev/null
+++ b/src/boards/Payload/Wing/AutomaticWingAlgorithm.h
@@ -0,0 +1,97 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Wing/Guidance/GuidanceAlgorithm.h>
+#include <Payload/Wing/WingAlgorithm.h>
+#include <algorithms/NAS/NASState.h>
+#include <algorithms/PIController.h>
+#include <algorithms/ReferenceValues.h>
+
+#include <Eigen/Core>
+
+namespace Payload
+{
+class AutomaticWingAlgorithm : public WingAlgorithm
+{
+
+public:
+    /**
+     * @brief Construct a new Automatic Wing Algorithm object
+     *
+     * @param Kp Proportional value for PI controller
+     * @param Ki Integral value for PI controller
+     * @param servo1 The first servo
+     * @param servo2 The second servo
+     * @param guidance The algorithm used to compute the target yaw and the
+     * heading
+     */
+    AutomaticWingAlgorithm(float Kp, float Ki, ServosList servo1,
+                           ServosList servo2, GuidanceAlgorithm& guidance);
+
+    /**
+     * @brief Destroy the Automatic Wing Algorithm object and the PI
+     */
+    ~AutomaticWingAlgorithm();
+
+protected:
+    // Guidance algorithm that sets the yaw.
+    GuidanceAlgorithm& guidance;
+
+    // PI controller tuned on the Kp and Ki passed through constructor
+    Boardcore::PIController* controller;
+
+    /**
+     * @brief Actual algorithm implementation, all parameters should be in NED
+     *
+     *  @param state NAS current state
+     * @param targetNED Target North & East
+     * @param windNED Wind velocity North & East
+     */
+    float algorithmStep(Boardcore::NASState state, Eigen::Vector2f windNED);
+
+    /**
+     * @brief This method implements the automatic algorithm that will steer the
+     * parafoil according to its position and velocity. IN THIS METHOD THE
+     * GUIDANCE IS TRANSLATED
+     */
+    void step() override;
+
+    /**
+     * @brief Computes the difference between two angles
+     *
+     * @param a The first angle
+     * @param b The second angle
+     *
+     * @returns angle(a) - angle(b)
+     */
+    float angleDiff(float a, float b);
+
+    // Logging structure
+    WingAlgorithmData data;
+
+    /**
+     * @brief Mutex
+     */
+    miosix::FastMutex mutex;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/FileWingAlgorithm.cpp b/src/boards/Payload/Wing/FileWingAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d4f8e7a18e9191b5fd55fa962ebb9febd8e31ff
--- /dev/null
+++ b/src/boards/Payload/Wing/FileWingAlgorithm.cpp
@@ -0,0 +1,68 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Wing/FileWingAlgorithm.h>
+#include <drivers/timer/TimestampTimer.h>
+
+using namespace Boardcore;
+
+namespace Payload
+{
+std::istream& operator>>(std::istream& input, WingAlgorithmData& data)
+{
+    input >> data.timestamp;
+    input.ignore(1, ',');
+    input >> data.servo1Angle;
+    input.ignore(1, ',');
+    input >> data.servo2Angle;
+    return input;
+}
+
+FileWingAlgorithm::FileWingAlgorithm(ServosList servo1, ServosList servo2,
+                                     const char* filename)
+    : WingAlgorithm(servo1, servo2), parser(filename)
+{
+    setServo(servo1, servo2);
+}
+
+bool FileWingAlgorithm::init()
+{
+    // Returns a std::vector which contains
+    // all the csv parsed with the data structure in mind
+    steps = parser.collect();
+
+    // Return if the size collected is greater than 0
+    fileValid = steps.size() > 0;
+
+    // Communicate it via serial
+    if (fileValid)
+    {
+        LOG_INFO(logger, "File valid");
+    }
+
+    // Close the file
+    parser.close();
+
+    return fileValid;
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/FileWingAlgorithm.h b/src/boards/Payload/Wing/FileWingAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..50884d5d1cb84d470189a7375ecff157a5ebada3
--- /dev/null
+++ b/src/boards/Payload/Wing/FileWingAlgorithm.h
@@ -0,0 +1,91 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <Payload/Wing/WingAlgorithm.h>
+#include <utils/CSVReader/CSVReader.h>
+
+/**
+ * @brief This class abstracts the concept of wing timestamp based
+ * algorithm. These algorithms are stored in files (formatted in csv).
+ * We use a CSV parser to properly parse the procedure and every step
+ * we check if it is time to advance and in case actuate the step with
+ * the two servos.
+ *
+ * Brief use case:
+ *
+ * Actuators::getInstance().enableServo(PARAFOIL_LEFT_SERVO);
+ * Actuators::getInstance().enableServo(PARAFOIL_RIGHT_SERVO);
+ *
+ * WingAlgorithm algorithm(PARAFOIL_LEFT_SERVO, PARAFOIL_RIGHT_SERVO, "Optional
+ * File") algorithm.init();
+ *
+ * //In case of a non valid file/empty string you can add the steps
+ * algorithm.addStep(WingAlgorithmData{timestamp, angle1, angle2});
+ *
+ * algorithm.begin();
+ *
+ * algorithm.update()...
+ *
+ * //End of algorithm
+ *
+ * algorithm.begin();
+ *
+ * algorithm.update()...
+ */
+
+namespace Payload
+{
+
+class FileWingAlgorithm : public WingAlgorithm
+{
+public:
+    /**
+     * @brief Construct a new Wing Algorithm object
+     *
+     * @param servo1 The first servo
+     * @param servo2 The second servo
+     * @param filename The csv file where all the operations are stored
+     */
+    FileWingAlgorithm(ServosList servo1, ServosList servo2,
+                      const char* filename);
+
+    /**
+     * @brief This method parses the file and stores it into a std::vector
+     *
+     * @return true If the initialization finished correctly
+     * @return false If something went wrong
+     */
+    bool init() override;
+
+protected:
+    /**
+     * @brief CSV format file parser
+     */
+    Boardcore::CSVParser<WingAlgorithmData> parser;
+
+    /**
+     * @brief Indicates whether the current file of the algorithm is readable
+     */
+    bool fileValid = false;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.cpp b/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..48b42619cc7e4376119b4cede8ffec75e8b2ee7d
--- /dev/null
+++ b/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.cpp
@@ -0,0 +1,42 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.h>
+#include <math.h>
+
+namespace Payload
+{
+
+float ClosedLoopGuidanceAlgorithm::calculateTargetAngle(
+    const Eigen::Vector3f& currentPositionNED, Eigen::Vector2f& heading)
+{
+    heading[0] = targetNED[0] - currentPositionNED[0];
+    heading[1] = targetNED[1] - currentPositionNED[1];
+    return atan2(heading[1], heading[0]);
+}
+
+void ClosedLoopGuidanceAlgorithm::setPoints(Eigen::Vector2f targetNED)
+{
+    this->targetNED = targetNED;
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.h b/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..5bc73969369f3a0b72bb0241a3affd5ee4a15c05
--- /dev/null
+++ b/src/boards/Payload/Wing/Guidance/ClosedLoopGuidanceAlgorithm.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Payload/Wing/Guidance/GuidanceAlgorithm.h>
+
+#include <Eigen/Core>
+
+namespace Payload
+{
+
+/**
+ * This class is the implementation of the Simple Closed Loop guidance.
+ * It calculates the yaw between the current position and the target position by
+ * calculating the difference between the two vectors and the angle formed by
+ * the diff vector
+ */
+class ClosedLoopGuidanceAlgorithm : public GuidanceAlgorithm
+{
+    /**
+     * @brief This method calculates the yaw angle of the parafoil knowing
+     * the current position and the target position.
+     *
+     * @param[in] position the current NED position of the parafoil in [m]
+     * @param[in] target NED position of the target in [m]
+     * @param[out] heading Saves the heading vector for logging purposes
+     *
+     * @returns the yaw angle of the parafoil in [rad]
+     */
+    float calculateTargetAngle(const Eigen::Vector3f& currentPositionNED,
+                               Eigen::Vector2f& heading) override;
+
+public:
+    void setPoints(Eigen::Vector2f targetNED);
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/Guidance/EarlyManeuverGuidanceAlgorithm.cpp b/src/boards/Payload/Wing/Guidance/EarlyManeuverGuidanceAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..66ee2bcce0faeb2d8dd7cf40835d892d8153638b
--- /dev/null
+++ b/src/boards/Payload/Wing/Guidance/EarlyManeuverGuidanceAlgorithm.cpp
@@ -0,0 +1,150 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Payload/Configs/WingConfig.h>
+#include <Payload/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h>
+
+#include <Eigen/Core>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Payload::WingConfig;
+
+namespace Payload
+{
+
+EarlyManeuversGuidanceAlgorithm::EarlyManeuversGuidanceAlgorithm()
+    : activeTarget(Target::EMC), targetAltitudeConfidence(0),
+      m2AltitudeConfidence(0), m1AltitudeConfidence(0),
+      emcAltitudeConfidence(0){};
+
+EarlyManeuversGuidanceAlgorithm::~EarlyManeuversGuidanceAlgorithm(){};
+
+float EarlyManeuversGuidanceAlgorithm::calculateTargetAngle(
+    const Eigen::Vector3f& currentPositionNED, Eigen::Vector2f& heading)
+{
+    using namespace Boardcore;
+
+    float altitude = abs(currentPositionNED[2]);
+
+    computeActiveTarget(altitude);
+
+    switch (activeTarget)
+    {
+        case Target::EMC:
+            heading[0] = EMC[0] - currentPositionNED[0];
+            heading[1] = EMC[1] - currentPositionNED[1];
+            break;
+        case Target::M1:
+            heading[0] = M1[0] - currentPositionNED[0];
+            heading[1] = M1[1] - currentPositionNED[1];
+            break;
+        case Target::M2:
+            heading[0] = M2[0] - currentPositionNED[0];
+            heading[1] = M2[1] - currentPositionNED[1];
+            break;
+        case Target::FINAL:
+            heading[0] = targetNED[0] - currentPositionNED[0];
+            heading[1] = targetNED[1] - currentPositionNED[1];
+            break;
+    }
+
+    return atan2(heading[1], heading[0]);
+}
+
+void EarlyManeuversGuidanceAlgorithm::computeActiveTarget(float altitude)
+{
+    if (altitude <=
+        GUIDANCE_TARGET_ALTITUDE_THRESHOLD)  // Altitude is low, head directly
+                                             // to target
+    {
+        targetAltitudeConfidence++;
+    }
+    else if (altitude <= GUIDANCE_M2_ALTITUDE_THRESHOLD)  // Altitude is almost
+                                                          // okay, go to M2
+    {
+        m2AltitudeConfidence++;
+    }
+    else if (altitude <=
+             GUIDANCE_M1_ALTITUDE_THRESHOLD)  // Altitude is high, go to M1
+    {
+        m1AltitudeConfidence++;
+    }
+    else
+    {
+        emcAltitudeConfidence++;  // Altitude is too high, head to the emc
+    }
+
+    switch (activeTarget)
+    {
+        case Target::EMC:
+            if (targetAltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget          = Target::FINAL;
+                emcAltitudeConfidence = 0;
+            }
+            else if (m2AltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget          = Target::M2;
+                emcAltitudeConfidence = 0;
+            }
+            else if (m1AltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget          = Target::M1;
+                emcAltitudeConfidence = 0;
+            }
+            break;
+        case Target::M1:
+            if (targetAltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget         = Target::FINAL;
+                m1AltitudeConfidence = 0;
+            }
+            else if (m2AltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget         = Target::M2;
+                m1AltitudeConfidence = 0;
+            }
+            break;
+        case Target::M2:
+            if (targetAltitudeConfidence >= GUIDANCE_CONFIDENCE)
+            {
+                activeTarget         = Target::FINAL;
+                m2AltitudeConfidence = 0;
+            }
+            break;
+        case Target::FINAL:
+            break;
+    }
+}
+
+void EarlyManeuversGuidanceAlgorithm::setPoints(Eigen::Vector2f targetNED,
+                                                Eigen::Vector2f EMC,
+                                                Eigen::Vector2f M1,
+                                                Eigen::Vector2f M2)
+{
+    this->targetNED = targetNED;
+    this->EMC       = EMC;
+    this->M1        = M1;
+    this->M2        = M2;
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h b/src/boards/Payload/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..30a432504dbf6404c0219f62d88c4490c8d0f4b0
--- /dev/null
+++ b/src/boards/Payload/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h
@@ -0,0 +1,112 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Payload/Wing/Guidance/GuidanceAlgorithm.h>
+
+#include <Eigen/Core>
+
+namespace Payload
+{
+
+/**
+ * This class is the implementation of the Simple Closed Loop guidance.
+ * It calculates the yaw between the current position and the target position by
+ * calculating the difference between the two vectors and the angle formed by
+ * the diff vector
+ *
+ * requires: WingControllers
+ */
+class EarlyManeuversGuidanceAlgorithm : public GuidanceAlgorithm
+{
+
+public:
+    /**
+     * @brief Constructor for the EM Algorithm
+     *
+     */
+    EarlyManeuversGuidanceAlgorithm();
+
+    virtual ~EarlyManeuversGuidanceAlgorithm();
+
+    /**
+     * @brief This method calculates the yaw angle of the parafoil knowing
+     * the current position and the target position.
+     *
+     * @param[in] currentPositionNED the current NED position of the parafoil in
+     * m
+     * @param[in] targetPositionNED NED position of the target in m
+     * @param[out] heading Saves the heading vector for logging purposes
+     *
+     * @returns the yaw angle of the parafoil in rad
+     *
+     */
+    float calculateTargetAngle(const Eigen::Vector3f& currentPositionNED,
+                               Eigen::Vector2f& heading) override;
+
+    /**
+     * @brief Set Early Maneuvers points
+     *
+     * @param[in] EMC NED
+     * @param[in] M1 NED
+     * @param[in] M2 NED
+     *
+     */
+    void setPoints(Eigen::Vector2f targetNED, Eigen::Vector2f EMC,
+                   Eigen::Vector2f M1, Eigen::Vector2f M2);
+
+private:
+    /** @brief Updates the class target
+     *
+     */
+    void computeActiveTarget(float altitude);
+
+    /**
+     * @brief Enumerates all the possible targets of the EM algorithm
+     */
+    enum class Target
+    {
+        EMC = 0,
+        M1,
+        M2,
+        FINAL
+    };
+
+    // Point we are currently poinying to
+    Target activeTarget;
+
+    // Eigen::Vector2f targetNED;  // NED
+
+    Eigen::Vector2f EMC;  // NED
+
+    Eigen::Vector2f M1;  // NED
+
+    Eigen::Vector2f M2;  // NED
+
+    unsigned int targetAltitudeConfidence;
+    unsigned int m2AltitudeConfidence;
+    unsigned int m1AltitudeConfidence;
+    unsigned int emcAltitudeConfidence;
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/Guidance/GuidanceAlgorithm.h b/src/boards/Payload/Wing/Guidance/GuidanceAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..4086116c32af40f2f6f7b858ed41dd963a9cde94
--- /dev/null
+++ b/src/boards/Payload/Wing/Guidance/GuidanceAlgorithm.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Eigen/Core>
+
+namespace Payload
+{
+
+/**
+ * This class acts as an interface for a generic guidance algorithm that is used
+ * by the Automatic Wing Algorithm.
+ */
+class GuidanceAlgorithm
+{
+public:
+    /**
+     * @brief This method must calculate the yaw angle of the parafoil knowing
+     * the current position and the target position without changing the vectors
+     * passed as arguments.
+     *
+     * @note the args are const references to reduce access time by avoiding
+     * copying objects that will be read-only.
+     *
+     * @param[in] position the current NED position of the parafoil in [m]
+     * @param[in] target NED position of the target in [m]
+     * @param[out] heading The current heading, it is used for logging purposes
+     *
+     * @returns the yaw angle of the parafoil in [rad]
+     */
+    virtual float calculateTargetAngle(
+        const Eigen::Vector3f& currentPositionNED,
+        Eigen::Vector2f& heading) = 0;
+
+    Eigen::Vector2f getTargetNED() { return targetNED; }
+
+protected:
+    Eigen::Vector2f targetNED{0, 0};  // NED
+};
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/WingAlgorithm.cpp b/src/boards/Payload/Wing/WingAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9bb3049916c59a6508ea7dcb0832b9ec3f51a2de
--- /dev/null
+++ b/src/boards/Payload/Wing/WingAlgorithm.cpp
@@ -0,0 +1,122 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Configs/WingConfig.h>
+#include <Payload/Wing/WingAlgorithm.h>
+#include <common/Events.h>
+#include <drivers/timer/TimestampTimer.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Boardcore;
+using namespace Payload::WingConfig;
+using namespace Common;
+namespace Payload
+{
+WingAlgorithm::WingAlgorithm(ServosList servo1, ServosList servo2)
+{
+    this->servo1 = servo1;
+    this->servo2 = servo2;
+    stepIndex    = 0;
+    // Create the vector for algorithm data
+    steps = std::vector<WingAlgorithmData>();
+}
+
+bool WingAlgorithm::init()
+{
+    return true;  // In this case the init is always true
+}
+
+void WingAlgorithm::setServo(ServosList servo1, ServosList servo2)
+{
+    this->servo1 = servo1;
+    this->servo2 = servo2;
+}
+
+void WingAlgorithm::addStep(WingAlgorithmData step) { steps.push_back(step); }
+
+void WingAlgorithm::begin()
+{
+    running     = true;
+    shouldReset = true;
+
+    // Set the reference timestamp
+    timeStart = TimestampTimer::getTimestamp();
+}
+
+void WingAlgorithm::end()
+{
+    running = false;
+
+    // Set the reference timestamp to 0
+    timeStart = 0;
+}
+
+void WingAlgorithm::step()
+{
+    ModuleManager& modules    = ModuleManager::getInstance();
+    uint64_t currentTimestamp = TimestampTimer::getTimestamp();
+
+    if (shouldReset)
+    {
+        // If the algorithm has been stopped
+        // i want to start from the beginning
+        stepIndex   = 0;
+        shouldReset = false;
+    }
+
+    if (stepIndex >= steps.size())
+    {
+        LOG_INFO(logger, "Algorithm end {:d} >= {:d}", stepIndex, steps.size());
+        // End the procedure so it won't be executed
+        // Set the index to 0 in case of another future execution
+        stepIndex = 0;
+        // Terminate here
+        // EventBroker::getInstance().post(ALGORITHM_ENDED, TOPIC_ALGOS);
+        return;
+    }
+
+    if (currentTimestamp - timeStart >= steps[stepIndex].timestamp)
+    {
+        // I need to execute the current step
+        modules.get<Actuators>()->setServoAngle(servo1,
+                                                steps[stepIndex].servo1Angle);
+        modules.get<Actuators>()->setServoAngle(servo2,
+                                                steps[stepIndex].servo2Angle);
+
+        // Log the data setting the timestamp to the absolute one
+        WingAlgorithmData data;
+        data.timestamp   = TimestampTimer::getTimestamp();
+        data.servo1Angle = steps[stepIndex].servo1Angle;
+        data.servo2Angle = steps[stepIndex].servo2Angle;
+
+        // After copy i can log the actual step
+        SDlogger->log(data);
+
+        // finally increment the stepIndex
+        stepIndex++;
+
+        LOG_INFO(logger, "Step");
+    }
+}
+
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/WingAlgorithm.h b/src/boards/Payload/Wing/WingAlgorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..4334300d8c0ee5f186ab91f2c8431c892dc691e4
--- /dev/null
+++ b/src/boards/Payload/Wing/WingAlgorithm.h
@@ -0,0 +1,107 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro, Federico Mandelli
+ *
+ * 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 <Payload/Actuators/Actuators.h>
+#include <Payload/Wing/WingAlgorithmData.h>
+#include <algorithms/Algorithm.h>
+#include <diagnostic/PrintLogger.h>
+#include <logger/Logger.h>
+#include <miosix.h>
+namespace Payload
+{
+class WingAlgorithm : public Boardcore::Algorithm
+{
+public:
+    /**
+     * @brief Construct a new Wing Algorithm object
+     *
+     * @param servo1 The first servo
+     * @param servo2 The second servo
+     */
+    WingAlgorithm(ServosList servo1, ServosList servo2);
+
+    /**
+     * @brief Method to initialize the algorithm
+     *
+     * @return true If the init process goes well
+     * @return false If the init process doesn't go well
+     */
+    bool init() override;
+
+    /**
+     * @brief Set the Servos object
+     *
+     * @param servo1 The first algorithm servo
+     * @param servo2 The second algorithm servo
+     */
+    void setServo(ServosList servo1, ServosList servo2);
+
+    /**
+     * @brief Add a step to the algorithm sequence
+     *
+     * @param step The step to add
+     */
+    void addStep(WingAlgorithmData step);
+
+    /**
+     * @brief This sets the reference timestamp for the algorithm
+     */
+    void begin();
+
+    /**
+     * @brief This method disables the algorithm
+     */
+    void end();
+    /**
+     * @brief This method implements the algorithm step based on the current
+     * timestamp
+     */
+    void step() override;
+
+protected:
+    // The actuators
+    ServosList servo1, servo2;
+
+    // Reference timestamp for relative algorithm timestamps
+    uint64_t timeStart;
+
+    // Procedure array to memorize all the steps needed to perform the algorithm
+    std::vector<WingAlgorithmData> steps;
+
+    // PrintLogger
+    Boardcore::PrintLogger logger =
+        Boardcore::Logging::getLogger("WingAlgorithm");
+
+    // SD logger
+    Boardcore::Logger* SDlogger = &Boardcore::Logger::getInstance();
+
+    // This boolean is used to understand when to reset
+    // the index where the algorithm has stopped.
+    // In case of end call, we want to be able to perform
+    // another time this algorithm starting from 0
+    std::atomic<bool> shouldReset;
+
+    // Variable to remember what step that has to be done
+    unsigned int stepIndex;
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/WingAlgorithmData.h b/src/boards/Payload/Wing/WingAlgorithmData.h
new file mode 100644
index 0000000000000000000000000000000000000000..38e20628c332a5965e0a50e62381d6df61fc4ab8
--- /dev/null
+++ b/src/boards/Payload/Wing/WingAlgorithmData.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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 <sensors/SensorData.h>
+
+namespace Payload
+{
+/**
+ * This class represents the algorithm data structure that needs to be logged
+ * into the onboard SD card. It has the timestamp(absolute) and the servo
+ * position set by the selected algorithm
+ */
+struct WingAlgorithmData
+{
+    uint64_t timestamp;   // First timestamp is 0 (in microseconds)
+    float servo1Angle;    // degrees
+    float servo2Angle;    // degrees
+    float targetAngle;    // radians (automatic only)
+    float velocityAngle;  // radians
+    float targetX;        // NED (only automatic algorithm)
+    float targetY;        // NED (only automatic algorithm)
+    float error;
+    float pidOutput;
+
+    static std::string header()
+    {
+        return "WingAlgorithmTimestamp,servo1Angle,servo2Angle,targetAngle,"
+               "velocityAngle,targetX,targetY,error,pidOutput\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << servo1Angle << "," << servo2Angle << ","
+           << targetAngle << "," << velocityAngle << "," << targetX << ","
+           << targetY << "," << error << "," << pidOutput << "\n";
+    }
+};
+}  // namespace Payload
diff --git a/src/boards/Payload/Wing/WingTargetPositionData.h b/src/boards/Payload/Wing/WingTargetPositionData.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1cab4dbf456c366d1a301487aacfd1b11a0099c
--- /dev/null
+++ b/src/boards/Payload/Wing/WingTargetPositionData.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Radu Raul
+ *
+ * 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 <ostream>
+#include <string>
+
+namespace Payload
+{
+
+struct WingTargetPositionData
+{
+    float receivedLat;
+    float receivedLon;
+    float targetN;
+    float targetE;
+    float emcN;
+    float emcE;
+    float m1N;
+    float m1E;
+    float m2N;
+    float m2E;
+
+    static std::string header()
+    {
+        return "receivedLat, receivedLon, "
+               "targetN,targetE,EMCN,EMCE,M1N,M1E,M2N,M2E\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << receivedLat << "," << receivedLon << "," << targetN << ","
+           << targetE << "," << emcN << "," << emcE << "," << m1N << "," << m1E
+           << "," << m2N << "," << m2E << "\n";
+    }
+};
+
+}  // namespace Payload
diff --git a/src/entrypoints/Payload/payload-entry.cpp b/src/entrypoints/Payload/payload-entry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b174e140bdca613a43e8da65d09215c41521cc5e
--- /dev/null
+++ b/src/entrypoints/Payload/payload-entry.cpp
@@ -0,0 +1,318 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * 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.
+ */
+
+// #define DEFAULT_STDOUT_LOG_LEVEL LOGL_WARNING
+#include <Payload/Actuators/Actuators.h>
+#include <Payload/AltitudeTrigger/AltitudeTrigger.h>
+#include <Payload/BoardScheduler.h>
+#include <Payload/Buses.h>
+#include <Payload/CanHandler/CanHandler.h>
+#include <Payload/FlightStatsRecorder/FlightStatsRecorder.h>
+#include <Payload/PinHandler/PinHandler.h>
+#include <Payload/Radio/Radio.h>
+#include <Payload/Sensors/Sensors.h>
+#include <Payload/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Payload/StateMachines/NASController/NASController.h>
+#include <Payload/StateMachines/WingController/WingController.h>
+#include <Payload/TMRepository/TMRepository.h>
+#include <Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h>
+#include <Payload/WindEstimationScheme/WindEstimation.h>
+#include <common/Events.h>
+#include <common/Topics.h>
+#include <diagnostic/CpuMeter/CpuMeter.h>
+#include <diagnostic/PrintLogger.h>
+#include <diagnostic/StackLogger.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <events/EventBroker.h>
+#include <events/EventData.h>
+#include <events/utils/EventSniffer.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Boardcore;
+using namespace Payload;
+using namespace Common;
+
+int main()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Overall status, if at some point it becomes false, there is a problem
+    // somewhere
+    bool initResult    = true;
+    PrintLogger logger = Logging::getLogger("Payload");
+
+    // Scheduler
+    BoardScheduler* scheduler = new BoardScheduler();
+
+    // Nas priority (Max priority)
+    NASController* nas =
+        new NASController(scheduler->getScheduler(miosix::PRIORITY_MAX));
+
+    // Sensors priority (MAX - 1)
+    Sensors* sensors =
+        new Sensors(scheduler->getScheduler(miosix::PRIORITY_MAX - 1));
+
+    // Other critical components (Max - 2)
+    Radio* radio = new Radio(scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+    AltitudeTrigger* altTrigger =
+        new AltitudeTrigger(scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+    WingController* wingController =
+        new WingController(scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+    VerticalVelocityTrigger* verticalVelocityTrigger =
+        new VerticalVelocityTrigger(
+            scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+    WindEstimation* windEstimation =
+        new WindEstimation(scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+    CanHandler* canHandler =
+        new CanHandler(scheduler->getScheduler(miosix::PRIORITY_MAX - 2));
+
+    // Non critical components (Max - 3)
+    // Actuators is considered non-critical since the scheduler is only used for
+    // the led and buzzer tasks
+    Actuators* actuators =
+        new Actuators(scheduler->getScheduler(miosix::PRIORITY_MAX - 3));
+    FlightStatsRecorder* statesRecorder = new FlightStatsRecorder(
+        scheduler->getScheduler(miosix::PRIORITY_MAX - 3));
+
+    // Components without a scheduler
+    TMRepository* tmRepo   = new TMRepository();
+    FlightModeManager* fmm = new FlightModeManager();
+    Buses* buses           = new Buses();
+    PinHandler* pinHandler = new PinHandler();
+
+    // Insert modules
+    if (!modules.insert<BoardScheduler>(scheduler))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Board Scheduler module");
+    }
+
+    if (!modules.insert<Buses>(buses))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Buses module");
+    }
+
+    if (!modules.insert<Sensors>(sensors))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Sensor module");
+    }
+
+    if (!modules.insert<NASController>(nas))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the NAS module");
+    }
+
+    if (!modules.insert<FlightModeManager>(fmm))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the FMM module");
+    }
+    if (!modules.insert<Radio>(radio))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Radio module");
+    }
+
+    if (!modules.insert<TMRepository>(tmRepo))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the TMRepository module");
+    }
+
+    if (!modules.insert<Actuators>(actuators))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Actuators module");
+    }
+
+    if (!modules.insert<AltitudeTrigger>(altTrigger))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the Altitude Trigger module");
+    }
+
+    if (!modules.insert<WingController>(wingController))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the WingController module");
+    }
+
+    if (!modules.insert<WindEstimation>(windEstimation))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the WindEstimation module");
+    }
+
+    if (!modules.insert<PinHandler>(pinHandler))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the PinHandler module");
+    }
+
+    if (!modules.insert<VerticalVelocityTrigger>(verticalVelocityTrigger))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the VerticalVelocityTrigger module");
+    }
+
+    if (!modules.insert<CanHandler>(canHandler))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the CanHandler module");
+    }
+
+    if (!modules.insert<FlightStatsRecorder>(statesRecorder))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error inserting the FlightStatsRecorder module");
+    }
+
+    // Start modules
+    if (!Logger::getInstance().testSDCard())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error testing the Logger module");
+    }
+
+    if (!EventBroker::getInstance().start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the EventBroker module");
+    }
+
+    if (!modules.get<Sensors>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the Sensors module");
+    }
+
+    if (!modules.get<NASController>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the NAS module");
+    }
+
+    if (!modules.get<FlightModeManager>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the FMM module");
+    }
+
+    if (!modules.get<Radio>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the Radio module");
+    }
+
+    if (!modules.get<Actuators>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the Actuators module");
+    }
+
+    if (!modules.get<AltitudeTrigger>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the AltitudeTrigger module");
+    }
+
+    if (!modules.get<WingController>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the WingController module");
+    }
+
+    if (!modules.get<WindEstimation>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the WindEstimation module");
+    }
+
+    if (!modules.get<PinHandler>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the PinHandler module");
+    }
+
+    if (!modules.get<VerticalVelocityTrigger>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the VerticalVelocityTrigger module");
+    }
+
+    if (!modules.get<CanHandler>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the CanHandler module");
+    }
+
+    if (!modules.get<FlightStatsRecorder>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the FlightStatsRecorder module");
+    }
+
+    if (!modules.get<BoardScheduler>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the Board Scheduler module");
+    }
+
+    // Log all the events
+    EventSniffer sniffer(
+        EventBroker::getInstance(), TOPICS_LIST,
+        [](uint8_t event, uint8_t topic)
+        {
+            EventData ev{TimestampTimer::getTimestamp(), event, topic};
+            Logger::getInstance().log(ev);
+        });
+
+    // Check the init result and launch an event
+    if (initResult)
+    {
+        // Post OK
+        EventBroker::getInstance().post(FMM_INIT_OK, TOPIC_FMM);
+
+        // Set the LED status
+        miosix::led1On();
+    }
+    else
+    {
+        EventBroker::getInstance().post(FMM_INIT_ERROR, TOPIC_FMM);
+        LOG_ERR(logger, "Failed to initialize");
+    }
+
+    // Periodic statistics
+    while (true)
+    {
+        Thread::sleep(1000);
+        Logger::getInstance().log(CpuMeter::getCpuStats());
+        CpuMeter::resetCpuStats();
+        StackLogger::getInstance().log();
+    }
+
+    return 0;
+}
diff --git a/src/tests/Payload/payload-test-VerticalVelocityTrigger.cpp b/src/tests/Payload/payload-test-VerticalVelocityTrigger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3dc7ac5df34b09bf744f135f145db216e2183153
--- /dev/null
+++ b/src/tests/Payload/payload-test-VerticalVelocityTrigger.cpp
@@ -0,0 +1,166 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <Payload/BoardScheduler.h>
+#include <Payload/Configs/FailSafeConfig.h>
+#include <Payload/NASControllerMock/NASControllerMock.h>
+#include <Payload/VerticalVelocityTrigger/VerticalVelocityTrigger.h>
+#include <events/EventBroker.h>
+#include <miosix.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace Boardcore;
+using namespace Payload;
+
+// Initial velocity for the Vertical Velocity Trigger
+constexpr float MOCK_INITIAL_VERTICAL_VELOCITY = -30;
+constexpr float MOCK_VELOCITY_DECELERATION     = 0.5;  // m/s^2
+
+/**
+ * This class mocks the NASController class since for this test we cannot start
+ * the NASController as it needs the sensor reading and we cannot test the
+ * velocity trigger unless we launch the rocket
+ */
+class NASMock : public NASController
+{
+public:
+    /**
+     * Default constructor for NASMock
+     * It initializes the mockedVerticalSpeed
+     */
+    NASMock() : mockedVerticalSpeed(MOCK_INITIAL_VERTICAL_VELOCITY) {}
+
+    /**
+     * Default destructor for NASMock
+     */
+    ~NASMock() {}
+
+    /**
+     * This method mocks the NASController::getNasState() and returns a
+     * NASState with a personalized vertical velocity that starts at
+     * NASMock::MOCK_INITIAL_VERTICAL_VELOCITY and decreases by 0.5 m/s^2
+     *
+     * @return NASState object that contains the vertical vector in NED
+     * orientation
+     */
+    NASState getNasState() override
+    {
+        mockedVerticalSpeed +=
+            MOCK_VELOCITY_DECELERATION *
+            ((float)FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_PERIOD /
+             1000.f);
+        NASState n = NASState();
+        n.vd       = mockedVerticalSpeed;
+        return n;
+    }
+
+private:
+    // Keeps trace of the vertical velocity in NED orientation
+    float mockedVerticalSpeed;
+};
+
+/**
+ * This function halts the board
+ */
+void halt()
+{
+    while (1)
+    {
+    }
+}
+
+/**
+ * This function calculates the maximum wait time needed by the Vertical
+ * Velocity Trigger to trigger
+ */
+float calculateMaxWaitTime()
+{
+    float confidenceTime =
+        ((float)FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_CONFIDENCE *
+         (float)FailSafe::FAILSAFE_VERTICAL_VELOCITY_TRIGGER_PERIOD) /
+        1000.f;
+    float targetReachTime = (-MOCK_INITIAL_VERTICAL_VELOCITY -
+                             FailSafe::FAILSAFE_VERTICAL_VELOCITY_THRESHOLD) /
+                            MOCK_VELOCITY_DECELERATION;
+    TRACE("Target reach time: %f [s]\n", targetReachTime);
+    TRACE("Confidence time: %f [s]\n", confidenceTime);
+
+    return confidenceTime + targetReachTime + 1;
+}
+
+int main()
+{
+
+    // Insert the modules
+    ModuleManager::getInstance().insert<BoardScheduler>(new BoardScheduler());
+    ModuleManager::getInstance().insert<NASController>(new NASMock());
+    ModuleManager::getInstance().insert<VerticalVelocityTrigger>(
+        new VerticalVelocityTrigger());
+
+    // start the scheduler
+    TRACE("Starting the board scheduler\n");
+    if (!ModuleManager::getInstance().get<BoardScheduler>()->start())
+    {
+        TRACE("Error starting the General Purpose Scheduler\n");
+        halt();
+    }
+
+    // start the modules
+    if (!ModuleManager::getInstance().get<NASController>()->start())
+    {
+        TRACE("Error starting the NAS Controller\n");
+        halt();
+    }
+    if (!ModuleManager::getInstance()
+             .get<VerticalVelocityTrigger>()
+             ->startModule())
+    {
+        TRACE("Error starting the Vertical Velocity Trigger\n");
+        halt();
+    }
+
+    ModuleManager::getInstance().get<VerticalVelocityTrigger>()->enable();
+
+    TRACE("Starting... \n");
+    // wait for the trigger
+    int count       = 0;
+    int maxWaitTime = calculateMaxWaitTime() * 100;
+    while (
+        ModuleManager::getInstance().get<VerticalVelocityTrigger>()->isActive())
+    {
+        Thread::sleep(100);
+        count++;
+
+        if (count > maxWaitTime)
+        {
+            TRACE("Vertical Velocity Trigger not working properly\n");
+            break;
+        }
+    }
+    if (count < maxWaitTime)
+    {
+        TRACE("Vertical Velocity Triggered \n");
+    }
+
+    halt();
+}