diff --git a/CMakeLists.txt b/CMakeLists.txt
index e50a959a0d54b57a185d1331bb1f1b49add26e68..239e46323be079d1091eab9766450382a3565995 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,7 +60,10 @@ add_executable(rig-entry src/entrypoints/RIG/rig-entry.cpp ${RIG_COMPUTER})
 target_include_directories(rig-entry PRIVATE ${OBSW_INCLUDE_DIRS})
 sbs_target(rig-entry stm32f429zi_skyward_rig)
 
+add_executable(con_rig-entry src/entrypoints/con_RIG/con_rig-entry.cpp ${CON_RIG_COMPUTER})
+target_include_directories(con_rig-entry PRIVATE ${OBSW_INCLUDE_DIRS})
+sbs_target(con_rig-entry stm32f429zi_stm32f4discovery)
+
 #-----------------------------------------------------------------------------#
 #                               Test entrypoints                              #
 #-----------------------------------------------------------------------------#
-
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index 65ae83089d526ae7967dc9be87e3a8175b094772..da4dc6d1f4720e9ad6e5cbb1ccb6dd009373b949 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -67,3 +67,9 @@ set(RIG_COMPUTER
     src/boards/RIG/CanHandler/CanHandler.cpp
     src/boards/RIG/StatesMonitor/StatesMonitor.cpp
 )
+
+set(CON_RIG_COMPUTER
+    src/boards/con_RIG/Buttons/Buttons.cpp
+    src/boards/con_RIG/Radio/Radio.cpp
+    src/boards/con_RIG/BoardScheduler.cpp
+)
diff --git a/src/boards/con_RIG/BoardScheduler.cpp b/src/boards/con_RIG/BoardScheduler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a9371f3e4239f586392b3cbc45219f8cbe623320
--- /dev/null
+++ b/src/boards/con_RIG/BoardScheduler.cpp
@@ -0,0 +1,66 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Matteo Pignataro
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "BoardScheduler.h"
+
+using namespace Boardcore;
+
+namespace con_RIG
+{
+// TODO: UPDATE THE SCHEDULER PRIORITY PARAMETER ONCE MERGED NEW TASK SCHEDULER
+BoardScheduler::BoardScheduler()
+{
+    scheduler1 = new TaskScheduler(miosix::PRIORITY_MAX - 4);
+    scheduler2 = new TaskScheduler(miosix::PRIORITY_MAX - 3);
+    scheduler3 = new TaskScheduler(miosix::PRIORITY_MAX - 2);
+    scheduler4 = new TaskScheduler(miosix::PRIORITY_MAX - 1);
+}
+
+TaskScheduler* BoardScheduler::getScheduler(miosix::Priority priority)
+{
+    switch (priority.get())
+    {
+        case miosix::PRIORITY_MAX:
+            return scheduler4;
+        case miosix::PRIORITY_MAX - 1:
+            return scheduler3;
+        case miosix::PRIORITY_MAX - 2:
+            return scheduler2;
+        case miosix::MAIN_PRIORITY:
+            return scheduler1;
+        default:
+            return scheduler1;
+    }
+}
+
+bool BoardScheduler::start()
+{
+    return scheduler1->start() && scheduler2->start() && scheduler3->start() &&
+           scheduler4->start();
+}
+
+bool BoardScheduler::isStarted()
+{
+    return scheduler1->isRunning() && scheduler2->isRunning() &&
+           scheduler3->isRunning() && scheduler4->isRunning();
+}
+}  // namespace con_RIG
\ No newline at end of file
diff --git a/src/boards/con_RIG/BoardScheduler.h b/src/boards/con_RIG/BoardScheduler.h
new file mode 100644
index 0000000000000000000000000000000000000000..9fe83776e8aee7421c1b264b1299833fcc6e85fe
--- /dev/null
+++ b/src/boards/con_RIG/BoardScheduler.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace con_RIG
+{
+
+/**
+ * @brief Class that wraps the 4 main task schedulers of the entire OBSW.
+ * There is a task scheduler for every miosix priority
+ */
+class BoardScheduler : public Boardcore::Module
+{
+public:
+    BoardScheduler();
+
+    /**
+     * @brief Get the Scheduler object relative to the requested priority
+     *
+     * @param priority The task scheduler priority
+     * @return Boardcore::TaskScheduler& Reference to the requested task
+     * scheduler.
+     * @note Min priority scheduler is returned in case of non valid priority.
+     */
+    Boardcore::TaskScheduler* getScheduler(miosix::Priority priority);
+
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Returns if all the schedulers are up and running
+     */
+    bool isStarted();
+
+private:
+    Boardcore::TaskScheduler* scheduler1;
+    Boardcore::TaskScheduler* scheduler2;
+    Boardcore::TaskScheduler* scheduler3;
+    Boardcore::TaskScheduler* scheduler4;
+};
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Buses.h b/src/boards/con_RIG/Buses.h
new file mode 100644
index 0000000000000000000000000000000000000000..7fb368dade3952e30df1c26807f0d43db6e648ca
--- /dev/null
+++ b/src/boards/con_RIG/Buses.h
@@ -0,0 +1,43 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <drivers/spi/SPIBus.h>
+#include <drivers/usart/USART.h>
+#include <miosix.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace con_RIG
+{
+
+struct Buses : public Boardcore::Module
+{
+    Boardcore::SPIBus spi1;
+    Boardcore::SPIBus spi2;
+
+public:
+    Buses() : spi1(SPI1), spi2(SPI2) {}
+};
+
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Buttons/Buttons.cpp b/src/boards/con_RIG/Buttons/Buttons.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a604346d62281d9ae92554c123d1359200584ea
--- /dev/null
+++ b/src/boards/con_RIG/Buttons/Buttons.cpp
@@ -0,0 +1,184 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * 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 "Buttons.h"
+
+#include <con_RIG/BoardScheduler.h>
+#include <con_RIG/Configs/ButtonsConfig.h>
+#include <con_RIG/Radio/Radio.h>
+
+using namespace std;
+using namespace miosix;
+using namespace Boardcore;
+using namespace con_RIG::Config::Buttons;
+
+namespace con_RIG
+{
+
+Buttons::Buttons(TaskScheduler* sched) : scheduler(sched)
+{
+    resetState();
+    state.arm_switch = false;
+    remoteArm        = 0;
+}
+
+bool Buttons::start()
+{
+    return scheduler->addTask([&]() { periodicStatusCheck(); },
+                              BUTTON_SAMPLE_PERIOD);
+}
+
+Buttons::~Buttons()
+{
+    // Delete all the buttons
+}
+
+mavlink_conrig_state_tc_t Buttons::getState() { return state; }
+
+void Buttons::resetState()
+{
+    state.ignition_btn         = false;
+    state.filling_valve_btn    = false;
+    state.venting_valve_btn    = false;
+    state.release_pressure_btn = false;
+    state.quick_connector_btn  = false;
+    state.start_tars_btn       = false;
+}
+
+void Buttons::periodicStatusCheck()
+{
+    // TODO: This should be in bsp
+    using GpioIgnitionBtn        = Gpio<GPIOB_BASE, 4>;
+    using GpioFillingValveBtn    = Gpio<GPIOE_BASE, 6>;
+    using GpioVentingValveBtn    = Gpio<GPIOE_BASE, 4>;
+    using GpioReleasePressureBtn = Gpio<GPIOG_BASE, 9>;
+    using GpioQuickConnectorBtn  = Gpio<GPIOD_BASE, 7>;
+    using GpioStartTarsBtn       = Gpio<GPIOD_BASE, 5>;
+    using GpioArmedSwitch        = Gpio<GPIOE_BASE, 2>;
+
+    state.arm_switch = GpioArmedSwitch::getPin().value();
+
+    if (!GpioIgnitionBtn::getPin().value() && state.arm_switch)
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard              = 0;
+            state.ignition_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else if (GpioFillingValveBtn::getPin().value())
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard                   = 0;
+            state.filling_valve_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else if (GpioVentingValveBtn::getPin().value())
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard                   = 0;
+            state.venting_valve_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else if (GpioReleasePressureBtn::getPin().value())
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard                      = 0;
+            state.release_pressure_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else if (GpioQuickConnectorBtn::getPin().value())
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard                     = 0;
+            state.quick_connector_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else if (GpioStartTarsBtn::getPin().value())
+    {
+        if (guard > Config::Buttons::GUARD_THRESHOLD)
+        {
+            guard                = 0;
+            state.start_tars_btn = true;
+        }
+        else
+        {
+            guard++;
+        }
+    }
+    else
+    {
+        // Reset all the states and guard
+        guard = 0;
+        resetState();
+    }
+
+    // Set the internal button state in Radio module
+    ModuleManager::getInstance().get<Radio>()->setInternalState(state);
+
+    // printf("%d %d %d %d %d %d %d\n", state.ignition, state.filling_valve,
+    //        state.venting_valve, state.release_filling_line_pressure,
+    //        state.detach_quick_connector, state.startup_tars, state.armed);
+}
+
+// 1 if rocket is armed, 0 if disarmed
+void Buttons::setRemoteArmState(int armed)
+{
+    using armed_led = Gpio<GPIOC_BASE, 13>;
+
+    if (armed)
+    {
+        remoteArm = true;
+        armed_led::high();
+    }
+    else
+    {
+        remoteArm = false;
+        armed_led::low();
+    }
+}
+
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Buttons/Buttons.h b/src/boards/con_RIG/Buttons/Buttons.h
new file mode 100644
index 0000000000000000000000000000000000000000..221a456a461406f078b717046f8c9892b0e23911
--- /dev/null
+++ b/src/boards/con_RIG/Buttons/Buttons.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <common/Mavlink.h>
+#include <diagnostic/PrintLogger.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace con_RIG
+{
+
+class Buttons : public Boardcore::Module
+{
+
+public:
+    explicit Buttons(Boardcore::TaskScheduler* sched);
+
+    ~Buttons();
+
+    bool start();
+
+    mavlink_conrig_state_tc_t getState();
+
+    void resetState();
+
+    void setRemoteArmState(int state);
+
+private:
+    mavlink_conrig_state_tc_t state;
+    void periodicStatusCheck();
+    std::atomic<bool> remoteArm{false};
+
+    // Counter guard to avoid spurious triggers
+    uint8_t guard = 0;
+
+    Boardcore::TaskScheduler* scheduler = nullptr;
+
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("buttons");
+};
+
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Configs/ButtonsConfig.h b/src/boards/con_RIG/Configs/ButtonsConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..671d55fd46c5834d0b651aebcd60b93a1cf4d9a4
--- /dev/null
+++ b/src/boards/con_RIG/Configs/ButtonsConfig.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * 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.
+ */
+
+/**
+ * This class specifies the sensors constants that the sensor manager
+ * needs to know about every device. For example the sample time is
+ * essential to understand how much time a sensor should wait before
+ * another sample.
+ */
+
+#pragma once
+
+namespace con_RIG
+{
+namespace Config
+{
+namespace Buttons
+{
+
+static constexpr uint32_t BUTTON_SAMPLE_PERIOD = 20;  // 50Hz
+
+constexpr uint8_t CHECK_BUTTON_STATE_TASK_ID = 150;
+
+static constexpr uint32_t BUZZER_PERIOD = 100;
+static constexpr uint32_t BUZZER_DELAY  = 3000;
+constexpr uint8_t BUZZER_ON_TASK_ID     = 160;
+constexpr uint8_t BUZZER_OFF_TASK_ID    = 161;
+
+constexpr uint8_t GUARD_THRESHOLD =
+    5;  // 5 samples to trigger the guard and activate a single button
+
+}  // namespace Buttons
+}  // namespace Config
+
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Configs/RadioConfig.h b/src/boards/con_RIG/Configs/RadioConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..1785b129073302133e26f895f8e378aa72f7dc74
--- /dev/null
+++ b/src/boards/con_RIG/Configs/RadioConfig.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Matteo Pignataro
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <common/Mavlink.h>
+
+namespace con_RIG
+{
+
+namespace Config
+{
+namespace Radio
+{
+
+// Mavlink driver template parameters
+constexpr uint32_t RADIO_PKT_LENGTH     = 255;
+constexpr uint32_t RADIO_OUT_QUEUE_SIZE = 10;
+constexpr uint32_t RADIO_MAV_MSG_LENGTH = MAVLINK_MAX_DIALECT_PAYLOAD_SIZE;
+
+// Mavlink driver parameters
+constexpr size_t MAV_OUT_BUFFER_MAX_AGE = 200;
+
+// Mavlink ids
+constexpr uint8_t MAV_SYSTEM_ID    = 171;
+constexpr uint8_t MAV_COMPONENT_ID = 96;
+
+// Periodic telemetries frequency
+constexpr uint32_t PING_GSE_PERIOD = 500;  // [ms]
+
+// Periodic telemetries tasks ids
+constexpr uint8_t PING_GSE_TASK_ID = 200;
+
+constexpr uint32_t MAVLINK_QUEUE_SIZE = 3;
+
+}  // namespace Radio
+}  // namespace Config
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Radio/Radio.cpp b/src/boards/con_RIG/Radio/Radio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4c70e239c2f4d7cf5b09c7a6b8f6549122297b9
--- /dev/null
+++ b/src/boards/con_RIG/Radio/Radio.cpp
@@ -0,0 +1,280 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * 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 <common/Mavlink.h>
+#include <con_RIG/BoardScheduler.h>
+#include <con_RIG/Buses.h>
+#include <con_RIG/Buttons/Buttons.h>
+#include <diagnostic/SkywardStack.h>
+#include <drivers/interrupt/external_interrupts.h>
+#include <events/EventBroker.h>
+#include <radio/SX1278/SX1278Frontends.h>
+#include <radio/Xbee/ATCommands.h>
+
+#include <thread>
+
+using namespace std;
+using namespace miosix;
+using namespace placeholders;
+using namespace Boardcore;
+using namespace con_RIG::Config::Radio;
+
+void __attribute__((used)) EXTI1_IRQHandlerImpl()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<con_RIG::Radio>()->transceiver != nullptr)
+    {
+        modules.get<con_RIG::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+
+void __attribute__((used)) EXTI12_IRQHandlerImpl()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<con_RIG::Radio>()->transceiver != nullptr)
+    {
+        modules.get<con_RIG::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+
+void __attribute__((used)) EXTI13_IRQHandlerImpl()
+{
+    ModuleManager& modules = ModuleManager::getInstance();
+    if (modules.get<con_RIG::Radio>()->transceiver != nullptr)
+    {
+        modules.get<con_RIG::Radio>()->transceiver->handleDioIRQ();
+    }
+}
+
+namespace con_RIG
+{
+
+void Radio::handleMavlinkMessage(MavDriver* driver,
+                                 const mavlink_message_t& msg)
+{
+    switch (msg.msgid)
+    {
+        case MAVLINK_MSG_ID_GSE_TM:
+        {
+            int arming_state = mavlink_msg_gse_tm_get_arming_state(&msg);
+            ModuleManager::getInstance().get<Buttons>()->setRemoteArmState(
+                arming_state);
+            messageReceived +=
+                arming_state == 1
+                    ? 10
+                    : 2;  // The beep increments in speed as the state is armed
+        }
+        case MAVLINK_MSG_ID_ACK_TM:
+        {
+            int id = mavlink_msg_ack_tm_get_recv_msgid(&msg);
+            // we assume this ack is about the last sent message
+            if (id == MAVLINK_MSG_ID_CONRIG_STATE_TC)
+            {
+                Lock<FastMutex> lock(internalStateMutex);
+                // Reset the internal button state
+                buttonState.ignition_btn         = false;
+                buttonState.filling_valve_btn    = false;
+                buttonState.venting_valve_btn    = false;
+                buttonState.release_pressure_btn = false;
+                buttonState.quick_connector_btn  = false;
+                buttonState.start_tars_btn       = false;
+                buttonState.arm_switch           = false;
+            }
+        }
+    }
+    mavlinkWriteToUsart(msg);
+}
+
+void Radio::mavlinkWriteToUsart(const mavlink_message_t& msg)
+{
+    uint8_t temp_buf[MAVLINK_NUM_NON_PAYLOAD_BYTES +
+                     MAVLINK_MAX_DIALECT_PAYLOAD_SIZE];
+    int len = mavlink_msg_to_send_buffer(temp_buf, &msg);
+
+    auto serial = miosix::DefaultConsole::instance().get();
+    serial->writeBlock(temp_buf, len, 0);
+}
+
+void Radio::sendMessages()
+{
+    mavlink_message_t msg;
+
+    {
+        Lock<FastMutex> lock(internalStateMutex);
+        mavlink_msg_conrig_state_tc_encode(Config::Radio::MAV_SYSTEM_ID,
+                                           Config::Radio::MAV_COMPONENT_ID,
+                                           &msg, &buttonState);
+    }
+
+    {
+        Lock<FastMutex> lock(mutex);
+        for (uint8_t i = 0; i < message_queue_index; i++)
+        {
+            mavDriver->enqueueMsg(message_queue[i]);
+        }
+        message_queue_index = 0;
+
+        // The last is the button state message
+        mavDriver->enqueueMsg(msg);
+    }
+}
+
+void Radio::loopReadFromUsart()
+{
+    mavlink_status_t status;
+    mavlink_message_t msg;
+    int chan = 0;  // TODO: what is this?
+    uint8_t byte;
+
+    while (true)
+    {
+        auto serial = miosix::DefaultConsole::instance().get();
+        int len     = serial->readBlock(&byte, 1, 0);
+
+        if (len && mavlink_parse_char(chan, byte, &msg, &status))
+        {
+            if (message_queue_index == MAVLINK_QUEUE_SIZE - 1)
+            {
+                mavlink_message_t nack_msg;
+                mavlink_nack_tm_t nack_tm;
+                nack_tm.recv_msgid = msg.msgid;
+                nack_tm.seq_ack    = msg.seq;
+                mavlink_msg_nack_tm_encode(Config::Radio::MAV_SYSTEM_ID,
+                                           Config::Radio::MAV_COMPONENT_ID,
+                                           &nack_msg, &nack_tm);
+                mavlinkWriteToUsart(nack_msg);
+            }
+            else
+            {
+                Lock<FastMutex> lock(mutex);
+                message_queue[message_queue_index] = msg;
+                message_queue_index += 1;
+            }
+        }
+    }
+}
+
+void Radio::setInternalState(mavlink_conrig_state_tc_t state)
+{
+    Lock<FastMutex> lock(internalStateMutex);
+    // The OR operator is introduced to make sure that the receiver
+    // understood the command
+    buttonState.ignition_btn = state.ignition_btn || buttonState.ignition_btn;
+    buttonState.filling_valve_btn =
+        state.filling_valve_btn || buttonState.filling_valve_btn;
+    buttonState.venting_valve_btn =
+        state.venting_valve_btn || buttonState.venting_valve_btn;
+    buttonState.release_pressure_btn =
+        state.release_pressure_btn || buttonState.release_pressure_btn;
+    buttonState.quick_connector_btn =
+        state.quick_connector_btn || buttonState.quick_connector_btn;
+    buttonState.start_tars_btn =
+        state.start_tars_btn || buttonState.start_tars_btn;
+    buttonState.arm_switch = state.arm_switch || buttonState.arm_switch;
+}
+
+bool Radio::start()
+{
+    // TODO: constants should be in bps
+    using dio0 = Gpio<GPIOB_BASE, 1>;
+    using dio1 = Gpio<GPIOD_BASE, 12>;
+    using dio3 = Gpio<GPIOD_BASE, 13>;
+    using txEn = Gpio<GPIOG_BASE, 2>;
+    using rxEn = Gpio<GPIOG_BASE, 3>;
+    using cs   = Gpio<GPIOF_BASE, 6>;
+
+    ModuleManager& modules = ModuleManager::getInstance();
+
+    // Config the transceiver
+    SX1278Lora::Config config{};
+    config.power            = 2;
+    config.ocp              = 0;  // Over current protection
+    config.coding_rate      = SX1278Lora::Config::Cr::CR_1;
+    config.spreading_factor = SX1278Lora::Config::Sf::SF_7;
+
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<EbyteFrontend>(txEn::getPin(), rxEn::getPin());
+
+    transceiver =
+        new SX1278Lora(modules.get<Buses>()->spi1, cs::getPin(), dio0::getPin(),
+                       dio1::getPin(), dio3::getPin(),
+                       SPI::ClockDivider::DIV_64, std::move(frontend));
+
+    SX1278Lora::Error error = transceiver->init(config);
+
+    // Config mavDriver
+    mavDriver = new MavDriver(transceiver,
+                              bind(&Radio::handleMavlinkMessage, this, _1, _2),
+                              0, MAV_OUT_BUFFER_MAX_AGE);
+
+    // In case of failure the mav driver must be created at least
+    if (error != SX1278Lora::Error::NONE)
+    {
+        return false;
+    }
+
+    scheduler->addTask([&]() { sendMessages(); }, PING_GSE_PERIOD,
+                       TaskScheduler::Policy::RECOVER);
+
+    receiverLooper = std::thread([=]() { loopReadFromUsart(); });
+    beeperLooper   = std::thread(
+        [&]()
+        {
+            using buzzer = Gpio<GPIOB_BASE, 7>;
+            while (true)
+            {
+                Thread::sleep(100);
+                // Doesn't matter the precision, the important thing is
+                // the beep, this is because an atomic is used
+                if (messageReceived > 5)
+                {
+                    messageReceived = 0;
+                    buzzer::low();
+                    Thread::sleep(100);
+                    buzzer::high();
+                }
+            }
+        });
+
+    return mavDriver->start();
+}
+
+bool Radio::isStarted() { return mavDriver->isStarted(); }
+
+MavlinkStatus Radio::getMavlinkStatus() { return mavDriver->getStatus(); }
+
+Radio::Radio(TaskScheduler* sched) : scheduler(sched)
+{
+    Lock<FastMutex> lock(internalStateMutex);
+
+    buttonState.ignition_btn         = false;
+    buttonState.filling_valve_btn    = false;
+    buttonState.venting_valve_btn    = false;
+    buttonState.release_pressure_btn = false;
+    buttonState.quick_connector_btn  = false;
+    buttonState.start_tars_btn       = false;
+    buttonState.arm_switch           = false;
+}
+
+}  // namespace con_RIG
diff --git a/src/boards/con_RIG/Radio/Radio.h b/src/boards/con_RIG/Radio/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..6765e51d69905c2149557e4ad66102c32ae91d6e
--- /dev/null
+++ b/src/boards/con_RIG/Radio/Radio.h
@@ -0,0 +1,83 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <common/Mavlink.h>
+#include <con_RIG/Configs/RadioConfig.h>
+#include <diagnostic/PrintLogger.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+#include <radio/SX1278/SX1278Lora.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <cstdint>
+#include <thread>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace con_RIG
+{
+
+using MavDriver = Boardcore::MavlinkDriver<Config::Radio::RADIO_PKT_LENGTH,
+                                           Config::Radio::RADIO_OUT_QUEUE_SIZE,
+                                           Config::Radio::RADIO_MAV_MSG_LENGTH>;
+
+class Radio : public Boardcore::Module
+{
+public:
+    explicit Radio(Boardcore::TaskScheduler* sched);
+
+    bool start();
+
+    bool isStarted();
+
+    Boardcore::MavlinkStatus getMavlinkStatus();
+
+    void sendMessages();
+
+    void loopReadFromUsart();
+
+    void setInternalState(mavlink_conrig_state_tc_t state);
+
+    Boardcore::SX1278Lora* transceiver = nullptr;
+    MavDriver* mavDriver               = nullptr;
+
+private:
+    void handleMavlinkMessage(MavDriver* driver, const mavlink_message_t& msg);
+
+    void mavlinkWriteToUsart(const mavlink_message_t& msg);
+
+    mavlink_message_t message_queue[Config::Radio::MAVLINK_QUEUE_SIZE];
+    uint8_t message_queue_index = 0;
+    miosix::FastMutex mutex;
+    miosix::FastMutex internalStateMutex;
+
+    // Button internal state
+    mavlink_conrig_state_tc_t buttonState;
+
+    std::thread receiverLooper;
+    std::thread beeperLooper;
+    std::atomic<uint8_t> messageReceived{0};
+    Boardcore::TaskScheduler* scheduler = nullptr;
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("radio");
+};
+
+}  // namespace con_RIG
diff --git a/src/entrypoints/con_RIG/con_rig-entry.cpp b/src/entrypoints/con_RIG/con_rig-entry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e305fb8b3c396681e7f254e750a3a6368eb3df9
--- /dev/null
+++ b/src/entrypoints/con_RIG/con_rig-entry.cpp
@@ -0,0 +1,189 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Giacomo Caironi
+ *
+ * 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 <con_RIG/BoardScheduler.h>
+#include <con_RIG/Buses.h>
+#include <con_RIG/Buttons/Buttons.h>
+#include <con_RIG/Configs/ButtonsConfig.h>
+#include <con_RIG/Radio/Radio.h>
+#include <diagnostic/CpuMeter/CpuMeter.h>
+#include <diagnostic/PrintLogger.h>
+#include <events/EventBroker.h>
+#include <miosix.h>
+
+#include <thread>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace con_RIG;
+
+// TODO delete with hwmapping and bsp
+void initPins()
+{
+    // SPI1
+    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+    using sck  = Gpio<GPIOA_BASE, 5>;
+    using miso = Gpio<GPIOA_BASE, 6>;
+    using mosi = Gpio<GPIOA_BASE, 7>;
+    using cs   = Gpio<GPIOF_BASE, 6>;
+
+    using dio0 = Gpio<GPIOB_BASE, 1>;
+    using dio1 = Gpio<GPIOD_BASE, 12>;
+    using dio3 = Gpio<GPIOD_BASE, 13>;
+
+    using txEn = Gpio<GPIOG_BASE, 2>;
+    using rxEn = Gpio<GPIOG_BASE, 3>;
+    using nrst = Gpio<GPIOB_BASE, 0>;
+
+    sck::mode(Mode::ALTERNATE);
+    miso::mode(Mode::ALTERNATE);
+    mosi::mode(Mode::ALTERNATE);
+
+    sck::alternateFunction(5);
+    miso::alternateFunction(5);
+    mosi::alternateFunction(5);
+
+    cs::mode(Mode::OUTPUT);
+    dio0::mode(Mode::INPUT_PULL_UP);
+    dio1::mode(Mode::INPUT_PULL_UP);
+    dio3::mode(Mode::INPUT_PULL_UP);
+    txEn::mode(Mode::OUTPUT);
+    rxEn::mode(Mode::OUTPUT);
+    nrst::mode(Mode::OUTPUT);
+
+    cs::high();
+    nrst::high();
+
+    // USART 1
+    using tx = Gpio<GPIOA_BASE, 9>;
+    using rx = Gpio<GPIOA_BASE, 10>;
+
+    tx::mode(Mode::ALTERNATE);
+    rx::mode(Mode::ALTERNATE);
+
+    tx::alternateFunction(7);
+    rx::alternateFunction(7);
+
+    // Buttons and switch
+    using GpioIgnitionBtn        = Gpio<GPIOB_BASE, 4>;
+    using GpioFillingValveBtn    = Gpio<GPIOE_BASE, 6>;
+    using GpioVentingValveBtn    = Gpio<GPIOE_BASE, 4>;
+    using GpioReleasePressureBtn = Gpio<GPIOG_BASE, 9>;
+    using GpioQuickConnectorBtn  = Gpio<GPIOD_BASE, 7>;
+    using GpioStartTarsBtn       = Gpio<GPIOD_BASE, 5>;
+    using GpioArmedSwitch        = Gpio<GPIOE_BASE, 2>;
+
+    GpioIgnitionBtn::mode(Mode::INPUT);
+    GpioFillingValveBtn::mode(Mode::INPUT);
+    GpioVentingValveBtn::mode(Mode::INPUT);
+    GpioReleasePressureBtn::mode(Mode::INPUT);
+    GpioQuickConnectorBtn::mode(Mode::INPUT);
+    GpioStartTarsBtn::mode(Mode::INPUT);
+    GpioArmedSwitch::mode(Mode::INPUT);
+
+    // Actuators
+    using armed_led = Gpio<GPIOC_BASE, 13>;
+    using buzzer    = Gpio<GPIOB_BASE, 7>;
+    using redLed    = Gpio<GPIOG_BASE, 14>;  // Red LED
+
+    armed_led::mode(Mode::OUTPUT);
+    buzzer::mode(Mode::OUTPUT);
+    redLed::mode(Mode::OUTPUT);
+
+    buzzer::high();
+}
+
+int main()
+{
+
+    bool initResult        = true;
+    ModuleManager& modules = ModuleManager::getInstance();
+    PrintLogger logger     = Logging::getLogger("main");
+
+    initPins();
+
+    BoardScheduler* scheduler = new BoardScheduler();
+    Buses* buses              = new Buses();
+    Radio* radio     = new Radio(scheduler->getScheduler(PRIORITY_MAX));
+    Buttons* buttons = new Buttons(scheduler->getScheduler(PRIORITY_MAX - 1));
+
+    if (!modules.insert<BoardScheduler>(scheduler))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error initializing BoardScheduler module");
+    }
+
+    if (!modules.insert<Buses>(buses))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error initializing Buses module");
+    }
+
+    if (!modules.insert<Radio>(radio))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error initializing Radio module");
+    }
+
+    if (!modules.insert<Buttons>(buttons))
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error initializing Buttons module");
+    }
+
+    if (!modules.get<Radio>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the radio");
+    }
+
+    if (!modules.get<Buttons>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the buttons");
+    }
+
+    if (!modules.get<BoardScheduler>()->start())
+    {
+        initResult = false;
+        LOG_ERR(logger, "Error starting the General Purpose Scheduler");
+    }
+
+    if (initResult)
+    {
+        // POST OK
+        // EventBroker::getInstance().post(FMM_INIT_OK, TOPIC_FMM);
+    }
+    else
+    {
+        using redLed = Gpio<GPIOG_BASE, 14>;  // Red LED
+        redLed::high();
+    }
+
+    // Periodical statistics
+    while (true)
+    {
+        Thread::sleep(1000);
+        CpuMeter::resetCpuStats();
+    }
+}