diff --git a/.clang-format b/.clang-format
index 673f71de96f7438eaa3691f49126bd05c6a52af9..7abb256cdd1d5fc9f2a79f19dfa70d6f55c6fe63 100644
--- a/.clang-format
+++ b/.clang-format
@@ -3,12 +3,13 @@ BasedOnStyle: LLVM
 ColumnLimit: 120
 IndentWidth: 4
 TabWidth: 4
-UseTab: ForIndentation
+#UseTab: ForIndentation
 AlignEscapedNewlines: DontAlign
 AllowShortFunctionsOnASingleLine: Inline
 AlwaysBreakTemplateDeclarations: Yes
 BreakBeforeBinaryOperators: NonAssignment
 BreakBeforeBraces: Custom
+NamespaceIndentation: All
 BraceWrapping:
-  AfterFunction: true
+  AfterFunction: false
 ...
diff --git a/host/host_central_mast/include/MessageType.h b/host/host_central_mast/include/MessageType.h
new file mode 100644
index 0000000000000000000000000000000000000000..787afad32ef6fba58a5e7519ca06dff97b827988
--- /dev/null
+++ b/host/host_central_mast/include/MessageType.h
@@ -0,0 +1,16 @@
+//
+// Created by zoe on 1/24/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_MESSAGETYPE_H
+#define HOST_CENTRAL_MAST_MESSAGETYPE_H
+
+enum MessageType { dataAck, hostChange };
+
+typedef struct response {
+    MessageType type;
+    uint8_t mac[6];
+    long time; // Clang-Tidy: Narrowing conversion from 'unsigned long' to signed type 'long' is implementation-defined
+               // (see rtc in main.cpp)
+} response;
+#endif // HOST_CENTRAL_MAST_MESSAGETYPE_H
diff --git a/host/host_central_mast/lib/NTPManager/NTPException.hpp b/host/host_central_mast/lib/NTPManager/NTPException.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c64c55c49ffa888f61a0364aeb5d1868eb6fcceb
--- /dev/null
+++ b/host/host_central_mast/lib/NTPManager/NTPException.hpp
@@ -0,0 +1,23 @@
+//
+// Created by zoe on 12/19/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_NTPEXCEPTION_HPP
+#define HOST_CENTRAL_MAST_NTPEXCEPTION_HPP
+
+#include <exception>
+#include <string>
+
+class NTPException : public std::exception {
+public:
+    explicit NTPException(const std::string &message) : message(message) {}
+
+    const char *what() const noexcept override {
+        return message.c_str();
+    }
+
+private:
+    std::string message;
+};
+
+#endif //HOST_CENTRAL_MAST_NTPEXCEPTION_HPP
diff --git a/host/host_central_mast/lib/NTPManager/NTPManager.cpp b/host/host_central_mast/lib/NTPManager/NTPManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f99ff9e7c17706d16cb9ab1a33debc07f283df1d
--- /dev/null
+++ b/host/host_central_mast/lib/NTPManager/NTPManager.cpp
@@ -0,0 +1,88 @@
+//
+// Created by zoe on 12/19/22.
+//
+
+#include "NTPManager.h"
+
+NTPManager::NTPManager(TinyGsmSim7000 &modem) : modem(modem) {}
+
+void NTPManager::syncNTP(const std::string &ntpServer, const std::string &backupNtpServer) {
+    // create list of ntp servers
+    std::vector<std::string> ntpServers;
+    ntpServers.push_back(ntpServer);
+    ntpServers.push_back(backupNtpServer);
+    tryNtpServerSync(backupNtpServer, ntpServers);
+}
+void NTPManager::tryNtpServerSync(const std::string &backupNtpServer,
+                                   std::vector<std::string> &ntpServers) { // try to sync ntp with each server
+    for (const auto &server : ntpServers) {
+        try {
+            synchronizeNTPWithModem(server);
+            return;
+        } catch (NTPException &e) {
+            if (server == backupNtpServer) {
+                esp_log_write(ESP_LOG_ERROR, TAG_TIMEMANAGER, "Failed to sync NTP with %s (%s)\n", server.c_str(),
+                              e.what());
+            }
+            esp_log_write(ESP_LOG_ERROR, TAG_TIMEMANAGER, "Failed to sync NTP with %s (%s). Retrying with backup...\n",
+                          server.c_str(), e.what());
+        }
+    }
+}
+void NTPManager::synchronizeNTPWithModem(const std::string &ntpServer) {
+    esp_log_write(ESP_LOG_DEBUG, TAG_TIMEMANAGER, "NTP Server Syncing...\n");
+
+    long error = modem.NTPServerSync(ntpServer.c_str(), timeZone);
+    /**
+      According to TinGsmNTP.tpp, the error codes are:
+      case 1: "Network time synchronization is successful";
+      case 61: "Network error";
+      case 62: "DNS resolution error";
+      case 63: "Connection error";
+      case 64: "Service response error";
+      case 65: "Service response timeout";
+      default: "Unknown error: " + String(error);
+     */
+    // 255 is ok in our case (https://github.com/vshymanskyy/TinyGSM/pull/652)
+    if (error != 1 && error != 255) {
+        throw NTPException(modem.ShowNTPError(error).c_str());
+    } else {
+        esp_log_write(ESP_LOG_DEBUG, TAG_TIMEMANAGER, "NTP: Network time synchronization is successful\n");
+    }
+}
+
+time_t NTPManager::timeToUnixEpochSeconds(const std::string &time, const std::string &format) {
+    //	22/10/27,10:16:20+00
+    struct tm tm {};
+    time_t dateInEpoch = 0;
+
+    // For some unknown reason, we have to use the C functions here instead of the std:: ones.
+    if (strptime(time.c_str(), format.c_str(), &tm)) {
+        time_t curTime;
+        struct tm *timeinfo;
+
+        timeinfo = localtime(&curTime);
+
+        timeinfo->tm_year = tm.tm_year;
+        timeinfo->tm_mon = tm.tm_mon;
+        timeinfo->tm_mday = tm.tm_mday;
+        timeinfo->tm_hour = tm.tm_hour;
+        timeinfo->tm_min = tm.tm_min;
+        timeinfo->tm_sec = tm.tm_sec;
+        timeinfo->tm_isdst = -1;
+
+        dateInEpoch = mktime(timeinfo);
+    } else {
+        throw StringToTimeConversionException("Failed to parse date");
+    }
+    return dateInEpoch;
+}
+
+void NTPManager::writeModemTimeToRTC() {
+    auto gsmDateTimeString = modem.getGSMDateTime(DATE_FULL);
+    esp_log_write(ESP_LOG_DEBUG, TAG_TIMEMANAGER, "GSM DateTime: %s\n", gsmDateTimeString.c_str());
+    time_t time = timeToUnixEpochSeconds(gsmDateTimeString.c_str());
+
+    rtc.setTime(time);
+    esp_log_write(ESP_LOG_INFO, TAG_TIMEMANAGER, "Time set to EPOCH: %s\n", String(rtc.getEpoch()).c_str());
+}
diff --git a/host/host_central_mast/lib/NTPManager/NTPManager.h b/host/host_central_mast/lib/NTPManager/NTPManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..37a2a192bd13c63fec4e1627e962dca6d07067ce
--- /dev/null
+++ b/host/host_central_mast/lib/NTPManager/NTPManager.h
@@ -0,0 +1,42 @@
+//
+// Created by zoe on 12/19/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_NTPMANAGER_H
+#define HOST_CENTRAL_MAST_NTPMANAGER_H
+
+#define TINY_GSM_MODEM_SIM7000
+
+#include "Definitions.h"
+#include "ESP32Time.h"
+#include "NTPException.hpp"
+#include "StringToTimeConversionException.hpp"
+#include <TinyGsmClientSIM7000.h>
+#include <iomanip>
+#include <string>
+
+class NTPManager {
+  public:
+    explicit NTPManager(TinyGsmSim7000 &modem);
+
+  private:
+    int timeZone = 0;
+
+    // I would like this to be a const reference but the functions used by the modem are not const
+    TinyGsmSim7000 &modem;
+
+    // convert time to unix epoch seconds
+    static time_t timeToUnixEpochSeconds(const std::string &time, const std::string &format = "%y/%m/%d,%T+00");
+
+    void synchronizeNTPWithModem(const std::string &ntpServer);
+    void tryNtpServerSync(const std::string &backupNtpServer, std::vector<std::string> &ntpServers);
+
+  public:
+    // sync ntp
+    void syncNTP(const std::string &ntpServer, const std::string &backupNtpServer);
+
+    // write modem time to rtc
+    void writeModemTimeToRTC();
+};
+
+#endif // HOST_CENTRAL_MAST_NTPMANAGER_H
\ No newline at end of file
diff --git a/host/host_central_mast/lib/NTPManager/StringToTimeConversionException.hpp b/host/host_central_mast/lib/NTPManager/StringToTimeConversionException.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f93acc337a2333fae8e3d8c493f7cc2b069d0a5c
--- /dev/null
+++ b/host/host_central_mast/lib/NTPManager/StringToTimeConversionException.hpp
@@ -0,0 +1,26 @@
+//
+// Created by zoe on 12/19/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_STRINGTOTIMECONVERSIONEXCEPTION_HPP
+#define HOST_CENTRAL_MAST_STRINGTOTIMECONVERSIONEXCEPTION_HPP
+
+#include "NTPManager.h"
+#include <exception>
+#include <string>
+
+class StringToTimeConversionException : public std::exception {
+public:
+    explicit StringToTimeConversionException(const std::string &message) : message(message) {}
+
+    const char *what() const noexcept override {
+        esp_log_write(ESP_LOG_ERROR, "NTPManager", "Error converting time to epoch: %s",
+                      message.c_str());
+        return message.c_str();
+    }
+
+private:
+    std::string message;
+};
+
+#endif //HOST_CENTRAL_MAST_STRINGTOTIMECONVERSIONEXCEPTION_HPP
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.cpp b/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bdadaf9ee807376bef8b8450d30e54380bb3a49d
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.cpp
@@ -0,0 +1,181 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#include "ConnectionManager.h"
+
+ConnectionManager::ConnectionManager(TinyGsm &modem, GPRSCredentials credentials, ModemPins modemPins)
+    : modem(modem), credentials(std::move(credentials)), modemPins(modemPins) {}
+
+void ConnectionManager::restartModem() {
+    // Restart takes quite some time
+    // To skip it, call init() instead of restart()
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Restarting modem...\n");
+    if (!modem.restart()) {
+        esp_log_write(ESP_LOG_WARN, TAG_GSM, "Failed to restart modem, attempting to continue without restarting\n");
+    }
+}
+
+void ConnectionManager::initModem() {
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Initializing modem...\n");
+    if (!modem.init()) {
+        esp_log_write(ESP_LOG_WARN, TAG_GSM, "Failed to initialize modem, attempting to continue...\n");
+    }
+    setModemFunctionalityLevel(MINIMAL);
+}
+
+void ConnectionManager::setModemFunctionalityLevel(
+    ModemFunctionalityLevel functionalityLevel) { // This sets the level of functionality of the modem. Full
+                                                  // functionality is where the highest
+    // level of power is drawn. Minimum functionality (default) is where the lowest level of power is drawn.
+    // https://m2msupport.net/m2msupport/atcfun-set-phone-functionality/
+    modem.sendAT(("+CFUN=" + std::to_string(functionalityLevel) + " ").c_str());
+    if (modem.waitResponse(10000L) != 1) {
+        DBG(" +CFUN=0  false ");
+    }
+}
+
+void ConnectionManager::disableGPS() {
+    // Set SIM7000G GPIO4 LOW ,turn off GPS power
+    // CMD:AT+SGPIO=0,4,1,0
+    // Only in version 20200415 is there a function to control GPS power
+    modem.sendAT("+SGPIO=0,4,1,0");
+    if (modem.waitResponse(10000L) != 1) {
+        DBG(" SGPIO=0,4,1,0 false ");
+    }
+    modem.disableGPS();
+}
+
+void ConnectionManager::enableGPS() {
+    // Set SIM7000G GPIO4 LOW ,turn on GPS power
+    // CMD:AT+SGPIO=0,4,1,1
+    // Only in version 20200415 is there a function to control GPS power
+    modem.sendAT("+SGPIO=0,4,1,1");
+    if (modem.waitResponse(10000L) != 1) {
+        DBG(" SGPIO=0,4,1,1 false ");
+    }
+    modem.enableGPS();
+}
+
+void ConnectionManager::setNetworkMode(NetworkMode networkMode) {
+
+    String res;
+    res = modem.setNetworkMode(networkMode);
+    if (res != "1") {
+        DBG("setNetworkMode  false ");
+        throw LTEConnectionException("Failed to set network mode");
+    }
+}
+
+void ConnectionManager::setPreferredMode(Mode mode) {
+    String res;
+    res = modem.setPreferredMode(mode);
+    if (res != "1") {
+        DBG("setPreferredMode  false ");
+        return;
+    }
+}
+void ConnectionManager::setBand(BandMode bandMode, int band) {
+    /*AT+CBANDCFG=<mode>,<band>[,<band>…]
+     * <mode> "CAT-M"   "NB-IOT"
+     * <band>  The value of <band> must is in the band list of getting from  AT+CBANDCFG=?
+     * For example, my SIM card carrier "NB-iot" supports B8.  I will configure +CBANDCFG= "Nb-iot ",8
+     */
+
+    bandMode == BandMode::BAND_CAT_M ? modem.sendAT(("+CBANDCFG=\"CAT-M\"," + std::to_string(band) + " ").c_str())
+                                     : modem.sendAT(("+CBANDCFG=\"NB-IOT\"," + std::to_string(band) + " ").c_str());
+
+    if (modem.waitResponse(10000L) != 1) {
+        DBG(" +CBANDCFG=\"NB-IOT\" ");
+    }
+}
+bool ConnectionManager::gprsConnect() {
+    return modem.gprsConnect(credentials.apn.c_str(), credentials.user.c_str(), credentials.password.c_str());
+}
+bool ConnectionManager::isNetworkConnected() {
+    return modem.isNetworkConnected();
+}
+std::string ConnectionManager::connect(RequestInformation requestInformation) {
+    TinyGsmClient client{modem, 0};
+    if (!client.connect(requestInformation.host.c_str(), requestInformation.port)) {
+        throw LTEConnectionException("Failed to connect to host");
+    }
+
+    String request = requestInformation.buildRequest();
+
+    client.print(request);
+
+    String line;
+    // print response
+    while (client.connected()) {
+        line += client.readStringUntil('\n');
+        if (line == "\r") {
+            esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "headers received\n");
+            break;
+        }
+    }
+    client.stop();
+    return line.c_str();
+}
+
+void ConnectionManager::modemPowerOn() {
+    pinMode(modemPins.pwr, OUTPUT);
+    digitalWrite(modemPins.pwr, LOW);
+    delay(1000);
+    digitalWrite(modemPins.pwr, HIGH);
+}
+
+void ConnectionManager::modemPowerOff() {
+    // Try to power-off (modem may decide to restart automatically)
+    // To turn off modem completely, please use Reset/Enable pins
+    modem.sendAT("+CPOWD=1");
+    if (modem.waitResponse(10000L) != 1) {
+        esp_log_write(ESP_LOG_WARN, TAG_GSM, "Failed to power off modem\n");
+    }
+    modem.poweroff();
+    pinMode(modemPins.pwr, OUTPUT);
+    digitalWrite(modemPins.pwr, LOW);
+    delay(1500);
+    digitalWrite(modemPins.pwr, HIGH);
+}
+void ConnectionManager::unlockSimCard() {
+    // Unlock your SIM card with a PIN if needed
+    if (modem.getSimStatus() != 3) {
+        modem.simUnlock(credentials.password.c_str());
+    }
+}
+bool ConnectionManager::waitForNetwork() {
+    if (!modem.waitForNetwork(600000L, true)) {
+        delay(10000);
+        return false;
+    }
+    return true;
+}
+bool ConnectionManager::gprsDisconnect() {
+    return modem.gprsDisconnect();
+}
+void ConnectionManager::logModemInformation() {
+    bool res = modem.isGprsConnected();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "GPRS connected: %s\n", res ? "true" : "false");
+
+    String ccid = modem.getSimCCID();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "CCID: %s\n", ccid.c_str());
+
+    String imei = modem.getIMEI();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "IMEI: %s\n", imei.c_str());
+
+    String imsi = modem.getIMSI();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "IMSI: %s\n", imsi.c_str());
+
+    String cop = modem.getOperator();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Operator: %s\n", cop.c_str());
+
+    IPAddress local = modem.localIP();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Local IP: %s\n", local.toString().c_str());
+
+    int csq = modem.getSignalQuality();
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Signal quality: %d\n", csq);
+}
+bool ConnectionManager::isGprsConnected() {
+    return modem.isGprsConnected();
+}
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.h b/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..70b4668a810fb0ceb0a858f4f4d52218201603c4
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/ConnectionManager.h
@@ -0,0 +1,75 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_CONNECTIONMANAGER_H
+#define HOST_CENTRAL_MAST_CONNECTIONMANAGER_H
+
+#define TINY_GSM_MODEM_SIM7000
+
+#include "Definitions.h"
+#include "GPRSCredentials.h"
+#include "LTEConnectionException.hpp"
+#include "Mode.h"
+#include "ModemFunctionalityLevel.h"
+#include "NetworkMode.h"
+#include "RequestInformation.h"
+#include "StreamDebugger.h"
+#include <TinyGsmClient.h>
+#include <utility>
+
+class ConnectionManager {
+
+  public:
+    struct ModemPins {
+        int dtr;
+        int tx;
+        int rx;
+        int pwr;
+    };
+
+  private:
+    TinyGsm &modem;
+    const GPRSCredentials credentials;
+    ModemPins modemPins;
+
+  public:
+    ConnectionManager(TinyGsm &modem, GPRSCredentials credentials, ModemPins modemPins);
+
+    std::string connect(RequestInformation requestInformation);
+
+    void enableGPS();
+
+    void disableGPS();
+
+    void initModem();
+
+    void restartModem();
+
+    void setNetworkMode(NetworkMode networkMode = NetworkMode::LTE_ONLY);
+
+    void setPreferredMode(Mode mode = Mode::CAT_M);
+
+    void setModemFunctionalityLevel(ModemFunctionalityLevel functionalityLevel = MINIMAL);
+
+    enum BandMode { BAND_CAT_M, BAND_NB_IoT };
+
+    void setBand(BandMode bandMode, int band);
+
+    bool gprsConnect();
+    bool gprsDisconnect();
+
+    bool isNetworkConnected();
+
+    void modemPowerOn();
+
+    void modemPowerOff();
+
+    void unlockSimCard();
+    bool waitForNetwork();
+
+    void logModemInformation();
+    bool isGprsConnected();
+};
+
+#endif // HOST_CENTRAL_MAST_CONNECTIONMANAGER_H
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/GPRSCredentials.h b/host/host_central_mast/lib/NetworkConnectionManager/GPRSCredentials.h
new file mode 100644
index 0000000000000000000000000000000000000000..1029c27f72637315ac1bda03204e224e4ab69b6b
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/GPRSCredentials.h
@@ -0,0 +1,22 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_GPRSCREDENTIALS_H
+#define HOST_CENTRAL_MAST_GPRSCREDENTIALS_H
+
+#include <string>
+#include <utility>
+
+struct GPRSCredentials {
+    std::string apn;
+    std::string user;
+    std::string password;
+
+    GPRSCredentials(std::string apn, std::string user, std::string password) {
+        this->apn = std::move(apn);
+        this->user = std::move(user);
+        this->password = std::move(password);
+    }
+};
+#endif //HOST_CENTRAL_MAST_GPRSCREDENTIALS_H
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/LTEConnectionException.hpp b/host/host_central_mast/lib/NetworkConnectionManager/LTEConnectionException.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c9067bdbab1a9721b1d41df2b0bbb3aef47bd66
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/LTEConnectionException.hpp
@@ -0,0 +1,23 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_LTECONNECTIONEXCEPTION_HPP
+#define HOST_CENTRAL_MAST_LTECONNECTIONEXCEPTION_HPP
+
+#include <exception>
+#include <string>
+
+class LTEConnectionException : public std::exception {
+public:
+    explicit LTEConnectionException(const std::string &message) : message(message) {}
+
+    const char *what() const noexcept override {
+        return message.c_str();
+    }
+
+private:
+    std::string message;
+};
+
+#endif //HOST_CENTRAL_MAST_LTECONNECTIONEXCEPTION_HPP
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/Mode.h b/host/host_central_mast/lib/NetworkConnectionManager/Mode.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb248b7920ad62aca446830742948f6fe8de2a95
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/Mode.h
@@ -0,0 +1,14 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_MODE_H
+#define HOST_CENTRAL_MAST_MODE_H
+
+enum Mode {
+    CAT_M = 1,
+    NB_IOT = 2,
+    CAT_M_AND_NB_IOT = 3
+};
+
+#endif //HOST_CENTRAL_MAST_MODE_H
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/ModemFunctionalityLevel.h b/host/host_central_mast/lib/NetworkConnectionManager/ModemFunctionalityLevel.h
new file mode 100644
index 0000000000000000000000000000000000000000..35a458033a388af7a8d7fe3a37649b5ffbc458e7
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/ModemFunctionalityLevel.h
@@ -0,0 +1,14 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_MODEMFUNCTIONALITYLEVEL_H
+#define HOST_CENTRAL_MAST_MODEMFUNCTIONALITYLEVEL_H
+
+enum ModemFunctionalityLevel {
+    MINIMAL = 0,
+    FULL = 1,
+    DISABLE = 4,
+};
+
+#endif //HOST_CENTRAL_MAST_MODEMFUNCTIONALITYLEVEL_H
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/NetworkMode.h b/host/host_central_mast/lib/NetworkConnectionManager/NetworkMode.h
new file mode 100644
index 0000000000000000000000000000000000000000..daafc19e6b94bcb6d6a818cb905606f559a23e04
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/NetworkMode.h
@@ -0,0 +1,15 @@
+//
+// Created by zoe on 12/20/22.
+//
+
+#ifndef HOST_CENTRAL_MAST_NETWORKMODE_H
+#define HOST_CENTRAL_MAST_NETWORKMODE_H
+
+enum NetworkMode {
+    AUTOMATIC = 2,
+    GSM_ONLY = 13,
+    LTE_ONLY = 38,
+    GSM_AND_LTE_ONLY = 51
+};
+
+#endif //HOST_CENTRAL_MAST_NETWORKMODE_H
diff --git a/host/host_central_mast/lib/NetworkConnectionManager/RequestInformation.h b/host/host_central_mast/lib/NetworkConnectionManager/RequestInformation.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d416119edc8ec9ad36e153efadc564a8f347c60
--- /dev/null
+++ b/host/host_central_mast/lib/NetworkConnectionManager/RequestInformation.h
@@ -0,0 +1,54 @@
+//
+// Created by zoe on 1/9/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_REQUESTINFORMATION_H
+#define HOST_CENTRAL_MAST_REQUESTINFORMATION_H
+
+#include <Arduino.h>
+#include <Definitions.h>
+#include <map>
+
+enum RequestMethod {
+    GET,
+    POST,
+};
+
+struct RequestInformation {
+    RequestMethod method;
+    std::map<String, String> headers;
+    int port;
+    String host;
+    String path;
+    String body;
+
+    RequestInformation(const RequestMethod &method, const String &host, const int &port, const String &path,
+                       const String &body, const std::map<String, String> &headers) {
+        this->method = method;
+        this->port = port;
+        this->headers = headers;
+        this->host = host;
+        this->path = path;
+        this->body = body;
+    }
+
+    String buildRequest() {
+        String methodString = method == RequestMethod::GET ? "GET" : "POST";
+        String request = "";
+        request += methodString + " " + path + " HTTP/1.1\r\n";
+        request += "Host: " + host + "\r\n";
+        request += "User-Agent: ESP32-Host\r\n";
+        for (auto &header : headers) {
+            request += header.first + ": " + header.second + "\r\n";
+        }
+        //        request += "Authorization: Token " + INFLUXDB_TOKEN + "\r\n";
+        request += "Content-Length: " + String(body.length()) + "\r\n";
+        if (body.length() > 0) {
+            request += "\r\n";
+            request += body;
+        }
+        return request;
+    }
+};
+
+#endif // HOST_CENTRAL_MAST_REQUESTINFORMATION_H
diff --git a/host/host_central_mast/lib/ResendManager/ResendManager.cpp b/host/host_central_mast/lib/ResendManager/ResendManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64d6557ab417c4535f6ab7801f0ce12b566d0021
--- /dev/null
+++ b/host/host_central_mast/lib/ResendManager/ResendManager.cpp
@@ -0,0 +1,72 @@
+//
+// Created by cynthya on 1/5/23.
+//
+
+#include "ResendManager.h"
+#include "Utilities.h"
+
+void ResendManager::init() {
+    createResendDirectory();
+    // log
+    uint lastResendFileId = getLastResendFileId();
+    nextResendFileId = lastResendFileId + 1;
+    esp_log_write(ESP_LOG_INFO, "ResendManager", "nextResendFileId: %d\n", nextResendFileId);
+}
+
+uint ResendManager::getLastResendFileId() const { // get the next file id to be resend
+    auto filesInDirectory = SDUtilities::getFilesInDirectory(resendDirectoryPath);
+    // convert the file names to uint
+    std::list<uint> fileUintIDs;
+
+    try {
+        for (const auto &fileName : filesInDirectory) {
+            fileUintIDs.emplace_back(std::stoul(fileName.c_str()));
+        }
+    } catch (std::invalid_argument &e) {
+        esp_log_write(ESP_LOG_ERROR, "getLastResendFileId", "Failed to convert file name to uint\n");
+        throw;
+    }
+    // get the max
+    uint max = 0;
+    for (const auto &fileID : fileUintIDs) {
+        if (fileID > max) {
+            max = fileID;
+        }
+    }
+
+    return max;
+}
+
+void ResendManager::createResendDirectory() const { // create directory if it doesn't exist
+    SDUtilities::createDirectory("/resend");
+}
+
+void ResendManager::storeForResend(const String &messageToBeSend) {
+    // create file
+    String filename = String(nextResendFileId);
+    SDUtilities::writeFile(messageToBeSend, resendDirectoryPath + "/" + filename);
+    ResendManager::incrementCount();
+}
+
+std::optional<ResendPointType> ResendManager::loadNextToBeResendMessage() {
+    // get first file in resend directory
+    auto filename = SDUtilities::getFirstFileNameInDirectory(resendDirectoryPath.c_str());
+
+    if (filename.has_value()) {
+
+        // read file
+        auto message = SDUtilities::readFile(resendDirectoryPath + "/" + filename.value().c_str());
+        return ResendPointType{message, filename.value()};
+    }
+    return std::nullopt;
+}
+
+void ResendManager::deleteResendMessage(const String &filename) {
+    auto filePath = resendDirectoryPath + "/" + filename;
+    if (SD.remove(filePath.c_str())) {
+        esp_log_write(ESP_LOG_INFO, "ResendManager", "Deleted file: %s\n", filePath.c_str());
+    } else {
+        esp_log_write(ESP_LOG_ERROR, "ResendManager", "Failed to delete file: %s\n", filePath.c_str());
+        throw;
+    }
+}
diff --git a/host/host_central_mast/lib/ResendManager/ResendManager.h b/host/host_central_mast/lib/ResendManager/ResendManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d7495a657f004248d8c667b326233f090a8bb59
--- /dev/null
+++ b/host/host_central_mast/lib/ResendManager/ResendManager.h
@@ -0,0 +1,35 @@
+//
+// Created by cynthya on 1/5/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_RESENDMANAGER_H
+#define HOST_CENTRAL_MAST_RESENDMANAGER_H
+
+#include "ResendPointType.h"
+#include "SD.h"
+#include "Utilities.h"
+#include <Arduino.h>
+#include <cstdio>
+
+class ResendManager {
+  public:
+    // File name (as uint) of the next file to be re-send (i.e. a counter of files to be resend). Max value is
+    // 4'294'967'295
+
+    void storeForResend(const String &messageToBeSend);
+    // I am making this an optional since i want to avoid creating a 'hasNextToBeResend' method since that would require
+    // another fs operation. If this method returns std::nullopt, it means that there is no file to be resend.
+    std::optional<ResendPointType> loadNextToBeResendMessage();
+    void deleteResendMessage(const String &filename);
+
+    void init();
+
+  private:
+    const String resendDirectoryPath = "/resend";
+    uint nextResendFileId = 0;
+    void incrementCount() { nextResendFileId++; }
+    void createResendDirectory() const;
+    uint getLastResendFileId() const;
+};
+
+#endif // HOST_CENTRAL_MAST_RESENDMANAGER_H
diff --git a/host/host_central_mast/lib/ResendManager/ResendPointType.h b/host/host_central_mast/lib/ResendManager/ResendPointType.h
new file mode 100644
index 0000000000000000000000000000000000000000..11a4bfce68c99422094f4b76e1b5b82e47b0eec0
--- /dev/null
+++ b/host/host_central_mast/lib/ResendManager/ResendPointType.h
@@ -0,0 +1,15 @@
+//
+// Created by cynthya on 1/5/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_RESENDPOINTTYPE_H
+#define HOST_CENTRAL_MAST_RESENDPOINTTYPE_H
+
+#include <Arduino.h>
+
+struct ResendPointType {
+	String message;
+	String filename;
+};
+
+#endif // HOST_CENTRAL_MAST_RESENDPOINTTYPE_H
diff --git a/host/host_central_mast/lib/Utilities/Definitions.h b/host/host_central_mast/lib/Utilities/Definitions.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f5cc45c375b37d12d8845de1fdf8f605cc936d2
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/Definitions.h
@@ -0,0 +1,46 @@
+//
+// Created by zoe on 1/12/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_DEFINITIONS_H
+#define HOST_CENTRAL_MAST_DEFINITIONS_H
+
+#include "Arduino.h"
+#include "ESP32Time.h"
+
+#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds
+#define TIME_TO_SLEEP 5           // Time ESP32 will go to sleep (in seconds)
+
+#define UART_BAUD 115200
+#define PIN_DTR 25
+#define PIN_TX 27
+#define PIN_RX 26
+#define PWR_PIN 4
+
+#define SD_MISO 2
+#define SD_MOSI 15
+#define SD_SCLK 14
+#define SD_CS 13
+#define LED_PIN 12
+
+// See all AT commands, if wanted
+// #define DUMP_AT_COMMANDS
+
+#define TAG_ESPNOW "ESPNOW"
+#define TAG_MAIN "MAIN"
+#define TAG_SD "SD"
+#define TAG_GSM "GSM"
+#define TAG_TIMEMANAGER "TIMEMANAGER"
+#define TAG_SDUTILITIES "SDUTILITIES"
+
+#define SD_CARD_FAIL_COUNTER 100
+
+constexpr bool PRINT_TO_SERIAL = true;
+
+// global definition of RTC. Initialised in main
+extern ESP32Time rtc;
+
+const String INFLUXDB_TOKEN =
+    "dUh2gbVLv7e3egqocxriDsJQNUacA9qZ5YXsYtdnVAglnHgy4nx-jDVO7nGlSF34BosfnuwnUDaviC7dQeC5RQ==";
+
+#endif // HOST_CENTRAL_MAST_DEFINITIONS_H
diff --git a/host/host_central_mast/lib/Utilities/SDCardException.h b/host/host_central_mast/lib/Utilities/SDCardException.h
new file mode 100644
index 0000000000000000000000000000000000000000..039d70328ab0463196bf2b78aa7559b88ab54089
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/SDCardException.h
@@ -0,0 +1,22 @@
+//
+// Created by zoe on 1/12/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_SDCARDEXCEPTION_H
+#define HOST_CENTRAL_MAST_SDCARDEXCEPTION_H
+
+#include <Arduino.h>
+#include <exception>
+
+// create a custom exception type called SD Card Exception
+
+class SDCardException : public std::exception {
+  public:
+    SDCardException(const String &message) : message(message) {}
+    const char *what() const noexcept override { return message.c_str(); }
+
+  private:
+    String message;
+};
+
+#endif // HOST_CENTRAL_MAST_SDCARDEXCEPTION_H
diff --git a/host/host_central_mast/lib/Utilities/SDCardLogger.cpp b/host/host_central_mast/lib/Utilities/SDCardLogger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b7fe7c7dcd2ef8b075cd67faae9ae187f01922b0
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/SDCardLogger.cpp
@@ -0,0 +1,60 @@
+//
+// Created by zoe on 1/24/23.
+//
+
+#include "SDCardLogger.h"
+
+namespace SDCardLogger {
+
+    char log_print_buffer[512];
+    bool printToSerial = false;
+
+    void printDebugToSerial(bool printToSerialAsWell) {
+        printToSerial = printToSerialAsWell;
+    }
+    int vprintf_into_sd(const char *szFormat, va_list args) {
+        String logstring = "[" + rtc.getDateTime() + "] ";
+        logstring += szFormat;
+
+        if (!SDUtilities::isSDAvailable()) {
+            if (printToSerial) {
+                logstring += " (SD card not available)\n";
+                vprintf(logstring.c_str(), args);
+            }
+            return 1;
+        }
+
+        // write evaluated format string into buffer
+        int ret = vsnprintf(log_print_buffer, sizeof(log_print_buffer), logstring.c_str(), args);
+
+        String date = rtc.getDate();
+        String filename = "/log_" + date + ".txt";
+
+        // output is now in buffer. write to file.
+        if (ret >= 0) {
+            if (!SD.exists(filename)) {
+                File writeLog = SD.open(filename, FILE_WRITE);
+                if (!writeLog) {
+                    Serial.println("Couldn't open " + filename + " for writing");
+                    SDUtilities::setSDAvailable(false);
+                    return 1;
+                }
+                delay(50);
+                writeLog.close();
+            }
+
+            File logFile = SD.open(filename, FILE_APPEND);
+
+            // debug output
+            if (printToSerial) {
+                vprintf(logstring.c_str(), args);
+            }
+            logFile.write((uint8_t *)log_print_buffer, (size_t)ret);
+            // to be safe in case of crashes: flush the output
+            logFile.flush();
+            logFile.close();
+        }
+        return ret;
+    }
+
+} // namespace SDCardLogger
diff --git a/host/host_central_mast/lib/Utilities/SDCardLogger.h b/host/host_central_mast/lib/Utilities/SDCardLogger.h
new file mode 100644
index 0000000000000000000000000000000000000000..c14a07dc0ae0e4579daeb1b41c775253ebd9ca25
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/SDCardLogger.h
@@ -0,0 +1,17 @@
+//
+// Created by zoe on 1/24/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_SDCARDLOGGER_H
+#define HOST_CENTRAL_MAST_SDCARDLOGGER_H
+
+#include "Definitions.h"
+#include "SD.h"
+#include "Utilities.h"
+
+namespace SDCardLogger {
+    void printDebugToSerial(bool printToSerialAsWell);
+    int vprintf_into_sd(const char *szFormat, va_list args);
+}; // namespace SDCardLogger
+
+#endif // HOST_CENTRAL_MAST_SDCARDLOGGER_H
diff --git a/host/host_central_mast/lib/Utilities/SDSetupException.h b/host/host_central_mast/lib/Utilities/SDSetupException.h
new file mode 100644
index 0000000000000000000000000000000000000000..09c1ed348b6bd38798a04117fb42c4446e52381b
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/SDSetupException.h
@@ -0,0 +1,21 @@
+//
+// Created by zoe on 1/12/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_SDSETUPEXCEPTION_H
+#define HOST_CENTRAL_MAST_SDSETUPEXCEPTION_H
+
+#include <Arduino.h>
+#include <exception>
+// create a custom exception type called SD Setup Exception
+
+class SDSetupException : public std::exception {
+  public:
+    SDSetupException(const String &message) : message(message) {}
+    const char *what() const noexcept override { return message.c_str(); }
+
+  private:
+    String message;
+};
+
+#endif // HOST_CENTRAL_MAST_SDSETUPEXCEPTION_H
diff --git a/host/host_central_mast/lib/Utilities/Utilities.cpp b/host/host_central_mast/lib/Utilities/Utilities.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..316d33c95d27260f7b3c0ca299ed049db6305bce
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/Utilities.cpp
@@ -0,0 +1,305 @@
+//
+// Created by zoe on 1/9/23.
+//
+
+#include "Utilities.h"
+
+namespace SDUtilities {
+
+    bool SDAvailable = true;
+
+    void setSDAvailable(bool sdAvailable) {
+        SDAvailable = sdAvailable;
+    }
+
+    // Used to try to remount SD card if it fails more than x times (100)
+    int sdFailCounter = 0;
+
+    bool isSDAvailable() {
+        return SDAvailable;
+    }
+
+    void tryRemountingSD() {
+        try {
+            setupSDCard(SD_MISO, SD_MOSI, SD_SCLK, SD_CS);
+            setSDAvailable(true);
+        } catch (SDSetupException &e) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Couldn't remount SD card: %s\n", e.what());
+        }
+        sdFailCounter = 0;
+    }
+
+    bool checkSDAvailability(const String &errorMessage) {
+        if (!isSDAvailable()) {
+            sdFailCounter++;
+            if (sdFailCounter % SD_CARD_FAIL_COUNTER == 0) {
+                esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "SD card not available. Trying to remount...\n");
+                tryRemountingSD();
+            }
+            String message = errorMessage + " (SD card not available, failed " + sdFailCounter + " times)\n";
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "%s", message.c_str());
+            // if re-mount was successful, this will be true
+            return isSDAvailable();
+        }
+        return true;
+    };
+
+    std::list<String> getFilesInDirectory(const String &dirname) {
+        if (!checkSDAvailability("Couldn't get files in directory " + dirname)) {
+            return {};
+        }
+        std::list<String> files;
+        File dir = openDirectory(dirname);
+        while (true) {
+            File nextFile = dir.openNextFile();
+            if (!nextFile) {
+                break;
+            }
+            if (!nextFile.isDirectory()) {
+                files.emplace_back(nextFile.name());
+            }
+            nextFile.close();
+        }
+        return files;
+    }
+
+    std::optional<String> getLastFileInDirectory(const String &dirname) {
+
+        if (!checkSDAvailability("Couldn't get last file in directory " + dirname)) {
+            return std::nullopt;
+        }
+
+        File root = openDirectory(dirname);
+        root.rewindDirectory();
+
+        File file = root.openNextFile();
+        while (file) {
+            File nextFile = root.openNextFile();
+            if (!nextFile) {
+                break;
+            }
+            file = nextFile;
+        }
+        // log
+        if (file) {
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "Last file name: %s\n", file.name());
+            return file.name();
+        } else {
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "No file found\n");
+            return std::nullopt;
+        }
+    }
+    File openDirectory(const String &dirname) {
+
+        if (!checkSDAvailability("Couldn't open directory " + dirname)) {
+            throw;
+        }
+
+        File root = SD.open(dirname);
+        if (!root) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Failed to open directory\n");
+            throw;
+        }
+        if (!root.isDirectory()) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Not a directory\n");
+            throw;
+        }
+        return root;
+    }
+
+    std::optional<String> getFirstFileNameInDirectory(const String &dirname) {
+
+        if (!checkSDAvailability("Couldn't get first file in directory " + dirname)) {
+            return std::nullopt;
+        }
+
+        File root = openDirectory(dirname);
+        root.rewindDirectory();
+
+        File file = root.openNextFile();
+        if (file) {
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "file found: %s\n", file.name());
+            return file.name();
+        } else {
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "no file found\n");
+            return std::nullopt;
+        }
+    }
+
+    File openForWrite(const String &filePath) {
+
+        if (!checkSDAvailability("Couldn't open file " + filePath + " for writing")) {
+            throw;
+        }
+
+        File file = SD.open(filePath, FILE_WRITE);
+        if (!file) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Failed to open file for writing\n");
+            throw;
+        }
+        return file;
+    }
+
+    File openForRead(const String &filePath) {
+
+        if (!checkSDAvailability("Couldn't open file " + filePath + " for reading")) {
+            throw;
+        }
+
+        File file = SD.open(filePath, FILE_READ);
+        if (!file) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Failed to open file for reading\n");
+            throw;
+        }
+        return file;
+    }
+
+    void writeFile(const String &messageToBeSend, const String &filePath) {
+
+        if (!checkSDAvailability("Couldn't write to file " + filePath)) {
+            throw;
+        }
+
+        File file = openForWrite(filePath);
+        if (file.print(messageToBeSend)) {
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "File written %s\n", filePath.c_str());
+        } else {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Write failed %s\n", filePath.c_str());
+            throw;
+        }
+        file.close();
+    }
+
+    String readFile(const String &filePath) {
+
+        if (!checkSDAvailability("Couldn't read file " + filePath)) {
+            throw;
+        }
+
+        File file = openForRead(filePath);
+
+        String ret;
+
+        while (file.available()) {
+            ret += (char)file.read();
+        }
+
+        file.close();
+        return ret;
+    }
+
+    void createDirectory(const String &dirname) {
+
+        if (!checkSDAvailability("Couldn't create directory " + dirname)) {
+            throw;
+        }
+
+        if (!SD.exists(dirname)) {
+            SD.mkdir(dirname);
+            esp_log_write(ESP_LOG_INFO, TAG_SDUTILITIES, "Created directory: %s\n", dirname.c_str());
+        } else {
+            esp_log_write(ESP_LOG_WARN, TAG_SDUTILITIES, "Directory already exists\n");
+        }
+    }
+
+    void setupSDCard(int MISO, int MOSI, int SCLK, int CS) {
+        SPI.begin(SCLK, MISO, MOSI, CS);
+        if (!SD.begin(CS)) {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "Card MOUNT FAIL\n");
+            throw SDSetupException("Card MOUNT FAIL");
+        } else {
+            uint32_t cardSize = SD.cardSize() / (1024 * 1024);
+            String sdcardSizeString = "SDCard Size: " + String(cardSize) + "MB";
+            esp_log_write(ESP_LOG_DEBUG, TAG_SDUTILITIES, "%s\n", sdcardSizeString.c_str());
+        }
+    }
+
+    void saveStringToSDCard(const std::string &dataString) {
+
+        if (!checkSDAvailability("Couldn't save string to SD card")) {
+            throw SDCardException("Couldn't save string to SD card");
+        }
+
+        File dataFile = SD.open("/datalog.txt", FILE_APPEND);
+
+        // if the file is available, write to it:
+        if (dataFile) {
+            if (dataString.length() > 0) {
+                dataFile.println(dataString.c_str());
+            }
+            dataFile.close();
+        }
+        // if the file isn't open, pop up an error:
+        else {
+            esp_log_write(ESP_LOG_ERROR, TAG_SDUTILITIES, "error opening datalog.txt\n");
+            throw SDCardException("error opening datalog.txt");
+        }
+    }
+} // namespace SDUtilities
+
+// I don't think this does anything. Copied from the example
+void turnOffLEDs() { // Set LED OFF
+    pinMode(LED_PIN, OUTPUT);
+    digitalWrite(LED_PIN, HIGH);
+
+    pinMode(PWR_PIN, OUTPUT);
+    digitalWrite(PWR_PIN, HIGH);
+    delay(300);
+    digitalWrite(PWR_PIN, LOW);
+}
+
+String getMacAddressAsString(const uint8_t *mac) {
+    String macAddress;
+    for (int i = 0; i < 6; i++) {
+        macAddress += String(mac[i], HEX);
+    }
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "MAC: %s\n", macAddress.c_str());
+    return macAddress;
+}
+
+String documentToLineProtocolString(const DynamicJsonDocument &doc) {
+    String measurementType = doc["measurementType"].as<String>();
+    String sensorName = doc["sensorName"].as<String>();
+    String timestamp = doc["timestamp"].as<String>();
+    String protocol = doc["protocol"].as<String>();
+    String value = doc["value"].as<String>();
+    String channel = doc["channel"].as<String>();
+    String clientMac = doc["clientMac"].as<String>();
+
+    String lineData = sensorName + ",clientMac=" + clientMac + ",protocol=" + protocol + ",channel=" + channel + " "
+                      + measurementType + "=" + value + " " + timestamp;
+    return lineData;
+}
+DynamicJsonDocument parseReceivedJsonData(char *data) {
+    DynamicJsonDocument doc(250);
+    auto error = deserializeJson(doc, data);
+    if (error) {
+        esp_log_write(ESP_LOG_ERROR, TAG_ESPNOW, "Error while parsing json: %s\n", error.c_str());
+        // TODO error handling
+    }
+    return doc;
+}
+
+String documentToServerReadableString(const DynamicJsonDocument &doc) {
+    StaticJsonDocument<1024> serverDoc;
+    String hostMacAddressString = WiFi.macAddress();
+    hostMacAddressString.replace(":", "");
+    hostMacAddressString.toLowerCase();
+
+    serverDoc["host"] = hostMacAddressString;
+    serverDoc["client"] = doc["clientMac"].as<String>();
+    serverDoc["sensorProtocol"] = doc["protocol"].as<String>();
+    serverDoc["protocolAddress"] = doc["channel"].as<String>();
+    serverDoc["hardwareName"] = doc["sensorName"].as<String>();
+    // each value is a element in the readigs array
+    JsonArray readings = serverDoc.createNestedArray("readings");
+    JsonObject reading = readings.createNestedObject();
+    reading["name"] = doc["measurementType"].as<String>();
+    reading["value"] = doc["value"].as<float>();
+    serverDoc["time"] = doc["timestamp"].as<uint32_t>();
+
+    String serverString;
+    serializeJson(serverDoc, serverString);
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Server readable data: %s\n", serverString.c_str());
+    return serverString;
+}
\ No newline at end of file
diff --git a/host/host_central_mast/lib/Utilities/Utilities.h b/host/host_central_mast/lib/Utilities/Utilities.h
new file mode 100644
index 0000000000000000000000000000000000000000..dc18e3459e0fabe83c64f2e09a4a9ae3bf8c45f6
--- /dev/null
+++ b/host/host_central_mast/lib/Utilities/Utilities.h
@@ -0,0 +1,38 @@
+//
+// Created by zoe on 1/9/23.
+//
+
+#ifndef HOST_CENTRAL_MAST_UTILITIES_H
+#define HOST_CENTRAL_MAST_UTILITIES_H
+
+#include "ArduinoJson.h"
+#include "SD.h"
+#include "SDCardException.h"
+#include "SDSetupException.h"
+#include "WiFi.h"
+#include <Arduino.h>
+#include <Definitions.h>
+#include <WString.h>
+#include <list>
+
+namespace SDUtilities {
+    File openDirectory(const String &dirname);
+    std::list<String> getFilesInDirectory(const String &dirname);
+    std::optional<String> getFirstFileNameInDirectory(const String &dirname);
+    std::optional<String> getLastFileInDirectory(const String &dirname);
+    void writeFile(const String &messageToBeSend, const String &filePath);
+    String readFile(const String &filePath);
+    void createDirectory(const String &dirname);
+    void setupSDCard(int MISO, int MOSI, int SCLK, int CS);
+    void saveStringToSDCard(const std::string &dataString);
+    void setSDAvailable(bool sdAvailable);
+    bool isSDAvailable();
+} // namespace SDUtilities
+
+void turnOffLEDs();
+String getMacAddressAsString(const uint8_t *mac);
+String documentToLineProtocolString(const DynamicJsonDocument &doc);
+DynamicJsonDocument parseReceivedJsonData(char *data);
+String documentToServerReadableString(const DynamicJsonDocument &doc);
+
+#endif // HOST_CENTRAL_MAST_UTILITIES_H
diff --git a/host/host_central_mast/platformio.ini b/host/host_central_mast/platformio.ini
index 1e0fb845c1ef545a0bf8d0d351697508405dd20b..f935fa80544f7810f4d3d5e7d9712f08d8551bde 100644
--- a/host/host_central_mast/platformio.ini
+++ b/host/host_central_mast/platformio.ini
@@ -13,6 +13,7 @@ platform = espressif32
 board = esp-wrover-kit
 framework = arduino
 monitor_speed = 115200
+lib_ldf_mode = deep
 monitor_port = /dev/ttyACM0
 upload_port = /dev/ttyACM0
 build_flags =
diff --git a/host/host_central_mast/src/main.cpp b/host/host_central_mast/src/main.cpp
index 83adf6abac662548043aabe3ad47fb452e621d0d..db4e69de644ebaedab6ae1ecc4bcc215bac99b35 100644
--- a/host/host_central_mast/src/main.cpp
+++ b/host/host_central_mast/src/main.cpp
@@ -1,44 +1,34 @@
-#include "FS.h"
-#include "SD.h"
+/***
+ * code is modified from the original code found at
+ * https://github.com/Xinyuan-LilyGO/LilyGO-T-SIM7000G/blob/master/examples/Arduino_TinyGSM/AllFunctions/AllFunctions.ino
+ */
+
+#define TINY_GSM_MODEM_SIM7000
+
+#include "ConnectionManager.h"
+#include "MessageType.h"
+#include "SDCardLogger.h"
 #include "SPI.h"
-#include "time.h"
-#include <Arduino.h>
+#include "Utilities.h"
 #include <ArduinoJson.h>
+#include <Definitions.h>
 #include <ESP32Time.h>
+#include <NTPManager.h>
+#include <ResendManager.h>
+#include <TinyGsmClient.h>
 #include <WiFi.h>
 #include <esp_log.h>
 #include <esp_now.h>
+#include <map>
 #include <queue>
 #include <sys/unistd.h>
 
-static const std::string TAG = "MAIN";
-static const std::string TAG_ESPNOW = "ESPNOW";
-static const std::string TAG_GSM = "GSM";
-
-/*
-  FILE: AllFunctions.ino
-  AUTHOR: Koby Hale
-  PURPOSE: Test functionality
-*/
+ESP32Time rtc;
 
-#define TINY_GSM_MODEM_SIM7000
 #define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb
 #define SerialAT Serial1
 
-// See all AT commands, if wanted
-#define DUMP_AT_COMMANDS
-
-// set GSM PIN, if any
-#define GSM_PIN ""
-
-// Your GPRS credentials, if any
-const char apn[] = "m2m.public.at"; // SET TO YOUR APN
-const char gprsUser[] = "";
-const char gprsPass[] = "";
-
-#include <TinyGsmClient.h>
-
-#ifdef DUMP_AT_COMMANDS // if enabled it requires the streamDebugger lib
+#ifdef DUMP_AT_COMMANDS // if enabled it requires the streamDebugger library
 #include <StreamDebugger.h>
 StreamDebugger debugger(SerialAT, Serial);
 TinyGsm modem(debugger);
@@ -46,518 +36,256 @@ TinyGsm modem(debugger);
 TinyGsm modem(SerialAT);
 #endif
 
-#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds
-#define TIME_TO_SLEEP 5           // Time ESP32 will go to sleep (in seconds)
-
-#define UART_BAUD 115200
-#define PIN_DTR 25
-#define PIN_TX 27
-#define PIN_RX 26
-#define PWR_PIN 4
-
-#define SD_MISO 2
-#define SD_MOSI 15
-#define SD_SCLK 14
-#define SD_CS 13
-#define LED_PIN 12
-
-enum MessageType{
-    dataAck,
-    hostChange
-};
-typedef struct response{
-	MessageType type;
-	uint8_t mac[6];
-	long time;
-}response;
-
-uint8_t BROADCAST_MAC[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
+const GPRSCredentials gprsCredentials = GPRSCredentials("m2m.public.at", "", "");
+
+ConnectionManager connectionManager{modem, gprsCredentials,
+                                    ConnectionManager::ModemPins{PIN_DTR, PIN_TX, PIN_RX, PWR_PIN}};
+
+NTPManager ntpManager{modem};
+
+uint8_t BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 esp_now_peer_info_t broadcast = {};
 response announce = {};
 
-ESP32Time rtc;
-
 SemaphoreHandle_t xMutex;
 
 TaskHandle_t ESPNOWTask;
 
 static std::queue<String> queue;
 
-void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status)
-{
-	// go to sleep
-}
+ResendManager resendManager;
 
-static char log_print_buffer[512];
-
-int vprintf_into_sd(const char *szFormat, va_list args)
-{
-	String logstring = "[" + rtc.getDateTime() + "] ";
-	logstring += szFormat;
-	// write evaluated format string into buffer
-	int ret = vsnprintf(log_print_buffer, sizeof(log_print_buffer), logstring.c_str(), args);
-
-	String date = rtc.getDate();
-	String filename = "/log_" + date + ".txt";
-
-	// output is now in buffer. write to file.
-	if (ret >= 0) {
-		if (!SD.exists(filename)) {
-			File writeLog = SD.open(filename, FILE_WRITE);
-			if (!writeLog)
-				Serial.println("Couldn't open " + filename + " for writing");
-			delay(50);
-			writeLog.close();
-		}
-
-		File logFile = SD.open(filename, FILE_APPEND);
-
-		// debug output
-		vprintf(logstring.c_str(), args);
-		logFile.write((uint8_t *)log_print_buffer, (size_t)ret);
-		// to be safe in case of crashes: flush the output
-		logFile.flush();
-		logFile.close();
-	}
-	return ret;
+void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) {
+    // go to sleep
 }
 
-String getMacAddressAsString(const uint8_t *mac);
-DynamicJsonDocument parseReceivedJsonData(char *data);
-void saveStringToSDCard(const std::string &dataString);
-String documentToLineProtocolString(const DynamicJsonDocument &doc);
-void turnOffLEDs();
-void setupSDCard();
-void syncUTCTimeToRTC();
-void on_data_recv(const uint8_t *mac, const uint8_t *incomingData, int len)
-{
-	response response = {};
-	// send a host change msg if client is new, simplifies client
-	response.type = esp_now_is_peer_exist(mac) ? dataAck : hostChange; 
-	esp_read_mac(response.mac, ESP_MAC_WIFI_STA);
-	response.time = rtc.getEpoch();
-
-	// this block needs to happen before we send the message, as it's not possible to
-	// send a message to an unregistered peer (i.e. new client)
-	if(!esp_now_is_peer_exist(mac)){
-		esp_now_peer_info_t client = {};
-		memcpy(client.peer_addr, mac, sizeof(uint8_t) * 6);
-		client.encrypt = false;
-		client.channel = 0;
-
-		esp_err_t status = esp_now_add_peer(&client);
-		if(status != ESP_OK){
-			esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Failed to add new Peer: %d", status);
-		}
-	}
-
-	esp_err_t success = esp_now_send(mac, (uint8_t*) &response, sizeof(response));
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), 
-		(success == ESP_OK) ? "Response sent\n" : "Failed to respond\n");
-
-		
-	esp_log_write(ESP_LOG_INFO, TAG_ESPNOW.c_str(), "Message recieved\n");
-	// copy received data to a char array
-	char data[len];
-	memcpy(data, incomingData, len);
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Raw received Data: %s\n", data);
-
-	DynamicJsonDocument doc = parseReceivedJsonData(data);
-
-	String macAddress = getMacAddressAsString(mac);
-
-	// add timestamp and mac address
-	// doc["timestamp"] = rtc.getEpoch();
-	doc["clientMac"] = macAddress;
-
-	// serialize json document again
-	std::string dataString{};
-	serializeJson(doc, dataString);
-
-	saveStringToSDCard(dataString);
-
-	String lineData = documentToLineProtocolString(doc);
-
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Line protocol data: %s\n", lineData.c_str());
-
-	xSemaphoreTake(xMutex, portMAX_DELAY);
-	queue.push(lineData);
-	xSemaphoreGive(xMutex);
+void on_data_recv(const uint8_t *mac, const uint8_t *incomingData, int len) {
 
-}
+    esp_log_write(ESP_LOG_INFO, TAG_ESPNOW, "Message received\n");
 
-String documentToLineProtocolString(const DynamicJsonDocument &doc)
-{
-	String measurementType = doc["measurementType"].as<String>();
-	String sensorName = doc["sensorName"].as<String>();
-	String timestamp = doc["timestamp"].as<String>();
-	String protocol = doc["protocol"].as<String>();
-	String value = doc["value"].as<String>();
-	String channel = doc["channel"].as<String>();
-	String clientMac = doc["clientMac"].as<String>();
-
-	String lineData = sensorName + ",clientMac=" + clientMac + ",protocol=" + protocol + ",channel=" + channel + " "
-	                  + measurementType + "=" + value + " " + timestamp;
-	return lineData;
-}
-void saveStringToSDCard(const std::string &dataString)
-{
-	File dataFile = SD.open("/datalog.txt", FILE_APPEND);
-
-	// if the file is available, write to it:
-	if (dataFile) {
-		if (dataString.length() > 0) {
-			dataFile.println(dataString.c_str());
-		}
-		dataFile.close();
-	}
-	// if the file isn't open, pop up an error:
-	else {
-		esp_log_write(ESP_LOG_ERROR, TAG.c_str(), "error opening datalog.txt\n");
-		// TODO: Error handling
-	}
-}
-DynamicJsonDocument parseReceivedJsonData(char *data)
-{
-	DynamicJsonDocument doc(250);
-	auto error = deserializeJson(doc, data);
-	if (error) {
-		esp_log_write(ESP_LOG_ERROR, TAG_ESPNOW.c_str(), "Error while parsing json: %s\n", error.f_str());
-		// TODO error handling
-	}
-	return doc;
-}
-String getMacAddressAsString(const uint8_t *mac)
-{
-	String macAddress;
-	for (int i = 0; i < 6; i++) {
-		macAddress += String(mac[i], HEX);
-	}
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "MAC: %s\n", macAddress.c_str());
-	return macAddress;
-}
+    if (!esp_now_is_peer_exist(mac)) {
+        esp_now_peer_info_t client = {};
+        memcpy(client.peer_addr, mac, sizeof(uint8_t) * 6);
+        client.encrypt = false;
+        client.channel = 0;
 
-[[noreturn]] void ESPNOWReceiveTask(void *parameter)
-{
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "ESPNOWReceiveTask started on core %d\n", xPortGetCoreID());
+        esp_err_t status = esp_now_add_peer(&client);
+        if (status != ESP_OK) {
+            esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Failed to add new Peer: %d", status);
+        }
+    }
 
-	WiFi.mode(WIFI_STA);
+    // TODO: Think if response to client may contain error messages
+    response response = {};
+    response.type = dataAck;
+    esp_read_mac(response.mac, ESP_MAC_WIFI_STA);
+    response.time = rtc.getEpoch();
+    esp_err_t success = esp_now_send(mac, (uint8_t *)&response, sizeof(response));
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, (success == ESP_OK) ? "Response sent\n" : "Failed to respond\n");
 
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Initialising ESPNow...\n");
+    char data[len];
+    memcpy(data, incomingData, len);
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Raw received Data: %s\n", data);
 
-	if (esp_now_init() != ESP_OK) {
-		// initialization failed
-		esp_log_write(ESP_LOG_ERROR, TAG_ESPNOW.c_str(), "Initialising ESPNow FAILED\n");
-		exit(ESP_FAIL);
-	}
+    DynamicJsonDocument doc = parseReceivedJsonData(data);
 
-	esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Initialising ESPNow SUCCESS\n");
+    String macAddress = getMacAddressAsString(mac);
 
-	esp_now_register_recv_cb(on_data_recv);
+    // add timestamp and mac address
+    // doc["timestamp"] = rtc.getEpoch();
+    doc["clientMac"] = macAddress;
 
-	while (true) {
-	}
-}
+    // serialize json document again
+    std::string dataString{};
+    serializeJson(doc, dataString);
 
-time_t timeToUnixEpochSeconds(const std::string &time)
-{
-	//	22/10/27,10:16:20+00
-	struct tm tm {};
-	time_t dateInEpoch = 0;
-
-	if (strptime(time.c_str(), "%y/%m/%d,%T+00", &tm)) {
-		time_t curTime;
-		struct tm *timeinfo;
-
-		timeinfo = localtime(&curTime);
-
-		timeinfo->tm_year = tm.tm_year;
-		timeinfo->tm_mon = tm.tm_mon;
-		timeinfo->tm_mday = tm.tm_mday;
-		timeinfo->tm_hour = tm.tm_hour;
-		timeinfo->tm_min = tm.tm_min;
-		timeinfo->tm_sec = tm.tm_sec;
-		timeinfo->tm_isdst = -1;
-
-		dateInEpoch = mktime(timeinfo);
-	}
-	return dateInEpoch;
-}
+    try {
+        SDUtilities::saveStringToSDCard(dataString);
+    } catch (const std::exception &e) {
+        esp_log_write(ESP_LOG_ERROR, TAG_ESPNOW, "Failed to save data to SD card: %s", e.what());
+    }
 
-void setup()
-{
-	// Set console baud rate
-	Serial.begin(115200);
-	delay(10);
-	setupSDCard();
-
-	//	https://stackoverflow.com/questions/60442350/arduinos-esp-log-set-vprintf-does-not-work-on-esp32
-	esp_log_set_vprintf(&vprintf_into_sd);
-	esp_log_level_set("*", ESP_LOG_VERBOSE);
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "%s", WiFi.macAddress().c_str());
-
-	turnOffLEDs();
-
-	xMutex = xSemaphoreCreateMutex();
-
-	delay(1000);
-
-	// create ESPNOWReceiveTask. TODO: Until the UTC time is not synced, this will not add the correct time. If we
-	// TODO: create the task after the time is synced, no messages will be received until synchronization is done
-	xTaskCreatePinnedToCore(ESPNOWReceiveTask,   /* Function to implement the task */
-	                        "ESPNOWReceiveTask", /* Name of the task */
-	                        10000,               /* Stack size in words */
-	                        nullptr,             /* Task input parameter */
-	                        0,                   /* Priority of the task */
-	                        &ESPNOWTask,         /* Task handle. */
-	                        0);                  /* Core where the task should run */
-
-	SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
-
-	// Restart takes quite some time
-	// To skip it, call init() instead of restart()
-	esp_log_write(ESP_LOG_DEBUG, TAG_GSM.c_str(), "Initializing modem...\n");
-	if (!modem.restart()) {
-		esp_log_write(ESP_LOG_WARN, TAG_GSM.c_str(),
-		              "Failed to restart modem, attempting to continue without restarting\n");
-	}
-
-	syncUTCTimeToRTC();
-
-	broadcast.channel = 0;
-	broadcast.encrypt = false;
-	memcpy(&broadcast.peer_addr, &BROADCAST_MAC, sizeof(BROADCAST_MAC));
-	if(esp_now_add_peer(&broadcast) != ESP_OK){
-		esp_log_write(ESP_LOG_WARN, TAG_ESPNOW.c_str(), "Failed to add Broadcast Host");
-	}
-
-	announce.type = hostChange;
-	esp_read_mac(announce.mac, ESP_MAC_WIFI_STA);
-	announce.time = rtc.getEpoch();
-	if(esp_now_send(BROADCAST_MAC, (uint8_t *) &announce, sizeof(announce)) != ESP_OK){
-		esp_log_write(ESP_LOG_WARN, TAG_ESPNOW.c_str(), "Failed to announce mac");
-	}
-	else{
-		esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Mac announced!");
-	}
-}
-void syncUTCTimeToRTC()
-{
-	esp_log_write(ESP_LOG_DEBUG, TAG_GSM.c_str(), "NTP Server Syncing...\n");
-	modem.NTPServerSync("pool.ntp.org", 0);
-	auto gsmDateTimeString = modem.getGSMDateTime(DATE_FULL);
-	esp_log_write(ESP_LOG_DEBUG, TAG_GSM.c_str(), "GSM DateTime: %s\n", gsmDateTimeString.c_str());
-	time_t time = timeToUnixEpochSeconds(gsmDateTimeString.c_str());
-	rtc.setTime(time);
-	esp_log_write(ESP_LOG_INFO, TAG_GSM.c_str(), "Time set to EPOCH: %s\n", String(rtc.getEpoch()).c_str());
+    String serverData = documentToServerReadableString(doc);
+
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Data to be sent: %s\n", serverData.c_str());
+
+    xSemaphoreTake(xMutex, portMAX_DELAY);
+    queue.push(serverData);
+    xSemaphoreGive(xMutex);
 }
-void setupSDCard()
-{
-	SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
-	if (!SD.begin(SD_CS)) {
-		esp_log_write(ESP_LOG_ERROR, TAG.c_str(), "Card MOUNT FAIL\n");
-		// TODO: Error handling
-	} else {
-		uint32_t cardSize = SD.cardSize() / (1024 * 1024);
-		String sdcardSizeString = "SDCard Size: " + String(cardSize) + "MB";
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "%s\n", sdcardSizeString.c_str());
-	}
+
+[[noreturn]] void esp_loop() {
+    while (true) {
+    }
 }
 
-// I don't think this does anything. Copied from the example
-void turnOffLEDs()
-{ // Set LED OFF
-	pinMode(LED_PIN, OUTPUT);
-	digitalWrite(LED_PIN, HIGH);
+[[noreturn]] void ESPNOWReceiveTask(void *parameter) {
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "ESPNOWReceiveTask started on core %d\n", xPortGetCoreID());
+
+    WiFi.mode(WIFI_STA);
 
-	pinMode(PWR_PIN, OUTPUT);
-	digitalWrite(PWR_PIN, HIGH);
-	delay(300);
-	digitalWrite(PWR_PIN, LOW);
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Initialising ESPNow...\n");
+
+    if (esp_now_init() != ESP_OK) {
+        // initialization failed
+        esp_log_write(ESP_LOG_ERROR, TAG_ESPNOW, "Initialising ESPNow FAILED\n");
+        exit(ESP_FAIL);
+    }
+
+    esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Initialising ESPNow SUCCESS\n");
+
+    esp_now_register_recv_cb(on_data_recv);
+
+    broadcast.channel = 0;
+    broadcast.encrypt = false;
+    memcpy(&broadcast.peer_addr, &BROADCAST_MAC, sizeof(BROADCAST_MAC));
+    if (esp_now_add_peer(&broadcast) != ESP_OK) {
+        esp_log_write(ESP_LOG_WARN, TAG_ESPNOW, "Failed to add Broadcast Host");
+    }
+
+    announce.type = hostChange;
+    esp_read_mac(announce.mac, ESP_MAC_WIFI_STA);
+    announce.time = rtc.getEpoch();
+    if (esp_now_send(BROADCAST_MAC, (uint8_t *)&announce, sizeof(announce)) != ESP_OK) {
+        esp_log_write(ESP_LOG_WARN, TAG_ESPNOW, "Failed to announce mac");
+    } else {
+        esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Mac announced!");
+    }
+
+    esp_loop();
 }
 
-const String INFLUXDB_TOKEN =
-    "dUh2gbVLv7e3egqocxriDsJQNUacA9qZ5YXsYtdnVAglnHgy4nx-jDVO7nGlSF34BosfnuwnUDaviC7dQeC5RQ==";
-
-struct RequestInformation {
-	String method;
-	String host;
-	String path;
-	String body;
-};
-
-String buildRequest(const RequestInformation &requestInformation)
-{
-	String request = "";
-	request += requestInformation.method + " " + requestInformation.path + " HTTP/1.1\r\n";
-	request += "Host: " + requestInformation.host + "\r\n";
-	request += "Authorization: Token " + INFLUXDB_TOKEN + "\r\n";
-	request += "User-Agent: ESP32\r\n";
-	request += "Content-Type: text/plain\r\n";
-	request += "Content-Length: " + String(requestInformation.body.length()) + "\r\n";
-	request += "\r\n";
-	request += requestInformation.body;
-	return request;
+void setup() {
+    // Set console baud rate
+    Serial.begin(115200);
+    delay(10);
+    try {
+        SDUtilities::setupSDCard(SD_MISO, SD_MOSI, SD_SCLK, SD_CS);
+    } catch (const SDSetupException &e) {
+        SDUtilities::setSDAvailable(false);
+        esp_log_write(ESP_LOG_ERROR, TAG_MAIN, "SD Card setup failed: %s\n", e.what());
+    }
+
+    SDCardLogger::printDebugToSerial(PRINT_TO_SERIAL);
+
+    //	https://stackoverflow.com/questions/60442350/arduinos-esp-log-set-vprintf-does-not-work-on-esp32
+    esp_log_set_vprintf(&SDCardLogger::vprintf_into_sd);
+    esp_log_level_set("*", ESP_LOG_VERBOSE);
+    esp_log_write(ESP_LOG_DEBUG, TAG_MAIN, "%s", WiFi.macAddress().c_str());
+
+    resendManager.init();
+    turnOffLEDs();
+
+    xMutex = xSemaphoreCreateMutex();
+
+    delay(1000);
+
+    // create ESPNOWReceiveTask. TODO: Until the UTC time is not synced, this will not add the correct time. If we
+    // TODO: create the task after the time is synced, no messages will be received until synchronization is done
+    xTaskCreatePinnedToCore(ESPNOWReceiveTask,   /* Function to implement the task */
+                            "ESPNOWReceiveTask", /* Name of the task */
+                            10000,               /* Stack size in words */
+                            nullptr,             /* Task input parameter */
+                            0,                   /* Priority of the task */
+                            &ESPNOWTask,         /* Task handle. */
+                            0);                  /* Core where the task should run */
+
+    SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
 }
 
-void loop()
-{
-
-	// Restart takes quite some time
-	// To skip it, call init() instead of restart()
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Initializing modem...\n");
-	if (!modem.init()) {
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(),
-		              "Failed to restart modem, attempting to continue without restarting\n");
-	}
-
-	announce.time = rtc.getEpoch();
-	if(esp_now_send(BROADCAST_MAC, (uint8_t *) &announce, sizeof(announce)) != ESP_OK){
-		esp_log_write(ESP_LOG_WARN, TAG_ESPNOW.c_str(), "Failed to announce mac\n");
-	}
-	else{
-		esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW.c_str(), "Mac announced!\n");
-	}
-
-	String name = modem.getModemName();
-	delay(500);
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Modem Name %s\n", name.c_str());
-
-	String modemInfo = modem.getModemInfo();
-	delay(500);
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Modem Info: %s\n", modemInfo.c_str());
-
-	// Set SIM7000G GPIO4 LOW ,turn off GPS power
-	// CMD:AT+SGPIO=0,4,1,0
-	// Only in version 20200415 is there a function to control GPS power
-	modem.sendAT("+SGPIO=0,4,1,0");
-	if (modem.waitResponse(10000L) != 1) {
-		DBG(" SGPIO=0,4,1,0 false ");
-	}
-
-	modem.sendAT("+CFUN=0 ");
-	if (modem.waitResponse(10000L) != 1) {
-		DBG(" +CFUN=0  false ");
-	}
-	delay(200);
-
-	/*
-	  2 Automatic
-	  13 GSM only
-	  38 LTE only
-	  51 GSM and LTE only
-	* * * */
-	String res;
-	res = modem.setNetworkMode(38);
-	if (res != "1") {
-		DBG("setNetworkMode  false ");
-		return;
-	}
-	delay(200);
-
-	/*
-	  1 CAT-M
-	  2 NB-Iot
-	  3 CAT-M and NB-IoT
-	* * */
-	//	res = modem.setPreferredMode(1);
-	//	if (res != "1") {
-	//
-	//		DBG("setPreferredMode  false ");
-	//		return;
-	//	}
-	delay(200);
-
-	/*AT+CBANDCFG=<mode>,<band>[,<band>…]
-	 * <mode> "CAT-M"   "NB-IOT"
-	 * <band>  The value of <band> must is in the band list of getting from  AT+CBANDCFG=?
-	 * For example, my SIM card carrier "NB-iot" supports B8.  I will configure +CBANDCFG= "Nb-iot ",8
-	 */
-	modem.sendAT("+CBANDCFG=\"CAT-M\",8 ");
-	if (modem.waitResponse(10000L) != 1) {
-		DBG(" +CBANDCFG=\"NB-IOT\" ");
-	}
-	delay(200);
-
-	modem.sendAT("+CFUN=1 ");
-	if (modem.waitResponse(10000L) != 1) {
-		DBG(" +CFUN=1  false ");
-	}
-	delay(200);
-
-	//	modem.disableGPS();
-	delay(200);
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "%s\n", String(modem.getSignalQuality()).c_str());
-	delay(200);
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Trying to connect to network\n");
-	modem.gprsConnect(apn, gprsUser, gprsPass);
-	delay(200);
-	syncUTCTimeToRTC();
-
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Waiting for network...\n");
-	if (!modem.isNetworkConnected()) {
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Network not connected\n");
-		return;
-	} else {
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Network connected\n");
-		delay(200);
-
-		// quality
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "%s\n", String(modem.getSignalQuality()).c_str());
-		// make a http post request
-		String url = "influxdb.qe-forte.uibk.ac.at";
-		String path = "/api/v2/write?org=QE&bucket=esp32test&precision=s";
-		Serial.print("Connecting to ");
-		esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "%s\n", url.c_str());
-		// Use WiFiClient class to create TCP connections
-
-		while (!queue.empty()) {
-
-			xSemaphoreTake(xMutex, portMAX_DELAY);
-			String lineData = queue.front();
-			queue.pop();
-			xSemaphoreGive(xMutex);
-
-			RequestInformation requestInformation{.method = "POST", .host = url, .path = path, .body = lineData};
-
-			//"sensorName":"DRS26","timestamp":1666872216,"protocol":"I2C","value":0,"channel":0,"measurementType":"CIRCUMFERENCE_INCREMENT"
-
-			String request = buildRequest(requestInformation);
-			esp_log_write(ESP_LOG_VERBOSE, TAG.c_str(), "request: %s\n", request.c_str());
-
-			TinyGsmClient client{modem};
-			const int httpPort = 80;
-			if (!client.connect(url.c_str(), httpPort)) {
-				esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "connection failed\n");
-				return;
-			}
-
-			client.print(request);
-
-			// print response
-			while (client.connected()) {
-				String line = client.readStringUntil('\n');
-				if (line == "\r") {
-					esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "headers received\n");
-					break;
-				}
-			}
-			client.stop();
-			delay(1000);
-		}
-		DBG("Network connected");
-	}
-
-#if TINY_GSM_POWERDOWN
-	// Try to power-off (modem may decide to restart automatically)
-	// To turn off modem completely, please use Reset/Enable pins
-	//	modem.sendAT("+CPOWD=1");
-	//	if (modem.waitResponse(10000L) != 1) {
-	//		DBG("+CPOWD=1");
-	//	}
-	//	modem.poweroff();
-	esp_log_write(ESP_LOG_DEBUG, TAG.c_str(), "Poweroff.");
-#endif
+void loop() {
+
+    connectionManager.modemPowerOn();
+    delay(5000L);
+
+    // Restart takes quite some time
+    // To skip it, call init() instead of restart()
+    connectionManager.restartModem();
+
+    announce.time = rtc.getEpoch();
+    if (esp_now_send(BROADCAST_MAC, (uint8_t *)&announce, sizeof(announce)) != ESP_OK) {
+        esp_log_write(ESP_LOG_WARN, TAG_ESPNOW, "Failed to announce mac\n");
+    } else {
+        esp_log_write(ESP_LOG_DEBUG, TAG_ESPNOW, "Mac announced!\n");
+    }
+
+    connectionManager.disableGPS();
+
+    try {
+        // may fail if already set
+        connectionManager.setNetworkMode(LTE_ONLY);
+        connectionManager.setPreferredMode(CAT_M);
+    } catch (const std::exception &e) {
+        esp_log_write(ESP_LOG_ERROR, TAG_GSM, "Error setting network mode: %s\n", e.what());
+    }
+
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Modem Name: %s\n", modem.getModemName().c_str());
+
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Modem Info: %s\n", modem.getModemInfo().c_str());
+
+    connectionManager.unlockSimCard();
+
+    if (!connectionManager.waitForNetwork()) {
+        esp_log_write(ESP_LOG_ERROR, TAG_GSM, "Network not available\n");
+        return;
+    }
+
+    if (connectionManager.isNetworkConnected()) {
+        esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Network connected\n");
+    }
+
+    delay(200);
+
+    esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Connecting to %s\n", gprsCredentials.apn.c_str());
+    if (!connectionManager.gprsConnect()) {
+        esp_log_write(ESP_LOG_ERROR, TAG_GSM, "GPRS not connected\n");
+        return;
+    }
+
+    connectionManager.logModemInformation();
+
+    ntpManager.syncNTP("pool.ntp.org", "at.pool.ntp.org");
+
+    try {
+        ntpManager.writeModemTimeToRTC();
+    } catch (const std::exception &e) {
+        esp_log_write(ESP_LOG_ERROR, TAG_GSM, "Error writing time to rtc: %s\n", e.what());
+    }
+
+    if (connectionManager.isGprsConnected()) {
+        esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "GPRS connected\n");
+        // make list of map of headers
+        std::map<String, String> headers;
+        headers["Content-Type"] = "application/json";
+        headers["Accept"] = "application/json";
+
+        xSemaphoreTake(xMutex, portMAX_DELAY);
+        String data = queue.front();
+        queue.pop();
+        xSemaphoreGive(xMutex);
+
+        RequestInformation requestInformation{POST, "influxdb.qe-forte.uibk.ac.at",
+                                              80,   "/api/v2/write?org=QE&bucket=esp32test&precision=s",
+                                              data, headers};
+
+        try {
+            connectionManager.connect(requestInformation);
+        } catch (const std::exception &e) {
+            esp_log_write(ESP_LOG_ERROR, TAG_GSM, "Error sending data: %s\n", e.what());
+        }
+        esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "Data sent: %s\n", data.c_str());
+    }
+
+    connectionManager.gprsDisconnect();
+    delay(5000L);
+
+    if (!modem.isGprsConnected()) {
+        esp_log_write(ESP_LOG_DEBUG, TAG_GSM, "GPRS disconnected\n");
+    } else {
+        esp_log_write(ESP_LOG_ERROR, TAG_GSM, "GPRS not disconnected\n");
+    }
+
+    connectionManager.modemPowerOff();
 
-	delay(1000);
+    delay(10000);
 }
diff --git a/host/host_central_mast/src/main2.cpp b/host/host_central_mast/src/main2.cpp
deleted file mode 100644
index aaedda0570dfadd1926531602f0c527d39e6f870..0000000000000000000000000000000000000000
--- a/host/host_central_mast/src/main2.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-///**************************************************************
-// *
-// * This sketch connects to a website and downloads a page.
-// * It can be used to perform HTTP/RESTful API calls.
-// *
-// * TinyGSM Getting Started guide:
-// *   https://tiny.cc/tinygsm-readme
-// *
-// **************************************************************/
-//
-//// Select your modem:
-//// #define TINY_GSM_MODEM_SIM800
-//// #define TINY_GSM_MODEM_SIM808
-//// #define TINY_GSM_MODEM_SIM868
-//// #define TINY_GSM_MODEM_SIM900
-//#define TINY_GSM_MODEM_SIM7000
-//// #define TINY_GSM_MODEM_SIM7000SSL
-//// #define TINY_GSM_MODEM_SIM7080
-//// #define TINY_GSM_MODEM_SIM5360
-//// #define TINY_GSM_MODEM_SIM7600
-//// #define TINY_GSM_MODEM_UBLOX
-//// #define TINY_GSM_MODEM_SARAR4
-//// #define TINY_GSM_MODEM_M95
-//// #define TINY_GSM_MODEM_BG96
-//// #define TINY_GSM_MODEM_A6
-//// #define TINY_GSM_MODEM_A7
-//// #define TINY_GSM_MODEM_M590
-//// #define TINY_GSM_MODEM_MC60
-//// #define TINY_GSM_MODEM_MC60E
-//// #define TINY_GSM_MODEM_ESP8266
-//// #define TINY_GSM_MODEM_XBEE
-//// #define TINY_GSM_MODEM_SEQUANS_MONARCH
-//
-//// Set serial for debug console (to the Serial Monitor, default speed 115200)
-//#define SerialMon Serial
-//
-//// Set serial for AT commands (to the module)
-//// Use Hardware Serial on Mega, Leonardo, Micro
-//#ifndef __AVR_ATmega328P__
-//#define SerialAT Serial1
-//
-//// or Software Serial on Uno, Nano
-//#else
-//#include <SoftwareSerial.h>
-//SoftwareSerial SerialAT(2, 3); // RX, TX
-//#endif
-//
-//// Increase RX buffer to capture the entire response
-//// Chips without internal buffering (A6/A7, ESP8266, M590)
-//// need enough space in the buffer for the entire response
-//// else data will be lost (and the http library will fail).
-//#if !defined(TINY_GSM_RX_BUFFER)
-//#define TINY_GSM_RX_BUFFER 650
-//#endif
-//
-//// See all AT commands, if wanted
-//#define DUMP_AT_COMMANDS
-//
-//// Define the serial console for debug prints, if needed
-//#define TINY_GSM_DEBUG SerialMon
-//
-//// Range to attempt to autobaud
-//// NOTE:  DO NOT AUTOBAUD in production code.  Once you've established
-//// communication, set a fixed baud rate using modem.setBaud(#).
-//#define GSM_AUTOBAUD_MIN 9600
-//#define GSM_AUTOBAUD_MAX 115200
-//
-//// Add a reception delay, if needed.
-//// This may be needed for a fast processor at a slow baud rate.
-//// #define TINY_GSM_YIELD() { delay(2); }
-//
-//// Uncomment this if you want to use SSL
-//// #define USE_SSL
-//
-//// Define how you're planning to connect to the internet.
-//// This is only needed for this example, not in other code.
-//#define TINY_GSM_USE_GPRS true
-//#define TINY_GSM_USE_WIFI false
-//
-//// set GSM PIN, if any
-//#define GSM_PIN ""
-//
-//// Your GPRS credentials, if any
-//const char apn[] = "m2m.public.at";
-//const char gprsUser[] = "";
-//const char gprsPass[] = "";
-//
-//// Your WiFi connection credentials, if applicable
-//const char wifiSSID[] = "YourSSID";
-//const char wifiPass[] = "YourWiFiPass";
-//
-//// Server details
-//const char server[] = "vsh.pp.ua";
-//const char resource[] = "/TinyGSM/logo.txt";
-//
-//#include <TinyGsmClient.h>
-//#include <WiFi.h>
-//#include <esp_now.h>
-//
-//// Just in case someone defined the wrong thing..
-//#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS
-//#undef TINY_GSM_USE_GPRS
-//#undef TINY_GSM_USE_WIFI
-//#define TINY_GSM_USE_GPRS false
-//#define TINY_GSM_USE_WIFI true
-//#endif
-//#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI
-//#undef TINY_GSM_USE_GPRS
-//#undef TINY_GSM_USE_WIFI
-//#define TINY_GSM_USE_GPRS true
-//#define TINY_GSM_USE_WIFI false
-//#endif
-//
-//#ifdef DUMP_AT_COMMANDS
-//#include <StreamDebugger.h>
-//StreamDebugger debugger(SerialAT, SerialMon);
-//TinyGsm modem(debugger);
-//#else
-//TinyGsm modem(SerialAT);
-//#endif
-//
-//#ifdef USE_SSL
-//TinyGsmClientSecure client_satellite(modem);
-//const int port = 443;
-//#else
-//TinyGsmClient client_satellite(modem);
-//const int port = 80;
-//#endif
-//
-//TaskHandle_t Task1;
-//
-//void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status)
-//{
-//	// go to sleep
-//}
-//
-//void on_data_recv(const uint8_t *mac, const uint8_t *incomingData, int len)
-//{
-//	// print mac
-//	Serial.print("Message recieved: Core ");
-//	Serial.println(xPortGetCoreID());
-//	for (int i = 0; i < 6; i++) {
-//		Serial.print(mac[i], HEX);
-//		Serial.print(":");
-//	}
-//	Serial.println();
-//	char data[len];
-//	memcpy(data, incomingData, len);
-//	Serial.println(data);
-//}
-//
-//[[noreturn]] void Task1code(void *parameter)
-//{
-//	Serial.println(xPortGetCoreID());
-//	WiFi.mode(WIFI_STA);
-//	Serial.println("ESPNow init");
-//	if (esp_now_init() != ESP_OK) {
-//		// initialization failed
-//		Serial.println("ESPNow init failed");
-//		// not sure about this
-//	}
-//	Serial.println("ESPNow init success");
-//	esp_now_register_recv_cb(on_data_recv);
-//
-//	while (true) {
-//	}
-//}
-//
-//void setup2()
-//{
-//	// Set console baud rate
-//	SerialMon.begin(115200);
-//	delay(10);
-//
-//	// !!!!!!!!!!!
-//	// Set your reset, enable, power pins here
-//	// !!!!!!!!!!!
-//
-//	SerialMon.println("Wait...");
-//
-//	xTaskCreatePinnedToCore(Task1code, /* Function to implement the task */
-//	                        "Task1",   /* Name of the task */
-//	                        10000,     /* Stack size in words */
-//	                        nullptr,   /* Task input parameter */
-//	                        0,         /* Priority of the task */
-//	                        &Task1,    /* Task handle. */
-//	                        0);        /* Core where the task should run */
-//
-//	// Set GSM module baud rate
-//	//	TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX);
-//	modem.setBaud(9600);
-//	// SerialAT.begin(9600);
-//	delay(6000);
-//
-//	// Restart takes quite some time
-//	// To skip it, call init() instead of restart()
-//	SerialMon.println("Initializing modem...");
-//	modem.restart();
-//	// modem.init();
-//
-//	String modemInfo = modem.getModemInfo();
-//	SerialMon.print("Modem Info: ");
-//	SerialMon.println(modemInfo);
-//
-//#if TINY_GSM_USE_GPRS
-//	// Unlock your SIM card with a PIN if needed
-//	if (GSM_PIN && modem.getSimStatus() != 3) {
-//		modem.simUnlock(GSM_PIN);
-//	}
-//#endif
-//}
-//
-//void loop2()
-//{
-//#if TINY_GSM_USE_WIFI
-//	// Wifi connection parameters must be set before waiting for the network
-//	SerialMon.print(F("Setting SSID/password..."));
-//	if (!modem.networkConnect(wifiSSID, wifiPass)) {
-//		SerialMon.println(" fail");
-//		delay(10000);
-//		return;
-//	}
-//	SerialMon.println(" success");
-//#endif
-//
-//#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE
-//	// The XBee must run the gprsConnect function BEFORE waiting for network!
-//	modem.gprsConnect(apn, gprsUser, gprsPass);
-//#endif
-//
-//	SerialMon.print("Waiting for network...");
-//	if (!modem.waitForNetwork()) {
-//		SerialMon.println(" fail");
-//		delay(10000);
-//		return;
-//	}
-//	SerialMon.println(" success");
-//
-//	if (modem.isNetworkConnected()) {
-//		SerialMon.println("Network connected");
-//	}
-//
-//#if TINY_GSM_USE_GPRS
-//	// GPRS connection parameters are usually set after network registration
-//	SerialMon.print(F("Connecting to "));
-//	SerialMon.print(apn);
-//	if (!modem.gprsConnect(apn, gprsUser, gprsPass)) {
-//		SerialMon.println(" fail");
-//		delay(10000);
-//		return;
-//	}
-//	SerialMon.println(" success");
-//
-//	if (modem.isGprsConnected()) {
-//		SerialMon.println("GPRS connected");
-//	}
-//#endif
-//
-//	SerialMon.print("Connecting to ");
-//	SerialMon.println(server);
-//	if (!client_satellite.connect(server, port)) {
-//		SerialMon.println(" fail");
-//		delay(10000);
-//		return;
-//	}
-//	SerialMon.println(" success");
-//
-//	// Make a HTTP GET request:
-//	SerialMon.println("Performing HTTP GET request...");
-//	client_satellite.print(String("GET ") + resource + " HTTP/1.1\r\n");
-//	client_satellite.print(String("Host: ") + server + "\r\n");
-//	client_satellite.print("Connection: close\r\n\r\n");
-//	client_satellite.println();
-//
-//	uint32_t timeout = millis();
-//	while (client_satellite.connected() && millis() - timeout < 10000L) {
-//		// Print available data
-//		while (client_satellite.available()) {
-//			char c = client_satellite.read();
-//			SerialMon.print(c);
-//			timeout = millis();
-//		}
-//	}
-//	SerialMon.println();
-//
-//	// Shutdown
-//
-//	client_satellite.stop();
-//	SerialMon.println(F("Server disconnected"));
-//
-//#if TINY_GSM_USE_WIFI
-//	modem.networkDisconnect();
-//	SerialMon.println(F("WiFi disconnected"));
-//#endif
-//#if TINY_GSM_USE_GPRS
-//	modem.gprsDisconnect();
-//	SerialMon.println(F("GPRS disconnected"));
-//#endif
-//
-//	// Do nothing forevermore
-//	while (true) {
-//		delay(1000);
-//	}
-//}