diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index 15e54a0346b313371b936c7395834a970304fefd..d68ffbe1ce3db7349a6c6e1c9b3bdfeea49c7b29 100755
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -114,7 +114,7 @@
             "name": "stm32f767zi_death_stack_v4",
             "cStandard": "c11",
             "cppStandard": "c++14",
-            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "compilerPath": null,
             "defines": [
                 "${defaultDefines}",
                 "_ARCH_CORTEXM7_STM32F7",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9f619c4c25cfc6271094ec17fa8889e1437a618a..b6b414798079ef19106c44c7252da132a6b6999d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -193,5 +193,6 @@
         "CMAKE_C_COMPILER_LAUNCHER": "ccache",
         "CMAKE_CXX_COMPILER_LAUNCHER": "ccache"
     },
-    "compilerexplorer.compilationDirectory": "${workspaceFolder}/build"
+    "compilerexplorer.compilationDirectory": "${workspaceFolder}/build",
+    "C_Cpp.default.compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++"
 }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e2a8cd631c663dc8ae3d578c786b9537531ef75f..a3d1c0d95c5290e7b130fb5a023132dda2eea5fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,10 +64,6 @@ target_include_directories(payload-entry-euroc PRIVATE ${OBSW_INCLUDE_DIRS})
 target_compile_definitions(payload-entry-euroc PRIVATE EUROC)
 sbs_target(payload-entry-euroc stm32f767zi_lyra_biscotto)
 
-add_executable(parafoil-entry src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
-target_include_directories(parafoil-entry PRIVATE ${OBSW_INCLUDE_DIRS})
-sbs_target(parafoil-entry stm32f429zi_death_stack_v2)
-
 add_executable(motor-entry src/Motor/motor-entry.cpp ${MOTOR_SOURCES})
 target_include_directories(motor-entry PRIVATE ${OBSW_INCLUDE_DIRS})
 sbs_target(motor-entry stm32f767zi_lyra_motor)
@@ -115,3 +111,30 @@ add_executable(lyra-gs-entry
 )
 target_include_directories(lyra-gs-entry PRIVATE ${OBSW_INCLUDE_DIRS})
 sbs_target(lyra-gs-entry stm32f767zi_lyra_gs)
+
+# Parafoil targets
+
+add_executable(parafoil-progressive-rotation src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
+target_include_directories(parafoil-progressive-rotation PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(parafoil-progressive-rotation PRIVATE ALGORITHM_PROGRESSIVE_ROTATION MILANO)
+sbs_target(parafoil-progressive-rotation stm32f429zi_death_stack_v2)
+
+add_executable(parafoil-guided-milano src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
+target_include_directories(parafoil-guided-milano PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(parafoil-guided-milano PRIVATE ALGORITHM_CLOSED_LOOP MILANO)
+sbs_target(parafoil-guided-milano stm32f429zi_death_stack_v2)
+
+add_executable(parafoil-guided-jesolo src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
+target_include_directories(parafoil-guided-jesolo PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(parafoil-guided-jesolo PRIVATE ALGORITHM_CLOSED_LOOP JESOLO)
+sbs_target(parafoil-guided-jesolo stm32f429zi_death_stack_v2)
+
+add_executable(parafoil-t-approach-jesolo src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
+target_include_directories(parafoil-t-approach-jesolo PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(parafoil-t-approach-jesolo PRIVATE ALGORITHM_EARLY_MANEUVER JESOLO)
+sbs_target(parafoil-t-approach-jesolo stm32f429zi_death_stack_v2)
+
+add_executable(parafoil-t-approach-milano src/Parafoil/parafoil-entry.cpp ${PARAFOIL_COMPUTER})
+target_include_directories(parafoil-t-approach-milano PRIVATE ${OBSW_INCLUDE_DIRS})
+target_compile_definitions(parafoil-t-approach-milano PRIVATE ALGORITHM_EARLY_MANEUVER MILANO)
+sbs_target(parafoil-t-approach-milano stm32f429zi_death_stack_v2)
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index 80f147ebead7a4a31ff720ed0104df4389a147d1..71a0052121fd66e140d2988c9fba3841d73f6e0d 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -112,7 +112,7 @@ set(GROUNDSTATION_NOKIA
     src/Groundstation/Nokia/Hub.cpp
 )
 
-set (LYRA_GS
+set(LYRA_GS
     src/Groundstation/LyraGS/Radio/Radio.cpp
     src/Groundstation/LyraGS/Ports/Ethernet.cpp
     src/Groundstation/LyraGS/BoardStatus.cpp
@@ -130,6 +130,9 @@ set(PARAFOIL_COMPUTER
     src/Parafoil/Actuators/Actuators.cpp
     src/Parafoil/AltitudeTrigger/AltitudeTrigger.cpp
     src/Parafoil/PinHandler/PinHandler.cpp
+    src/Parafoil/Radio/Radio.cpp
+    src/Parafoil/Radio/MessageHandler.cpp
+    src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.cpp
     src/Parafoil/Sensors/Sensors.cpp
     src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.cpp
     src/Parafoil/StateMachines/NASController/NASController.cpp
diff --git a/src/Parafoil/Actuators/Actuators.cpp b/src/Parafoil/Actuators/Actuators.cpp
index 1aa20a6e73db4fdaff41cd0fcc4ac625d29ce534..06dd1c262caf69a51ccadfd9589cca8b8c51f29e 100644
--- a/src/Parafoil/Actuators/Actuators.cpp
+++ b/src/Parafoil/Actuators/Actuators.cpp
@@ -29,6 +29,7 @@
 using namespace miosix;
 using namespace Boardcore;
 using namespace Boardcore::Units::Angle;
+using namespace Boardcore::Units::Time;
 namespace config = Parafoil::Config::Actuators;
 
 namespace Parafoil
@@ -38,14 +39,14 @@ Actuators::Actuators()
 {
     leftServo.servo = std::make_unique<Servo>(
         config::LeftServo::TIMER, config::LeftServo::PWM_CH,
-        config::LeftServo::MIN_PULSE.value(),
-        config::LeftServo::MAX_PULSE.value());
+        config::LeftServo::MIN_PULSE.value<Microsecond>(),
+        config::LeftServo::MAX_PULSE.value<Microsecond>());
     leftServo.fullRangeAngle = config::LeftServo::ROTATION;
 
     rightServo.servo = std::make_unique<Servo>(
         config::RightServo::TIMER, config::RightServo::PWM_CH,
-        config::RightServo::MIN_PULSE.value(),
-        config::RightServo::MAX_PULSE.value());
+        config::RightServo::MIN_PULSE.value<Microsecond>(),
+        config::RightServo::MAX_PULSE.value<Microsecond>());
     rightServo.fullRangeAngle = config::RightServo::ROTATION;
 }
 
@@ -84,8 +85,8 @@ bool Actuators::setServoAngle(ServosList servoId, Degree angle)
 
     miosix::Lock<miosix::FastMutex> lock(actuator->mutex);
 
-    actuator->servo->setPosition(
-        (angle / actuator->fullRangeAngle.value()).value());
+    actuator->servo->setPosition(angle.value<Degree>() /
+                                 actuator->fullRangeAngle.value<Degree>());
 
     return true;
 }
diff --git a/src/Parafoil/AltitudeTrigger/AltitudeTrigger.cpp b/src/Parafoil/AltitudeTrigger/AltitudeTrigger.cpp
index 7806a1a6c01e4176a5f78d089927962544e69d40..bce21a162d73a050d6d0f0ea36aab70eb41891a2 100644
--- a/src/Parafoil/AltitudeTrigger/AltitudeTrigger.cpp
+++ b/src/Parafoil/AltitudeTrigger/AltitudeTrigger.cpp
@@ -29,6 +29,7 @@
 #include <events/EventBroker.h>
 
 using namespace Boardcore;
+using namespace Boardcore::Units::Length;
 using namespace Common;
 namespace config = Parafoil::Config;
 
@@ -64,9 +65,9 @@ void AltitudeTrigger::disable() { running = false; }
 
 bool AltitudeTrigger::isEnabled() { return running; }
 
-void AltitudeTrigger::setDeploymentAltitude(float altitude)
+void AltitudeTrigger::setDeploymentAltitude(Meter altitude)
 {
-    targetAltitude = altitude;
+    targetAltitude = altitude.value<Meter>();
 }
 
 void AltitudeTrigger::update()
diff --git a/src/Parafoil/AltitudeTrigger/AltitudeTrigger.h b/src/Parafoil/AltitudeTrigger/AltitudeTrigger.h
index f5514b0889f4845dc4477624ba83335323d87165..816989fe5621ea72e9031731a56669351e8e7ba1 100644
--- a/src/Parafoil/AltitudeTrigger/AltitudeTrigger.h
+++ b/src/Parafoil/AltitudeTrigger/AltitudeTrigger.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2023 Skyward Experimental Rocketry
- * Author: Matteo Pignataro
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro, Davide Basso
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -65,7 +65,7 @@ public:
     /**
      * @return Set the altitude of the AltitudeTrigger
      */
-    void setDeploymentAltitude(float altitude);
+    void setDeploymentAltitude(Boardcore::Units::Length::Meter altitude);
 
 private:
     // Update method that posts a FLIGHT_WING_ALT_PASSED when the correct
@@ -80,7 +80,8 @@ private:
     int confidence = 0;
 
     std::atomic<float> targetAltitude{
-        Config::AltitudeTrigger::DEPLOYMENT_ALTITUDE.value()};
+        Config::AltitudeTrigger::DEPLOYMENT_ALTITUDE
+            .value<Boardcore::Units::Length::Meter>()};
 };
 
 }  // namespace Parafoil
diff --git a/src/Parafoil/BoardScheduler.h b/src/Parafoil/BoardScheduler.h
index 53a885511600d374961ecaf88bf8cfce72e9ff3a..0111a5b4435af656990a917737d72c142ce4b479 100644
--- a/src/Parafoil/BoardScheduler.h
+++ b/src/Parafoil/BoardScheduler.h
@@ -55,10 +55,12 @@ public:
     Boardcore::TaskScheduler& nasController() { return critical; }
     Boardcore::TaskScheduler& sensors() { return high; }
     Boardcore::TaskScheduler& pinHandler() { return high; }
+    Boardcore::TaskScheduler& radio() { return medium; }
     Boardcore::TaskScheduler& altitudeTrigger() { return medium; }
     Boardcore::TaskScheduler& wingController() { return medium; }
     Boardcore::TaskScheduler& windEstimation() { return low; }
     Boardcore::TaskScheduler& actuators() { return low; }
+    Boardcore::TaskScheduler& flightStatsRecorder() { return low; }
 
     static Priority::PriorityLevel flightModeManagerPriority()
     {
diff --git a/src/Parafoil/Configs/NASConfig.h b/src/Parafoil/Configs/NASConfig.h
index 6a9e041dcbbd471d71a8659ae36e9029b6a59d8f..1c4e4728ba4441d3afd09c56ac7468fe9148bf2f 100644
--- a/src/Parafoil/Configs/NASConfig.h
+++ b/src/Parafoil/Configs/NASConfig.h
@@ -46,7 +46,7 @@ constexpr int CALIBRATION_SAMPLES_COUNT = 20;
 constexpr auto CALIBRATION_SLEEP_TIME   = 100_ms;
 
 static const Boardcore::NASConfig CONFIG = {
-    .T              = UPDATE_RATE_SECONDS.value(),
+    .T              = UPDATE_RATE_SECONDS.value<Second>(),
     .SIGMA_BETA     = 0.0001,
     .SIGMA_W        = 0.0019,
     .SIGMA_ACC      = 0.202,
diff --git a/src/Parafoil/Configs/RadioConfig.h b/src/Parafoil/Configs/RadioConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..de4ccca21c220fd96b09fd1c9078f09d82677ccc
--- /dev/null
+++ b/src/Parafoil/Configs/RadioConfig.h
@@ -0,0 +1,65 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Basso
+ *
+ * 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/MavlinkOrion.h>
+#include <units/Frequency.h>
+#include <units/Time.h>
+
+namespace Parafoil
+{
+namespace Config
+{
+namespace Radio
+{
+
+/* linter off */ using namespace Boardcore::Units::Frequency;
+/* linter off */ using namespace Boardcore::Units::Time;
+
+constexpr auto LOW_RATE_TELEMETRY  = 0.5_hz;
+constexpr auto HIGH_RATE_TELEMETRY = 4_hz;
+constexpr auto MESSAGE_QUEUE_SIZE  = 10;
+
+namespace Mavlink
+{
+constexpr uint8_t SYSTEM_ID    = SysIDs::MAV_SYSID_PAYLOAD;
+constexpr uint8_t COMPONENT_ID = 0;
+}  // namespace Mavlink
+
+namespace MavlinkDriver
+{
+constexpr auto PKT_LENGTH       = 255;
+constexpr auto PKT_QUEUE_SIZE   = 20;
+constexpr auto MSG_LENGTH       = MAVLINK_MAX_DIALECT_PAYLOAD_SIZE;
+constexpr auto SLEEP_AFTER_SEND = 0_ms;
+constexpr auto MAX_PKT_AGE      = 200_ms;
+}  // namespace MavlinkDriver
+
+namespace Xbee
+{
+constexpr auto ENABLE_80KPS_DATA_RATE = true;
+constexpr auto TIMEOUT                = 5000_ms;
+}  // namespace Xbee
+
+}  // namespace Radio
+}  // namespace Config
+}  // namespace Parafoil
diff --git a/src/Parafoil/Configs/SensorsConfig.h b/src/Parafoil/Configs/SensorsConfig.h
index 3147326619b4fd54a39ed6346670f5c5661301ca..659e5537b8f4bcde9ea06387dc5048198083fca6 100644
--- a/src/Parafoil/Configs/SensorsConfig.h
+++ b/src/Parafoil/Configs/SensorsConfig.h
@@ -87,7 +87,7 @@ constexpr auto ODR           = Boardcore::LPS22DF::ODR_100;
 namespace UBXGPS
 {
 constexpr auto ENABLED       = true;
-constexpr auto SAMPLING_RATE = 10_hz;
+constexpr auto SAMPLING_RATE = 10_khz;
 }  // namespace UBXGPS
 
 namespace ADS131M08
diff --git a/src/Parafoil/Configs/WingConfig.h b/src/Parafoil/Configs/WingConfig.h
index f1fde1c1aa8a4062f6f53552ac522473809d6869..afa6ad074a599f1325f644ae25dfa9d6a326439e 100644
--- a/src/Parafoil/Configs/WingConfig.h
+++ b/src/Parafoil/Configs/WingConfig.h
@@ -71,15 +71,15 @@ constexpr auto TARGET_LAT = 45.5013853;
 constexpr auto TARGET_LON = 9.1544219;
 #endif
 
-#if defined(CLOSED_LOOP)
+#if defined(ALGORITHM_CLOSED_LOOP)
 constexpr auto ALGORITHM = AlgorithmId::CLOSED_LOOP;
-#elif EARLY_MANEUVER
+#elif defined(ALGORITHM_EARLY_MANEUVER)
 constexpr auto ALGORITHM = AlgorithmId::EARLY_MANEUVER;
-#elif SEQUENCE
+#elif defined(ALGORITHM_SEQUENCE)
 constexpr auto ALGORITHM = AlgorithmId::SEQUENCE;
-#elif ROTATION
+#elif defined(ALGORITHM_ROTATION)
 constexpr auto ALGORITHM = AlgorithmId::ROTATION;
-#elif PROGRESSIVE_ROTATION
+#elif defined(ALGORITHM_PROGRESSIVE_ROTATION)
 constexpr auto ALGORITHM = AlgorithmId::PROGRESSIVE_ROTATION;
 #else
 constexpr auto ALGORITHM = AlgorithmId::CLOSED_LOOP;
diff --git a/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.cpp b/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9399454e8bab9cef8d94898f15d45c6272017877
--- /dev/null
+++ b/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.cpp
@@ -0,0 +1,129 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor, Niccolò Betto
+ *
+ * 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 "FlightStatsRecorder.h"
+
+#include <Parafoil/Configs/NASConfig.h>
+#include <Parafoil/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Parafoil/StateMachines/WingController/WingController.h>
+#include <common/ReferenceConfig.h>
+#include <utils/AeroUtils/AeroUtils.h>
+
+using namespace Boardcore;
+using namespace Boardcore::Units::Speed;
+using namespace Boardcore::Units::Length;
+using namespace Boardcore::Units::Pressure;
+using namespace Boardcore::Units::Acceleration;
+using namespace miosix;
+using namespace Eigen;
+using namespace Common;
+
+namespace Parafoil
+{
+
+void FlightStatsRecorder::reset()
+{
+    Lock<FastMutex> lock{statsMutex};
+    stats = Stats{};
+}
+
+FlightStatsRecorder::Stats FlightStatsRecorder::getStats()
+{
+    Lock<FastMutex> lock{statsMutex};
+    return stats;
+}
+
+void FlightStatsRecorder::dropDetected(uint64_t ts)
+{
+    Lock<FastMutex> lock{statsMutex};
+    stats.dropTs = ts;
+}
+
+void FlightStatsRecorder::deploymentDetected(uint64_t ts, Meter alt)
+{
+    Lock<FastMutex> lock{statsMutex};
+    stats.dplTs  = ts;
+    stats.dplAlt = alt;
+}
+
+void FlightStatsRecorder::updateAcc(const AccelerometerData& data)
+{
+    auto fmmState = getModule<FlightModeManager>()->getState();
+    auto wcState  = getModule<WingController>()->getState();
+
+    // Do nothing if it was not dropped yet
+    if (fmmState != FlightModeManagerState::FLYING_WING_DESCENT)
+        return;
+
+    auto acc = MeterPerSecondSquared{static_cast<Vector3f>(data).norm()};
+    Lock<FastMutex> lock{statsMutex};
+
+    if (wcState == WingControllerState::IDLE)
+    {
+        // Record this event only after drop, before deployment
+        if (acc > stats.dropMaxAcc)
+        {
+            stats.dropMaxAcc   = acc;
+            stats.dropMaxAccTs = data.accelerationTimestamp;
+        }
+    }
+    else if (wcState == WingControllerState::FLYING_DEPLOYMENT ||
+             wcState == WingControllerState::FLYING_CONTROLLED_DESCENT)
+    {
+        // Record this event only after deployment
+        if (acc > stats.dplMaxAcc)
+        {
+            stats.dplMaxAcc   = acc;
+            stats.dplMaxAccTs = data.accelerationTimestamp;
+        }
+    }
+}
+
+void FlightStatsRecorder::updateNas(const NASState& data, float refTemperature)
+{
+    auto fmmState = getModule<FlightModeManager>()->getState();
+    auto wcState  = getModule<WingController>()->getState();
+
+    // Do nothing if it was not dropped yet
+    if (fmmState != FlightModeManagerState::FLYING_WING_DESCENT)
+        return;
+
+    Lock<FastMutex> lock{statsMutex};
+
+    if (wcState == WingControllerState::IDLE ||
+        wcState == WingControllerState::FLYING_DEPLOYMENT ||
+        wcState == WingControllerState::FLYING_CONTROLLED_DESCENT)
+    {
+        // Record this event only during flight
+        auto speed = MeterPerSecond{Vector3f{data.vn, data.vd, data.ve}.norm()};
+        auto alt   = Meter{-data.d};
+
+        if (speed > stats.maxSpeed)
+        {
+            stats.maxSpeed    = speed;
+            stats.maxSpeedAlt = alt;
+            stats.maxSpeedTs  = data.timestamp;
+        }
+    }
+}
+
+}  // namespace Parafoil
diff --git a/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.h b/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.h
new file mode 100644
index 0000000000000000000000000000000000000000..ccd7a3fdc96f3979c7ad6fde4ef7e68c43addc9a
--- /dev/null
+++ b/src/Parafoil/FlightStatsRecorder/FlightStatsRecorder.h
@@ -0,0 +1,83 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor, Niccolò Betto, Davide Basso
+ *
+ * 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/NASState.h>
+#include <miosix.h>
+#include <sensors/SensorData.h>
+#include <units/Acceleration.h>
+#include <units/Length.h>
+#include <units/Pressure.h>
+#include <units/Speed.h>
+#include <utils/DependencyManager/DependencyManager.h>
+
+namespace Parafoil
+{
+
+class WingController;
+class FlightModeManager;
+
+class FlightStatsRecorder
+    : public Boardcore::InjectableWithDeps<WingController, FlightModeManager>
+{
+public:
+    struct Stats
+    {
+        // Drop
+        uint64_t dropTs = 0;
+
+        // Maximum acceleration after drop, before deployment
+        uint64_t dropMaxAccTs = 0;
+        Boardcore::Units::Acceleration::MeterPerSecondSquared dropMaxAcc{0};
+
+        // Maximum vertical speed
+        uint64_t maxSpeedTs = 0;
+        Boardcore::Units::Speed::MeterPerSecond maxSpeed{0};
+        Boardcore::Units::Length::Meter maxSpeedAlt{0};
+
+        // Deployment
+        uint64_t dplTs = 0;
+        Boardcore::Units::Length::Meter dplAlt{0};
+
+        // Maximum acceleration after deployment
+        uint64_t dplMaxAccTs = 0;
+        Boardcore::Units::Acceleration::MeterPerSecondSquared dplMaxAcc{0};
+    };
+
+    void reset();
+
+    Stats getStats();
+
+    void dropDetected(uint64_t ts);
+    void deploymentDetected(uint64_t ts, Boardcore::Units::Length::Meter alt);
+
+    void updateAcc(const Boardcore::AccelerometerData& data);
+    void updateNas(const Boardcore::NASState& data, float refTemperature);
+    void updatePressure(const Boardcore::PressureData& data);
+
+private:
+    miosix::FastMutex statsMutex;
+    Stats stats;
+};
+
+}  // namespace Parafoil
diff --git a/src/Parafoil/Radio/MessageHandler.cpp b/src/Parafoil/Radio/MessageHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c0a4c51ea6f43f84db15a1cc1bae0b1eadf6a61
--- /dev/null
+++ b/src/Parafoil/Radio/MessageHandler.cpp
@@ -0,0 +1,977 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Davide Basso
+ *
+ * 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 <Parafoil/Actuators/Actuators.h>
+#include <Parafoil/AltitudeTrigger/AltitudeTrigger.h>
+#include <Parafoil/BoardScheduler.h>
+#include <Parafoil/Configs/RadioConfig.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
+#include <Parafoil/PinHandler/PinHandler.h>
+#include <Parafoil/Sensors/Sensors.h>
+#include <Parafoil/StateMachines/FlightModeManager/FlightModeManager.h>
+#include <Parafoil/StateMachines/NASController/NASController.h>
+#include <Parafoil/StateMachines/WingController/WingController.h>
+#include <Parafoil/WindEstimation/WindEstimation.h>
+#include <common/Events.h>
+#include <diagnostic/CpuMeter/CpuMeter.h>
+#include <events/EventBroker.h>
+
+#include "Radio.h"
+
+using namespace Boardcore;
+using namespace Boardcore::Units::Angle;
+using namespace Boardcore::Units::Speed;
+using namespace Boardcore::Units::Length;
+using namespace Boardcore::Units::Acceleration;
+using namespace Common;
+namespace config = Parafoil::Config::Radio;
+
+namespace Parafoil
+{
+
+void Radio::MavlinkBackend::handleMessage(const mavlink_message_t& msg)
+{
+    switch (msg.msgid)
+    {
+        case MAVLINK_MSG_ID_PING_TC:
+        {
+            return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_COMMAND_TC:
+        {
+            return handleCommand(msg);
+        }
+
+        case MAVLINK_MSG_ID_SYSTEM_TM_REQUEST_TC:
+        {
+            auto tmId = static_cast<SystemTMList>(
+                mavlink_msg_system_tm_request_tc_get_tm_id(&msg));
+
+            if (!enqueueSystemTm(tmId))
+                return enqueueNack(msg);
+
+            return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_SENSOR_TM_REQUEST_TC:
+        {
+            auto sensorId = static_cast<SensorsTMList>(
+                mavlink_msg_sensor_tm_request_tc_get_sensor_name(&msg));
+
+            if (!enqueueSensorsTm(sensorId))
+                return enqueueNack(msg);
+
+            return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_SERVO_TM_REQUEST_TC:
+        {
+            auto servo = static_cast<ServosList>(
+                mavlink_msg_servo_tm_request_tc_get_servo_id(&msg));
+
+            float position =
+                parent.getModule<Actuators>()->getServoPosition(servo);
+            if (position < 0)
+                return enqueueNack(msg);
+
+            mavlink_message_t tmMsg;
+            mavlink_servo_tm_t tm;
+
+            tm.servo_id       = static_cast<uint8_t>(servo);
+            tm.servo_position = position;
+
+            mavlink_msg_servo_tm_encode(config::Mavlink::SYSTEM_ID,
+                                        config::Mavlink::COMPONENT_ID, &tmMsg,
+                                        &tm);
+            enqueueMessage(tmMsg);
+            return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_SET_SERVO_ANGLE_TC:
+        {
+            bool allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            // Allow arbitrary servo movements in allowed states only
+            if (!allowed)
+                return enqueueNack(msg);
+
+            auto servo = static_cast<ServosList>(
+                mavlink_msg_set_servo_angle_tc_get_servo_id(&msg));
+            auto position = mavlink_msg_set_servo_angle_tc_get_angle(&msg);
+
+            if (parent.getModule<Actuators>()->setServoPosition(servo,
+                                                                position))
+                return enqueueAck(msg);
+            else
+                return enqueueNack(msg);
+        }
+
+        case MAVLINK_MSG_ID_RESET_SERVO_TC:
+        {
+            bool allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            // Reset servos in allowed states only
+            if (!allowed)
+                return enqueueNack(msg);
+
+            auto servo = static_cast<ServosList>(
+                mavlink_msg_reset_servo_tc_get_servo_id(&msg));
+
+            bool reset =
+                parent.getModule<Actuators>()->setServoPosition(servo, 0.0f);
+            if (reset)
+            {
+                // One of our servos was reset
+                return enqueueAck(msg);
+            }
+
+            return enqueueWack(msg);
+        }
+
+        case MAVLINK_MSG_ID_WIGGLE_SERVO_TC:
+        {
+            bool allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            // Perform the wiggle in allowed states only
+            if (!allowed)
+                return enqueueNack(msg);
+
+            auto servo = static_cast<ServosList>(
+                mavlink_msg_wiggle_servo_tc_get_servo_id(&msg));
+
+            bool wiggled = parent.getModule<Actuators>()->wiggleServo(servo);
+            if (wiggled)
+            {
+                // One of our servos was wiggled
+                return enqueueAck(msg);
+            }
+
+            return enqueueWack(msg);
+        }
+
+        case MAVLINK_MSG_ID_SET_REFERENCE_ALTITUDE_TC:
+        {
+            bool allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            if (!allowed)
+                return enqueueNack(msg);
+
+            float altitude =
+                mavlink_msg_set_reference_altitude_tc_get_ref_altitude(&msg);
+
+            parent.getModule<NASController>()->setReferenceAltitude(
+                Meter{altitude});
+            break;
+        }
+
+        case MAVLINK_MSG_ID_SET_REFERENCE_TEMPERATURE_TC:
+        {
+            auto allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            if (!allowed)
+                return enqueueNack(msg);
+
+            float temperature =
+                mavlink_msg_set_reference_temperature_tc_get_ref_temp(&msg);
+
+            parent.getModule<NASController>()->setReferenceTemperature(
+                temperature);
+            break;
+        }
+
+        case MAVLINK_MSG_ID_SET_COORDINATES_TC:
+        {
+            auto allowed = parent.getModule<FlightModeManager>()->isTestMode();
+            if (!allowed)
+                return enqueueNack(msg);
+
+            float latitude = mavlink_msg_set_coordinates_tc_get_latitude(&msg);
+            float longitude =
+                mavlink_msg_set_coordinates_tc_get_longitude(&msg);
+
+            parent.getModule<NASController>()->setReferenceCoordinates(
+                latitude, longitude);
+            break;
+        }
+
+        case MAVLINK_MSG_ID_SET_ORIENTATION_QUAT_TC:
+        {
+            if (parent.getModule<NASController>()->getState() !=
+                NASControllerState::READY)
+            {
+                return enqueueNack(msg);
+            }
+
+            // Scalar first quaternion, W is the first element
+            auto quat = Eigen::Quaternionf{
+                mavlink_msg_set_orientation_quat_tc_get_quat_w(&msg),
+                mavlink_msg_set_orientation_quat_tc_get_quat_x(&msg),
+                mavlink_msg_set_orientation_quat_tc_get_quat_y(&msg),
+                mavlink_msg_set_orientation_quat_tc_get_quat_z(&msg),
+            };
+
+            parent.getModule<NASController>()->setOrientation(
+                quat.normalized());
+
+            float qNorm = quat.norm();
+            if (std::abs(qNorm - 1) > 0.001)
+                return enqueueWack(msg);
+            else
+                return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_SET_DEPLOYMENT_ALTITUDE_TC:
+        {
+            float altitude =
+                mavlink_msg_set_deployment_altitude_tc_get_dpl_altitude(&msg);
+
+            parent.getModule<AltitudeTrigger>()->setDeploymentAltitude(
+                Meter{altitude});
+
+            if (altitude < 200 || altitude > 450)
+                return enqueueWack(msg);
+            else
+                return enqueueAck(msg);
+        }
+
+        case MAVLINK_MSG_ID_SET_TARGET_COORDINATES_TC:
+        {
+            float latitude =
+                mavlink_msg_set_target_coordinates_tc_get_latitude(&msg);
+            float longitude =
+                mavlink_msg_set_target_coordinates_tc_get_longitude(&msg);
+
+            bool targetSet =
+                parent.getModule<WingController>()->setTargetCoordinates(
+                    latitude, longitude);
+
+            if (targetSet)
+                return enqueueAck(msg);
+            else
+                return enqueueNack(msg);
+        }
+
+        case MAVLINK_MSG_ID_SET_ALGORITHM_TC:
+        {
+            uint8_t index =
+                mavlink_msg_set_algorithm_tc_get_algorithm_number(&msg);
+
+            bool algorithmSet =
+                parent.getModule<WingController>()->selectAlgorithm(
+                    static_cast<Config::Wing::AlgorithmId>(index));
+
+            if (algorithmSet)
+                return enqueueAck(msg);
+            else
+                return enqueueNack(msg);
+        }
+
+        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);
+
+            bool testMode = parent.getModule<FlightModeManager>()->isTestMode();
+            // Raw events are allowed in test mode only
+            if (!testMode)
+                return enqueueNack(msg);
+
+            EventBroker::getInstance().post(topicId, eventId);
+            return enqueueAck(msg);
+        }
+
+        default:
+        {
+            return enqueueNack(msg);
+        }
+    }
+}
+
+void Radio::MavlinkBackend::handleCommand(const mavlink_message_t& msg)
+{
+    auto command = static_cast<MavCommandList>(
+        mavlink_msg_command_tc_get_command_id(&msg));
+
+    switch (command)
+    {
+        case MAV_CMD_START_LOGGING:
+        {
+            bool started = Logger::getInstance().start();
+            if (!started)
+                return enqueueNack(msg);
+
+            Logger::getInstance().resetStats();
+            return enqueueAck(msg);
+        }
+
+        case MAV_CMD_STOP_LOGGING:
+        {
+            Logger::getInstance().stop();
+            return enqueueAck(msg);
+        }
+
+        case MAV_CMD_SAVE_CALIBRATION:
+        {
+            bool testMode = parent.getModule<FlightModeManager>()->isTestMode();
+            // Save calibration data in test mode only
+            if (!testMode)
+                return enqueueNack(msg);
+
+            bool magResult = parent.getModule<Sensors>()->saveMagCalibration();
+            if (magResult)
+                return enqueueAck(msg);
+            else
+                return enqueueNack(msg);
+        }
+
+        default:
+        {
+            // Map the command to an event and post it, if it exists
+            auto event = mavCmdToEvent(command);
+            if (event == LAST_EVENT)
+                return enqueueNack(msg);
+
+            EventBroker::getInstance().post(event, TOPIC_TMTC);
+            return enqueueAck(msg);
+        }
+    }
+}
+
+void Radio::MavlinkBackend::enqueueAck(const mavlink_message_t& msg)
+{
+    mavlink_message_t ackMsg;
+    mavlink_msg_ack_tm_pack(config::Mavlink::SYSTEM_ID,
+                            config::Mavlink::COMPONENT_ID, &ackMsg, msg.msgid,
+                            msg.seq);
+    enqueueMessage(ackMsg);
+}
+
+void Radio::MavlinkBackend::enqueueNack(const mavlink_message_t& msg)
+{
+    mavlink_message_t nackMsg;
+    mavlink_msg_nack_tm_pack(config::Mavlink::SYSTEM_ID,
+                             config::Mavlink::COMPONENT_ID, &nackMsg, msg.msgid,
+                             msg.seq, 0);
+    enqueueMessage(nackMsg);
+}
+
+void Radio::MavlinkBackend::enqueueWack(const mavlink_message_t& msg)
+{
+    mavlink_message_t wackMsg;
+    mavlink_msg_wack_tm_pack(config::Mavlink::SYSTEM_ID,
+                             config::Mavlink::COMPONENT_ID, &wackMsg, msg.msgid,
+                             msg.seq, 0);
+    enqueueMessage(wackMsg);
+}
+
+bool Radio::MavlinkBackend::enqueueSystemTm(SystemTMList tmId)
+{
+    switch (tmId)
+    {
+        case MAV_SYS_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_sys_tm_t tm;
+
+            tm.timestamp    = TimestampTimer::getTimestamp();
+            tm.logger       = Logger::getInstance().isStarted();
+            tm.event_broker = EventBroker::getInstance().isRunning();
+            tm.radio        = parent.isStarted();
+            tm.sensors      = parent.getModule<Sensors>()->isStarted();
+            tm.actuators    = parent.getModule<Actuators>()->isStarted();
+            tm.pin_handler  = parent.getModule<PinHandler>()->isStarted();
+            tm.scheduler    = parent.getModule<BoardScheduler>()->isStarted();
+            tm.can_handler  = false;  // Not present on parafoil board
+
+            mavlink_msg_sys_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_LOGGER_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_logger_tm_t tm;
+
+            // Get the logger stats
+            auto stats = Logger::getInstance().getStats();
+
+            tm.timestamp          = stats.timestamp;
+            tm.log_number         = stats.logNumber;
+            tm.too_large_samples  = stats.tooLargeSamples;
+            tm.dropped_samples    = stats.droppedSamples;
+            tm.queued_samples     = stats.queuedSamples;
+            tm.buffers_filled     = stats.buffersFilled;
+            tm.buffers_written    = stats.buffersWritten;
+            tm.writes_failed      = stats.writesFailed;
+            tm.last_write_error   = stats.lastWriteError;
+            tm.average_write_time = stats.averageWriteTime;
+            tm.max_write_time     = stats.maxWriteTime;
+
+            mavlink_msg_logger_tm_encode(config::Mavlink::SYSTEM_ID,
+                                         config::Mavlink::COMPONENT_ID, &msg,
+                                         &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_MAVLINK_STATS_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_mavlink_stats_tm_t tm;
+
+            // Get the mavlink stats
+            auto stats = driver->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(config::Mavlink::SYSTEM_ID,
+                                                config::Mavlink::COMPONENT_ID,
+                                                &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_NAS_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_nas_tm_t tm;
+
+            auto state    = parent.getModule<NASController>()->getState();
+            auto nasState = parent.getModule<NASController>()->getNasState();
+            auto ref = parent.getModule<NASController>()->getReferenceValues();
+
+            tm.timestamp       = nasState.timestamp;
+            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;
+            tm.ref_pressure    = ref.refPressure;
+            tm.ref_temperature = ref.refTemperature;
+            tm.ref_latitude    = ref.refLatitude;
+            tm.ref_longitude   = ref.refLongitude;
+            tm.state           = static_cast<uint8_t>(state);
+
+            mavlink_msg_nas_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_FLIGHT_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_payload_flight_tm_t tm;
+
+            auto* sensors = parent.getModule<Sensors>();
+            auto* nas     = parent.getModule<NASController>();
+            auto* fmm     = parent.getModule<FlightModeManager>();
+
+            auto imu      = sensors->getBMX160WithCorrectionLastSample();
+            auto gps      = sensors->getUBXGPSLastSample();
+            auto lps22df  = sensors->getLPS22DFLastSample();
+            auto nasState = nas->getNasState();
+            // auto ref      = nas->getReferenceValues();
+
+            tm.timestamp = TimestampTimer::getTimestamp();
+            // No digital pressure sensor, use static
+            tm.pressure_digi   = lps22df.pressure;
+            tm.pressure_static = lps22df.pressure;
+            // No dynamic pressure sensor
+            tm.pressure_dynamic = -1.f;
+            // Not present on parafoil board
+            tm.airspeed_pitot = 0;
+            tm.altitude_agl   = -nasState.d;
+
+            // Sensors
+            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   = imu.magneticFieldX;
+            tm.mag_y   = imu.magneticFieldY;
+            tm.mag_z   = imu.magneticFieldZ;
+            tm.gps_lat = gps.latitude;
+            tm.gps_lon = gps.longitude;
+            tm.gps_alt = gps.height;
+            tm.gps_fix = gps.fix;
+
+            // Servos
+            tm.left_servo_angle =
+                parent.getModule<Actuators>()
+                    ->getServoAngle(ServosList::PARAFOIL_LEFT_SERVO)
+                    .value<Degree>();
+            tm.right_servo_angle =
+                parent.getModule<Actuators>()
+                    ->getServoAngle(ServosList::PARAFOIL_RIGHT_SERVO)
+                    .value<Degree>();
+
+            // Algorithms
+            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;
+
+            // Wind estimation
+            auto wind = parent.getModule<WindEstimation>()->getPrediction();
+
+            tm.wes_n = wind.vn.value<MeterPerSecond>();
+            tm.wes_e = wind.ve.value<MeterPerSecond>();
+
+            tm.battery_voltage = sensors->getBatteryVoltage().batVoltage;
+            // No integrated camera
+            tm.cam_battery_voltage = -1.f;
+            tm.temperature         = lps22df.temperature;
+
+            // State machines
+            tm.fmm_state = static_cast<uint8_t>(fmm->getState());
+
+            mavlink_msg_payload_flight_tm_encode(config::Mavlink::SYSTEM_ID,
+                                                 config::Mavlink::COMPONENT_ID,
+                                                 &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_STATS_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_payload_stats_tm_t tm;
+
+            auto* nas        = parent.getModule<NASController>();
+            auto* wnc        = parent.getModule<WingController>();
+            auto* pinHandler = parent.getModule<PinHandler>();
+            auto* recorder   = parent.getModule<FlightStatsRecorder>();
+            auto& logger     = Logger::getInstance();
+
+            auto stats            = recorder->getStats();
+            auto ref              = nas->getReferenceValues();
+            auto wingTarget       = wnc->getTargetCoordinates();
+            auto wingActiveTarget = wnc->getActiveTarget();
+            auto wingAlgorithm    = wnc->getSelectedAlgorithm();
+            auto cpuStats         = CpuMeter::getCpuStats();
+            auto loggerStats      = logger.getStats();
+
+            // Log CPU stats and reset them
+            CpuMeter::resetCpuStats();
+            logger.log(cpuStats);
+
+            tm.timestamp = TimestampTimer::getTimestamp();
+
+            // No liftoff stats for parafoil, used for drop time
+            tm.liftoff_ts         = stats.dropTs;
+            tm.liftoff_max_acc_ts = stats.dropMaxAccTs;
+            tm.liftoff_max_acc =
+                stats.dropMaxAcc.value<MeterPerSecondSquared>();
+
+            // Max speed stats
+            tm.max_speed_ts       = stats.maxSpeedTs;
+            tm.max_speed          = stats.maxSpeed.value<MeterPerSecond>();
+            tm.max_speed_altitude = stats.maxSpeedAlt.value<Meter>();
+
+            // Useless for parafoil
+            tm.max_mach_ts = 0;
+            tm.max_mach    = -1.f;
+
+            // No apogee stats for parafoil
+            tm.apogee_ts         = -1;
+            tm.apogee_max_acc_ts = 0;
+            tm.apogee_lat        = -1.f;
+            tm.apogee_lon        = -1.f;
+            tm.apogee_alt        = -1.f;
+            tm.apogee_max_acc    = -1.f;
+
+            // Wing stats
+            tm.wing_target_lat      = wingTarget.x();
+            tm.wing_target_lon      = wingTarget.y();
+            tm.wing_active_target_n = wingActiveTarget.x();
+            tm.wing_active_target_e = wingActiveTarget.y();
+            tm.wing_algorithm       = wingAlgorithm;
+
+            // Deployment stats
+            tm.dpl_ts         = stats.dplTs;
+            tm.dpl_max_acc_ts = stats.dplMaxAccTs;
+            tm.dpl_alt        = stats.dplAlt.value<Meter>();
+            tm.dpl_max_acc    = stats.dplMaxAcc.value<MeterPerSecondSquared>();
+
+            // NAS reference values
+            tm.ref_lat = ref.refLatitude;
+            tm.ref_lon = ref.refLongitude;
+            tm.ref_alt = ref.refAltitude;
+
+            tm.min_pressure = -1.f;
+
+            // CPU stats
+            tm.cpu_load  = cpuStats.mean;
+            tm.free_heap = cpuStats.freeHeap;
+
+            // Logger stats
+            tm.log_good   = (loggerStats.lastWriteError == 0);
+            tm.log_number = loggerStats.logNumber;
+
+            tm.nas_state = static_cast<uint8_t>(nas->getState());
+            tm.wnc_state = static_cast<uint8_t>(wnc->getState());
+
+            // Pins, some not present on parafoil board
+            tm.pin_nosecone =
+                pinHandler->getPinData(PinList::NOSECONE_PIN).lastState;
+            tm.pin_launch      = 0;
+            tm.cutter_presence = 0;
+            // Can handler, not present on parafoil board
+            tm.main_board_state  = 0;
+            tm.motor_board_state = 0;
+
+            tm.main_can_status  = 0;
+            tm.motor_can_status = 0;
+            tm.rig_can_status   = 0;
+
+            tm.hil_state = 0;
+
+            mavlink_msg_payload_stats_tm_encode(config::Mavlink::SYSTEM_ID,
+                                                config::Mavlink::COMPONENT_ID,
+                                                &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_PIN_OBS_ID:
+        {
+            auto pinHandler = parent.getModule<PinHandler>();
+
+            for (auto pin : PinHandler::PIN_LIST)
+            {
+                mavlink_message_t msg;
+                mavlink_pin_tm_t tm;
+
+                auto pinData = pinHandler->getPinData(pin);
+
+                tm.timestamp             = TimestampTimer::getTimestamp();
+                tm.pin_id                = static_cast<uint8_t>(pin);
+                tm.last_change_timestamp = pinData.lastStateTimestamp;
+                tm.changes_counter       = pinData.changesCount;
+                tm.current_state         = pinData.lastState;
+
+                mavlink_msg_pin_tm_encode(config::Mavlink::SYSTEM_ID,
+                                          config::Mavlink::COMPONENT_ID, &msg,
+                                          &tm);
+                enqueueMessage(msg);
+            }
+
+            return true;
+        }
+
+        case MAV_SENSORS_STATE_ID:
+        {
+            auto sensors = parent.getModule<Sensors>()->getSensorInfo();
+            for (auto sensor : sensors)
+            {
+                mavlink_message_t msg;
+                mavlink_sensor_state_tm_t tm;
+
+                constexpr size_t maxNameLen =
+                    sizeof(tm.sensor_name) / sizeof(tm.sensor_name[0]) - 1;
+
+                std::strncpy(tm.sensor_name, sensor.id.c_str(), maxNameLen);
+                tm.sensor_name[maxNameLen] = '\0';  // Ensure null-termination
+                tm.initialized             = sensor.isInitialized;
+                tm.enabled                 = sensor.isEnabled;
+
+                mavlink_msg_sensor_state_tm_encode(
+                    config::Mavlink::SYSTEM_ID, config::Mavlink::COMPONENT_ID,
+                    &msg, &tm);
+                enqueueMessage(msg);
+            }
+
+            return true;
+        }
+
+        case MAV_REFERENCE_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_reference_tm_t tm;
+
+            auto ref = parent.getModule<NASController>()->getReferenceValues();
+
+            tm.timestamp       = TimestampTimer::getTimestamp();
+            tm.ref_altitude    = ref.refAltitude;
+            tm.ref_pressure    = ref.refPressure;
+            tm.ref_temperature = ref.refTemperature;
+            tm.ref_latitude    = ref.refLatitude;
+            tm.ref_longitude   = ref.refLongitude;
+            tm.msl_pressure    = ref.mslPressure;
+            tm.msl_temperature = ref.mslTemperature;
+
+            mavlink_msg_reference_tm_encode(config::Mavlink::SYSTEM_ID,
+                                            config::Mavlink::COMPONENT_ID, &msg,
+                                            &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_CALIBRATION_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_calibration_tm_t tm;
+
+            auto cal = parent.getModule<Sensors>()->getCalibrationData();
+
+            tm.timestamp            = TimestampTimer::getTimestamp();
+            tm.gyro_bias_x          = 0.0f;
+            tm.gyro_bias_y          = 0.0f;
+            tm.gyro_bias_z          = 0.0f;
+            tm.mag_bias_x           = cal.magBiasX;
+            tm.mag_bias_y           = cal.magBiasY;
+            tm.mag_bias_z           = cal.magBiasZ;
+            tm.mag_scale_x          = cal.magScaleX;
+            tm.mag_scale_y          = cal.magScaleY;
+            tm.mag_scale_z          = cal.magScaleZ;
+            tm.static_press_1_bias  = 0.0f;
+            tm.static_press_1_scale = 0.0f;
+            tm.static_press_2_bias  = 0.0f;
+            tm.static_press_2_scale = 0.0f;
+            tm.dpl_bay_press_bias   = 0.0f;
+            tm.dpl_bay_press_scale  = 0.0f;
+            tm.dynamic_press_bias   = 0.0f;
+            tm.dynamic_press_scale  = 0.0f;
+
+            mavlink_msg_calibration_tm_encode(config::Mavlink::SYSTEM_ID,
+                                              config::Mavlink::COMPONENT_ID,
+                                              &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        default:
+            return false;
+    }
+}
+
+bool Radio::MavlinkBackend::enqueueSensorsTm(SensorsTMList tmId)
+{
+    switch (tmId)
+    {
+        case MAV_GPS_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_gps_tm_t tm;
+
+            auto sample = parent.getModule<Sensors>()->getUBXGPSLastSample();
+
+            tm.fix          = sample.fix;
+            tm.height       = sample.height;
+            tm.latitude     = sample.latitude;
+            tm.longitude    = sample.longitude;
+            tm.n_satellites = sample.satellites;
+            tm.speed        = sample.speed;
+            tm.timestamp    = sample.gpsTimestamp;
+            tm.track        = sample.track;
+            tm.vel_down     = sample.velocityDown;
+            tm.vel_east     = sample.velocityEast;
+            tm.vel_north    = sample.velocityNorth;
+            strcpy(tm.sensor_name, "UBXGPS");
+
+            mavlink_msg_gps_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case SensorsTMList::MAV_BMX160_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_imu_tm_t tm;
+
+            auto imuData = parent.getModule<Sensors>()
+                               ->getBMX160WithCorrectionLastSample();
+
+            strcpy(tm.sensor_name, "BMX160");
+            tm.acc_x     = imuData.accelerationX;
+            tm.acc_y     = imuData.accelerationY;
+            tm.acc_z     = imuData.accelerationZ;
+            tm.gyro_x    = imuData.angularSpeedX;
+            tm.gyro_y    = imuData.angularSpeedY;
+            tm.gyro_z    = imuData.angularSpeedZ;
+            tm.mag_x     = imuData.magneticFieldX;
+            tm.mag_y     = imuData.magneticFieldY;
+            tm.mag_z     = imuData.magneticFieldZ;
+            tm.timestamp = imuData.accelerationTimestamp;
+
+            mavlink_msg_imu_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+
+            return true;
+        }
+
+        case SensorsTMList::MAV_LPS22DF_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_pressure_tm_t tm;
+
+            auto pressureData =
+                parent.getModule<Sensors>()->getLPS22DFLastSample();
+
+            tm.timestamp = pressureData.pressureTimestamp;
+            strcpy(tm.sensor_name, "LPS22");
+            tm.pressure = pressureData.pressure;
+
+            mavlink_msg_pressure_tm_encode(config::Mavlink::SYSTEM_ID,
+                                           config::Mavlink::COMPONENT_ID, &msg,
+                                           &tm);
+
+            return true;
+        }
+
+        case MAV_BATTERY_VOLTAGE_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_voltage_tm_t tm;
+
+            auto data = parent.getModule<Sensors>()->getBatteryVoltage();
+
+            tm.voltage   = data.voltage;
+            tm.timestamp = data.voltageTimestamp;
+            strcpy(tm.sensor_name, "BatteryVoltage");
+
+            mavlink_msg_voltage_tm_encode(config::Mavlink::SYSTEM_ID,
+                                          config::Mavlink::COMPONENT_ID, &msg,
+                                          &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_ADS131M08_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_adc_tm_t tm;
+
+            auto sample = parent.getModule<Sensors>()->getADS131LastSample();
+
+            tm.channel_0 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_0).voltage;
+            tm.channel_1 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_1).voltage;
+            tm.channel_2 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_2).voltage;
+            tm.channel_3 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_3).voltage;
+            tm.channel_4 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_4).voltage;
+            tm.channel_5 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_5).voltage;
+            tm.channel_6 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_6).voltage;
+            tm.channel_7 =
+                sample.getVoltage(ADS131M08Defs::Channel::CHANNEL_7).voltage;
+            tm.timestamp = sample.timestamp;
+            strcpy(tm.sensor_name, "ADS131M08");
+
+            mavlink_msg_adc_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_LIS3MDL_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_imu_tm_t tm;
+
+            auto sample = parent.getModule<Sensors>()->getLIS3MDLLastSample();
+
+            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     = sample.magneticFieldX;
+            tm.mag_y     = sample.magneticFieldY;
+            tm.mag_z     = sample.magneticFieldZ;
+            tm.timestamp = sample.magneticFieldTimestamp;
+            strcpy(tm.sensor_name, "LIS3MDL");
+
+            mavlink_msg_imu_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        case MAV_H3LIS331DL_ID:
+        {
+            mavlink_message_t msg;
+            mavlink_imu_tm_t tm;
+
+            auto sample = parent.getModule<Sensors>()->getH3LISLastSample();
+
+            tm.mag_x     = 0;
+            tm.mag_y     = 0;
+            tm.mag_z     = 0;
+            tm.gyro_x    = 0;
+            tm.gyro_y    = 0;
+            tm.gyro_z    = 0;
+            tm.acc_x     = sample.accelerationX;
+            tm.acc_y     = sample.accelerationY;
+            tm.acc_z     = sample.accelerationZ;
+            tm.timestamp = sample.accelerationTimestamp;
+            strcpy(tm.sensor_name, "H3LIS331DL");
+
+            mavlink_msg_imu_tm_encode(config::Mavlink::SYSTEM_ID,
+                                      config::Mavlink::COMPONENT_ID, &msg, &tm);
+            enqueueMessage(msg);
+            return true;
+        }
+
+        default:
+            return false;
+    }
+}
+
+}  // namespace Parafoil
diff --git a/src/Parafoil/Radio/Radio.cpp b/src/Parafoil/Radio/Radio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c49ae1f02308104e294291412b0fb524e0137d74
--- /dev/null
+++ b/src/Parafoil/Radio/Radio.cpp
@@ -0,0 +1,228 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Niccolò Betto, Davide Basso
+ *
+ * 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 "Radio.h"
+
+#include <Parafoil/BoardScheduler.h>
+#include <Parafoil/Buses.h>
+#include <common/Radio.h>
+#include <radio/Xbee/APIFramesLog.h>
+#include <radio/Xbee/ATCommands.h>
+
+using namespace Boardcore;
+using namespace Boardcore::Units::Time;
+using namespace Common;
+namespace config = Parafoil::Config::Radio;
+
+namespace
+{
+
+// Static radio instance that will handle the radio interrupts
+std::atomic<Xbee::Xbee*> staticTransceiver{nullptr};
+inline void handleDioIRQ()
+{
+    auto transceiver = staticTransceiver.load();
+    if (transceiver)
+        transceiver->handleATTNInterrupt();
+}
+
+}  // namespace
+
+void __attribute__((used)) EXTI1_IRQHandlerImpl() { handleDioIRQ(); }
+
+namespace Parafoil
+{
+
+Radio::~Radio()
+{
+    auto transceiverPtr = transceiver.get();
+    staticTransceiver.compare_exchange_strong(transceiverPtr, nullptr);
+}
+
+bool Radio::start()
+{
+    auto& scheduler = getModule<BoardScheduler>()->radio();
+
+    SPIBusConfig config{};
+    config.clockDivider = SPI::ClockDivider::DIV_16;
+
+    transceiver = std::make_unique<Xbee::Xbee>(
+        getModule<Buses>()->spi2, config, miosix::xbee::cs::getPin(),
+        miosix::xbee::attn::getPin(), miosix::xbee::reset::getPin());
+    transceiver->setOnFrameReceivedListener([this](Xbee::APIFrame& frame)
+                                            { handleXbeeFrame(frame); });
+
+    Xbee::setDataRate(*transceiver, Config::Radio::Xbee::ENABLE_80KPS_DATA_RATE,
+                      Config::Radio::Xbee::TIMEOUT.value<Millisecond>());
+
+    // Set the static instance for handling radio interrupts
+    staticTransceiver = transceiver.get();
+
+    // Initialize the Mavlink driver
+    radioMavlink.driver = std::make_unique<MavDriver>(
+        transceiver.get(), [this](MavDriver*, const mavlink_message_t& msg)
+        { handleRadioMessage(msg); },
+        config::MavlinkDriver::SLEEP_AFTER_SEND.value<Millisecond>(),
+        config::MavlinkDriver::MAX_PKT_AGE.value<Millisecond>());
+
+    if (!radioMavlink.driver->start())
+    {
+        LOG_ERR(logger, "Failed to initialize the Mavlink driver");
+        return false;
+    }
+
+    // Add the high rate telemetry task
+    auto highRateTask =
+        scheduler.addTask([this]() { enqueueHighRateTelemetry(); },
+                          Config::Radio::HIGH_RATE_TELEMETRY);
+
+    if (highRateTask == 0)
+    {
+        LOG_ERR(logger, "Failed to add the high rate telemetry task");
+        return false;
+    }
+
+    auto lowRateTask =
+        scheduler.addTask([this]() { enqueueLowRateTelemetry(); },
+                          Config::Radio::LOW_RATE_TELEMETRY);
+
+    if (lowRateTask == 0)
+    {
+        LOG_ERR(logger, "Failed to add the low rate telemetry task");
+        return false;
+    }
+
+    started = true;
+    return true;
+}
+
+bool Radio::isStarted() { return started; }
+
+void Radio::handleXbeeFrame(Boardcore::Xbee::APIFrame& frame)
+{
+    using namespace Xbee;
+    bool logged = false;
+    switch (frame.frameType)
+    {
+        case FTYPE_AT_COMMAND:
+        {
+            ATCommandFrameLog dest;
+            logged = ATCommandFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+        case FTYPE_AT_COMMAND_RESPONSE:
+        {
+            ATCommandResponseFrameLog dest;
+            logged = ATCommandResponseFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+        case FTYPE_MODEM_STATUS:
+        {
+            ModemStatusFrameLog dest;
+            logged = ModemStatusFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+        case FTYPE_TX_REQUEST:
+        {
+            TXRequestFrameLog dest;
+            logged = TXRequestFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+        case FTYPE_TX_STATUS:
+        {
+            TXStatusFrameLog dest;
+            logged = TXStatusFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+        case FTYPE_RX_PACKET_FRAME:
+        {
+            RXPacketFrameLog dest;
+            logged = RXPacketFrameLog::toFrameType(frame, &dest);
+            if (logged)
+                Logger::getInstance().log(dest);
+            break;
+        }
+    }
+
+    if (!logged)
+    {
+        APIFrameLog api;
+        APIFrameLog::fromAPIFrame(frame, &api);
+        Logger::getInstance().log(api);
+    }
+}
+
+void Radio::handleRadioMessage(const mavlink_message_t& msg)
+{
+    radioMavlink.handleMessage(msg);
+}
+
+void Radio::enqueueHighRateTelemetry()
+{
+    radioMavlink.enqueueSystemTm(MAV_FLIGHT_ID);
+    radioMavlink.flushQueue();
+}
+
+void Radio::enqueueLowRateTelemetry()
+{
+    radioMavlink.enqueueSystemTm(MAV_STATS_ID);
+}
+
+void Radio::MavlinkBackend::enqueueMessage(const mavlink_message_t& msg)
+{
+    miosix::Lock<miosix::FastMutex> lock(mutex);
+
+    // Insert the message inside the queue only if there is enough space
+    if (index < queue.size())
+    {
+        queue[index] = msg;
+        index++;
+    }
+}
+
+void Radio::MavlinkBackend::flushQueue()
+{
+    Lock<FastMutex> lock(mutex);
+
+    for (uint32_t i = 0; i < index; i++)
+        driver->enqueueMsg(queue[i]);
+
+    // Reset the index
+    index = 0;
+}
+
+void Radio::MavlinkBackend::logStatus()
+{
+    Logger::getInstance().log(driver->getStatus());
+}
+
+}  // namespace Parafoil
diff --git a/src/Parafoil/Radio/Radio.h b/src/Parafoil/Radio/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b39b0ca749985465c11176a690beda70ffaa8b8
--- /dev/null
+++ b/src/Parafoil/Radio/Radio.h
@@ -0,0 +1,123 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Niccolò Betto, Davide Basso
+ *
+ * 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 <Parafoil/Configs/RadioConfig.h>
+#include <common/MavlinkOrion.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+#include <radio/SerialTransceiver/SerialTransceiver.h>
+#include <radio/Xbee/Xbee.h>
+#include <utils/DependencyManager/DependencyManager.h>
+
+namespace Parafoil
+{
+using MavDriver =
+    Boardcore::MavlinkDriver<Config::Radio::MavlinkDriver::PKT_LENGTH,
+                             Config::Radio::MavlinkDriver::PKT_QUEUE_SIZE,
+                             Config::Radio::MavlinkDriver::MSG_LENGTH>;
+
+class BoardScheduler;
+class Sensors;
+class Buses;
+class FlightModeManager;
+class Actuators;
+class NASController;
+class WingController;
+class AltitudeTrigger;
+class PinHandler;
+class WindEstimation;
+class FlightStatsRecorder;
+
+class Radio : public Boardcore::InjectableWithDeps<
+                  BoardScheduler, Sensors, Buses, FlightModeManager, Actuators,
+                  NASController, WingController, AltitudeTrigger, PinHandler,
+                  WindEstimation, FlightStatsRecorder>
+{
+public:
+    /**
+     * @brief Unsets the static instance for handling radio interrupts, if the
+     * current one was set by this radio instance.
+     */
+    ~Radio();
+
+    /**
+     * @brief Initializes the radio and Mavlink driver, and sets the static
+     * instance for handling radio interrupts.
+     */
+    [[nodiscard]] bool start();
+
+    bool isStarted();
+
+private:
+    struct MavlinkBackend
+    {
+        Radio& parent;  // Reference to the parent Radio instance
+
+        std::unique_ptr<MavDriver> driver;
+
+        std::array<mavlink_message_t, Config::Radio::MESSAGE_QUEUE_SIZE> queue;
+        size_t index = 0;
+        miosix::FastMutex mutex;
+
+        void handleMessage(const mavlink_message_t& msg);
+        void handleCommand(const mavlink_message_t& msg);
+
+        void enqueueAck(const mavlink_message_t& msg);
+        void enqueueNack(const mavlink_message_t& msg);
+        void enqueueWack(const mavlink_message_t& msg);
+
+        bool enqueueSystemTm(SystemTMList tmId);
+        bool enqueueSensorsTm(SensorsTMList sensorId);
+
+        /**
+         * @brief Enqueues a message in the message queue.
+         */
+        void enqueueMessage(const mavlink_message_t& msg);
+
+        /**
+         * @brief Flushes the message queue to the driver.
+         */
+        void flushQueue();
+
+        /**
+         * @brief Logs the status of MavlinkDriver and the transceiver
+         */
+        void logStatus();
+    };
+
+    void handleXbeeFrame(Boardcore::Xbee::APIFrame& frame);
+
+    void handleRadioMessage(const mavlink_message_t& msg);
+
+    void enqueueHighRateTelemetry();
+    void enqueueLowRateTelemetry();
+
+    std::unique_ptr<Boardcore::Xbee::Xbee> transceiver;
+    MavlinkBackend radioMavlink{.parent = *this};
+
+    std::atomic<bool> started{false};
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("Radio");
+};
+
+}  // namespace Parafoil
diff --git a/src/Parafoil/Sensors/Sensors.cpp b/src/Parafoil/Sensors/Sensors.cpp
index 2ccb85b154d12cd1928b6137967d6d9dfa5215a9..8866054608aaf179975bd8f02aac3acf154330d5 100644
--- a/src/Parafoil/Sensors/Sensors.cpp
+++ b/src/Parafoil/Sensors/Sensors.cpp
@@ -24,10 +24,12 @@
 
 #include <Parafoil/BoardScheduler.h>
 #include <Parafoil/Buses.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 
 #include <chrono>
 
 using namespace Boardcore;
+using namespace Boardcore::Units::Frequency;
 namespace config = Parafoil::Config::Sensors;
 namespace hwmap  = miosix::sensors;
 
@@ -341,9 +343,9 @@ void Sensors::ubxGpsInit()
     auto spiConfig         = UBXGPSSpi::getDefaultSPIConfig();
     spiConfig.clockDivider = SPI::ClockDivider::DIV_64;
 
-    ubxgps = std::make_unique<UBXGPSSpi>(getModule<Buses>()->spi1,
-                                         hwmap::ubxgps::cs::getPin(), spiConfig,
-                                         config::UBXGPS::SAMPLING_RATE.value());
+    ubxgps = std::make_unique<UBXGPSSpi>(
+        getModule<Buses>()->spi1, hwmap::ubxgps::cs::getPin(), spiConfig,
+        config::UBXGPS::SAMPLING_RATE.value<Kilohertz>());
 
     LOG_INFO(logger, "UBXGPS initialized!");
 }
@@ -390,6 +392,10 @@ void Sensors::bmx160Callback()
 void Sensors::bmx160WithCorrectionCallback()
 {
     BMX160WithCorrectionData lastSample = bmx160WithCorrection->getLastSample();
+
+    // Update acceleration stats
+    getModule<FlightStatsRecorder>()->updateAcc(lastSample);
+
     Logger::getInstance().log(lastSample);
 }
 
diff --git a/src/Parafoil/Sensors/Sensors.h b/src/Parafoil/Sensors/Sensors.h
index 11f590a49c97c4f19931ae7d5e2ae60016b38cd4..f90040b0a08424325f71a6875da8b4a0ffa0fca4 100644
--- a/src/Parafoil/Sensors/Sensors.h
+++ b/src/Parafoil/Sensors/Sensors.h
@@ -42,11 +42,13 @@ namespace Parafoil
 {
 class BoardScheduler;
 class Buses;
+class FlightStatsRecorder;
 
 /**
  * @brief Manages all the sensors of the parafoil board.
  */
-class Sensors : public Boardcore::InjectableWithDeps<BoardScheduler, Buses>
+class Sensors : public Boardcore::InjectableWithDeps<BoardScheduler, Buses,
+                                                     FlightStatsRecorder>
 {
 public:
     [[nodiscard]] bool start();
diff --git a/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.cpp b/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.cpp
index 94c15de3a31690dddb8bd37545efd3da453bd1b7..5415f579975d4406124b0e731c39a216420f2b33 100644
--- a/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.cpp
+++ b/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.cpp
@@ -24,6 +24,7 @@
 
 #include <Parafoil/BoardScheduler.h>
 #include <Parafoil/Configs/FlightModeManagerConfig.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 #include <Parafoil/Sensors/Sensors.h>
 #include <common/Events.h>
 #include <drivers/timer/TimestampTimer.h>
@@ -448,7 +449,11 @@ State FlightModeManager::FlyingWingDescent(const Event& event)
             // Send the event to the WingController
             controlDelayId = EventBroker::getInstance().postDelayed(
                 FLIGHT_WING_DESCENT, TOPIC_FLIGHT,
-                Millisecond{config::CONTROL_DELAY}.value());
+                config::CONTROL_DELAY.value<Millisecond>());
+
+            getModule<FlightStatsRecorder>()->dropDetected(
+                TimestampTimer::getTimestamp());
+
             return HANDLED;
         }
 
@@ -492,7 +497,7 @@ State FlightModeManager::Landed(const Event& event)
                                             TOPIC_FLIGHT);
             EventBroker::getInstance().postDelayed(
                 FMM_STOP_LOGGING, TOPIC_FMM,
-                Millisecond{config::LOGGING_DELAY}.value());
+                config::LOGGING_DELAY.value<Millisecond>());
 
             return HANDLED;
         }
diff --git a/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.h b/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.h
index 549636b2d5bcca03c974fa177edce57823b621bf..c360f549284ce12b33d32a959e24c4d4b837c7b3 100644
--- a/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.h
+++ b/src/Parafoil/StateMachines/FlightModeManager/FlightModeManager.h
@@ -31,6 +31,7 @@ namespace Parafoil
 {
 class Sensors;
 class Actuators;
+class FlightStatsRecorder;
 
 /**
  * State machine that manages the flight modes of the Parafoil.
@@ -54,7 +55,8 @@ class Actuators;
  */
 class FlightModeManager
     : public Boardcore::HSM<FlightModeManager>,
-      public Boardcore::InjectableWithDeps<Sensors, Actuators>
+      public Boardcore::InjectableWithDeps<Sensors, Actuators,
+                                           FlightStatsRecorder>
 {
 public:
     FlightModeManager();
diff --git a/src/Parafoil/StateMachines/NASController/NASController.cpp b/src/Parafoil/StateMachines/NASController/NASController.cpp
index 3317629072692bf12b5f11f539934044f53eed75..b71d3a8cfe4e14fa4cdbd7b1f3c336d183882c56 100644
--- a/src/Parafoil/StateMachines/NASController/NASController.cpp
+++ b/src/Parafoil/StateMachines/NASController/NASController.cpp
@@ -22,6 +22,7 @@
 
 #include <Parafoil/BoardScheduler.h>
 #include <Parafoil/Configs/NASConfig.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 #include <Parafoil/Sensors/Sensors.h>
 #include <Parafoil/StateMachines/NASController/NASController.h>
 #include <algorithms/NAS/StateInitializer.h>
@@ -32,6 +33,7 @@
 
 using namespace Boardcore;
 using namespace Boardcore::Units::Time;
+using namespace Boardcore::Units::Length;
 using namespace Boardcore::Units::Acceleration;
 using namespace Eigen;
 using namespace Common;
@@ -222,7 +224,7 @@ void NASController::calibrate()
 
         baroSum += baro.pressure;
 
-        Thread::sleep(Millisecond{config::CALIBRATION_SLEEP_TIME}.value());
+        Thread::sleep(config::CALIBRATION_SLEEP_TIME.value<Millisecond>());
     }
 
     Vector3f meanAcc = accSum / config::CALIBRATION_SAMPLES_COUNT;
@@ -281,6 +283,8 @@ void NASController::update()
     nas.predictGyro(imu);
     nas.predictAcc(imu);
 
+    // NOTE: Magnetometer correction has been disabled
+
     if (lastGpsTimestamp < gps.gpsTimestamp && gps.fix == 3 &&
         accLength < Config::NAS::DISABLE_GPS_ACCELERATION_THRESHOLD)
     {
@@ -318,8 +322,9 @@ void NASController::update()
     lastBaroTimestamp = baro.pressureTimestamp;
 
     auto state = nas.getState();
-    // auto ref   = nas.getReferenceValues();
+    auto ref   = nas.getReferenceValues();
 
+    getModule<FlightStatsRecorder>()->updateNas(state, ref.refTemperature);
     Logger::getInstance().log(state);
 }
 
@@ -334,4 +339,32 @@ void NASController::updateState(NASControllerState newState)
     Logger::getInstance().log(status);
 }
 
+void NASController::setReferenceAltitude(Meter altitude)
+{
+    miosix::Lock<miosix::FastMutex> l(nasMutex);
+
+    auto ref        = nas.getReferenceValues();
+    ref.refAltitude = altitude.value<Meter>();
+    nas.setReferenceValues(ref);
+}
+
+void NASController::setReferenceTemperature(float temperature)
+{
+    miosix::Lock<miosix::FastMutex> l(nasMutex);
+
+    auto ref           = nas.getReferenceValues();
+    ref.refTemperature = temperature;
+    nas.setReferenceValues(ref);
+}
+
+void NASController::setReferenceCoordinates(float latitude, float longitude)
+{
+    miosix::Lock<miosix::FastMutex> l(nasMutex);
+
+    auto ref         = nas.getReferenceValues();
+    ref.refLatitude  = latitude;
+    ref.refLongitude = longitude;
+    nas.setReferenceValues(ref);
+}
+
 }  // namespace Parafoil
diff --git a/src/Parafoil/StateMachines/NASController/NASController.h b/src/Parafoil/StateMachines/NASController/NASController.h
index 6c75c5830ea337fbc48d56011c0acba99d23269f..2998a5462eafb8a2abb8f992d09280a891709574 100644
--- a/src/Parafoil/StateMachines/NASController/NASController.h
+++ b/src/Parafoil/StateMachines/NASController/NASController.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2024 Skyward Experimental Rocketry
+/* Copyright (c) 2025 Skyward Experimental Rocketry
  * Authors: Niccolò Betto, Davide Basso
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -33,10 +33,12 @@ namespace Parafoil
 {
 class BoardScheduler;
 class Sensors;
+class FlightStatsRecorder;
 
 class NASController
     : public Boardcore::FSM<NASController>,
-      public Boardcore::InjectableWithDeps<BoardScheduler, Sensors>
+      public Boardcore::InjectableWithDeps<BoardScheduler, Sensors,
+                                           FlightStatsRecorder>
 {
 public:
     /**
@@ -60,6 +62,10 @@ public:
 
     void setOrientation(const Eigen::Quaternionf& orientation);
 
+    void setReferenceAltitude(Boardcore::Units::Length::Meter altitude);
+    void setReferenceTemperature(float temperature);
+    void setReferenceCoordinates(float latitude, float longitude);
+
 private:
     void calibrate();
 
diff --git a/src/Parafoil/StateMachines/WingController/WingController.cpp b/src/Parafoil/StateMachines/WingController/WingController.cpp
index 90ab7e46e88bf061afda0b42e8763c70bb91d377..3536fa810ea760c8d07997135ac2f462e23f3ebb 100644
--- a/src/Parafoil/StateMachines/WingController/WingController.cpp
+++ b/src/Parafoil/StateMachines/WingController/WingController.cpp
@@ -25,10 +25,12 @@
 #include <Parafoil/Configs/ActuatorsConfig.h>
 #include <Parafoil/Configs/WESConfig.h>
 #include <Parafoil/Configs/WingConfig.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 #include <Parafoil/StateMachines/NASController/NASController.h>
 #include <Parafoil/WindEstimation/WindEstimation.h>
 #include <Parafoil/Wing/AutomaticWingAlgorithm.h>
 #include <Parafoil/Wing/FileWingAlgorithm.h>
+#include <Parafoil/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h>
 #include <Parafoil/Wing/WingAlgorithm.h>
 #include <Parafoil/Wing/WingAlgorithmData.h>
 #include <Parafoil/Wing/WingTargetPositionData.h>
@@ -107,7 +109,7 @@ State WingController::Flying(const Event& event)
         }
         case EV_INIT:
         {
-            return transition(&WingController::FlyingCalibration);
+            return transition(&WingController::FlyingDeployment);
         }
         case FLIGHT_LANDING_DETECTED:
         {
@@ -120,15 +122,20 @@ State WingController::Flying(const Event& event)
     }
 }
 
-State WingController::FlyingCalibration(const Boardcore::Event& event)
+State WingController::FlyingDeployment(const Boardcore::Event& event)
 {
     static uint16_t calibrationTimeoutEventId;
 
     switch (event)
     {
-        case EV_ENTRY:  // starts twirling and calibration wes
+        case EV_ENTRY:
         {
-            updateState(WingControllerState::FLYING_CALIBRATION);
+            updateState(WingControllerState::FLYING_DEPLOYMENT);
+
+            auto nasState = getModule<NASController>()->getNasState();
+            auto altitude = Meter{-nasState.d};
+            getModule<FlightStatsRecorder>()->deploymentDetected(
+                TimestampTimer::getTimestamp(), altitude);
 
             flareWing();
             calibrationTimeoutEventId = EventBroker::getInstance().postDelayed(
@@ -169,7 +176,7 @@ State WingController::FlyingCalibration(const Boardcore::Event& event)
                 calibrationTimeoutEventId =
                     EventBroker::getInstance().postDelayed(
                         DPL_WES_CAL_DONE, TOPIC_DPL,
-                        Millisecond{WES::CALIBRATION_TIMEOUT}.value());
+                        WES::CALIBRATION_TIMEOUT.value<Millisecond>());
                 getModule<WindEstimation>()->startAlgorithm();
 
                 getModule<Actuators>()->startTwirl();
@@ -391,6 +398,11 @@ Eigen::Vector2f WingController::getActiveTarget()
     return emGuidance.getActiveTarget();
 }
 
+uint8_t WingController::getSelectedAlgorithm()
+{
+    return static_cast<uint8_t>(selectedAlgorithm.load());
+}
+
 void WingController::loadAlgorithms()
 {
     using namespace Wing;
@@ -419,12 +431,12 @@ void WingController::loadAlgorithms()
         step.servo2Angle = 120_deg;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{STRAIGHT_FLIGHT_TIMEOUT}.value();
+        step.timestamp += STRAIGHT_FLIGHT_TIMEOUT.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{STRAIGHT_FLIGHT_TIMEOUT}.value();
+        step.timestamp += STRAIGHT_FLIGHT_TIMEOUT.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
@@ -444,32 +456,32 @@ void WingController::loadAlgorithms()
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = RightServo::ROTATION / 2;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = LeftServo::ROTATION;
         step.servo2Angle = RightServo::ROTATION;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = RightServo::ROTATION / 2;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
 
-        step.timestamp += Microsecond{WES::ROTATION_PERIOD}.value();
+        step.timestamp += WES::ROTATION_PERIOD.value<Microsecond>();
         step.servo1Angle = 0_deg;
         step.servo2Angle = 0_deg;
         algorithm->addStep(step);
@@ -484,19 +496,19 @@ void WingController::loadAlgorithms()
                                                          PARAFOIL_RIGHT_SERVO);
         WingAlgorithmData step;
 
-        step.timestamp = Microsecond(PROGRESSIVE_ROTATION_TIMEOUT).value();
+        step.timestamp = PROGRESSIVE_ROTATION_TIMEOUT.value<Microsecond>();
 
         for (auto angle = 150_deg; angle >= 0_deg; angle -= WING_DECREMENT)
         {
             step.servo1Angle = angle;
             step.servo2Angle = 0_deg;
             algorithm->addStep(step);
-            step.timestamp += Microsecond(COMMAND_PERIOD).value();
+            step.timestamp += COMMAND_PERIOD.value<Microsecond>();
 
             step.servo1Angle = 0_deg;
             step.servo2Angle = angle;
             algorithm->addStep(step);
-            step.timestamp += Microsecond(COMMAND_PERIOD).value();
+            step.timestamp += COMMAND_PERIOD.value<Microsecond>();
         }
 
         algorithms[static_cast<size_t>(AlgorithmId::PROGRESSIVE_ROTATION)] =
diff --git a/src/Parafoil/StateMachines/WingController/WingController.h b/src/Parafoil/StateMachines/WingController/WingController.h
index a38d95ece554b46e72d0acd7a15a377f90173ef2..5850784f0443f90a2e6f6b943cfda892d3b767b4 100644
--- a/src/Parafoil/StateMachines/WingController/WingController.h
+++ b/src/Parafoil/StateMachines/WingController/WingController.h
@@ -23,6 +23,7 @@
 #pragma once
 
 #include <Parafoil/Configs/WingConfig.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 #include <Parafoil/Wing/Guidance/ClosedLoopGuidanceAlgorithm.h>
 #include <Parafoil/Wing/Guidance/EarlyManeuversGuidanceAlgorithm.h>
 #include <Parafoil/Wing/WingAlgorithm.h>
@@ -72,7 +73,6 @@ class WindEstimation;
  * Idle
  *
  * Flying
- * ├── FlyingCalibration
  * ├── FlyingDeployment
  * └── FlyingControlledDescent
  *
@@ -101,7 +101,6 @@ public:
     // HSM states
     Boardcore::State Idle(const Boardcore::Event& event);
     Boardcore::State Flying(const Boardcore::Event& event);
-    Boardcore::State FlyingCalibration(const Boardcore::Event& event);
     Boardcore::State FlyingDeployment(const Boardcore::Event& event);
     Boardcore::State FlyingControlledDescent(const Boardcore::Event& event);
     Boardcore::State OnGround(const Boardcore::Event& event);
diff --git a/src/Parafoil/StateMachines/WingController/WingControllerData.h b/src/Parafoil/StateMachines/WingController/WingControllerData.h
index 1b239e58463db25f9d5692b5cbc326cbe493c8f0..5efcded47967ba7c14c08280e6e58223860855d3 100644
--- a/src/Parafoil/StateMachines/WingController/WingControllerData.h
+++ b/src/Parafoil/StateMachines/WingController/WingControllerData.h
@@ -32,7 +32,6 @@ namespace Parafoil
 enum class WingControllerState : uint8_t
 {
     IDLE = 0,
-    FLYING_CALIBRATION,
     FLYING_DEPLOYMENT,
     FLYING_CONTROLLED_DESCENT,
     ON_GROUND,
diff --git a/src/Parafoil/WindEstimation/WindEstimation.cpp b/src/Parafoil/WindEstimation/WindEstimation.cpp
index 7704390aaf98693436c08d6496a59cdd3a3e6178..be909e97baac5bd7e5785755485319683c5a3fde 100644
--- a/src/Parafoil/WindEstimation/WindEstimation.cpp
+++ b/src/Parafoil/WindEstimation/WindEstimation.cpp
@@ -27,8 +27,8 @@
 #include <Parafoil/Sensors/Sensors.h>
 
 using namespace Boardcore;
+using namespace Boardcore::Units::Speed;
 using namespace Parafoil::Config;
-using namespace Units::Speed;
 
 namespace Parafoil
 {
@@ -133,9 +133,9 @@ void WindEstimation::updateCalibration()
                                 MeterPerSecond{gps.velocityEast}};
 
                 calibrationMatrix(nSampleCalibration, 0) =
-                    gpsVelocity.vn.value();
+                    gpsVelocity.vn.value<MeterPerSecond>();
                 calibrationMatrix(nSampleCalibration, 1) =
-                    gpsVelocity.ve.value();
+                    gpsVelocity.ve.value<MeterPerSecond>();
                 calibrationV2(nSampleCalibration) = gpsVelocity.normSquared();
 
                 velocity.vn += gpsVelocity.vn;
@@ -154,9 +154,11 @@ void WindEstimation::updateCalibration()
                 for (int i = 0; i < nSampleCalibration; i++)
                 {
                     calibrationMatrix(i, 0) =
-                        calibrationMatrix(i, 0) - velocity.vn.value();
+                        calibrationMatrix(i, 0) -
+                        velocity.vn.value<MeterPerSecond>();
                     calibrationMatrix(i, 1) =
-                        calibrationMatrix(i, 1) - velocity.ve.value();
+                        calibrationMatrix(i, 1) -
+                        velocity.ve.value<MeterPerSecond>();
                     calibrationV2(i) = 0.5f * (calibrationV2(i) - speedSquared);
                 }
 
@@ -230,9 +232,11 @@ void WindEstimation::updateAlgorithm()
             speedSquared = (speedSquared * nSampleAlgorithm +
                             (gpsVelocity.normSquared())) /
                            (nSampleAlgorithm + 1);
-            phi(0) = gpsVelocity.vn.value() - velocity.vn.value();
-            phi(1) = gpsVelocity.ve.value() - velocity.ve.value();
-            y      = 0.5f * ((gpsVelocity.normSquared()) - speedSquared);
+            phi(0) = gpsVelocity.vn.value<MeterPerSecond>() -
+                     velocity.vn.value<MeterPerSecond>();
+            phi(1) = gpsVelocity.ve.value<MeterPerSecond>() -
+                     velocity.ve.value<MeterPerSecond>();
+            y = 0.5f * ((gpsVelocity.normSquared()) - speedSquared);
 
             phiT = phi.transpose();
             funv =
@@ -242,8 +246,8 @@ void WindEstimation::updateAlgorithm()
 
             {
                 miosix::Lock<FastMutex> l(mutex);
-                wind.vn = MeterPerSecond(wind.vn.value() + temp(0));
-                wind.ve = MeterPerSecond(wind.ve.value() + temp(1));
+                wind.vn = wind.vn + MeterPerSecond(temp(0));
+                wind.ve = wind.ve + MeterPerSecond(temp(1));
             }
 
             logStatus();
diff --git a/src/Parafoil/WindEstimation/WindEstimationData.h b/src/Parafoil/WindEstimation/WindEstimationData.h
index 60e9668095b11b24d5cf3883a85eb4ea40c76e92..a5faaee6f4646908a4ae65b6aff80076210bacf0 100644
--- a/src/Parafoil/WindEstimation/WindEstimationData.h
+++ b/src/Parafoil/WindEstimation/WindEstimationData.h
@@ -64,10 +64,17 @@ struct GeoVelocity
      */
     float normSquared() const
     {
-        return vn.value() * vn.value() + ve.value() * ve.value();
+        return vn.value<Boardcore::Units::Speed::MeterPerSecond>() *
+                   vn.value<Boardcore::Units::Speed::MeterPerSecond>() +
+               ve.value<Boardcore::Units::Speed::MeterPerSecond>() *
+                   ve.value<Boardcore::Units::Speed::MeterPerSecond>();
     }
 
-    Eigen::Vector2f asVector() const { return {vn.value(), ve.value()}; }
+    Eigen::Vector2f asVector() const
+    {
+        return {vn.value<Boardcore::Units::Speed::MeterPerSecond>(),
+                ve.value<Boardcore::Units::Speed::MeterPerSecond>()};
+    }
 };
 
 }  // namespace Parafoil
diff --git a/src/Parafoil/Wing/AutomaticWingAlgorithm.cpp b/src/Parafoil/Wing/AutomaticWingAlgorithm.cpp
index 3c750418f2be5a0290e11b73ac3231ea6de2c7a4..ce9113b51c46cb00a127cb5052fa5b596bab5e2d 100644
--- a/src/Parafoil/Wing/AutomaticWingAlgorithm.cpp
+++ b/src/Parafoil/Wing/AutomaticWingAlgorithm.cpp
@@ -45,7 +45,7 @@ AutomaticWingAlgorithm::AutomaticWingAlgorithm(float Kp, float Ki,
     : Super(servo1, servo2), guidance(guidance)
 {
     // PIController needs the sample period in floating point seconds
-    auto samplePeriod = 1.0f / UPDATE_RATE.value();
+    auto samplePeriod = 1.0f / UPDATE_RATE.value<Hertz>();
 
     controller = std::make_unique<PIController>(Kp, Ki, samplePeriod,
                                                 PI::SATURATION_MIN_LIMIT,
@@ -121,7 +121,7 @@ Degree AutomaticWingAlgorithm::algorithmStep(const NASState& state)
     // Call the PI with the just calculated error. The result is in RADIANS,
     // if positive we activate one servo, if negative the other
     // We also need to convert the result from radians back to degrees
-    auto result = Degree(Radian(controller->update(error.value())));
+    auto result = Degree(Radian(controller->update(error.value<Radian>())));
 
     // Logs the outputs
     {
@@ -139,7 +139,7 @@ Degree AutomaticWingAlgorithm::algorithmStep(const NASState& state)
 
 Radian AutomaticWingAlgorithm::angleDiff(Radian a, Radian b)
 {
-    auto diff = (a - b).value();
+    auto diff = (a - b).value<Radian>();
 
     // Angle difference
     if (diff < -Constants::PI || Constants::PI < diff)
diff --git a/src/Parafoil/parafoil-entry.cpp b/src/Parafoil/parafoil-entry.cpp
index 00bb72f05d2f2f7d52af7fc174dc9bf08d226adc..a6321ac32bb5f38b9c4ebe72b97ee5174908e04b 100644
--- a/src/Parafoil/parafoil-entry.cpp
+++ b/src/Parafoil/parafoil-entry.cpp
@@ -24,7 +24,9 @@
 #include <Parafoil/AltitudeTrigger/AltitudeTrigger.h>
 #include <Parafoil/BoardScheduler.h>
 #include <Parafoil/Buses.h>
+#include <Parafoil/FlightStatsRecorder/FlightStatsRecorder.h>
 #include <Parafoil/PinHandler/PinHandler.h>
+#include <Parafoil/Radio/Radio.h>
 #include <Parafoil/Sensors/Sensors.h>
 #include <Parafoil/StateMachines/FlightModeManager/FlightModeManager.h>
 #include <Parafoil/StateMachines/NASController/NASController.h>
@@ -114,7 +116,9 @@ int main()
     auto pinHandler = new PinHandler();
     initResult &= depman.insert(pinHandler);
 
-    // TODO: Radio
+    // Radio
+    auto radio = new Radio();
+    initResult &= depman.insert(radio);
 
     // Flight algorithms
     auto altitudeTrigger = new AltitudeTrigger();
@@ -124,6 +128,10 @@ int main()
     auto windEstimation = new WindEstimation();
     initResult &= depman.insert(windEstimation);
 
+    // Stats recorder
+    auto flightStatsRecorder = new FlightStatsRecorder();
+    initResult &= depman.insert(flightStatsRecorder);
+
     // Actuators
     auto actuators = new Actuators();
     initResult &= depman.insert(actuators);
@@ -138,7 +146,7 @@ int main()
     START_MODULE(flightModeManager);
 
     START_MODULE(pinHandler);
-    // START_MODULE(radio) { miosix::led2On(); }
+    START_MODULE(radio);
     START_MODULE(nasController);
     START_MODULE(altitudeTrigger);
     START_MODULE(windEstimation);