Creating a Server Status Monitoring System with NodeMCU

Creating a Server Status Monitoring System with NodeMCU

Creating a Server Status Monitoring System with NodeMCU


Hello dear readers! In this article, we will show step by step how to build a server status monitoring system integrated with a Telegram bot using the NodeMCU development board. This system provides a useful tool for monitoring the status of your server ports and getting notifications whenever a port crashes. Let’s get started!

Summary

Requirements

NodeMCU (ESP8266 based)
Access to a WiFi network
Telegram account and bot token
Arduino IDE and necessary libraries (ESP8266WiFi, UniversalTelegramBot, LittleFS, ArduinoJson)

Step 1: Downloading Required Libraries

First, open the Arduino IDE and download the required libraries. We will need the ESP8266WiFi, UniversalTelegramBot, LittleFS, and ArduinoJson libraries. After adding these libraries to the Arduino IDE, we can start our project.

Step 2: Creating a Telegram Bot

To create your Telegram bot, contact BotFather and create a new bot. After creating your bot, you will be given a bot token. Save this token for use later.

Step 3: Preparing the Code

Paste the above code by creating a new project in Arduino IDE. Update the necessary information inside the code for your WiFi network and your Telegram bot token.

Step 4: Main Control Loop

The main control loop of the code performs two important functions: listening for Telegram messages and monitoring server status. While processing Telegram messages, commands such as /start, /commands, /register send helpful messages to the user. While monitoring server status, the statuses of servers are checked at regular intervals and a notification is sent to the user for crashed ports.

Step 5: Saving Server Information

The server information added by users is kept in JSON format on LittleFS. Thus, even if the device is restarted, the information is not lost.

Step 6: Bot Statistics

The code shows the number of registered accounts and the total number of servers when the /stats command is used.

Step 7: Compiling and Uploading

Compile the code in the Arduino IDE and upload it to the NodeMCU. Then connect the NodeMCU to a power source and make sure your code is running.

Result

Now we have created our own server status monitoring system! Your NodeMCU device will continuously monitor the ports of your servers and notify you if there is any problem. This project provides great convenience in server management and monitoring.

Project Codes and Description

Urhoba.h - Codes

#ifndef URHOBA_H
#define URHOBA_H

#include <arduino .h="">
#include <vector>
#include <wificlient .h="">
#include <ipaddress .h="">

class UrhobaServer{
    private:
        String serverName;
        IPAddress targetIP;
        int targetPort;
    public:
        UrhobaServer(String server_name, IPAddress ip_address, int port);
        IPAddress getTargetIP() const;
        int getTargetPort() const;
        String getServerName() const;
        bool getServerStatus() const;
        void setServerName(String server_name);
        void setServerPort(int port);
        void setServerIp(IPAddress ip_address);
};

class Account{
    private:
        String chatId;
        std::vector<urhobaserver> servers;
    public:
        Account(String chat_id);
        String getChatId();
        bool addServer(String server_name, IPAddress ip_address, int port);
        bool removeServer(int server_index);
        UrhobaServer getServer(int server_index);
        UrhobaServer& getServerRef(int server_index);
        std::vector<urhobaserver> getAllServers();
        std::vector<urhobaserver> getAllServersConst() const;
};  

class AccountManager{
    private:
        std::vector<account> accounts;
    public:
        bool addAccount(String chat_id);
        bool removeAccount(String chat_id);
        Account* findAccount(String chat_id);
        std::vector<account> getAccounts();
};

#endif

Explanation of the Codes

UrhobaServer Class

This class creates a server object that holds the information of the server to be monitored. Here are the properties and functions of this class:

  • In the private section, member variables named serverName, targetIP and targetPort are located. These variables hold the server name, target IP address, and target port.
  • The UrhobaServer constructor receives the necessary information when creating a server object.
  • getTargetIP(), getTargetPort(), and getServerName() return the relevant server properties.
  • getServerStatus() determines the status of the server (running/crashed).
  • setServerName(String server_name), setServerPort(int port), and setServerIp(IPAddress ip_address) update the relevant server properties.

Account Class

This class manages the servers owned by the user and their related operations. Here are the properties and functions of this class:

In the private section, member variables named chatId and servers exist. chatId keeps the Telegram chat ID of the user. servers keeps a vector of UrhobaServer that contains the servers added by the user.
Account constructor receives the chat ID when creating the user's account.
getChatId() returns the user's chat ID.
addServer(String server_name, IPAddress ip_address, int port) adds a new server to the user's account.
removeServer(int server_index) removes the server at the specified index from the account.
getServer(int server_index) returns the information of the server at the given index.
getServerRef(int server_index) returns a reference to the server at the specified index (used for updating).
getAllServers() returns all servers added to the account.
getAllServersConst() returns all servers added to the account for read-only purposes.

AccountManager Class

This class manages all user accounts. Here are the properties and functions of this class:

In the private section, there is a vector named accounts of Account. This vector holds all user accounts.
addAccount(String chat_id) adds a new user account.
removeAccount(String chat_id) removes the user account with the specified chat ID.
findAccount(String chat_id) finds and returns the user account with the specified chat ID.
getAccounts() returns all user accounts.

Urhoba.cpp - Codes

#include "urhoba.h"

UrhobaServer::UrhobaServer(String server_name, IPAddress ip_address, int port) {
  serverName = server_name;
  targetIP = ip_address;
  targetPort = port;
}

IPAddress UrhobaServer::getTargetIP() const {
  return targetIP;
}

int UrhobaServer::getTargetPort() const {
  return targetPort;
}

String UrhobaServer::getServerName() const {
  return serverName;
}

bool UrhobaServer::getServerStatus() const {
  WiFiClient client;

  IPAddress serverIP = targetIP;
  int serverPort = targetPort;

  int maxAttempts = 3;
  unsigned long startTime = millis();
  for (int i = 0; i < maxAttempts; i++) {
    if (client.connect(serverIP, serverPort)) {
      client.stop();
      return true;
    }
    unsigned long currentTime = millis();
    if (currentTime - startTime >= 100) {
      startTime = currentTime;
    }
  }
  return false;
}

void UrhobaServer::setServerName(String server_name) {
  serverName = server_name;
}

void UrhobaServer::setServerPort(int port) {
  targetPort = port;
}

void UrhobaServer::setServerIp(IPAddress ip_address) {
  targetIP = ip_address;
}

Account::Account(String chat_id) {
  chatId = chat_id;
}

String Account::getChatId() {
  return chatId;
}

bool Account::addServer(String server_name, IPAddress ip_address, int port) {
  for (const UrhobaServer& server : servers) {
    if (server.getTargetIP() == ip_address && server.getTargetPort() == port) {
      return false;
    }
  }
  UrhobaServer addedServer(server_name, ip_address, port);
  servers.push_back(addedServer);
  return true;
}

bool Account::removeServer(int server_index) {
  if (server_index >= 0 && server_index < servers.size()) {
    servers.erase(servers.begin() + server_index);
    return true;
  }
  return false;
}

UrhobaServer Account::getServer(int server_index) {
  if (server_index >= 0 && server_index < servers.size()) {
    return servers[server_index];
  }
  return UrhobaServer("", IPAddress(0, 0, 0, 0), 0);
}

UrhobaServer& Account::getServerRef(int server_index) {
  if (server_index >= 0 && server_index < servers.size()) {
    return servers[server_index];
  }
  static UrhobaServer dummyServer("", IPAddress(0, 0, 0, 0), 0);
  return dummyServer;
}

std::vector<urhobaserver> Account::getAllServers() {
  return servers;
}

std::vector<urhobaserver> Account::getAllServersConst() const {
  return servers;
}


bool AccountManager::addAccount(String chat_id) {
  bool accountExists = false;
  for (int i = 0; i < accounts.size(); i++) {
    if (accounts[i].getChatId() == chat_id) {
      accountExists = true;
      break;
    }
  }

  if (!accountExists) {
    accounts.push_back(Account(chat_id));
    return true;
  } else {
    return false;
  }
}

bool AccountManager::removeAccount(String chat_id) {
  for (int i = 0; i < accounts.size(); i++) {
    if (accounts[i].getChatId() == chat_id) {
      accounts.erase(accounts.begin() + i);
      return true;
    }
  }

  return false;
}

Account* AccountManager::findAccount(String chat_id) {
  for (int i = 0; i < accounts.size(); i++) {
    if (accounts[i].getChatId() == chat_id) {
      return &accounts[i];
    }
  }
  return nullptr;
}

std::vector<account> AccountManager::getAccounts() {
  return accounts;
}

Explanation of the Codes

UrhobaServer Class Implementation
This section contains the implementation of the member functions and constructor of the UrhobaServer class.

  • UrhobaServer::UrhobaServer(String server_name, IPAddress ip_address, int port) constructor receives the necessary information while creating a server object and sets the member variables.
  • UrhobaServer::getTargetIP() const member function returns the target IP address of the server.
  • UrhobaServer::getTargetPort() const member function returns the target port of the server.
  • UrhobaServer::getServerName() const member function returns the server's name.
  • UrhobaServer::getServerStatus() const member function checks the server's status (running/crashed). This function connects to the server and checks the status.
  • UrhobaServer::setServerName(String server_name) member function updates the server's name.
  • UrhobaServer::setServerPort(int port) member function updates the server's port.
  • UrhobaServer::setServerIp(IPAddress ip_address) member function updates the server's IP address.

Account Class Implementation
This section includes the implementation of the member functions and constructor of the Account class.

  • Account::Account(String chat_id) constructor receives the chat ID while creating a user account and sets the relevant member variable.
  • Account::getChatId() member function returns the user's chat ID.
  • Account::addServer(String server_name, IPAddress ip_address, int port) member function adds a new server to the user's account.
  • Account::removeServer(int server_index) member function removes the server at the specified index from the account.
  • Account::getServer(int server_index) member function returns the information of the server at the specified index.
  • Account::getServerRef(int server_index) member function returns a reference to the server at the specified index (used for updating).
  • Account::getAllServers() member function returns all servers added to the account.
  • Account::getAllServersConst() const member function returns all servers added to the account for read-only purposes.

AccountManager Class Implementation
This section contains the implementation of the AccountManager class's member functions.

  • AccountManager::addAccount(String chat_id) member function adds a new user account.
  • AccountManager::removeAccount(String chat_id) member function removes the user account with the specified chat ID.
  • AccountManager::findAccount(String chat_id) member function finds and returns the user account with the specified chat ID.
  • AccountManager::getAccounts() member function returns all user accounts.

server_checker.ino - Codes

// www.urhoba.net
#include <esp8266wifi .h="">
#include <wificlient .h="">
#include <wificlientsecure .h="">
#include <universaltelegrambot .h="">
#include <littlefs .h="">
#include <arduinojson .h="">
#include "urhoba.h"

#define WIFI_SSID "wifi_ssid"
#define WIFI_PASSWORD "wifi_sifresi"
#define BOT_TOKEN "telegram_bot_token"

AccountManager accountManager;

const unsigned long BOT_MTBS = 1000;

X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);
unsigned long bot_lasttime;

const unsigned long SERVER_CHECK_INTERVAL = 1800000;
unsigned long lastServerCheckTime = 0;

// Start messages
void startMessage(String chat_id, String text, String from_name) {
  if (text == "/start") {
    String msg = "Welcome to UrhobA Server Checker System, " + from_name + ".\n\n";
    msg += "This system is designed to help you monitor the status of your server's ports. If any port crashes, you'll receive a notification message to keep you informed.\n";
    msg += "To get started, use the /commands command to see a list of available commands.\n";
    msg += "Feel free to explore the system and manage your server effortlessly!";
    bot.sendMessage(chat_id, msg, "");
  }
}

// Command messages
void commandsMessage(String chat_id, String text, String from_name) {
  if (text == "/commands") {
    String msg = "Hello " + from_name + "!\n\n";
    msg += "Available commands:\n\n";
    msg += "Account:\n";
    msg += "/register: Create a new account.\n";
    msg += "/remove: Remove your account.\n\n";
    msg += "Server:\n";
    msg += "/add_server [ip] [port] [name]: Add a new server.\n";
    msg += "/remove_server [index]: Remove a server.\n";
    msg += "/get_server [index]: Get server details.\n";
    msg += "/set_server_name [index] [name]: Set server name.\n";
    msg += "/set_server_port [index] [port]: Set server port.\n";
    msg += "/set_server_ip [index] [ip]: Set server ip.\n";
    msg += "/get_servers: List all your servers.\n";
    msg += "/stats: Show bot stats.\n\n";
    msg += "For more info or help, feel free to ask. Enjoy using our system!";
    bot.sendMessage(chat_id, msg, "");
  }
}

// Create account
void registerCommand(String chat_id, String text, String from_name) {
  if (text == "/register") {
    String msg = "Your registration request has been received.";

    bot.sendMessage(chat_id, msg, "");

    bool accountAdded = accountManager.addAccount(chat_id);

    if (accountAdded) {
      msg = "You have successfully registered!";
      saveAccountsToFile();
    } else {
      msg = "Your account already exists!";
    }

    bot.sendMessage(chat_id, msg, "");
  }
}

// Remove account
void removeCommand(String chat_id, String text, String from_name) {
  if (text == "/remove") {
    String msg = "Removing your account.";

    bot.sendMessage(chat_id, msg, "");

    bool found = accountManager.removeAccount(chat_id);

    if (found) {
      msg = "Your account has been removed.";
      saveAccountsToFile();
    } else {
      msg = "Your account does not exist.";
    }

    bot.sendMessage(chat_id, msg, "");
  }
}

// Add server to account
void addServerCommand(String chat_id, String text, String from_name) {
  if (text.startsWith("/add_server")) {
    String msg;

    // Extracting server address, port, and server name from text
    int ip_start = text.indexOf("[") + 1;
    int ip_end = text.indexOf("]");
    int port_start = text.indexOf("[", ip_end) + 1;
    int port_end = text.indexOf("]", port_start);
    int name_start = text.indexOf("[", port_end) + 1;
    int name_end = text.indexOf("]", name_start);

    if (ip_start != -1 && ip_end != -1 && port_start != -1 && port_end != -1 && name_start != -1 && name_end != -1) {
      String server_address = text.substring(ip_start, ip_end);
      String port_str = text.substring(port_start, port_end);
      String server_name = text.substring(name_start, name_end);

      IPAddress ip_address;
      if (WiFi.hostByName(server_address.c_str(), ip_address)) {
        int port = port_str.toInt();

        Account* account = accountManager.findAccount(chat_id);
        if (account) {
          bool serverAdded = account->addServer(server_name, ip_address, port);
          if (serverAdded) {
            msg = "Server added successfully!";
            saveAccountsToFile();
          } else {
            msg = "Server with the same IP address and port already exists.";
          }
        } else {
          msg = "Account not found. Please register first.";
        }
      } else {
        msg = "Invalid server address.";
      }
    } else {
      msg = "Invalid format. Please use /add_server [server_address] [port] [server_name].";
    }

    bot.sendMessage(chat_id, msg, "");
  }
}

// Remove server to account
void removeServerCommand(String chat_id, String text, String from_name) {
  if (text.startsWith("/remove_server")) {
    String msg;

    int index_start = text.indexOf("[") + 1;
    int index_end = text.indexOf("]");

    if (index_start != -1 && index_end != -1) {
      String index_str = text.substring(index_start, index_end);

      int server_index = index_str.toInt();

      Account* account = accountManager.findAccount(chat_id);

      if (account) {
        bool serverRemoved = account->removeServer(server_index);

        if (serverRemoved) {
          msg = "Server removed successfully!";
          saveAccountsToFile();
        } else {
          msg = "Invalid server index.";
        }
      } else {
        msg = "Account not found. Please register first.";
      }
    } else {
      msg = "Invalid format. Please use /remove_server [server_index].";
    }

    bot.sendMessage(chat_id, msg, "");
  }
}

// Get server
void getServerCommand(String chat_id, String text, String from_name) {
  if (text.startsWith("/get_server")) {
    int server_index = -1;
    int index_start = text.indexOf("[") + 1;
    int index_end = text.indexOf("]");

    if (index_start != -1 && index_end != -1) {
      String index_str = text.substring(index_start, index_end);
      server_index = index_str.toInt();
    }

    if (server_index >= 0) {
      Account* account = accountManager.findAccount(chat_id);

      if (account) {
        UrhobaServer server = account->getServer(server_index);

        if (server.getTargetPort() != -1) {
          String msg = "Here is server number " + String(server_index) + ":\n";
          msg += "Name: " + server.getServerName() + "\n";
          msg += "IP: " + server.getTargetIP().toString() + "\n";
          msg += "Port: " + String(server.getTargetPort()) + "\n";
          msg += "Status: " + String(server.getServerStatus()) + "\n";

          bot.sendMessage(chat_id, msg, "");
        } else {
          bot.sendMessage(chat_id, "Invalid server index.", "");
        }
      } else {
        bot.sendMessage(chat_id, "Account not found. Please register first.", "");
      }
    } else {
      bot.sendMessage(chat_id, "Invalid server index format. Please use /get_server [server_index].", "");
    }
  }
}

// Get all servers
void getServersCommand(String chat_id, String text, String from_name) {
  if (text == "/get_servers") {
    Account* account = accountManager.findAccount(chat_id);

    if (account) {
      std::vector<urhobaserver> servers = account->getAllServers();

      if (!servers.empty()) {
        String msg = "Here are your added servers:\n\n";

        for (int i = 0; i < servers.size(); i++) {
          UrhobaServer server = servers[i];
          msg += "[" + String(i) + "] Name: " + server.getServerName() + ", Status: " + String(server.getServerStatus()) + "\n";
        }

        msg += "\nTo view details of a specific server, use /get_server [server_index].";
        bot.sendMessage(chat_id, msg, "");
      } else {
        bot.sendMessage(chat_id, "You haven't added any servers yet.", "");
      }
    } else {
      bot.sendMessage(chat_id, "Account not found. Please register first.", "");
    }
  }
}

// Bot stats
void statsCommand(String chat_id, String text) {
  if (text == "/stats") {
    int numAccounts = accountManager.getAccounts().size();
    int totalServers = 0;

    for (const Account& account : accountManager.getAccounts()) {
      totalServers += account.getAllServersConst().size();
    }

    String msg = "Total accounts: " + String(numAccounts) + "\n";
    msg += "Total servers: " + String(totalServers);

    bot.sendMessage(chat_id, msg, "");
  }
}

// Set server name
void setServerNameCommand(String chat_id, String text) {
  if (text.startsWith("/set_server_name")) {
    int index_start = text.indexOf("[") + 1;
    int index_end = text.indexOf("]");
    int name_start = text.indexOf("[", index_end) + 1;
    int name_end = text.indexOf("]", name_start);

    if (index_start != -1 && index_end != -1 && name_start != -1 && name_end != -1) {
      String index_str = text.substring(index_start, index_end);
      String server_name = text.substring(name_start, name_end);

      int server_index = index_str.toInt();

      Account* account = accountManager.findAccount(chat_id);

      if (account) {
        UrhobaServer& server = account->getServerRef(server_index);
        server.setServerName(server_name);
        bot.sendMessage(chat_id, "Server name updated successfully!", "");
        saveAccountsToFile();
      } else {
        bot.sendMessage(chat_id, "Account not found. Please register first.", "");
      }
    } else {
      bot.sendMessage(chat_id, "Invalid format. Please use /set_server_name [server_index] [server_name].", "");
    }
  }
}

// Set server port
void setServerPortCommand(String chat_id, String text) {
  if (text.startsWith("/set_server_port")) {
    int index_start = text.indexOf("[") + 1;
    int index_end = text.indexOf("]");
    int port_start = text.indexOf("[", index_end) + 1;
    int port_end = text.indexOf("]", port_start);

    if (index_start != -1 && index_end != -1 && port_start != -1 && port_end != -1) {
      String index_str = text.substring(index_start, index_end);
      String port_str = text.substring(port_start, port_end);

      int server_index = index_str.toInt();
      int new_port = port_str.toInt();

      Account* account = accountManager.findAccount(chat_id);

      if (account) {
        UrhobaServer& server = account->getServerRef(server_index);
        server.setServerPort(new_port);
        bot.sendMessage(chat_id, "Server port updated successfully!", "");
        saveAccountsToFile();
      } else {
        bot.sendMessage(chat_id, "Account not found. Please register first.", "");
      }
    } else {
      bot.sendMessage(chat_id, "Invalid format. Please use /set_server_port [server_index] [new_port].", "");
    }
  } 
}

// Set server ip
void setServerIpCommand(String chat_id, String text) {
  if (text.startsWith("/set_server_ip")) {
    int index_start = text.indexOf("[") + 1;
    int index_end = text.indexOf("]");
    int ip_start = text.indexOf("[", index_end) + 1;
    int ip_end = text.indexOf("]", ip_start);

    if (index_start != -1 && index_end != -1 && ip_start != -1 && ip_end != -1) {
      String index_str = text.substring(index_start, index_end);
      String ip_str = text.substring(ip_start, ip_end);

      int server_index = index_str.toInt();
      IPAddress new_ip;

      if (WiFi.hostByName(ip_str.c_str(), new_ip)) {
        Account* account = accountManager.findAccount(chat_id);

        if (account) {
          UrhobaServer& server = account->getServerRef(server_index);
          server.setServerIp(new_ip);
          bot.sendMessage(chat_id, "Server IP updated successfully!", "");
          saveAccountsToFile();
        } else {
          bot.sendMessage(chat_id, "Account not found. Please register first.", "");
        }
      } else {
        bot.sendMessage(chat_id, "Invalid IP address.", "");
      }
    } else {
      bot.sendMessage(chat_id, "Invalid format. Please use /set_server_ip [server_index] [new_ip].", "");
    }
  }
}

// Handle to telegram messages
void handleNewMessages(int numNewMessages) {
  for (int i = 0; i < numNewMessages; i++) {
    String chat_id = bot.messages[i].chat_id;
    String text = bot.messages[i].text;
    String from_name = bot.messages[i].from_name;
    if (from_name == "")
      from_name = "Guest";

    startMessage(chat_id, text, from_name);
    commandsMessage(chat_id, text, from_name);
    registerCommand(chat_id, text, from_name);
    removeCommand(chat_id, text, from_name);
    addServerCommand(chat_id, text, from_name);
    statsCommand(chat_id, text);
    removeServerCommand(chat_id, text, from_name);
    setServerNameCommand(chat_id, text);
    setServerPortCommand(chat_id, text);
    setServerIpCommand(chat_id, text);
    if (text == "/get_servers")
      getServersCommand(chat_id, text, from_name);
    else
      getServerCommand(chat_id, text, from_name);
  }
}

// Check servers
void checkServers() {
  unsigned long currentTime = millis();

  if (currentTime - lastServerCheckTime >= SERVER_CHECK_INTERVAL) {
    lastServerCheckTime = currentTime;

    for (Account& account : accountManager.getAccounts()) {
      String chatId = account.getChatId();
      String msg = "Your servers:\n";

      for (int i = 0; i < account.getAllServers().size(); i++) {
        UrhobaServer& server = account.getServerRef(i);
        bool serverStatus = server.getServerStatus();

        if (!serverStatus) {
          msg += "Server IP: " + server.getTargetIP().toString() + ", Port: " + String(server.getTargetPort()) + " is down.\n";
        }
      }

      if (msg != "Your servers:\n") {
        bot.sendMessage(chatId, msg, "");
      }
    }
  }
}

// Save all accounts
void saveAccountsToFile() {
  File file = LittleFS.open("/accounts.json", "w");
  if (!file) {
    Serial.println("Error opening file for writing");
    return;
  }

  DynamicJsonDocument doc(1024);
  JsonArray accountsArray = doc.createNestedArray("accounts");

  for (Account& account : accountManager.getAccounts()) {
    JsonObject accountObject = accountsArray.createNestedObject();
    accountObject["chatId"] = account.getChatId();

    JsonArray serversArray = accountObject.createNestedArray("servers");
    std::vector<urhobaserver> servers = account.getAllServers();
    for (const UrhobaServer& server : servers) {
      JsonObject serverObject = serversArray.createNestedObject();
      serverObject["name"] = server.getServerName();
      serverObject["ip"] = server.getTargetIP().toString();
      serverObject["port"] = server.getTargetPort();
    }
  }

  serializeJson(doc, file);

  file.close();
}

// Get all accounts
void loadAccountsFromFile() {
  File file = LittleFS.open("/accounts.json", "r");
  if (!file) {
    Serial.println("Error opening file for reading");
    return;
  }

  DynamicJsonDocument doc(1024);
  DeserializationError error = deserializeJson(doc, file);

  if (error) {
    Serial.println("Error parsing JSON");
    return;
  }

  JsonArray accountsArray = doc["accounts"];

  for (const JsonVariant& accountVariant : accountsArray) {
    JsonObject accountObject = accountVariant.as<jsonobject>();
    String chatId = accountObject["chatId"].as<string>();
    accountManager.addAccount(chatId);

    JsonArray serversArray = accountObject["servers"];

    for (const JsonVariant& serverVariant : serversArray) {
      JsonObject serverObject = serverVariant.as<jsonobject>();
      String ipStr = serverObject["ip"].as<string>();
      String nameStr = serverObject["name"].as<string>();
      IPAddress ip;
      if (ip.fromString(ipStr)) {
        int port = serverObject["port"].as<int>();
        accountManager.findAccount(chatId)->addServer(nameStr, ip, port);
      }
    }
  }

  file.close();
}

void setup() {

  // WIFI & Telegram Begin

  Serial.begin(115200);
  Serial.println();

  Serial.print("Connecting to Wifi SSID ");
  Serial.print(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  secured_client.setTrustAnchors(&cert);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.print("\nWiFi connected. IP address: ");
  Serial.println(WiFi.localIP());

  Serial.print("Retrieving time: ");
  configTime(0, 0, "pool.ntp.org");
  time_t now = time(nullptr);
  while (now < 24 * 3600) {
    Serial.print(".");
    delay(100);
    now = time(nullptr);
  }
  Serial.println(now);

  // Save System Begin

  LittleFS.begin();

  loadAccountsFromFile();

  // OTA Begin
  //ArduinoOTA.begin();
}

void loop() {

  // Telegram Loop

  if (millis() - bot_lasttime > BOT_MTBS) {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

    while (numNewMessages) {
      Serial.println("Message sending");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }

    bot_lasttime = millis();
  }

  // Server Check Loop

  checkServers();

  // OTA Loop
  //ArduinoOTA.handle();
}

Explanation of the Codes



Including Libraries and Dependencies:
  • At the beginning of the code, the necessary libraries (ESP8266WiFi, WiFiClient, WiFiClientSecure, UniversalTelegramBot, LittleFS, ArduinoJson) and the "urhoba.h" header file from the previous code are included.
  • Then, WiFi network information (WIFI_SSID and WIFI_PASSWORD) and the Telegram bot token (BOT_TOKEN) are defined.

Creating AccountManager and Bot Objects:
  • An AccountManager object is created to manage user accounts.
  • Necessary objects are created for the Telegram bot (X509List, WiFiClientSecure, UniversalTelegramBot) and the bot token and secure connection are configured.

Time Variables:
  • BOT_MTBS specifies a certain time interval for the Telegram bot to check for updates.
  • SERVER_CHECK_INTERVAL specifies the interval at which server statuses will be checked.
  • bot_lasttime and lastServerCheckTime are used to store the last times.

Message Handling Functions:
  • startMessage creates and sends a welcome message in response to the /start command.
  • commandsMessage creates and sends a message with the available commands in response to the /commands command.
  • Other functions create and send respective messages for commands in similar ways.

User Account Operations:
  • registerCommand responds to the /register command and registers the user.
  • removeCommand responds to the /remove command and removes the user’s account.
  • addServerCommand responds to the /add_server command and adds a new server to the user’s account.
  • removeServerCommand responds to the /remove_server command and removes a server from the user’s account.
  • Other account operations are performed in similar ways.

Server Status Monitoring:
  • The checkServers function periodically checks the status of servers added by all users. It reports crashed servers.

Saving and Loading Account Information:
  • saveAccountsToFile saves all user accounts and the servers added to these accounts in a JSON-formatted file.
  • loadAccountsFromFile loads the saved account and server information from the file and adds it to the AccountManager object.
  • setup() and loop() Functions: setup() includes connecting to WiFi, time synchronization, starting the file system, and loading account information.
  • loop() retrieves Telegram updates, checks server status, and handles OTA (Over-The-Air) updates.
Note: OTA has not been included in the project.

You can access the project source codes via GitHub.