diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index 94d0850d8867b04e774e33ee911382d074d8f2fc..74b7c3142b06876e4ffd041b775273d43340eaf6 100755
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -763,6 +763,52 @@
                 "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/arch/cortexM7_stm32f7/stm32f769ni_discovery",
                 "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/config/arch/cortexM7_stm32f7/stm32f769ni_discovery"
             ]
+        },
+        {
+            "name": "stm32f767zi_gemini_gs",
+            "cStandard": "c11",
+            "cppStandard": "c++14",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "{defaultDefines}",
+                "_MIOSIX_BOARDNAME=stm32f767zi_gemini_gs",
+                "_BOARD_STM32F767ZI_GEMINI_GS",
+                "_ARCH_CORTEXM7_STM32F7",
+                "STM32F769xx",
+                "HSE_VALUE=25000000",
+                "SYSCLK_FREQ_216MHz=216000000",
+                "__ENABLE_XRAM",
+                "V_DDA_VOLTAGE=3.3f"
+            ],
+            "includePath": [
+                "${defaultIncludePaths}",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/arch/cortexM7_stm32f7/common",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/arch/cortexM7_stm32f7/stm32f767zi_gemini_gs",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/config/arch/cortexM7_stm32f7/stm32f767zi_gemini_gs"
+            ]
+        },
+        {
+            "name": "stm32f429zi_skyward_groundstation_v2",
+            "cStandard": "c11",
+            "cppStandard": "c++14",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "{defaultDefines}",
+                "_MIOSIX_BOARDNAME=stm32f429zi_skyward_groundstation_v2",
+                "_BOARD_STM32F429ZI_SKYWARD_GS_V2",
+                "_ARCH_CORTEXM4_STM32F4",
+                "STM32F429xx",
+                "HSE_VALUE=8000000",
+                "SYSCLK_FREQ_168MHz=168000000",
+                "__ENABLE_XRAM",
+                "V_DDA_VOLTAGE=3.0f"
+            ],
+            "includePath": [
+                "${defaultIncludePaths}",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_skyward_groundstation_v2",
+                "${workspaceFolder}/skyward-boardcore/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f429zi_skyward_groundstation_v2"
+            ]
         }
     ],
     "version": 4
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 6c2786aa3dce9c51c5660c36873f7ab01e2a8e4b..a51e6cc187fb81ae22cabe4c3fa1497faa877e3e 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -118,13 +118,6 @@
         "numbers": "cpp",
         "semaphore": "cpp"
     },
-    "editor.formatOnSave": true,
-    "[c]": {
-        "editor.formatOnSave": false
-    },
-    "[cmake]": {
-        "editor.formatOnSave": false
-    },
     "cSpell.words": [
         "Aeroutils",
         "AIRBRAKES",
@@ -152,6 +145,7 @@
         "Duca",
         "Eigen",
         "EXTI",
+        "Gpio",
         "GPIOF",
         "KALM",
         "kalman",
@@ -161,6 +155,7 @@
         "MPXH",
         "Nidasio",
         "pitot",
+        "RSSI",
         "SATS",
         "setb",
         "Setpoint",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65a2c2f39dcc2afed86092df03fcbe3658d809c1..bb34a759c97938be60a951c975b88d18189764c6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -73,3 +73,17 @@ 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)
+
+add_executable(base-groundstation-entry 
+    src/entrypoints/Groundstation/base-groundstation-entry.cpp 
+    ${GROUNDSTATION_COMMON} ${GROUNDSTATION_BASE}
+)
+target_include_directories(base-groundstation-entry PRIVATE ${OBSW_INCLUDE_DIRS})
+sbs_target(base-groundstation-entry stm32f767zi_gemini_gs)
+
+add_executable(nokia-groundstation-entry 
+    src/entrypoints/Groundstation/nokia-groundstation-entry.cpp 
+    ${GROUNDSTATION_COMMON} ${GROUNDSTATION_NOKIA}
+)
+target_include_directories(nokia-groundstation-entry PRIVATE ${OBSW_INCLUDE_DIRS})
+sbs_target(nokia-groundstation-entry stm32f429zi_skyward_groundstation_v2)
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index 65860823f9cd53c58948c5d92b194c6c28dc06c5..41e4ba50104153efdbed696ce42c47d2b654a726 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -95,3 +95,22 @@ set(PAYLOAD_COMPUTER
     src/boards/Payload/Sensors/RotatedIMU/RotatedIMU.cpp
     src/boards/Payload/WindEstimationScheme/WindEstimation.cpp
 )
+
+set(GROUNDSTATION_BASE
+    src/boards/Groundstation/Base/Radio/Radio.cpp
+    src/boards/Groundstation/Base/Ports/Ethernet.cpp
+    src/boards/Groundstation/Base/BoardStatus.cpp
+    src/boards/Groundstation/Base/Hub.cpp
+)
+
+set(GROUNDSTATION_NOKIA
+    src/boards/Groundstation/Nokia/Radio/Radio.cpp
+    src/boards/Groundstation/Nokia/Hub.cpp
+)
+
+set(GROUNDSTATION_COMMON
+    src/boards/Groundstation/Common/Ports/Serial.cpp
+    src/boards/Groundstation/Common/Ports/EthernetBase.cpp
+    src/boards/Groundstation/Common/Radio/RadioBase.cpp
+    src/boards/Groundstation/Common/HubBase.cpp
+)
diff --git a/src/boards/Groundstation/Base/BoardStatus.cpp b/src/boards/Groundstation/Base/BoardStatus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a8fb81e1e4cba8715d87f4039f07fa4f98ec5d15
--- /dev/null
+++ b/src/boards/Groundstation/Base/BoardStatus.cpp
@@ -0,0 +1,128 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "BoardStatus.h"
+
+#include <Groundstation/Base/Ports/Ethernet.h>
+#include <Groundstation/Base/Radio/Radio.h>
+#include <Groundstation/Common/Config/GeneralConfig.h>
+#include <Groundstation/Common/HubBase.h>
+#include <common/Mavlink.h>
+#include <drivers/timer/TimestampTimer.h>
+
+using namespace Boardcore;
+using namespace Groundstation;
+using namespace GroundstationBase;
+
+bool BoardStatus::isMainRadioPresent() { return main_radio_present; }
+bool BoardStatus::isPayloadRadioPresent() { return payload_radio_present; }
+bool BoardStatus::isEthernetPresent() { return ethernet_present; }
+
+bool BoardStatus::start()
+{
+    if (!ActiveObject::start())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void BoardStatus::setMainRadioPresent(bool present)
+{
+    main_radio_present = present;
+}
+
+void BoardStatus::setPayloadRadioPresent(bool present)
+{
+    payload_radio_present = present;
+}
+
+void BoardStatus::setEthernetPresent(bool present)
+{
+    ethernet_present = present;
+}
+
+void BoardStatus::run()
+{
+    while (!shouldStop())
+    {
+        miosix::Thread::sleep(RADIO_STATUS_PERIOD);
+
+        mavlink_receiver_tm_t tm = {0};
+        tm.timestamp             = TimestampTimer::getTimestamp();
+        tm.battery_voltage       = -420.0;
+
+        if (main_radio_present)
+        {
+            tm.main_radio_present = 1;
+
+            auto stats =
+                ModuleManager::getInstance().get<RadioMain>()->getStats();
+            tm.main_packet_tx_error_count = stats.send_errors;
+            tm.main_tx_bitrate = main_tx_bitrate.update(stats.bits_tx_count);
+            tm.main_packet_rx_success_count = stats.packet_rx_success_count;
+            tm.main_packet_rx_drop_count    = stats.packet_rx_drop_count;
+            tm.main_rx_bitrate = main_rx_bitrate.update(stats.bits_rx_count);
+            tm.main_rx_rssi    = stats.rx_rssi;
+            tm.main_rx_fei     = stats.rx_fei;
+
+            last_main_stats = stats;
+        }
+
+        if (payload_radio_present)
+        {
+            tm.payload_radio_present = 1;
+
+            auto stats =
+                ModuleManager::getInstance().get<RadioPayload>()->getStats();
+            tm.payload_packet_tx_error_count = stats.send_errors;
+            tm.payload_tx_bitrate =
+                payload_tx_bitrate.update(stats.bits_tx_count);
+            tm.payload_packet_rx_success_count = stats.packet_rx_success_count;
+            tm.payload_packet_rx_drop_count    = stats.packet_rx_drop_count;
+            tm.payload_rx_bitrate =
+                payload_rx_bitrate.update(stats.bits_rx_count);
+            tm.payload_rx_rssi = stats.rx_rssi;
+            tm.payload_rx_fei  = stats.rx_fei;
+
+            last_payload_stats = stats;
+        }
+
+        if (ethernet_present)
+        {
+            auto stats =
+                ModuleManager::getInstance().get<Ethernet>()->getState();
+
+            tm.ethernet_present = 1;
+            tm.ethernet_status  = (stats.link_up ? 1 : 0) |
+                                 (stats.full_duplex ? 2 : 0) |
+                                 (stats.based_100mbps ? 4 : 0);
+        }
+
+        mavlink_message_t msg;
+        mavlink_msg_receiver_tm_encode(GS_SYSTEM_ID, GS_COMPONENT_ID, &msg,
+                                       &tm);
+
+        ModuleManager::getInstance().get<HubBase>()->dispatchIncomingMsg(msg);
+    }
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/BoardStatus.h b/src/boards/Groundstation/Base/BoardStatus.h
new file mode 100644
index 0000000000000000000000000000000000000000..c684a33193256e2fdde4041c28ce41c27820fbe9
--- /dev/null
+++ b/src/boards/Groundstation/Base/BoardStatus.h
@@ -0,0 +1,123 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <ActiveObject.h>
+#include <Groundstation/Common/Config/RadioConfig.h>
+#include <Groundstation/Common/Radio/RadioBase.h>
+#include <utils/collections/CircularBuffer.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace GroundstationBase
+{
+
+/**
+ * @brief Utility to calculate the bitrate
+ */
+template <unsigned int WINDOW_SIZE, unsigned int PERIOD>
+class BitrateCalculator
+{
+public:
+    BitrateCalculator() {}
+
+    /**
+     * @brief Update the calculator, should be called every PERIOD ms
+     */
+    uint16_t update(uint32_t sample)
+    {
+        if (window.isFull())
+        {
+            uint32_t last = window.pop();
+            window.put(sample);
+
+            uint16_t delta = sample - last;
+
+            // window size is in ms, we want the result in s
+            return delta * 1000 / WINDOW_SIZE;
+        }
+        else
+        {
+            window.put(sample);
+            return 0;
+        }
+    }
+
+private:
+    Boardcore::CircularBuffer<uint32_t, WINDOW_SIZE / PERIOD> window;
+};
+
+/**
+ * @brief Class responsible for keeping track of radio status and metrics.
+ */
+class BoardStatus : public Boardcore::Module, private Boardcore::ActiveObject
+{
+public:
+    BoardStatus() {}
+
+    bool start();
+
+    /**
+     * @brief Check wether the main radio was found during boot.
+     */
+    bool isMainRadioPresent();
+
+    /**
+     * @brief Check wether the payload radio was found during boot.
+     */
+    bool isPayloadRadioPresent();
+
+    /**
+     * @brief Check wether the ethernet was found during boot.
+     */
+    bool isEthernetPresent();
+
+    void setMainRadioPresent(bool present);
+    void setPayloadRadioPresent(bool present);
+    void setEthernetPresent(bool present);
+
+private:
+    void run() override;
+
+    Groundstation::RadioStats last_main_stats    = {0};
+    Groundstation::RadioStats last_payload_stats = {0};
+
+    BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE,
+                      Groundstation::RADIO_STATUS_PERIOD>
+        main_tx_bitrate;
+    BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE,
+                      Groundstation::RADIO_STATUS_PERIOD>
+        main_rx_bitrate;
+    BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE,
+                      Groundstation::RADIO_STATUS_PERIOD>
+        payload_tx_bitrate;
+    BitrateCalculator<Groundstation::RADIO_BITRATE_WINDOW_SIZE,
+                      Groundstation::RADIO_STATUS_PERIOD>
+        payload_rx_bitrate;
+
+    bool main_radio_present    = false;
+    bool payload_radio_present = false;
+    bool ethernet_present      = false;
+};
+
+}  // namespace GroundstationBase
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Buses.h b/src/boards/Groundstation/Base/Buses.h
new file mode 100644
index 0000000000000000000000000000000000000000..e776148116ef7d9d5a8dd6da54ff568eb840ee23
--- /dev/null
+++ b/src/boards/Groundstation/Base/Buses.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <drivers/spi/SPIBus.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "interfaces-impl/hwmapping.h"
+
+namespace GroundstationBase
+{
+
+class Buses : public Boardcore::Module
+{
+public:
+    Boardcore::SPIBus radio1_bus;
+    Boardcore::SPIBus radio2_bus;
+    Boardcore::SPIBus ethernet_bus;
+
+    Buses()
+        : radio1_bus(MIOSIX_RADIO1_SPI), radio2_bus(MIOSIX_RADIO2_SPI),
+          ethernet_bus(MIOSIX_ETHERNET_SPI)
+    {
+    }
+};
+
+}  // namespace GroundstationBase
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Hub.cpp b/src/boards/Groundstation/Base/Hub.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..af0b0608d7006b26b6cb3622a1faa34a85f9c81d
--- /dev/null
+++ b/src/boards/Groundstation/Base/Hub.cpp
@@ -0,0 +1,75 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Hub.h"
+
+#include <Groundstation/Base/BoardStatus.h>
+#include <Groundstation/Base/Ports/Ethernet.h>
+#include <Groundstation/Base/Radio/Radio.h>
+#include <Groundstation/Common/Config/GeneralConfig.h>
+#include <Groundstation/Common/Ports/Serial.h>
+
+using namespace Groundstation;
+using namespace GroundstationBase;
+using namespace Boardcore;
+
+void Hub::dispatchOutgoingMsg(const mavlink_message_t& msg)
+{
+    BoardStatus* status = ModuleManager::getInstance().get<BoardStatus>();
+
+    bool send_ok = false;
+
+    if (status->isMainRadioPresent() && msg.sysid == MAV_SYSID_MAIN)
+    {
+        RadioMain* radio = ModuleManager::getInstance().get<RadioMain>();
+        send_ok |= radio->sendMsg(msg);
+    }
+
+    if (status->isPayloadRadioPresent() && msg.sysid == MAV_SYSID_PAYLOAD)
+    {
+        RadioPayload* radio = ModuleManager::getInstance().get<RadioPayload>();
+        send_ok |= radio->sendMsg(msg);
+    }
+
+    (void)send_ok;
+
+    // If both of the sends went wrong, just send a nack
+    // This doesn't work well with multiple GS on the same ethernet network
+    // if (!send_ok)
+    // {
+    //     sendNack(msg);
+    // }
+}
+
+void Hub::dispatchIncomingMsg(const mavlink_message_t& msg)
+{
+    BoardStatus* status = ModuleManager::getInstance().get<BoardStatus>();
+
+    Serial* serial = ModuleManager::getInstance().get<Serial>();
+    serial->sendMsg(msg);
+
+    if (status->isEthernetPresent())
+    {
+        Ethernet* ethernet = ModuleManager::getInstance().get<Ethernet>();
+        ethernet->sendMsg(msg);
+    }
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Hub.h b/src/boards/Groundstation/Base/Hub.h
new file mode 100644
index 0000000000000000000000000000000000000000..c22a7339f62554cc8fc91ae8707fd58f5cb3da05
--- /dev/null
+++ b/src/boards/Groundstation/Base/Hub.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Groundstation/Common/HubBase.h>
+#include <common/Mavlink.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace GroundstationBase
+{
+
+/**
+ * @brief Central hub connecting all outgoing and ingoing modules.
+ */
+class Hub : public Groundstation::HubBase
+{
+public:
+    Hub() {}
+
+    /**
+     * @brief Dispatch to the correct interface and outgoing packet (gs ->
+     * rocket).
+     */
+    void dispatchOutgoingMsg(const mavlink_message_t& msg) override;
+
+    /**
+     * @brief Dispatch to the correct interface and incoming packet (rocket ->
+     * gs).
+     */
+    void dispatchIncomingMsg(const mavlink_message_t& msg) override;
+};
+
+}  // namespace GroundstationBase
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Ports/Ethernet.cpp b/src/boards/Groundstation/Base/Ports/Ethernet.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..875c083cfee7d3869e1c0ede395c2ccda207b131
--- /dev/null
+++ b/src/boards/Groundstation/Base/Ports/Ethernet.cpp
@@ -0,0 +1,61 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Ethernet.h"
+
+#include <Groundstation/Base/BoardStatus.h>
+#include <Groundstation/Base/Buses.h>
+#include <interfaces-impl/hwmapping.h>
+
+using namespace Groundstation;
+using namespace GroundstationBase;
+using namespace Boardcore;
+using namespace miosix;
+
+void __attribute__((used)) MIOSIX_ETHERNET_IRQ()
+{
+    ModuleManager::getInstance().get<Ethernet>()->handleINTn();
+}
+
+bool Ethernet::start()
+{
+    std::unique_ptr<Wiz5500> wiz5500 = std::make_unique<Wiz5500>(
+        ModuleManager::getInstance().get<Buses>()->ethernet_bus,
+        ethernet::cs::getPin(), ethernet::intr::getPin(),
+        SPI::ClockDivider::DIV_64);
+
+    // First check if the device is even connected
+    bool present = wiz5500->checkVersion();
+
+    ModuleManager::getInstance().get<BoardStatus>()->setEthernetPresent(
+        present);
+
+    if (present)
+    {
+        if (!EthernetBase::start(std::move(wiz5500)))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Ports/Ethernet.h b/src/boards/Groundstation/Base/Ports/Ethernet.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a8283b1915e0600dff19e94d191038b88d9274a
--- /dev/null
+++ b/src/boards/Groundstation/Base/Ports/Ethernet.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Groundstation/Common/Ports/EthernetBase.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace GroundstationBase
+{
+
+class Ethernet : public Groundstation::EthernetBase, public Boardcore::Module
+{
+public:
+    [[nodiscard]] bool start();
+};
+
+}  // namespace GroundstationBase
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Radio/Radio.cpp b/src/boards/Groundstation/Base/Radio/Radio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a8bf2456f544496a8c5c9608fb131fe850fbd0bd
--- /dev/null
+++ b/src/boards/Groundstation/Base/Radio/Radio.cpp
@@ -0,0 +1,150 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Radio.h"
+
+#include <Groundstation/Base/BoardStatus.h>
+#include <Groundstation/Base/Buses.h>
+#include <Groundstation/Base/Hub.h>
+#include <Groundstation/Common/Ports/Serial.h>
+#include <radio/SX1278/SX1278Frontends.h>
+
+using namespace Groundstation;
+using namespace GroundstationBase;
+using namespace Boardcore;
+using namespace miosix;
+
+void __attribute__((used)) MIOSIX_RADIO1_DIO0_IRQ()
+{
+    ModuleManager::getInstance().get<RadioMain>()->handleDioIRQ();
+}
+
+void __attribute__((used)) MIOSIX_RADIO1_DIO1_IRQ()
+{
+    ModuleManager::getInstance().get<RadioMain>()->handleDioIRQ();
+}
+
+void __attribute__((used)) MIOSIX_RADIO1_DIO3_IRQ()
+{
+    ModuleManager::getInstance().get<RadioMain>()->handleDioIRQ();
+}
+
+void __attribute__((used)) MIOSIX_RADIO2_DIO0_IRQ()
+{
+    ModuleManager::getInstance().get<RadioPayload>()->handleDioIRQ();
+}
+
+void __attribute__((used)) MIOSIX_RADIO2_DIO1_IRQ()
+{
+    ModuleManager::getInstance().get<RadioPayload>()->handleDioIRQ();
+}
+
+void __attribute__((used)) MIOSIX_RADIO2_DIO3_IRQ()
+{
+    ModuleManager::getInstance().get<RadioPayload>()->handleDioIRQ();
+}
+
+bool RadioMain::start()
+{
+#ifdef SKYWARD_GS_MAIN_USE_BACKUP_RF
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<EbyteFrontend>(radio1::txen::getPin(),
+                                        radio1::rxen::getPin());
+#else
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<Skyward433Frontend>();
+#endif
+
+    std::unique_ptr<Boardcore::SX1278Fsk> sx1278 =
+        std::make_unique<Boardcore::SX1278Fsk>(
+            ModuleManager::getInstance().get<Buses>()->radio1_bus,
+            radio1::cs::getPin(), radio1::dio0::getPin(),
+            radio1::dio1::getPin(), radio1::dio3::getPin(),
+            SPI::ClockDivider::DIV_64, std::move(frontend));
+
+    // First check if the device is even connected
+    bool present = sx1278->checkVersion();
+
+    ModuleManager::getInstance().get<BoardStatus>()->setMainRadioPresent(
+        present);
+
+    if (present)
+    {
+        // Configure the radio
+        if (sx1278->configure(Common::MAIN_RADIO_CONFIG) !=
+            SX1278Fsk::Error::NONE)
+        {
+            return false;
+        }
+
+        // Initialize if only if present
+        if (!RadioBase::start(std::move(sx1278)))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool RadioPayload::start()
+{
+#ifdef SKYWARD_GS_PAYLOAD_USE_BACKUP_RF
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<EbyteFrontend>(radio2::txen::getPin(),
+                                        radio2::rxen::getPin());
+#else
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<Skyward433Frontend>();
+#endif
+
+    std::unique_ptr<Boardcore::SX1278Fsk> sx1278 =
+        std::make_unique<Boardcore::SX1278Fsk>(
+            ModuleManager::getInstance().get<Buses>()->radio2_bus,
+            radio2::cs::getPin(), radio2::dio0::getPin(),
+            radio2::dio1::getPin(), radio2::dio3::getPin(),
+            SPI::ClockDivider::DIV_64, std::move(frontend));
+
+    // First check if the device is even connected
+    bool present = sx1278->checkVersion();
+
+    ModuleManager::getInstance().get<BoardStatus>()->setPayloadRadioPresent(
+        present);
+
+    if (present)
+    {
+        // Configure the radio
+        if (sx1278->configure(Common::PAYLOAD_RADIO_CONFIG) !=
+            SX1278Fsk::Error::NONE)
+        {
+            return false;
+        }
+
+        // Initialize if only if present
+        if (!RadioBase::start(std::move(sx1278)))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Base/Radio/Radio.h b/src/boards/Groundstation/Base/Radio/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..e09d207c9166a97403167b204dc25681b059cc8e
--- /dev/null
+++ b/src/boards/Groundstation/Base/Radio/Radio.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Groundstation/Common/Radio/RadioBase.h>
+
+namespace GroundstationBase
+{
+
+class RadioMain : public Groundstation::RadioBase, public Boardcore::Module
+{
+public:
+    [[nodiscard]] bool start();
+};
+
+class RadioPayload : public Groundstation::RadioBase, public Boardcore::Module
+{
+public:
+    [[nodiscard]] bool start();
+};
+
+}  // namespace GroundstationBase
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Config/EthernetConfig.h b/src/boards/Groundstation/Common/Config/EthernetConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..ddb9872a061c453c240f1d61c6b77cb189b333ea
--- /dev/null
+++ b/src/boards/Groundstation/Common/Config/EthernetConfig.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <drivers/WIZ5500/WIZ5500.h>
+
+#include <cstdint>
+
+namespace Groundstation
+{
+
+constexpr uint16_t RECV_PORT = 42070;
+constexpr uint16_t SEND_PORT = 42069;
+
+constexpr Boardcore::WizMac MAC_BASE = {0x69, 0x69, 0x69, 0x69, 0, 0};
+constexpr Boardcore::WizIp IP_BASE   = {192, 168, 1, 0};
+constexpr Boardcore::WizIp GATEWAY   = {192, 168, 1, 1};
+constexpr Boardcore::WizIp SUBNET    = {0, 0, 0, 0};
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Config/GeneralConfig.h b/src/boards/Groundstation/Common/Config/GeneralConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7c8b8d3fe845fc9ad13a9ec2a3226f201273bd6
--- /dev/null
+++ b/src/boards/Groundstation/Common/Config/GeneralConfig.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace Groundstation
+{
+
+constexpr uint8_t GS_COMPONENT_ID = 1;
+constexpr uint8_t GS_SYSTEM_ID    = 1;
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Config/RadioConfig.h b/src/boards/Groundstation/Common/Config/RadioConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..c32abe0d514e7145a4f19e319d123f450ba7b25d
--- /dev/null
+++ b/src/boards/Groundstation/Common/Config/RadioConfig.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+// Uncomment the following line to enable backup RF for main
+// #define SKYWARD_GS_MAIN_USE_BACKUP_RF
+// Uncomment the following line to enable backup RF for payload
+// #define SKYWARD_GS_PAYLOAD_USE_BACKUP_RF
+
+namespace Groundstation
+{
+
+constexpr size_t MAV_OUT_QUEUE_SIZE         = 10;
+constexpr size_t MAV_PENDING_OUT_QUEUE_SIZE = 10;
+constexpr uint16_t MAV_SLEEP_AFTER_SEND     = 5;
+constexpr size_t MAV_OUT_BUFFER_MAX_AGE     = 10;
+
+/// @brief Every how many ms force the flush of the send queue.
+constexpr unsigned int AUTOMATIC_FLUSH_PERIOD = 250;
+/// @brief After how many ms stop waiting for the other side to send commands.
+constexpr long long AUTOMATIC_FLUSH_DELAY = 2000;
+
+/// @brief Period of the radio status telemetry.
+constexpr unsigned int RADIO_STATUS_PERIOD = 250;
+/// @brief Size in ms of the radio moving bitrate window size.
+constexpr size_t RADIO_BITRATE_WINDOW_SIZE = 1000;
+
+}  // namespace Groundstation
diff --git a/src/boards/Groundstation/Common/HubBase.cpp b/src/boards/Groundstation/Common/HubBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b767dae7335db14cf2cfbd5be481971b7fdd77d
--- /dev/null
+++ b/src/boards/Groundstation/Common/HubBase.cpp
@@ -0,0 +1,36 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "HubBase.h"
+
+#include <Groundstation/Common/Config/GeneralConfig.h>
+
+using namespace Groundstation;
+
+void HubBase::sendNack(const mavlink_message_t& msg)
+{
+    mavlink_message_t nack_msg;
+    mavlink_msg_nack_tm_pack(GS_SYSTEM_ID, GS_COMPONENT_ID, &nack_msg,
+                             msg.msgid, msg.seq);
+
+    dispatchIncomingMsg(nack_msg);
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/HubBase.h b/src/boards/Groundstation/Common/HubBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7caa7ed04d3f593a0b148e279b6a72f608e13f8
--- /dev/null
+++ b/src/boards/Groundstation/Common/HubBase.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <common/Mavlink.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Groundstation
+{
+
+/**
+ * @brief Central hub connecting all outgoing and ingoing modules.
+ */
+class HubBase : public Boardcore::Module
+{
+public:
+    /**
+     * @brief Dispatch to the correct interface and outgoing packet (gs ->
+     * rocket).
+     */
+    virtual void dispatchOutgoingMsg(const mavlink_message_t& msg) = 0;
+
+    /**
+     * @brief Dispatch to the correct interface and incoming packet (rocket ->
+     * gs).
+     */
+    virtual void dispatchIncomingMsg(const mavlink_message_t& msg) = 0;
+
+protected:
+    /**
+     * @brief Used internally to signal to the gs computer that something went
+     * wrong, and the packet could not be delivered.
+     */
+    void sendNack(const mavlink_message_t& msg);
+};
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Ports/EthernetBase.cpp b/src/boards/Groundstation/Common/Ports/EthernetBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f6c627b234ded251bbd0d6f44bc914e269c69063
--- /dev/null
+++ b/src/boards/Groundstation/Common/Ports/EthernetBase.cpp
@@ -0,0 +1,124 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "EthernetBase.h"
+
+#include <Groundstation/Common/Config/EthernetConfig.h>
+#include <Groundstation/Common/HubBase.h>
+
+#include <random>
+
+using namespace Groundstation;
+using namespace Boardcore;
+using namespace miosix;
+
+WizIp Groundstation::genNewRandomIp()
+{
+    WizIp ip = IP_BASE;
+    ip.d     = (rand() % 253) + 1;  // Generate in range 1-254
+
+    return ip;
+}
+
+WizMac Groundstation::genNewRandomMac()
+{
+    WizMac mac = MAC_BASE;
+    mac.e      = (rand() % 253) + 1;  // Generate in range 1-254
+    mac.f      = (rand() % 253) + 1;  // Generate in range 1-254
+
+    return mac;
+}
+
+void EthernetBase::handleINTn()
+{
+    if (wiz5500)
+    {
+        wiz5500->handleINTn();
+    }
+}
+
+void EthernetBase::sendMsg(const mavlink_message_t& msg)
+{
+    if (mav_driver && mav_driver->isStarted())
+    {
+        mav_driver->enqueueMsg(msg);
+    }
+}
+
+Boardcore::Wiz5500::PhyState EthernetBase::getState()
+{
+    return wiz5500->getPhyState();
+}
+
+bool EthernetBase::start(std::unique_ptr<Boardcore::Wiz5500> wiz5500)
+{
+    this->wiz5500 = std::move(wiz5500);
+
+    // Reset the device
+    this->wiz5500->reset();
+
+    // Setup ip and other stuff
+    this->wiz5500->setSubnetMask(SUBNET);
+    this->wiz5500->setGatewayIp(GATEWAY);
+    this->wiz5500->setSourceIp(genNewRandomIp());
+    this->wiz5500->setSourceMac(genNewRandomMac());
+
+    this->wiz5500->setOnIpConflict(
+        [this]() { this->wiz5500->setSourceIp(genNewRandomIp()); });
+
+    // Ok now open the UDP socket
+    if (!this->wiz5500->openUdp(0, RECV_PORT, {255, 255, 255, 255}, SEND_PORT,
+                                500))
+    {
+        return false;
+    }
+
+    auto mav_handler = [this](EthernetMavDriver* channel,
+                              const mavlink_message_t& msg) { handleMsg(msg); };
+
+    mav_driver = std::make_unique<EthernetMavDriver>(this, mav_handler, 0, 10);
+
+    if (!mav_driver->start())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void EthernetBase::handleMsg(const mavlink_message_t& msg)
+{
+    // Dispatch the message through the hub.
+    ModuleManager::getInstance().get<HubBase>()->dispatchOutgoingMsg(msg);
+}
+
+ssize_t EthernetBase::receive(uint8_t* pkt, size_t max_len)
+{
+    WizIp dst_ip;
+    uint16_t dst_port;
+    return wiz5500->recvfrom(0, pkt, max_len, dst_ip, dst_port);
+}
+
+bool EthernetBase::send(uint8_t* pkt, size_t len)
+{
+    return wiz5500->send(0, pkt, len, 100);
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Ports/EthernetBase.h b/src/boards/Groundstation/Common/Ports/EthernetBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd377b5744d86893fbaf592e9bf652cf8440fd34
--- /dev/null
+++ b/src/boards/Groundstation/Common/Ports/EthernetBase.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <ActiveObject.h>
+#include <common/Mavlink.h>
+#include <drivers/WIZ5500/WIZ5500.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+
+#include <memory>
+
+namespace Groundstation
+{
+
+Boardcore::WizIp genNewRandomIp();
+Boardcore::WizMac genNewRandomMac();
+
+using EthernetMavDriver =
+    Boardcore::MavlinkDriver<1024, 10, MAVLINK_MAX_DIALECT_PAYLOAD_SIZE>;
+
+class EthernetBase : public Boardcore::Transceiver
+{
+public:
+    EthernetBase() {}
+
+    void handleINTn();
+
+    void sendMsg(const mavlink_message_t& msg);
+
+    Boardcore::Wiz5500::PhyState getState();
+
+protected:
+    bool start(std::unique_ptr<Boardcore::Wiz5500> wiz5500);
+
+private:
+    /**
+     * @brief Called internally when a message is received.
+     */
+    void handleMsg(const mavlink_message_t& msg);
+
+    ssize_t receive(uint8_t* pkt, size_t max_len) override;
+
+    bool send(uint8_t* pkt, size_t len) override;
+
+    bool started = false;
+    std::unique_ptr<Boardcore::Wiz5500> wiz5500;
+    std::unique_ptr<EthernetMavDriver> mav_driver;
+};
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Ports/Serial.cpp b/src/boards/Groundstation/Common/Ports/Serial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..45f4b7712016451e394c9ce4284e5bcb8c6fb4ce
--- /dev/null
+++ b/src/boards/Groundstation/Common/Ports/Serial.cpp
@@ -0,0 +1,71 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Serial.h"
+
+#include <Groundstation/Common/HubBase.h>
+#include <filesystem/console/console_device.h>
+
+using namespace miosix;
+using namespace Groundstation;
+using namespace Boardcore;
+
+bool Serial::start()
+{
+    auto mav_handler = [this](SerialMavDriver* channel,
+                              const mavlink_message_t& msg) { handleMsg(msg); };
+
+    mav_driver = std::make_unique<SerialMavDriver>(this, mav_handler, 0, 10);
+
+    if (!mav_driver->start())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void Serial::sendMsg(const mavlink_message_t& msg)
+{
+    if (mav_driver && mav_driver->isStarted())
+    {
+        mav_driver->enqueueMsg(msg);
+    }
+}
+
+void Serial::handleMsg(const mavlink_message_t& msg)
+{
+    // Dispatch the message through the hub.
+    ModuleManager::getInstance().get<HubBase>()->dispatchOutgoingMsg(msg);
+}
+
+ssize_t Serial::receive(uint8_t* pkt, size_t max_len)
+{
+    auto serial = miosix::DefaultConsole::instance().get();
+    return serial->readBlock(pkt, max_len, 0);
+}
+
+bool Serial::send(uint8_t* pkt, size_t len)
+{
+    auto serial = miosix::DefaultConsole::instance().get();
+    return serial->writeBlock(pkt, len, 0) != static_cast<ssize_t>(len);
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Ports/Serial.h b/src/boards/Groundstation/Common/Ports/Serial.h
new file mode 100644
index 0000000000000000000000000000000000000000..ea39b639428dd17b623c6e0899ac2cd6c5e7e630
--- /dev/null
+++ b/src/boards/Groundstation/Common/Ports/Serial.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <ActiveObject.h>
+#include <common/Mavlink.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+
+#include <memory>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Groundstation
+{
+
+using SerialMavDriver =
+    Boardcore::MavlinkDriver<1024, 10, MAVLINK_MAX_DIALECT_PAYLOAD_SIZE>;
+
+/**
+ * @brief Class responsible for UART communication.
+ */
+class Serial : public Boardcore::Module, public Boardcore::Transceiver
+{
+public:
+    Serial() {}
+
+    /**
+     * @brief Initialize the serial module.
+     */
+    [[nodiscard]] bool start();
+
+    /**
+     * @brief Send a mavlink message through this port.
+     */
+    void sendMsg(const mavlink_message_t& msg);
+
+private:
+    /**
+     * @brief Called internally when a message is received.
+     */
+    void handleMsg(const mavlink_message_t& msg);
+
+    ssize_t receive(uint8_t* pkt, size_t max_len) override;
+
+    bool send(uint8_t* pkt, size_t len) override;
+
+    miosix::FastMutex mutex;
+    std::unique_ptr<SerialMavDriver> mav_driver;
+};
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Radio/RadioBase.cpp b/src/boards/Groundstation/Common/Radio/RadioBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..22be5ff3b345e82f6ab3be4e18705317eec2b7c1
--- /dev/null
+++ b/src/boards/Groundstation/Common/Radio/RadioBase.cpp
@@ -0,0 +1,167 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "RadioBase.h"
+
+#include <Groundstation/Common/HubBase.h>
+
+#include <memory>
+
+using namespace miosix;
+using namespace Groundstation;
+using namespace Boardcore;
+
+bool RadioBase::sendMsg(const mavlink_message_t& msg)
+{
+    Lock<FastMutex> l(pending_msgs_mutex);
+    if (pending_msgs_count >= MAV_PENDING_OUT_QUEUE_SIZE)
+    {
+        return false;
+    }
+    else
+    {
+        pending_msgs[pending_msgs_count] = msg;
+        pending_msgs_count += 1;
+
+        return true;
+    }
+}
+
+void RadioBase::handleDioIRQ()
+{
+    if (started)
+    {
+        sx1278->handleDioIRQ();
+    }
+}
+
+RadioStats RadioBase::getStats()
+{
+    if (started)
+    {
+        auto mav_stats = mav_driver->getStatus();
+
+        return {.send_errors = mav_stats.nSendErrors,
+                .packet_rx_success_count =
+                    mav_stats.mavStats.packet_rx_success_count,
+                .packet_rx_drop_count = mav_stats.mavStats.packet_rx_drop_count,
+                .bits_rx_count        = bits_rx_count,
+                .bits_tx_count        = bits_tx_count,
+                .rx_rssi              = sx1278->getLastRxRssi(),
+                .rx_fei               = sx1278->getLastRxFei()};
+    }
+    else
+    {
+        return {0};
+    }
+}
+
+bool RadioBase::start(std::unique_ptr<SX1278Fsk> sx1278)
+{
+    this->sx1278 = std::move(sx1278);
+
+    auto mav_handler = [this](RadioMavDriver* channel,
+                              const mavlink_message_t& msg) { handleMsg(msg); };
+
+    mav_driver = std::make_unique<RadioMavDriver>(
+        this, mav_handler, Groundstation::MAV_SLEEP_AFTER_SEND,
+        Groundstation::MAV_OUT_BUFFER_MAX_AGE);
+
+    if (!mav_driver->start())
+    {
+        return false;
+    }
+
+    if (!ActiveObject::start())
+    {
+        return false;
+    }
+
+    started = true;
+    return true;
+}
+
+void RadioBase::run()
+{
+
+    while (!shouldStop())
+    {
+        miosix::Thread::sleep(AUTOMATIC_FLUSH_PERIOD);
+
+        // If enough time has passed, automatically flush.
+        if (miosix::getTick() > last_eot_packet_ts + AUTOMATIC_FLUSH_DELAY)
+        {
+            flush();
+        }
+    }
+}
+
+ssize_t RadioBase::receive(uint8_t* pkt, size_t max_len)
+{
+    ssize_t ret = sx1278->receive(pkt, max_len);
+    if (ret > 0)
+    {
+        bits_rx_count += ret * 8;
+    }
+
+    return ret;
+}
+
+bool RadioBase::send(uint8_t* pkt, size_t len)
+{
+    bool ret = sx1278->send(pkt, len);
+    if (ret)
+    {
+        bits_tx_count += len * 8;
+    }
+
+    return ret;
+}
+
+void RadioBase::handleMsg(const mavlink_message_t& msg)
+{
+    // Dispatch the message through the hub.
+    ModuleManager::getInstance().get<HubBase>()->dispatchIncomingMsg(msg);
+
+    if (isEndOfTransmissionPacket(msg))
+    {
+        last_eot_packet_ts = miosix::getTick();
+        flush();
+    }
+}
+
+void RadioBase::flush()
+{
+    Lock<FastMutex> l(pending_msgs_mutex);
+    for (size_t i = 0; i < pending_msgs_count; i++)
+    {
+        mav_driver->enqueueMsg(pending_msgs[i]);
+    }
+
+    pending_msgs_count = 0;
+}
+
+bool RadioBase::isEndOfTransmissionPacket(const mavlink_message_t& msg)
+{
+    return msg.msgid == MAVLINK_MSG_ID_ROCKET_FLIGHT_TM ||
+           msg.msgid == MAVLINK_MSG_ID_PAYLOAD_FLIGHT_TM;
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Common/Radio/RadioBase.h b/src/boards/Groundstation/Common/Radio/RadioBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..3067912106fb345926e74fc8c63039ae43592115
--- /dev/null
+++ b/src/boards/Groundstation/Common/Radio/RadioBase.h
@@ -0,0 +1,128 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <ActiveObject.h>
+#include <Groundstation/Common/Config/RadioConfig.h>
+#include <common/Mavlink.h>
+#include <common/Radio.h>
+#include <radio/MavlinkDriver/MavlinkDriver.h>
+#include <radio/SX1278/SX1278Fsk.h>
+
+#include <memory>
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace Groundstation
+{
+
+using RadioMavDriver =
+    Boardcore::MavlinkDriver<Boardcore::SX1278Fsk::MTU,
+                             Groundstation::MAV_OUT_QUEUE_SIZE,
+                             MAVLINK_MAX_DIALECT_PAYLOAD_SIZE>;
+
+/**
+ * @brief Statistics of the radio.
+ */
+struct RadioStats
+{
+    uint16_t send_errors;              //< Number of failed sends.
+    uint16_t packet_rx_success_count;  //< Number of received packets.
+    uint16_t packet_rx_drop_count;     //< Number of packet drops.
+    uint32_t bits_rx_count;            //< Number of bits received.
+    uint32_t bits_tx_count;            //< Number of bits sent.
+    float rx_rssi;                     //< RSSI in dBm of last received packet.
+    float rx_fei;  //< Frequency error index in Hz of last received packet.
+};
+
+/**
+ * @brief Base radio class, used to implement functionality independent of
+ * main/payload radios.
+ */
+class RadioBase : private Boardcore::ActiveObject, public Boardcore::Transceiver
+{
+public:
+    RadioBase() {}
+
+    /**
+     * @brief Send a mavlink message through this radio.
+     *
+     * @returns false when the queue is full.
+     */
+    bool sendMsg(const mavlink_message_t& msg);
+
+    /**
+     * @brief Handle generic DIO irq.
+     */
+    void handleDioIRQ();
+
+    /**
+     * @brief Retrieve current statistics metrics.
+     */
+    RadioStats getStats();
+
+protected:
+    /**
+     * @brief Initialize this radio module.
+     */
+    bool start(std::unique_ptr<Boardcore::SX1278Fsk> sx1278);
+
+private:
+    void run() override;
+
+    ssize_t receive(uint8_t* pkt, size_t max_len) override;
+
+    bool send(uint8_t* pkt, size_t len) override;
+
+    /**
+     * @brief Called internally when a message is received.
+     */
+    void handleMsg(const mavlink_message_t& msg);
+
+    /**
+     * @brief Flush all pending messages.
+     */
+    void flush();
+
+    /**
+     * @brief Check if a message signals an end of transmission
+     */
+    bool isEndOfTransmissionPacket(const mavlink_message_t& msg);
+
+    bool started = false;
+
+    miosix::FastMutex pending_msgs_mutex;
+    mavlink_message_t pending_msgs[Groundstation::MAV_PENDING_OUT_QUEUE_SIZE];
+    size_t pending_msgs_count = 0;
+
+    long long last_eot_packet_ts = 0;
+
+    uint32_t bits_rx_count = 0;
+    uint32_t bits_tx_count = 0;
+
+    // Objects are always destructed in reverse order, so keep them in this
+    // order
+    std::unique_ptr<Boardcore::SX1278Fsk> sx1278;
+    std::unique_ptr<RadioMavDriver> mav_driver;
+};
+
+}  // namespace Groundstation
\ No newline at end of file
diff --git a/src/boards/Groundstation/Nokia/Buses.h b/src/boards/Groundstation/Nokia/Buses.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a3f17a1865b311d626f89ceb74b858fe26db83a
--- /dev/null
+++ b/src/boards/Groundstation/Nokia/Buses.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <drivers/spi/SPIBus.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+#include "interfaces-impl/hwmapping.h"
+
+namespace GroundstationNokia
+{
+
+class Buses : public Boardcore::Module
+{
+public:
+    Boardcore::SPIBus radio_bus;
+
+    Buses() : radio_bus(SPI4) {}
+};
+
+}  // namespace GroundstationNokia
\ No newline at end of file
diff --git a/src/boards/Groundstation/Nokia/Hub.cpp b/src/boards/Groundstation/Nokia/Hub.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab06368e13450c6af99606ff8291560584f188f1
--- /dev/null
+++ b/src/boards/Groundstation/Nokia/Hub.cpp
@@ -0,0 +1,47 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Hub.h"
+
+#include <Groundstation/Common/Config/GeneralConfig.h>
+#include <Groundstation/Common/Ports/Serial.h>
+#include <Groundstation/Nokia/Radio/Radio.h>
+
+using namespace Groundstation;
+using namespace GroundstationNokia;
+using namespace Boardcore;
+
+void Hub::dispatchOutgoingMsg(const mavlink_message_t& msg)
+{
+    Radio* radio = ModuleManager::getInstance().get<Radio>();
+
+    if (!radio->sendMsg(msg))
+    {
+        sendNack(msg);
+    }
+}
+
+void Hub::dispatchIncomingMsg(const mavlink_message_t& msg)
+{
+    Serial* serial = ModuleManager::getInstance().get<Serial>();
+    serial->sendMsg(msg);
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Nokia/Hub.h b/src/boards/Groundstation/Nokia/Hub.h
new file mode 100644
index 0000000000000000000000000000000000000000..0351d7ab839d6b4c5e25faa51bfb8eebf0751cbf
--- /dev/null
+++ b/src/boards/Groundstation/Nokia/Hub.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Groundstation/Common/HubBase.h>
+#include <common/Mavlink.h>
+
+#include <utils/ModuleManager/ModuleManager.hpp>
+
+namespace GroundstationNokia
+{
+
+/**
+ * @brief Central hub connecting all outgoing and ingoing modules.
+ */
+class Hub : public Groundstation::HubBase
+{
+public:
+    Hub() {}
+
+    /**
+     * @brief Dispatch to the correct interface and outgoing packet (gs ->
+     * rocket).
+     */
+    void dispatchOutgoingMsg(const mavlink_message_t& msg) override;
+
+    /**
+     * @brief Dispatch to the correct interface and incoming packet (rocket ->
+     * gs).
+     */
+    void dispatchIncomingMsg(const mavlink_message_t& msg) override;
+};
+
+}  // namespace GroundstationNokia
\ No newline at end of file
diff --git a/src/boards/Groundstation/Nokia/Radio/Radio.cpp b/src/boards/Groundstation/Nokia/Radio/Radio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e27dfac357304bfcc07248e2bc505819a19b03c
--- /dev/null
+++ b/src/boards/Groundstation/Nokia/Radio/Radio.cpp
@@ -0,0 +1,94 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Radio.h"
+
+#include <Groundstation/Common/Ports/Serial.h>
+#include <Groundstation/Nokia/Buses.h>
+#include <radio/SX1278/SX1278Frontends.h>
+
+#include "interfaces-impl/hwmapping.h"
+
+using namespace Groundstation;
+using namespace GroundstationNokia;
+using namespace Boardcore;
+using namespace miosix;
+
+#define SX1278_DIO0_IRQ EXTI6_IRQHandlerImpl
+#define SX1278_DIO1_IRQ EXTI4_IRQHandlerImpl
+#define SX1278_DIO3_IRQ EXTI11_IRQHandlerImpl
+
+void __attribute__((used)) SX1278_DIO0_IRQ()
+{
+    ModuleManager::getInstance().get<Radio>()->handleDioIRQ();
+}
+
+void __attribute__((used)) SX1278_DIO1_IRQ()
+{
+    ModuleManager::getInstance().get<Radio>()->handleDioIRQ();
+}
+
+void __attribute__((used)) SX1278_DIO3_IRQ()
+{
+    ModuleManager::getInstance().get<Radio>()->handleDioIRQ();
+}
+
+bool Radio::start()
+{
+#ifdef SKYWARD_GS_MAIN_USE_BACKUP_RF
+#error "Backup RF not supported on nokia"
+#else
+    std::unique_ptr<SX1278::ISX1278Frontend> frontend =
+        std::make_unique<RA01Frontend>();
+#endif
+
+    std::unique_ptr<Boardcore::SX1278Fsk> sx1278 =
+        std::make_unique<Boardcore::SX1278Fsk>(
+            ModuleManager::getInstance()
+                .get<GroundstationNokia::Buses>()
+                ->radio_bus,
+            peripherals::ra01::pc13::cs::getPin(),
+            peripherals::ra01::pc13::dio0::getPin(),
+            peripherals::ra01::pc13::dio1::getPin(),
+            peripherals::ra01::pc13::dio3::getPin(), SPI::ClockDivider::DIV_64,
+            std::move(frontend));
+
+    // First check if the device is even connected
+    if (!sx1278->checkVersion())
+    {
+        return false;
+    }
+
+    // Configure the radio
+    if (sx1278->configure(Common::MAIN_RADIO_CONFIG) != SX1278Fsk::Error::NONE)
+    {
+        return false;
+    }
+
+    // Initialize if only if present
+    if (!RadioBase::start(std::move(sx1278)))
+    {
+        return false;
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/boards/Groundstation/Nokia/Radio/Radio.h b/src/boards/Groundstation/Nokia/Radio/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..950b8243a3ccbded6daa6e9e9d58bcc18ff6f2f7
--- /dev/null
+++ b/src/boards/Groundstation/Nokia/Radio/Radio.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <Groundstation/Common/Radio/RadioBase.h>
+
+namespace GroundstationNokia
+{
+
+class Radio : public Groundstation::RadioBase, public Boardcore::Module
+{
+public:
+    [[nodiscard]] bool start();
+};
+
+}  // namespace GroundstationNokia
\ No newline at end of file
diff --git a/src/boards/common/Radio.h b/src/boards/common/Radio.h
new file mode 100644
index 0000000000000000000000000000000000000000..efdfa4b891240c09733cb2fd0fef96239c898d35
--- /dev/null
+++ b/src/boards/common/Radio.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <radio/SX1278/SX1278Fsk.h>
+
+namespace Common
+{
+
+static const Boardcore::SX1278Fsk::Config MAIN_RADIO_CONFIG = {
+    .freq_rf    = 419000000,
+    .freq_dev   = 50000,
+    .bitrate    = 48000,
+    .rx_bw      = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+    .afc_bw     = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+    .ocp        = 120,
+    .power      = 13,
+    .shaping    = Boardcore::SX1278Fsk::Config::Shaping::GAUSSIAN_BT_1_0,
+    .dc_free    = Boardcore::SX1278Fsk::Config::DcFree::WHITENING,
+    .enable_crc = false};
+
+static const Boardcore::SX1278Fsk::Config PAYLOAD_RADIO_CONFIG = {
+    .freq_rf    = 868000000,
+    .freq_dev   = 50000,
+    .bitrate    = 48000,
+    .rx_bw      = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+    .afc_bw     = Boardcore::SX1278Fsk::Config::RxBw::HZ_125000,
+    .ocp        = 120,
+    .power      = 13,
+    .shaping    = Boardcore::SX1278Fsk::Config::Shaping::GAUSSIAN_BT_1_0,
+    .dc_free    = Boardcore::SX1278Fsk::Config::DcFree::WHITENING,
+    .enable_crc = false};
+
+}  // namespace Common
\ No newline at end of file
diff --git a/src/entrypoints/Groundstation/base-groundstation-entry.cpp b/src/entrypoints/Groundstation/base-groundstation-entry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..99b7374fcdf25f5f52de7378682f9f132abf8f4e
--- /dev/null
+++ b/src/entrypoints/Groundstation/base-groundstation-entry.cpp
@@ -0,0 +1,143 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <Groundstation/Base/BoardStatus.h>
+#include <Groundstation/Base/Buses.h>
+#include <Groundstation/Base/Hub.h>
+#include <Groundstation/Base/Ports/Ethernet.h>
+#include <Groundstation/Base/Radio/Radio.h>
+#include <Groundstation/Common/Ports/Serial.h>
+#include <miosix.h>
+
+using namespace Groundstation;
+using namespace GroundstationBase;
+using namespace Boardcore;
+using namespace miosix;
+
+void idleLoop()
+{
+    while (1)
+    {
+        Thread::wait();
+    }
+}
+
+void errorLoop()
+{
+    while (1)
+    {
+        led1On();
+        Thread::sleep(100);
+        led1Off();
+        Thread::sleep(100);
+    }
+}
+
+int main()
+{
+    ledOff();
+
+    Hub *hub                    = new Hub();
+    Buses *buses                = new Buses();
+    Serial *serial              = new Serial();
+    Ethernet *ethernet          = new Ethernet();
+    RadioMain *radio_main       = new RadioMain();
+    RadioPayload *radio_payload = new RadioPayload();
+    BoardStatus *board_status   = new BoardStatus();
+
+    ModuleManager &modules = ModuleManager::getInstance();
+
+    bool ok = true;
+
+    ok &= modules.insert<HubBase>(hub);
+    ok &= modules.insert(buses);
+    ok &= modules.insert(serial);
+    ok &= modules.insert(ethernet);
+    ok &= modules.insert(radio_main);
+    ok &= modules.insert(radio_payload);
+    ok &= modules.insert(board_status);
+
+    // If insertion failed, stop right here
+    if (!ok)
+    {
+        printf("[error] Failed to insert all modules!\n");
+        errorLoop();
+    }
+
+    // Ok now start them
+
+    ok &= serial->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start serial!\n");
+    }
+
+    ok &= ethernet->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start ethernet!\n");
+    }
+
+    ok &= radio_main->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start main radio!\n");
+    }
+
+    ok &= radio_payload->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start payload radio!\n");
+    }
+
+    ok &= board_status->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start board status!\n");
+    }
+
+    if (board_status->isMainRadioPresent())
+    {
+        printf("Main radio detected!\n");
+        led2On();
+    }
+
+    if (board_status->isPayloadRadioPresent())
+    {
+        printf("Payload radio detected!\n");
+        led3On();
+    }
+
+    if (board_status->isEthernetPresent())
+    {
+        printf("Ethernet detected!\n");
+    }
+
+    if (!ok)
+    {
+        errorLoop();
+    }
+
+    led1On();
+    idleLoop();
+    return 0;
+}
\ No newline at end of file
diff --git a/src/entrypoints/Groundstation/nokia-groundstation-entry.cpp b/src/entrypoints/Groundstation/nokia-groundstation-entry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..da685704b93e00882cb953e9e0a398d95714abd6
--- /dev/null
+++ b/src/entrypoints/Groundstation/nokia-groundstation-entry.cpp
@@ -0,0 +1,88 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <Groundstation/Common/Ports/Serial.h>
+#include <Groundstation/Nokia/Buses.h>
+#include <Groundstation/Nokia/Hub.h>
+#include <Groundstation/Nokia/Radio/Radio.h>
+#include <miosix.h>
+
+using namespace Groundstation;
+using namespace GroundstationNokia;
+using namespace Boardcore;
+using namespace miosix;
+
+void idleLoop()
+{
+    while (1)
+    {
+        Thread::wait();
+    }
+}
+
+int main()
+{
+    ledOff();
+
+    Hub *hub       = new Hub();
+    Buses *buses   = new Buses();
+    Radio *radio   = new Radio();
+    Serial *serial = new Serial();
+
+    ModuleManager &modules = ModuleManager::getInstance();
+
+    bool ok = true;
+
+    ok &= modules.insert<HubBase>(hub);
+    ok &= modules.insert(buses);
+    ok &= modules.insert(serial);
+    ok &= modules.insert(radio);
+
+    // If insertion failed, stop right here
+    if (!ok)
+    {
+        printf("[error] Failed to insert all modules!\n");
+        idleLoop();
+    }
+
+    // Ok now start them
+
+    ok &= serial->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start serial!\n");
+    }
+
+    ok &= radio->start();
+    if (!ok)
+    {
+        printf("[error] Failed to start radio!\n");
+    }
+
+    if (ok)
+    {
+        printf("Init complete!\n");
+    }
+
+    idleLoop();
+    return 0;
+}
\ No newline at end of file