diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index cbdccca2a01c990308245275350930a16d9b4c34..ac0c876bc981f542ded4bf4bcef87ce48af68af8 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -72,6 +72,7 @@ set(RIG_V2_COMPUTER
     src/boards/RIGv2/Radio/Radio.cpp
     src/boards/RIGv2/Sensors/Sensors.cpp
     src/boards/RIGv2/Actuators/Actuators.cpp
+    src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.cpp
 )
 
 set(CON_RIG_COMPUTER
diff --git a/src/boards/RIGv2/Radio/Radio.cpp b/src/boards/RIGv2/Radio/Radio.cpp
index 9e98aa18cc48cb33e8b5845e40f777e1d3ebcfa1..ddf68abd5e4f956b3af3337987a4269484a0fdf0 100644
--- a/src/boards/RIGv2/Radio/Radio.cpp
+++ b/src/boards/RIGv2/Radio/Radio.cpp
@@ -25,6 +25,8 @@
 #include <RIGv2/Actuators/Actuators.h>
 #include <RIGv2/Buses.h>
 #include <RIGv2/Sensors/Sensors.h>
+#include <RIGv2/StateMachines/GroundModeManager/GroundModeManager.h>
+#include <common/Events.h>
 #include <common/Radio.h>
 #include <events/EventBroker.h>
 #include <radio/SX1278/SX1278Frontends.h>
@@ -196,7 +198,8 @@ void Radio::handleMessage(const mavlink_message_t& msg)
             ServosList servo = static_cast<ServosList>(
                 mavlink_msg_wiggle_servo_tc_get_servo_id(&msg));
 
-            if (modules.get<Actuators>()->wiggleServo(servo))
+            if (modules.get<GroundModeManager>()->isDisarmed() &&
+                modules.get<Actuators>()->wiggleServo(servo))
             {
                 sendAck(msg);
             }
@@ -288,7 +291,14 @@ void Radio::handleCommand(const mavlink_message_t& msg)
 
         case MAV_CMD_CALIBRATE:
         {
-            modules.get<Sensors>()->calibrate();
+            EventBroker::getInstance().post(TMTC_CALIBRATE, TOPIC_MOTOR);
+            sendAck(msg);
+            break;
+        }
+
+        case MAV_CMD_FORCE_INIT:
+        {
+            EventBroker::getInstance().post(TMTC_FORCE_INIT, TOPIC_MOTOR);
             sendAck(msg);
             break;
         }
@@ -312,11 +322,11 @@ bool Radio::packSystemTm(uint8_t tmId, 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.logger       = Logger::getInstance().isStarted() ? 1 : 0;
+            tm.event_broker = EventBroker::getInstance().isRunning() ? 1 : 0;
             // What? Why is this here? Of course the radio is started!
-            tm.radio           = isStarted();
-            tm.sensors         = modules.get<Sensors>()->isStarted();
+            tm.radio           = isStarted() ? 1 : 0;
+            tm.sensors         = modules.get<Sensors>()->isStarted() ? 1 : 0;
             tm.board_scheduler = 0;  // TODO(davide.mor): No BoardScheduler yet
 
             mavlink_msg_sys_tm_encode(Config::Radio::MAV_SYSTEM_ID,
@@ -395,6 +405,8 @@ bool Radio::packSystemTm(uint8_t tmId, mavlink_message_t& msg)
                 actuators->isServoOpen(ServosList::RELEASE_VALVE) ? 1 : 0;
             tm.main_valve_state =
                 actuators->isServoOpen(ServosList::MAIN_VALVE) ? 1 : 0;
+            tm.arming_state = modules.get<GroundModeManager>()->isArmed();
+            tm.ignition_state = modules.get<GroundModeManager>()->isIgniting();
             // TODO(davide.mor): Add the rest of these
 
             mavlink_msg_gse_tm_encode(Config::Radio::MAV_SYSTEM_ID,
@@ -458,7 +470,9 @@ void Radio::handleConrigState(const mavlink_message_t& msg)
         if (oldConrigState.arm_switch == 0 && state.arm_switch == 1)
         {
             // The ARM switch was pressed
-            // TODO(davide.mor): Arm the system
+            // TODO(davide.mor): Notify everybody of a manual actuation
+
+            EventBroker::getInstance().post(TMTC_ARM, TOPIC_MOTOR);
 
             lastManualActuation = currentTime;
         }
@@ -519,7 +533,9 @@ void Radio::handleConrigState(const mavlink_message_t& msg)
     // Special case for disarming, that can be done bypassing the timeout
     if (oldConrigState.arm_switch == 1 && state.arm_switch == 0)
     {
-        // TODO(davide.mor): Disarm the system
+        // TODO(davide.mor): Notify everybody of a manual actuation
+
+        EventBroker::getInstance().post(TMTC_DISARM, TOPIC_MOTOR);
 
         lastManualActuation = currentTime;
     }
diff --git a/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.cpp b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a3d6944797961734f90fa9d2726aa2fe59bc721
--- /dev/null
+++ b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.cpp
@@ -0,0 +1,150 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "GroundModeManager.h"
+
+#include <RIGv2/Sensors/Sensors.h>
+#include <common/Events.h>
+#include <events/EventBroker.h>
+// TODO(davide.mor): Remove TimestampTimer
+#include <drivers/timer/TimestampTimer.h>
+
+using namespace Boardcore;
+using namespace miosix;
+using namespace Common;
+using namespace RIGv2;
+
+GroundModeManager::GroundModeManager() : FSM(&GroundModeManager::state_idle)
+{
+    EventBroker::getInstance().subscribe(this, TOPIC_MOTOR);
+}
+
+bool GroundModeManager::isArmed()
+{
+    return testState(&GroundModeManager::state_armed);
+}
+bool GroundModeManager::isDisarmed()
+{
+    return testState(&GroundModeManager::state_disarmed);
+}
+bool GroundModeManager::isIgniting()
+{
+    return testState(&GroundModeManager::state_igniting);
+}
+
+void GroundModeManager::state_idle(const Boardcore::Event &event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(GroundModeManagerState::STATE_IDLE);
+            break;
+        }
+
+        case FMM_INIT_ERROR:
+        {
+            transition(&GroundModeManager::state_init_err);
+            break;
+        }
+
+        case FMM_INIT_OK:
+        {
+            transition(&GroundModeManager::state_disarmed);
+            break;
+        }
+    }
+}
+
+void GroundModeManager::state_init_err(const Boardcore::Event &event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(GroundModeManagerState::STATE_INIT_ERR);
+            break;
+        }
+
+        case TMTC_FORCE_INIT:
+        {
+            transition(&GroundModeManager::state_disarmed);
+            break;
+        }
+    }
+}
+
+void GroundModeManager::state_disarmed(const Boardcore::Event &event)
+{
+    ModuleManager &modules = ModuleManager::getInstance();
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(GroundModeManagerState::STATE_DISARMED);
+            break;
+        }
+
+        case TMTC_ARM:
+        {
+            transition(&GroundModeManager::state_armed);
+            break;
+        }
+
+        case TMTC_CALIBRATE:
+        {
+            modules.get<Sensors>()->calibrate();
+
+            // TODO(davide.mor): Also send CAN command
+            break;
+        }
+    }
+}
+
+void GroundModeManager::state_armed(const Boardcore::Event &event)
+{
+    switch (event)
+    {
+        case EV_ENTRY:
+        {
+            logStatus(GroundModeManagerState::STATE_ARMED);
+            break;
+        }
+
+        case TMTC_DISARM:
+        {
+            transition(&GroundModeManager::state_disarmed);
+            break;
+        }
+    }
+}
+
+void GroundModeManager::state_igniting(const Boardcore::Event &event)
+{
+    // TODO(davide.mor): Not yet implemented
+}
+
+void GroundModeManager::logStatus(GroundModeManagerState newState)
+{
+    GroundModeManagerData data = {TimestampTimer::getTimestamp(), newState};
+    sdLogger.log(data);
+}
\ No newline at end of file
diff --git a/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.h b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..68ecb4855898109747d36399e1d67246b6336b47
--- /dev/null
+++ b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManager.h
@@ -0,0 +1,59 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <RIGv2/StateMachines/GroundModeManager/GroundModeManagerData.h>
+#include <diagnostic/PrintLogger.h>
+#include <events/FSM.h>
+#include <logger/Logger.h>
+#include <atomic>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace RIGv2
+{
+
+class GroundModeManager : public Boardcore::Module,
+                          public Boardcore::FSM<GroundModeManager>
+{
+public:
+    GroundModeManager();
+
+    bool isArmed();
+    bool isDisarmed();
+    bool isIgniting();
+
+private:
+    void state_idle(const Boardcore::Event &event);
+    void state_init_err(const Boardcore::Event &event);
+    void state_disarmed(const Boardcore::Event &event);
+    void state_armed(const Boardcore::Event &event);
+    void state_igniting(const Boardcore::Event &event);
+
+    void logStatus(GroundModeManagerState newState);
+
+    Boardcore::Logger &sdLogger   = Boardcore::Logger::getInstance();
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("sensors");
+};
+
+}  // namespace RIGv2
\ No newline at end of file
diff --git a/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManagerData.h b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManagerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb83cd754fc9e244046c992cf18cde3702d24621
--- /dev/null
+++ b/src/boards/RIGv2/StateMachines/GroundModeManager/GroundModeManagerData.h
@@ -0,0 +1,65 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+namespace RIGv2
+{
+
+enum GroundModeManagerState : uint8_t
+{
+    UNINIT         = 0,
+    STATE_IDLE     = 1,
+    STATE_INIT_ERR = 2,
+    STATE_DISARMED = 3,
+    STATE_ARMED    = 4,
+    STATE_IGNITING = 5,
+};
+
+struct GroundModeManagerData
+{
+    uint64_t timestamp;
+    GroundModeManagerState state;
+
+    GroundModeManagerData()
+        : timestamp{0}, state{GroundModeManagerState::UNINIT}
+    {
+    }
+
+    GroundModeManagerData(uint64_t timestamp, GroundModeManagerState state)
+        : timestamp{timestamp}, state{state}
+    {
+    }
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)state << "\n";
+    }
+};
+
+}  // namespace RIGv2
\ No newline at end of file
diff --git a/src/entrypoints/RIGv2/rig-v2-entry.cpp b/src/entrypoints/RIGv2/rig-v2-entry.cpp
index 15f02567cdcbab1578b2fe93084f5689d18afc48..0becedcc2d5ec66a9a747f121d6625dc6b4aafb8 100644
--- a/src/entrypoints/RIGv2/rig-v2-entry.cpp
+++ b/src/entrypoints/RIGv2/rig-v2-entry.cpp
@@ -24,10 +24,14 @@
 #include <RIGv2/Buses.h>
 #include <RIGv2/Radio/Radio.h>
 #include <RIGv2/Sensors/Sensors.h>
+#include <RIGv2/StateMachines/GroundModeManager/GroundModeManager.h>
+#include <common/Events.h>
 #include <diagnostic/CpuMeter/CpuMeter.h>
 #include <diagnostic/StackLogger.h>
+#include <events/EventBroker.h>
 
 using namespace Boardcore;
+using namespace Common;
 using namespace RIGv2;
 using namespace miosix;
 
@@ -41,12 +45,14 @@ int main()
     TaskScheduler *scheduler1 = new TaskScheduler(3);
     TaskScheduler *scheduler2 = new TaskScheduler(4);
 
-    Buses *buses         = new Buses();
-    Sensors *sensors     = new Sensors(*scheduler1);
-    Actuators *actuators = new Actuators(*scheduler2);
-    Radio *radio         = new Radio();
+    Buses *buses           = new Buses();
+    Sensors *sensors       = new Sensors(*scheduler1);
+    Actuators *actuators   = new Actuators(*scheduler2);
+    GroundModeManager *gmm = new GroundModeManager();
+    Radio *radio           = new Radio();
 
-    Logger &sdLogger = Logger::getInstance();
+    Logger &sdLogger    = Logger::getInstance();
+    EventBroker &broker = EventBroker::getInstance();
 
     bool initResult = true;
 
@@ -75,7 +81,25 @@ int main()
         LOG_ERR(logger, "Error failed to insert Radio");
     }
 
+    if (!modules.insert<GroundModeManager>(gmm))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error failed to insert GroundModeManager");
+    }
+
     // Start modules
+    if (sdLogger.testSDCard())
+    {
+        initResult = false;
+        LOG_ERR(logger, "SD card test failed");
+    }
+
+    if (broker.start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Failed to start EventBroker");
+    }
+
     if (!sensors->start())
     {
         initResult = false;
@@ -94,17 +118,28 @@ int main()
         LOG_ERR(logger, "Error failed to start Radio module");
     }
 
+    if (!gmm->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error failed to start GroundModeManager module");
+    }
+
     if (!scheduler1->start() || !scheduler2->start())
     {
         initResult = false;
         LOG_ERR(logger, "Error failed to start scheduler");
     }
 
-    if (!initResult)
+    if (initResult)
     {
-        // TODO(davide.mor): What to do in case of not good?
+        broker.post(FMM_INIT_OK, TOPIC_MOTOR);
         LOG_INFO(logger, "All good!");
     }
+    else
+    {
+        broker.post(FMM_INIT_ERROR, TOPIC_MOTOR);
+        LOG_ERR(logger, "Init failure!");
+    }
 
     // Periodic statistics
     while (true)
diff --git a/src/scripts/logdecoder/RIGv2/logdecoder.cpp b/src/scripts/logdecoder/RIGv2/logdecoder.cpp
index eab10f51c11e5b25893a3eeb31029ae54057ad06..75bbe6dc64885e7e6a9c6dd99a6212a1dae4fc6f 100644
--- a/src/scripts/logdecoder/RIGv2/logdecoder.cpp
+++ b/src/scripts/logdecoder/RIGv2/logdecoder.cpp
@@ -22,6 +22,7 @@
 
 #include <RIGv2/Sensors/SensorsData.h>
 #include <RIGv2/Actuators/ActuatorsData.h>
+#include <RIGv2/StateMachines/GroundModeManager/GroundModeManagerData.h>
 #include <logger/Deserializer.h>
 #include <logger/LogTypes.h>
 #include <tscpp/stream.h>
@@ -54,6 +55,7 @@ void registerTypes(Deserializer& ds)
     ds.registerType<ADCsData>();
     ds.registerType<TCsData>();
     ds.registerType<ActuatorsData>();
+    ds.registerType<GroundModeManagerData>();
 }
 
 void showUsage(const string& cmdName)