acid-drop

- Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.acid.vegas/-c.git
Log | Files | Refs | Archive | README | LICENSE

commit a5304929373c5a2939c49a4187769ec0250ecf12
parent 8950faf44a651aff9efb4352216c9f4c27601d5e
Author: acidvegas <acid.vegas@acid.vegas>
Date: Fri, 7 Jun 2024 23:58:03 -0400

Finally got the code split up into multiple files for organization

Diffstat:
Asrc/Display.cpp | 694+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Display.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/IRC.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/IRC.h | 14++++++++++++++
Msrc/Lora.cpp | 63++++++++++++++++++++++++++++++++-------------------------------
Msrc/Lora.h | 3++-
Asrc/Network.cpp | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Network.h | 36++++++++++++++++++++++++++++++++++++
Msrc/Speaker.cpp | 93++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/Speaker.h | 6+++---
Msrc/Storage.cpp | 5+++--
Msrc/Storage.h | 6+++---
Asrc/Utilities.cpp | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Utilities.h | 17+++++++++++++++++
Msrc/bootScreen.h | 3++-
Msrc/main.ino | 1265+------------------------------------------------------------------------------

16 files changed, 1380 insertions(+), 1348 deletions(-)

diff --git a/src/Display.cpp b/src/Display.cpp
@@ -0,0 +1,694 @@
+#include "Display.h"
+#include "Storage.h"
+#include "Utilities.h"
+#include "Speaker.h"
+#include "pins.h"
+#include "bootScreen.h"
+#include "IRC.h"
+
+// External variables definitions
+bool infoScreen = false;
+bool configScreen = false;
+bool screenOn = true;
+const char* channel = "#comms";
+unsigned long infoScreenStartTime = 0;
+unsigned long configScreenStartTime = 0;
+unsigned long lastStatusUpdateTime = 0;
+unsigned long lastActivityTime = 0;
+String inputBuffer = "";
+
+std::vector<String> lines;
+std::vector<bool> mentions;
+std::map<String, uint32_t> nickColors;
+
+TFT_eSPI tft = TFT_eSPI();
+
+void addLine(String senderNick, String message, String type, bool mention, uint16_t errorColor, uint16_t reasonColor) {
+    if (type != "error" && nickColors.find(senderNick) == nickColors.end())
+        nickColors[senderNick] = generateRandomColor();
+
+    String formattedMessage;
+    if (type == "join") {
+        formattedMessage = "JOIN " + senderNick + " has joined " + String(channel);
+    } else if (type == "part") {
+        formattedMessage = "PART " + senderNick + " has EMO-QUIT " + String(channel);
+    } else if (type == "quit") {
+        formattedMessage = "QUIT " + senderNick;
+    } else if (type == "nick") {
+        int arrowPos = message.indexOf(" -> ");
+        String oldNick = senderNick;
+        String newNick = message.substring(arrowPos + 4);
+        if (nickColors.find(newNick) == nickColors.end()) {
+            nickColors[newNick] = generateRandomColor();
+        }
+        formattedMessage = "NICK " + oldNick + " -> " + newNick;
+    } else if (type == "kick") {
+        formattedMessage = "KICK " + senderNick + message;
+    } else if (type == "mode") {
+        formattedMessage = "MODE " + message;
+        tft.setTextColor(TFT_BLUE);
+    } else if (type == "action") {
+        formattedMessage = "* " + senderNick + " " + message;
+    } else if (type == "error") {
+        formattedMessage = "ERROR " + message;
+        senderNick = "ERROR";
+    } else {
+        formattedMessage = senderNick + ": " + message;
+    }
+
+    int linesRequired = calculateLinesRequired(formattedMessage);
+
+    while (lines.size() + linesRequired > MAX_LINES) {
+        lines.erase(lines.begin());
+        mentions.erase(mentions.begin());
+    }
+
+    if (type == "error") {
+        lines.push_back("ERROR " + message);
+        mentions.push_back(false);
+    } else {
+        lines.push_back(formattedMessage);
+        mentions.push_back(mention);
+    }
+
+    displayLines();
+}
+
+int calculateLinesRequired(String message) {
+    int linesRequired = 1;
+    int lineWidth = 0;
+
+    for (unsigned int i = 0; i < message.length(); i++) {
+        char c = message[i];
+        if (c == '\x03') {
+            if (i + 1 < message.length() && isdigit(message[i + 1])) {
+                i++;
+                if (i + 1 < message.length() && isdigit(message[i + 1]))
+                    i++;
+            }
+            if (i + 1 < message.length() && message[i + 1] == ',' && isdigit(message[i + 2])) {
+                i += 2;
+                if (i + 1 < message.length() && isdigit(message[i + 1]))
+                    i++;
+            }
+        } else if (c != '\x02' && c != '\x0F' && c != '\x1F') {
+            lineWidth += tft.textWidth(String(c));
+            if (lineWidth > SCREEN_WIDTH) {
+                linesRequired++;
+                lineWidth = tft.textWidth(String(c));
+            }
+        }
+    }
+
+    return linesRequired;
+}
+
+void displayCenteredText(String text) {
+    tft.fillScreen(TFT_BLACK);
+    tft.setTextDatum(MC_DATUM);
+    tft.setTextColor(TFT_GREEN, TFT_BLACK);
+    tft.drawString(text, SCREEN_WIDTH / 2, (SCREEN_HEIGHT + STATUS_BAR_HEIGHT) / 2);
+}
+
+void displayInputLine() {
+    tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
+    tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
+    tft.setTextColor(TFT_WHITE);
+    tft.setTextSize(1);
+
+    String displayInput = inputBuffer;
+    int displayWidth = tft.textWidth(displayInput);
+    int inputWidth = SCREEN_WIDTH - tft.textWidth("> ");
+    
+    while (displayWidth > inputWidth) {
+        displayInput = displayInput.substring(1);
+        displayWidth = tft.textWidth(displayInput);
+    }
+
+    if (inputBuffer.length() >= 510)
+        tft.setTextColor(TFT_RED);
+
+    tft.print("> " + displayInput);
+    tft.setTextColor(TFT_WHITE);
+}
+
+void displayLines() {
+    tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT, TFT_BLACK);
+
+    int cursorY = STATUS_BAR_HEIGHT;
+    int totalLinesHeight = 0;
+    std::vector<int> lineHeights;
+
+    for (const String& line : lines) {
+        int lineHeight = calculateLinesRequired(line) * CHAR_HEIGHT;
+        lineHeights.push_back(lineHeight);
+        totalLinesHeight += lineHeight;
+    }
+
+    while (totalLinesHeight > SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT) {
+        totalLinesHeight -= lineHeights.front();
+        lines.erase(lines.begin());
+        mentions.erase(mentions.begin());
+        lineHeights.erase(lineHeights.begin());
+    }
+
+    for (size_t i = 0; i < lines.size(); ++i) {
+        const String& line = lines[i];
+        bool mention = mentions[i];
+
+        tft.setCursor(0, cursorY);
+
+        if (line.startsWith("JOIN ")) {
+            tft.setTextColor(TFT_GREEN);
+            tft.print("JOIN ");
+            int startIndex = 5;
+            int endIndex = line.indexOf(" has joined ");
+            String senderNick = line.substring(startIndex, endIndex);
+            tft.setTextColor(nickColors[senderNick]);
+            tft.print(senderNick);
+            tft.setTextColor(TFT_WHITE);
+            tft.print(" has joined ");
+            tft.setTextColor(TFT_CYAN);
+            tft.print(channel);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("PART ")) {
+            tft.setTextColor(TFT_RED);
+            tft.print("PART ");
+            int startIndex = 5;
+            int endIndex = line.indexOf(" has EMO-QUIT ");
+            String senderNick = line.substring(startIndex, endIndex);
+            tft.setTextColor(nickColors[senderNick]);
+            tft.print(senderNick);
+            tft.setTextColor(TFT_WHITE);
+            tft.print(" has EMO-QUIT ");
+            tft.setTextColor(TFT_CYAN);
+            tft.print(channel);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("QUIT ")) {
+            tft.setTextColor(TFT_RED);
+            tft.print("QUIT ");
+            String senderNick = line.substring(5);
+            tft.setTextColor(nickColors[senderNick]);
+            tft.print(senderNick);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("NICK ")) {
+            tft.setTextColor(TFT_BLUE);
+            tft.print("NICK ");
+            int startIndex = 5;
+            int endIndex = line.indexOf(" -> ");
+            String oldNick = line.substring(startIndex, endIndex);
+            String newNick = line.substring(endIndex + 4);
+            tft.setTextColor(nickColors[oldNick]);
+            tft.print(oldNick);
+            tft.setTextColor(TFT_WHITE);
+            tft.print(" -> ");
+            tft.setTextColor(nickColors[newNick]);
+            tft.print(newNick);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("KICK ")) {
+            tft.setTextColor(TFT_RED);
+            tft.print("KICK ");
+            int startIndex = 5;
+            int endIndex = line.indexOf(" by ");
+            String kickedNick = line.substring(startIndex, endIndex);
+            String kicker = line.substring(endIndex + 4);
+            tft.setTextColor(nickColors[kickedNick]);
+            tft.print(kickedNick);
+            tft.setTextColor(TFT_WHITE);
+            tft.print(" by ");
+            tft.setTextColor(nickColors[kicker]);
+            tft.print(kicker);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("MODE ")) {
+            tft.setTextColor(TFT_BLUE);
+            tft.print("MODE ");
+            String modeChange = line.substring(5);
+            tft.setTextColor(TFT_WHITE);
+            tft.print(modeChange);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("ERROR ")) {
+            tft.setTextColor(TFT_RED);
+            tft.print("ERROR ");
+            String errorReason = line.substring(6);
+            tft.setTextColor(TFT_DARKGREY);
+            tft.print(errorReason);
+            cursorY += CHAR_HEIGHT;
+        } else if (line.startsWith("* ")) {
+            tft.setTextColor(TFT_MAGENTA);
+            tft.print("* ");
+            int startIndex = 2;
+            int endIndex = line.indexOf(' ', startIndex);
+            String senderNick = line.substring(startIndex, endIndex);
+            String actionMessage = line.substring(endIndex + 1);
+            tft.setTextColor(nickColors[senderNick]);
+            tft.print(senderNick);
+            tft.setTextColor(TFT_MAGENTA);
+            tft.print(" " + actionMessage);
+            cursorY += CHAR_HEIGHT;
+        } else {
+            int colonIndex = line.indexOf(':');
+            String senderNick = line.substring(0, colonIndex);
+            String message = line.substring(colonIndex + 1);
+
+            tft.setTextColor(nickColors[senderNick]);
+            tft.print(senderNick);
+            tft.setTextColor(TFT_WHITE);
+            cursorY = renderFormattedMessage(":" + message, cursorY, CHAR_HEIGHT, mention);
+        }
+    }
+
+    displayInputLine();
+}
+
+void displayXBM() {
+    tft.fillScreen(TFT_BLACK);
+
+    int x = (SCREEN_WIDTH - logo_width) / 2;
+    int y = (SCREEN_HEIGHT - logo_height) / 2;
+
+    tft.drawXBitmap(x, y, logo_bits, logo_width, logo_height, TFT_GREEN);
+
+    unsigned long startTime = millis();
+    bool wipeInitiated = false;
+
+    while (millis() - startTime < 3000) {
+        if (getKeyboardInput() == 'w') {
+            wipeNVS();
+            tft.fillScreen(TFT_BLACK);
+            displayCenteredText("NVS WIPED");
+            delay(2000);
+            wipeInitiated = true;
+            break;
+        }
+    }
+}
+
+uint32_t generateRandomColor() {
+    return tft.color565(random(0, 255), random(0, 255), random(0, 255));
+}
+
+uint16_t getColorFromCode(int colorCode) {
+    switch (colorCode) {
+        case 0: return TFT_WHITE;
+        case 1: return TFT_BLACK;
+        case 2: return tft.color565(0, 0, 128); // Dark Blue (Navy)
+        case 3: return TFT_GREEN;
+        case 4: return TFT_RED;
+        case 5: return tft.color565(128, 0, 0); // Brown (Maroon)
+        case 6: return tft.color565(128, 0, 128); // Purple
+        case 7: return tft.color565(255, 165, 0); // Orange
+        case 8: return TFT_YELLOW;
+        case 9: return tft.color565(144, 238, 144); // Light Green
+        case 10: return tft.color565(0, 255, 255); // Cyan (Light Blue)
+        case 11: return tft.color565(224, 255, 255); // Light Cyan (Aqua)
+        case 12: return TFT_BLUE;
+        case 13: return tft.color565(255, 192, 203); // Pink (Light Purple)
+        case 14: return tft.color565(128, 128, 128); // Grey
+        case 15: return tft.color565(211, 211, 211); // Light Grey
+        case 16: return 0x4000;
+        case 17: return 0x4100;
+        case 18: return 0x4220;
+        case 19: return 0x3220;
+        case 20: return 0x0220;
+        case 21: return 0x0225;
+        case 22: return 0x0228;
+        case 23: return 0x0128;
+        case 24: return 0x0008;
+        case 25: return 0x2808;
+        case 26: return 0x4008;
+        case 27: return 0x4005;
+        case 28: return 0x7000;
+        case 29: return 0x71C0;
+        case 30: return 0x73A0;
+        case 31: return 0x53A0;
+        case 32: return 0x03A0;
+        case 33: return 0x03A9;
+        case 34: return 0x03AE;
+        case 35: return 0x020E;
+        case 36: return 0x000E;
+        case 37: return 0x480E;
+        case 38: return 0x700E;
+        case 39: return 0x7008;
+        case 40: return 0xB000;
+        case 41: return 0xB300;
+        case 42: return 0xB5A0;
+        case 43: return 0x7DA0;
+        case 44: return 0x05A0;
+        case 45: return 0x05AE;
+        case 46: return 0x05B6;
+        case 47: return 0x0316;
+        case 48: return 0x0016;
+        case 49: return 0x7016;
+        case 50: return 0xB016;
+        case 51: return 0xB00D;
+        case 52: return 0xF800;
+        case 53: return 0xFC60;
+        case 54: return 0xFFE0;
+        case 55: return 0xB7E0;
+        case 56: return 0x07E0;
+        case 57: return 0x07F4;
+        case 58: return 0x07FF;
+        case 59: return 0x047F;
+        case 60: return 0x001F;
+        case 61: return 0xA01F;
+        case 62: return 0xF81F;
+        case 63: return 0xF813;
+        case 64: return 0xFACB;
+        case 65: return 0xFDAB;
+        case 66: return 0xFFEE;
+        case 67: return 0xCFEC;
+        case 68: return 0x6FED;
+        case 69: return 0x67F9;
+        case 70: return 0x6FFF;
+        case 71: return 0x5DBF;
+        case 72: return 0x5ADF;
+        case 73: return 0xC2DF;
+        case 74: return 0xFB3F;
+        case 75: return 0xFAD7;
+        case 76: return 0xFCF3;
+        case 77: return 0xFE93;
+        case 78: return 0xFFF3;
+        case 79: return 0xE7F3;
+        case 80: return 0x9FF3;
+        case 81: return 0x9FFB;
+        case 82: return 0x9FFF;
+        case 83: return 0x9E9F;
+        case 84: return 0x9CFF;
+        case 85: return 0xDCFF;
+        case 86: return 0xFCFF;
+        case 87: return 0xFCBA;
+        case 88: return 0x0000;
+        case 89: return 0x1082;
+        case 90: return 0x2945;
+        case 91: return 0x31A6;
+        case 92: return 0x4A69;
+        case 93: return 0x632C;
+        case 94: return 0x8410;
+        case 95: return 0x9CF3;
+        case 96: return 0xBDF7;
+        case 97: return 0xE71C;
+        case 98: return 0xFFFF;
+        default: return TFT_WHITE;
+    }
+}
+
+uint16_t getColorFromPercentage(int percentage) {
+    if      (percentage > 75) return TFT_GREEN;
+    else if (percentage > 50) return TFT_YELLOW;
+    else if (percentage > 25) return TFT_ORANGE;
+    else                      return TFT_RED;
+}
+
+void handleKeyboardInput(char key) {
+    lastActivityTime = millis();
+
+    if (!screenOn) {
+        turnOnScreen();
+        screenOn = true;
+        return;
+    }
+
+    static bool altPressed = false;
+
+    if (key == '\n' || key == '\r') {
+        if (inputBuffer.startsWith("/nick ")) {
+            String newNick = inputBuffer.substring(6);
+            sendIRC("NICK " + newNick);
+            inputBuffer = "";
+        } else if (inputBuffer.startsWith("/config")) {
+            configScreen = true;
+            configScreenStartTime = millis();
+            inputBuffer = "";
+        } else if (inputBuffer.startsWith("/info")) {
+            infoScreen = true;
+            infoScreenStartTime = millis();
+            tft.fillScreen(TFT_BLACK);
+            printDeviceInfo();
+            inputBuffer = "";
+        } else if (inputBuffer.startsWith("/raw ")) {
+            String rawCommand = inputBuffer.substring(5);
+            sendIRC(rawCommand);
+        } else if (inputBuffer.startsWith("/me ")) {
+            String actionMessage = inputBuffer.substring(4);
+            sendIRC("PRIVMSG " + String(channel) + " :\001ACTION " + actionMessage + "\001");
+            addLine(irc_nickname, actionMessage, "action");
+            inputBuffer = "";
+        } else {
+            sendIRC("PRIVMSG " + String(channel) + " :" + inputBuffer);
+            addLine(irc_nickname, inputBuffer, "message");
+        }
+        inputBuffer = "";
+        displayInputLine();
+    } else if (key == '\b') {
+        if (inputBuffer.length() > 0) {
+            inputBuffer.remove(inputBuffer.length() - 1);
+            displayInputLine();
+        }
+    } else {
+        inputBuffer += key;
+        displayInputLine();
+    }
+}
+
+void parseAndDisplay(String line) {
+    int firstSpace = line.indexOf(' ');
+    int secondSpace = line.indexOf(' ', firstSpace + 1);
+
+    if (firstSpace != -1 && secondSpace != -1) {
+        String command = line.substring(firstSpace + 1, secondSpace);
+
+        if (command == "PRIVMSG") {
+            int thirdSpace = line.indexOf(' ', secondSpace + 1);
+            String target = line.substring(secondSpace + 1, thirdSpace);
+            if (target == String(channel)) {
+                int colonPos = line.indexOf(':', thirdSpace);
+                String message = line.substring(colonPos + 1);
+                String senderNick = line.substring(1, line.indexOf('!'));
+                bool mention = message.indexOf(irc_nickname) != -1;
+
+                if (mention)
+                    playNotificationSound();
+
+                if (message.startsWith(String("\x01") + "ACTION ") && message.endsWith("\x01")) {
+                    String actionMessage = message.substring(8, message.length() - 1);
+                    addLine(senderNick, actionMessage, "action");
+                } else {
+                    addLine(senderNick, message, "message", mention);
+                }
+            }
+        } else if (command == "JOIN" && line.indexOf(channel) != -1) {
+            String senderNick = line.substring(1, line.indexOf('!'));
+            addLine(senderNick, " has joined " + String(channel), "join");
+        } else if (command == "PART" && line.indexOf(channel) != -1) {
+            String senderNick = line.substring(1, line.indexOf('!'));
+            addLine(senderNick, " has EMO-QUIT " + String(channel), "part");
+        } else if (command == "QUIT") {
+            String senderNick = line.substring(1, line.indexOf('!'));
+            addLine(senderNick, "", "quit");
+        } else if (command == "NICK") {
+            String prefix = line.startsWith(":") ? line.substring(1, firstSpace) : "";
+            String newNick = line.substring(line.lastIndexOf(':') + 1);
+
+            if (prefix.indexOf('!') == -1) {
+                addLine(irc_nickname, " -> " + newNick, "nick");
+                irc_nickname = newNick;
+            } else {
+                String oldNick = prefix.substring(0, prefix.indexOf('!'));
+                addLine(oldNick, " -> " + newNick, "nick");
+                if (oldNick == irc_nickname) {
+                    irc_nickname = newNick;
+                }
+            }
+        } else if (command == "KICK") {
+            int thirdSpace = line.indexOf(' ', secondSpace + 1);
+            int fourthSpace = line.indexOf(' ', thirdSpace + 1);
+            String kicker = line.substring(1, line.indexOf('!'));
+            String kicked = line.substring(thirdSpace + 1, fourthSpace);
+            addLine(kicked, " by " + kicker, "kick");
+        } else if (command == "MODE") {
+            String modeChange = line.substring(secondSpace + 1);
+            addLine("", modeChange, "mode");
+        } else if (command == "432") {
+            addLine("ERROR", "ERR_ERRONEUSNICKNAME", "error", TFT_RED, TFT_DARKGREY);
+        } else if (command == "433") {
+            addLine("ERROR", "ERR_NICKNAMEINUSE", "error", TFT_RED, TFT_DARKGREY);
+            irc_nickname = "ACID_" + String(random(1000, 9999));
+            sendIRC("NICK " + irc_nickname);
+        }
+    }
+}
+
+int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick) {
+    uint16_t fgColor = TFT_WHITE;
+    uint16_t bgColor = TFT_BLACK;
+    bool bold = false;
+    bool underline = false;
+    bool nickHighlighted = false;
+
+    int nickPos = -1;
+    if (highlightNick) {
+        nickPos = message.indexOf(irc_nickname);
+    }
+
+    for (unsigned int i = 0; i < message.length(); i++) {
+        char c = message[i];
+        if (c == '\x02') {
+            bold = !bold;
+        } else if (c == '\x1F') {
+            underline = !underline;
+        } else if (c == '\x03') {
+            fgColor = TFT_WHITE;
+            bgColor = TFT_BLACK;
+
+            if (i + 1 < message.length() && (isdigit(message[i + 1]) || message[i + 1] == ',')) {
+                int colorCode = -1;
+                if (isdigit(message[i + 1])) {
+                    colorCode = message[++i] - '0';
+                    if (i + 1 < message.length() && isdigit(message[i + 1]))
+                        colorCode = colorCode * 10 + (message[++i] - '0');
+                }
+
+                if (colorCode != -1)
+                    fgColor = getColorFromCode(colorCode);
+
+                if (i + 1 < message.length() && message[i + 1] == ',') {
+                    i++;
+                    int bgColorCode = -1;
+                    if (isdigit(message[i + 1])) {
+                        bgColorCode = message[++i] - '0';
+                        if (i + 1 < message.length() && isdigit(message[i + 1]))
+                            bgColorCode = bgColorCode * 10 + (message[++i] - '0');
+                    }
+
+                    if (bgColorCode != -1)
+                        bgColor = getColorFromCode(bgColorCode);
+
+                }
+
+                tft.setTextColor(fgColor, bgColor);
+            }
+        } else if (c == '\x0F') {
+            fgColor = TFT_WHITE;
+            bgColor = TFT_BLACK;
+            bold = false;
+            underline = false;
+            tft.setTextColor(fgColor, bgColor);
+            tft.setTextFont(1);
+        } else {
+            if (highlightNick && !nickHighlighted && nickPos != -1 && i == nickPos) {
+                tft.setTextColor(TFT_YELLOW, bgColor);
+                for (char nc : irc_nickname) {
+                    tft.print(nc);
+                    i++;
+                }
+                i--;
+                tft.setTextColor(TFT_WHITE, bgColor);
+                nickHighlighted = true;
+            } else {
+                if (tft.getCursorX() + tft.textWidth(String(c)) > SCREEN_WIDTH) {
+                    cursorY += lineHeight;
+                    tft.setCursor(0, cursorY);
+                }
+                if (c == ' ') {
+                    int spaceWidth = tft.textWidth(" ");
+                    tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
+                    tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
+                } else {
+                    tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.textWidth(String(c)), lineHeight, bgColor);
+                    tft.setTextColor(fgColor, bgColor);
+                    tft.print(c);
+                }
+            }
+        }
+    }
+
+    if (message.endsWith(" ")) {
+        int trailingSpaces = 0;
+        for (int i = message.length() - 1; i >= 0 && message[i] == ' '; i--) {
+            trailingSpaces++;
+        }
+        for (int i = 0; i < trailingSpaces; i++) {
+            int spaceWidth = tft.textWidth(" ");
+            tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
+            tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
+        }
+    }
+
+    cursorY += lineHeight;
+    return cursorY;
+}
+
+void turnOffScreen() {
+    Serial.println("Screen turned off");
+    tft.writecommand(TFT_DISPOFF);
+    tft.writecommand(TFT_SLPIN);
+    digitalWrite(TFT_BL, LOW);
+    screenOn = false;
+}
+
+void turnOnScreen() {
+    Serial.println("Screen turned on");
+    digitalWrite(TFT_BL, HIGH);
+    tft.writecommand(TFT_SLPOUT);
+    tft.writecommand(TFT_DISPON);
+    screenOn = true;
+}
+
+void updateStatusBar() {
+    Serial.println("Updating status bar...");
+    uint16_t darkerGrey = tft.color565(25, 25, 25);
+    tft.fillRect(0, 0, SCREEN_WIDTH, STATUS_BAR_HEIGHT, darkerGrey);
+
+    struct tm timeinfo;
+    char timeStr[9];
+    if (!getLocalTime(&timeinfo)) {
+        sprintf(timeStr, "12:00 AM");
+    } else {
+        int hour = timeinfo.tm_hour;
+        char ampm[] = "AM";
+        if (hour == 0) {
+            hour = 12;
+        } else if (hour >= 12) {
+            if (hour > 12)
+                hour -= 12;
+            strcpy(ampm, "PM");
+        }
+        sprintf(timeStr, "%02d:%02d %s", hour, timeinfo.tm_min, ampm);
+    }
+    tft.setTextDatum(ML_DATUM);
+    tft.setTextColor(TFT_WHITE, darkerGrey);
+    tft.drawString(timeStr, 0, STATUS_BAR_HEIGHT / 2);
+
+    char wifiStr[15];
+    int wifiSignal = 0;
+    if (WiFi.status() != WL_CONNECTED) {
+        sprintf(wifiStr, "WiFi: N/A");
+        tft.setTextColor(TFT_PINK, darkerGrey);
+        tft.setTextDatum(MR_DATUM);
+        tft.drawString(wifiStr, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
+    } else {
+        int32_t rssi = WiFi.RSSI();
+        if      (rssi > -50) wifiSignal = 100;
+        else if (rssi > -60) wifiSignal = 80;
+        else if (rssi > -70) wifiSignal = 60;
+        else if (rssi > -80) wifiSignal = 40;
+        else if (rssi > -90) wifiSignal = 20;
+        else wifiSignal = 0;
+
+        sprintf(wifiStr, "WiFi: %d%%", wifiSignal);
+        tft.setTextDatum(MR_DATUM);
+        tft.setTextColor(TFT_PINK, darkerGrey);
+        tft.drawString("WiFi:", SCREEN_WIDTH - 120, STATUS_BAR_HEIGHT / 2);
+        tft.setTextColor(getColorFromPercentage(wifiSignal), darkerGrey);
+        tft.drawString(wifiStr + 6, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
+    }
+
+    int batteryLevel = BL.getBatteryChargeLevel();
+    char batteryStr[15];
+    sprintf(batteryStr, "Batt: %d%%", batteryLevel);
+    tft.setTextDatum(MR_DATUM);
+    tft.setTextColor(TFT_CYAN, darkerGrey);
+    tft.drawString("Batt:", SCREEN_WIDTH - 40, STATUS_BAR_HEIGHT / 2);
+    tft.setTextColor(getColorFromPercentage(batteryLevel), darkerGrey);
+    tft.drawString(batteryStr + 5, SCREEN_WIDTH - 5, STATUS_BAR_HEIGHT / 2);
+}
diff --git a/src/Display.h b/src/Display.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <TFT_eSPI.h>
+#include <map>
+#include <vector>
+#include <WiFi.h>
+#include <time.h>
+
+// Constants
+#define CHAR_HEIGHT       10
+#define LINE_SPACING      0
+#define STATUS_BAR_HEIGHT 10
+#define INPUT_LINE_HEIGHT (CHAR_HEIGHT + LINE_SPACING)
+#define MAX_LINES         ((SCREEN_HEIGHT - INPUT_LINE_HEIGHT - STATUS_BAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING))
+
+// External variables
+extern bool infoScreen;
+extern bool configScreen;
+extern bool screenOn;
+extern const char* channel;
+extern unsigned long infoScreenStartTime;
+extern unsigned long configScreenStartTime;
+extern unsigned long lastStatusUpdateTime;
+extern unsigned long lastActivityTime;
+extern String inputBuffer;
+
+extern std::vector<String> lines;
+extern std::vector<bool> mentions;
+extern std::map<String, uint32_t> nickColors;
+
+extern TFT_eSPI tft;
+
+// Function declarations
+void addLine(String senderNick, String message, String type, bool mention = false, uint16_t errorColor = TFT_WHITE, uint16_t reasonColor = TFT_WHITE);
+int calculateLinesRequired(String message);
+void displayCenteredText(String text);
+void displayInputLine();
+void displayLines();
+void displayXBM();
+uint32_t generateRandomColor();
+uint16_t getColorFromCode(int colorCode);
+uint16_t getColorFromPercentage(int percentage);
+void handleKeyboardInput(char key);
+void parseAndDisplay(String line);
+int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick = false);
+void turnOffScreen();
+void turnOnScreen();
+void updateStatusBar();
diff --git a/src/IRC.cpp b/src/IRC.cpp
@@ -0,0 +1,76 @@
+#include "IRC.h"
+
+
+unsigned long joinChannelTime    = 0;
+bool          readyToJoinChannel = false;
+
+WiFiClient* client;
+
+
+bool connectToIRC() {
+    if (irc_tls) {
+        Serial.println("Connecting to IRC with TLS: " + String(irc_server) + ":" + String(irc_port));
+        client = new WiFiClientSecure();
+        static_cast<WiFiClientSecure*>(client)->setInsecure();
+        return static_cast<WiFiClientSecure*>(client)->connect(irc_server.c_str(), irc_port);
+    } else {
+        Serial.println("Connecting to IRC: " + String(irc_server) + ":" + String(irc_port));
+        client = new WiFiClient();
+        return client->connect(irc_server.c_str(), irc_port);
+    }
+}
+
+
+void handleIRC() {
+    while (client->available()) {
+        String line = client->readStringUntil('\n');
+
+        if (line.length() > 512) {
+            Serial.println("WARNING: IRC line length exceeds 512 characters!");
+            line = line.substring(0, 512);
+        }
+
+        Serial.println("IRC: " + line);
+
+        int firstSpace = line.indexOf(' ');
+        int secondSpace = line.indexOf(' ', firstSpace + 1);
+
+        if (firstSpace != -1 && secondSpace != -1) {
+            String prefix = line.substring(0, firstSpace);
+            String command = line.substring(firstSpace + 1, secondSpace);
+
+            if (command == "001") {
+                joinChannelTime = millis() + 2500;
+                readyToJoinChannel = true;
+            }
+        }
+
+        if (line.startsWith("PING")) {
+            String pingResponse = "PONG " + line.substring(line.indexOf(' ') + 1);
+            sendIRC(pingResponse);
+        } else {
+            parseAndDisplay(line);
+            lastActivityTime = millis();
+        }
+    }
+}
+
+
+void sendIRC(String command) {
+    if (command.length() > 510) {
+        Serial.println("Failed to send: Command too long");
+        return;
+    }
+
+    if (client->connected()) {
+        if (client->println(command)) {
+            Serial.println("IRC: >>> " + command);
+        } else {
+            Serial.println("Failed to send: " + command);
+        }
+    } else {
+        Serial.println("Failed to send: Not connected to IRC");
+    }
+}
+
+
diff --git a/src/IRC.h b/src/IRC.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <WiFiClientSecure.h>
+
+#include "Display.h"
+#include "Storage.h"
+
+extern WiFiClient* client;
+extern unsigned long joinChannelTime;
+extern bool readyToJoinChannel;
+
+bool connectToIRC();
+void handleIRC();
+void sendIRC(String command);
diff --git a/src/Lora.cpp b/src/Lora.cpp
@@ -4,6 +4,38 @@
 SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
 
 
+void recvLoop() {
+    String recv;
+
+    while (true) {
+        if (radio.available()) {
+            int state = radio.readData(recv);
+
+            if (state == RADIOLIB_ERR_NONE) {
+                playNotificationSound();
+                Serial.print(F("[RADIO] Received packet!"));
+                Serial.print(F(" Data:"));
+                Serial.print(recv);
+                Serial.print(F(" RSSI:"));
+                Serial.print(radio.getRSSI());
+                Serial.print(F(" dBm"));
+                Serial.print(F("  SNR:"));
+                Serial.print(radio.getSNR());
+                Serial.println(F(" dB"));
+            } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
+                Serial.println(F("CRC error!"));
+            } else {
+                Serial.print(F("Failed, code "));
+                Serial.println(state);
+            }
+        } else {
+            Serial.println(F("Radio became unavailable!"));
+            break;
+        }
+    }
+}
+
+
 bool setupRadio() {
     pinMode(RADIO_CS_PIN, OUTPUT);
     digitalWrite(RADIO_CS_PIN, HIGH);
@@ -91,34 +123,3 @@ bool transmit() {
         return false;
     }
 }
-
-
-void recvLoop() {
-    String recv;
-
-    while (true) {
-        if (radio.available()) {
-            int state = radio.readData(recv);
-
-            if (state == RADIOLIB_ERR_NONE) {
-                Serial.print(F("[RADIO] Received packet!"));
-                Serial.print(F(" Data:"));
-                Serial.print(recv);
-                Serial.print(F(" RSSI:"));
-                Serial.print(radio.getRSSI());
-                Serial.print(F(" dBm"));
-                Serial.print(F("  SNR:"));
-                Serial.print(radio.getSNR());
-                Serial.println(F(" dB"));
-            } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
-                Serial.println(F("CRC error!"));
-            } else {
-                Serial.print(F("Failed, code "));
-                Serial.println(state);
-            }
-        } else {
-            Serial.println(F("Radio became unavailable!"));
-            break;
-        }
-    }
-}
diff --git a/src/Lora.h b/src/Lora.h
@@ -3,9 +3,10 @@
 #include <RadioLib.h>
 
 #include "pins.h"
+#include "Speaker.h"
 
 extern SX1262 radio;
 
+void recvLoop();
 bool setupRadio();
 bool transmit();
-void recvLoop();
diff --git a/src/Network.cpp b/src/Network.cpp
@@ -0,0 +1,250 @@
+#include "Network.h"
+
+
+std::vector<WiFiNetwork> wifiNetworks;
+int selectedNetworkIndex = 0;
+
+WireGuard wg;
+
+
+void connectToWiFi(String ssid, String password) {
+    Serial.println("Connecting to WiFi network: " + ssid);
+    WiFi.begin(ssid.c_str(), password.c_str());
+
+    int attempts = 0;
+    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
+        delay(500);
+        displayCenteredText("CONNECTING TO " + ssid);
+        attempts++;
+    }
+
+    if (WiFi.status() == WL_CONNECTED) {
+        displayCenteredText("CONNECTED TO " + ssid);
+
+        updateTimeFromNTP();
+
+        preferences.begin("config", false);
+        preferences.putString("wifi_ssid", ssid);
+        preferences.putString("wifi_password", password);
+        preferences.end();
+        Serial.println("Stored WiFi credentials updated");
+
+        wifi_ssid = ssid;
+        wifi_password = password;
+    } else {
+        Serial.println("Failed to connect to WiFi network: " + ssid);
+        displayCenteredText("WIFI CONNECTION FAILED");
+        
+        preferences.begin("config", false);
+        preferences.putString("wifi_ssid", "");
+        preferences.putString("wifi_password", "");
+        preferences.end();
+        Serial.println("Stored WiFi credentials removed");
+
+        wifi_ssid = "";
+        wifi_password = "";
+
+        scanWiFiNetworks();
+    }
+}
+
+
+void displayPasswordInputLine() {
+    tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
+    tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
+    tft.setTextColor(TFT_WHITE);
+    tft.setTextSize(1);
+    tft.print("> " + inputBuffer);
+}
+
+
+void displayWiFiNetworks() {
+    tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT, TFT_BLACK);
+
+    tft.setTextSize(1);
+    tft.setTextColor(TFT_CYAN);
+    tft.setCursor(0, STATUS_BAR_HEIGHT);
+    tft.printf("#  CH  RSSI  ENC      SSID\n");
+    tft.setTextColor(TFT_WHITE);
+
+    int maxDisplayedLines = (SCREEN_HEIGHT - STATUS_BAR_HEIGHT - CHAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING);
+    int startIdx = selectedNetworkIndex >= maxDisplayedLines ? selectedNetworkIndex - maxDisplayedLines + 1 : 0;
+
+    for (int i = startIdx; i < wifiNetworks.size() && i < startIdx + maxDisplayedLines; ++i) {
+        displayWiFiNetwork(i, i - startIdx + 1);
+    }
+}
+
+
+void displayWiFiNetwork(int index, int displayIndex) {
+    int y = STATUS_BAR_HEIGHT + displayIndex * (CHAR_HEIGHT + LINE_SPACING);
+    tft.setCursor(0, y);
+
+    if (index == selectedNetworkIndex)
+        tft.setTextColor(TFT_GREEN, TFT_BLACK);
+    else
+        tft.setTextColor(TFT_WHITE, TFT_BLACK);
+
+    WiFiNetwork net = wifiNetworks[index];
+
+    uint16_t rssiColor = getColorFromPercentage((int32_t)net.rssi);
+    tft.printf("%-2d %-3d ", net.index, net.channel);
+
+    tft.setTextColor(rssiColor, TFT_BLACK);
+    tft.printf("%-5d ", net.rssi);
+
+    tft.setTextColor(index == selectedNetworkIndex ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
+    tft.printf("%-8s %s", net.encryption.c_str(), net.ssid.c_str());
+}
+
+
+String getEncryptionType(wifi_auth_mode_t encryptionType) {
+    switch (encryptionType) {
+        case (WIFI_AUTH_OPEN):
+            return "Open";
+        case (WIFI_AUTH_WEP):
+            return "WEP";
+        case (WIFI_AUTH_WPA_PSK):
+            return "WPA_PSK";
+        case (WIFI_AUTH_WPA2_PSK):
+            return "WPA2_PSK";
+        case (WIFI_AUTH_WPA_WPA2_PSK):
+            return "WPA_WPA2_PSK";
+        case (WIFI_AUTH_WPA2_ENTERPRISE):
+            return "WPA2_ENTERPRISE";
+        default:
+            return "Unknown";
+    }
+}
+
+
+void handlePasswordInput(char key) {
+    if (key == '\n' || key == '\r') {
+        wifi_password = inputBuffer;
+        inputBuffer = "";
+        connectToWiFi(wifi_ssid, wifi_password);
+    } else if (key == '\b') {
+        if (inputBuffer.length() > 0) {
+            inputBuffer.remove(inputBuffer.length() - 1);
+            displayPasswordInputLine();
+        }
+    } else if (inputBuffer.length() < 63) {
+        inputBuffer += key;
+        displayPasswordInputLine();
+    }
+}
+
+
+void handleWiFiSelection(char key) {
+    if (key == 'u') {
+        updateSelectedNetwork(-1);
+    } else if (key == 'd') {
+        updateSelectedNetwork(1);
+    } else if (key == '\n' || key == '\r') {
+        wifi_ssid = wifiNetworks[selectedNetworkIndex].ssid;
+        if (wifiNetworks[selectedNetworkIndex].encryption == "Secured") {
+            inputBuffer = "";
+            tft.fillScreen(TFT_BLACK);
+            tft.setTextSize(1);
+            tft.setTextColor(TFT_WHITE);
+            tft.setCursor(0, STATUS_BAR_HEIGHT);
+            tft.println("ENTER PASSWORD:");
+            displayPasswordInputLine();
+
+            while (true) {
+                char incoming = getKeyboardInput();
+                if (incoming != 0) {
+                    handlePasswordInput(incoming);
+                    if (incoming == '\n' || incoming == '\r')
+                        break;
+                }
+            }
+        } else {
+            wifi_password = "";
+            connectToWiFi(wifi_ssid, wifi_password);
+        }
+    }
+}
+
+
+void randomizeMacAddress() {
+    Serial.println("Current MAC Address: " + WiFi.macAddress());
+
+    uint8_t new_mac[6];
+
+    for (int i = 0; i < 6; ++i)
+        new_mac[i] = random(0x00, 0xFF);
+
+    if (esp_wifi_set_mac(WIFI_IF_STA, new_mac) == ESP_OK)
+        Serial.println("New MAC Address: " + WiFi.macAddress());
+    else
+        Serial.print("Failed to set new MAC Address");
+}
+
+
+void scanWiFiNetworks() {
+    Serial.println("Scanning for WiFi networks...");
+    displayCenteredText("SCANNING WIFI");
+    delay(1000);
+
+    int n = WiFi.scanNetworks();
+
+    if (n == 0) {
+        Serial.println("No WiFi networks found");
+        displayCenteredText("NO NETWORKS FOUND");
+        delay(2000);
+        scanWiFiNetworks();
+        return;
+    }
+    
+    Serial.print("Total number of networks found: ");
+    Serial.println(n);
+
+    for (int i = 0; i < n && i < 100; i++) {
+        WiFiNetwork net;
+        net.index = i + 1;
+        net.channel = WiFi.channel(i);
+        net.rssi = WiFi.RSSI(i);
+        net.encryption = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "Open" : "Secured";
+        String ssid = WiFi.SSID(i).substring(0, 32);
+        net.ssid = ssid;
+        wifiNetworks.push_back(net);
+    }
+
+    displayWiFiNetworks();
+}
+
+
+void updateSelectedNetwork(int delta) {
+    int newIndex = selectedNetworkIndex + delta;
+
+    if (newIndex >= 0 && newIndex < wifiNetworks.size()) {
+        selectedNetworkIndex = newIndex;
+        displayWiFiNetworks();
+    }
+}
+
+
+void wgConnect(const IPAddress& localIp, const char* privateKey, const char* endpointAddress, const char* publicKey, uint16_t endpointPort) {
+    wg.begin(localIp, privateKey, endpointAddress, publicKey, endpointPort);
+}
+
+
+void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
+    switch (event) {
+        case SYSTEM_EVENT_STA_CONNECTED:
+            Serial.println("WiFi connected");
+            break;
+        case SYSTEM_EVENT_STA_DISCONNECTED:
+            Serial.println("WiFi disconnected");
+            break;
+        case SYSTEM_EVENT_STA_GOT_IP:
+            Serial.println("WiFi got IP address: " + WiFi.localIP().toString());
+            break;
+        case SYSTEM_EVENT_STA_LOST_IP:
+            Serial.println("WiFi lost IP address");
+            break;
+        default:
+            break;
+    }
+}
+\ No newline at end of file
diff --git a/src/Network.h b/src/Network.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <vector>
+
+#include <esp_wifi.h>
+#include <WireGuard-ESP32.h>
+
+#include "Display.h"
+#include "Storage.h"
+#include "Utilities.h"
+#include "WiFi.h"
+
+struct WiFiNetwork {
+    int index;
+    int channel;
+    int rssi;
+    String encryption;
+    String ssid;
+};
+
+extern std::vector<WiFiNetwork> wifiNetworks;
+extern int selectedNetworkIndex;
+extern WireGuard wg;
+
+void connectToWiFi(String ssid, String password);
+void displayPasswordInputLine();
+void displayWiFiNetworks();
+void displayWiFiNetwork(int index, int displayIndex);
+String getEncryptionType(wifi_auth_mode_t encryptionType);
+void handlePasswordInput(char key);
+void handleWiFiSelection(char key);
+void randomizeMacAddress();
+void scanWiFiNetworks();
+void updateSelectedNetwork(int delta);
+void wgConnect(const IPAddress& localIp, const char* privateKey, const char* endpointAddress, const char* publicKey, uint16_t endpointPort);
+void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
diff --git a/src/Speaker.cpp b/src/Speaker.cpp
@@ -1,31 +1,30 @@
 #include "Speaker.h"
 
 
-void setupI2S() {
-    i2s_config_t i2s_config = {
-        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
-        .sample_rate = SAMPLE_RATE,
-        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
-        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
-        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
-        .intr_alloc_flags = 0,
-        .dma_buf_count = 8,
-        .dma_buf_len = 64,
-        .use_apll = false,
-        .tx_desc_auto_clear = true,
-        .fixed_mclk = 0
-    };
+void playNotificationSound() {
+    playTone(1000, 50);
+    delay(100);
+    playTone(1500, 50);
+    delay(100);
+    playTone(2000, 50);
+    delay(100);
+    playTone(500, 50);
+}
 
-    i2s_pin_config_t pin_config = {
-        .bck_io_num   = BOARD_I2S_BCK,
-        .ws_io_num    = BOARD_I2S_WS,
-        .data_out_num = BOARD_I2S_DOUT,
-        .data_in_num  = I2S_PIN_NO_CHANGE
-    };
 
-    i2s_driver_install(BOARD_I2S_PORT, &i2s_config, 0, NULL);
-    i2s_set_pin(BOARD_I2S_PORT, &pin_config);
-    i2s_set_clk(BOARD_I2S_PORT, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
+void playRTTTL(const char* rtttl) {
+    static AudioGeneratorRTTTL *rtttlGenerator = new AudioGeneratorRTTTL();
+    static AudioOutputI2S *audioOutput = new AudioOutputI2S();
+    static AudioFileSourcePROGMEM *fileSource = new AudioFileSourcePROGMEM(rtttl, strlen(rtttl));
+
+    audioOutput->begin();
+    rtttlGenerator->begin(fileSource, audioOutput);
+
+    while (rtttlGenerator->isRunning())
+        rtttlGenerator->loop();
+
+    rtttlGenerator->stop();
+    fileSource->close();
 }
 
 
@@ -48,28 +47,29 @@ void playTone(float frequency, int duration, int volume) {
 }
 
 
-void playRTTTL(const char* rtttl) {
-    static AudioGeneratorRTTTL *rtttlGenerator = new AudioGeneratorRTTTL();
-    static AudioOutputI2S *audioOutput = new AudioOutputI2S();
-    static AudioFileSourcePROGMEM *fileSource = new AudioFileSourcePROGMEM(rtttl, strlen(rtttl));
-
-    audioOutput->begin();
-    rtttlGenerator->begin(fileSource, audioOutput);
-
-    while (rtttlGenerator->isRunning())
-        rtttlGenerator->loop();
-
-    rtttlGenerator->stop();
-    fileSource->close();
-}
+void setupI2S() {
+    i2s_config_t i2s_config = {
+        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
+        .sample_rate = SAMPLE_RATE,
+        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
+        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
+        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
+        .intr_alloc_flags = 0,
+        .dma_buf_count = 8,
+        .dma_buf_len = 64,
+        .use_apll = false,
+        .tx_desc_auto_clear = true,
+        .fixed_mclk = 0
+    };
 
+    i2s_pin_config_t pin_config = {
+        .bck_io_num   = BOARD_I2S_BCK,
+        .ws_io_num    = BOARD_I2S_WS,
+        .data_out_num = BOARD_I2S_DOUT,
+        .data_in_num  = I2S_PIN_NO_CHANGE
+    };
 
-void playNotificationSound() {
-    playTone(1000, 150);
-    delay(100);
-    playTone(1500, 150);
-    delay(100);
-    playTone(2000, 150);
-    delay(100);
-    playTone(500, 150);
-}
+    i2s_driver_install(BOARD_I2S_PORT, &i2s_config, 0, NULL);
+    i2s_set_pin(BOARD_I2S_PORT, &pin_config);
+    i2s_set_clk(BOARD_I2S_PORT, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
+}
+\ No newline at end of file
diff --git a/src/Speaker.h b/src/Speaker.h
@@ -11,7 +11,7 @@
 #define BOARD_I2S_PORT I2S_NUM_0
 #define SAMPLE_RATE 44100
 
-void setupI2S();
-void playTone(float frequency, int duration, int volume = 16383);
-void playRTTTL(const char* rtttl);
 void playNotificationSound();
+void playRTTTL(const char* rtttl);
+void playTone(float frequency, int duration, int volume = 16383);
+void setupI2S();
diff --git a/src/Storage.cpp b/src/Storage.cpp
@@ -3,6 +3,7 @@
 
 Preferences preferences;
 
+// Config variables
 String irc_nickname;
 String irc_username;
 String irc_realname;
@@ -82,9 +83,9 @@ bool mountSD() {
             else
                 Serial.println("UNKNOWN");
 
-            uint32_t cardSize = SD.cardSize() / (1024 * 1024);
+            uint32_t cardSize  = SD.cardSize()   / (1024 * 1024);
             uint32_t cardTotal = SD.totalBytes() / (1024 * 1024);
-            uint32_t cardUsed = SD.usedBytes() / (1024 * 1024);
+            uint32_t cardUsed  = SD.usedBytes()  / (1024 * 1024);
             Serial.printf("SD Card Size: %lu MB\n", cardSize);
             Serial.printf("Total space: %lu MB\n",  cardTotal);
             Serial.printf("Used space: %lu MB\n",   cardUsed);
diff --git a/src/Storage.h b/src/Storage.h
@@ -1,8 +1,8 @@
 #pragma once
 
 #include <Arduino.h>
-#include <Preferences.h>
 #include "nvs_flash.h"
+#include <Preferences.h>
 #include <SD.h>
 
 #include "pins.h"
@@ -13,8 +13,8 @@ extern String irc_nickname;
 extern String irc_username;
 extern String irc_realname;
 extern String irc_server;
-extern int irc_port;
-extern bool irc_tls;
+extern    int irc_port;
+extern   bool irc_tls;
 extern String irc_channel;
 extern String irc_nickserv;
 extern String wifi_ssid;
diff --git a/src/Utilities.cpp b/src/Utilities.cpp
@@ -0,0 +1,148 @@
+#include "Utilities.h"
+
+
+Pangodream_18650_CL BL(BOARD_BAT_ADC, CONV_FACTOR, READS);
+
+
+String formatBytes(size_t bytes) {
+    if (bytes < 1024)
+        return String(bytes) + " B";
+    else if (bytes < (1024 * 1024))
+        return String(bytes / 1024.0, 2) + " KB";
+    else if (bytes < (1024 * 1024 * 1024))
+        return String(bytes / 1024.0 / 1024.0, 2) + " MB";
+    else
+        return String(bytes / 1024.0 / 1024.0 / 1024.0, 2) + " GB";
+}
+
+
+char getKeyboardInput() {
+    char incoming = 0;
+    Wire.requestFrom(LILYGO_KB_SLAVE_ADDRESS, 1);
+    if (Wire.available()) {
+        incoming = Wire.read();
+        if (incoming != (char)0x00) {
+            return incoming;
+        }
+    }
+    return 0;
+}
+
+
+void setBrightness(uint8_t value) {
+    static uint8_t level = 16;
+    static uint8_t steps = 16;
+    value = constrain(value, 0, steps); // Ensure the brightness value is within the valid range
+
+    if (value == 0) {
+        digitalWrite(BOARD_BL_PIN, 0);
+        delay(3);
+        level = 0;
+        return;
+    }
+
+    if (level == 0) {
+        digitalWrite(BOARD_BL_PIN, 1);
+        level = steps;
+        delayMicroseconds(30);
+    }
+
+    int from = steps - level;
+    int to = steps - value;
+    int num = (steps + to - from) % steps;
+    for (int i = 0; i < num; i++) {
+        digitalWrite(BOARD_BL_PIN, 0);
+        digitalWrite(BOARD_BL_PIN, 1);
+    }
+
+    level = value;
+}
+
+
+void updateTimeFromNTP() {
+    Serial.println("Syncing time with NTP server...");
+    configTime(-5 * 3600, 3600, "pool.ntp.org", "north-america.pool.ntp.org", "time.nist.gov");
+
+    for (int i = 0; i < 10; ++i) { // Try up to 10 times
+        delay(2000);
+        struct tm timeinfo;
+        if (getLocalTime(&timeinfo)) {
+            Serial.println(&timeinfo, "Time synchronized: %A, %B %d %Y %H:%M:%S");
+            return;
+        } else {
+            Serial.println("Failed to synchronize time, retrying...");
+        }
+    }
+
+    Serial.println("Failed to synchronize time after multiple attempts."); // What do we do if we can't sync with the NTP server?
+}
+
+
+void printDeviceInfo() {
+    Serial.println("Gathering device info...");
+
+    // Get MAC Address
+    uint8_t mac[6];
+    esp_efuse_mac_get_default(mac);
+    String macAddress = String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX);
+
+    // Get Chip Info
+    uint32_t chipId = ESP.getEfuseMac();
+    esp_chip_info_t chip_info;
+    esp_chip_info(&chip_info);
+    String chipInfo = String(chip_info.model) + " Rev " + String(chip_info.revision) + ", " + String(chip_info.cores) + " cores, " +  String(ESP.getCpuFreqMHz()) + " MHz";
+
+    // Get Flash Info
+    size_t flashSize = spi_flash_get_chip_size();
+    size_t flashUsed = ESP.getFlashChipSize() - ESP.getFreeSketchSpace();
+    String flashInfo = formatBytes(flashUsed) + " / " + formatBytes(flashSize);
+    
+    // Get PSRAM Info
+    size_t total_psram = ESP.getPsramSize();
+    size_t free_psram = ESP.getFreePsram();
+    String psramInfo = formatBytes(total_psram - free_psram) + " / " + formatBytes(total_psram);
+
+    // Get Heap Info
+    size_t total_heap = ESP.getHeapSize();
+    size_t free_heap = ESP.getFreeHeap();
+    String heapInfo = formatBytes(total_heap - free_heap) + " / " + formatBytes(total_heap);
+
+    // Get WiFi Info
+    String wifiInfo = "Not connected";
+    String wifiSSID = "";
+    String wifiChannel = "";
+    String wifiSignal = "";
+    String wifiLocalIP = "";
+    String wifiGatewayIP = "";
+    if (WiFi.status() == WL_CONNECTED) {
+        wifiSSID = WiFi.SSID();
+        wifiChannel = String(WiFi.channel());
+        wifiSignal = String(WiFi.RSSI()) + " dBm";
+        wifiLocalIP = WiFi.localIP().toString();
+        wifiGatewayIP = WiFi.gatewayIP().toString();
+    }
+
+    Serial.println("MCU: ESP32-S3FN16R8");
+    Serial.println("LoRa Tranciever: Semtech SX1262 (915 MHz)"); // Need to set the frequency in pins.h
+    Serial.println("LCD: ST7789 SPI"); // Update this
+    Serial.println("Chip ID: " + String(chipId, HEX));
+    Serial.println("MAC Address: " + macAddress);
+    Serial.println("Chip Info: " + chipInfo);
+    Serial.println("Battery:");
+    Serial.println("  Pin Value: " + String(analogRead(34)));
+    //Serial.println("  Average Pin Value: " + String(BL.pinRead()));
+    //Serial.println("  Volts: " + String(BL.getBatteryVolts()));
+    //Serial.println("  Charge Level: " + String(BL.getBatteryChargeLevel()) + "%");
+    Serial.println("Memory:");
+    Serial.println("  Flash: " + flashInfo);
+    Serial.println("  PSRAM: " + psramInfo);
+    Serial.println("  Heap: " + heapInfo);
+    if (WiFi.status() == WL_CONNECTED) {
+        Serial.println("WiFi Info: " + wifiInfo);
+        Serial.println("  SSID: " + wifiSSID);
+        Serial.println("  Channel: " + wifiChannel);
+        Serial.println("  Signal: " + wifiSignal);
+        Serial.println("  Local IP: " + wifiLocalIP);
+        Serial.println("  Gateway IP: " + wifiGatewayIP);
+    }
+}
diff --git a/src/Utilities.h b/src/Utilities.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Arduino.h"
+#include "WiFi.h"
+#include "Wire.h"
+#include <Pangodream_18650_CL.h> // Power management
+
+#include "pins.h"
+
+extern Pangodream_18650_CL BL;
+
+String formatBytes(size_t bytes);
+char getKeyboardInput();
+void printDeviceInfo();
+void setBrightness(uint8_t value);
+void updateTimeFromNTP();
+
diff --git a/src/bootScreen.h b/src/bootScreen.h
@@ -1,5 +1,6 @@
-#define logo_width 199
+#define logo_width  199
 #define logo_height 165
+
 static unsigned char logo_bits[] = {
 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
diff --git a/src/main.ino b/src/main.ino
@@ -1,18 +1,5 @@
 // Standard includes
-#include <map>
 #include <time.h>
-#include <vector>
-
-// Aurduino includes
-#include <esp_wifi.h> // Needed for Mac spoofing
-#include <Pangodream_18650_CL.h> // Power management
-#include <Preferences.h>
-#include <SD.h>
-#include <TFT_eSPI.h>
-#include <WiFi.h>
-#include <WiFiClientSecure.h>
-#include <Wire.h>
-#include <WireGuard-ESP32.h>
 
 // Local includes
 #include "bootScreen.h"
@@ -20,96 +7,17 @@
 #include "pins.h"
 #include "Storage.h"
 #include "Speaker.h"
-
-
-// Constants
-#define CHAR_HEIGHT 10
-#define LINE_SPACING  0
-#define STATUS_BAR_HEIGHT 10
-#define INPUT_LINE_HEIGHT (CHAR_HEIGHT + LINE_SPACING)
-#define MAX_LINES ((SCREEN_HEIGHT - INPUT_LINE_HEIGHT - STATUS_BAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING))
-
-/// Struct to hold information about a WiFi network (do we want to include mac addresses)
-struct WiFiNetwork {
-    int index;
-    int channel;
-    int rssi;
-    String encryption;
-    String ssid;
-};
-
-// Initialize components and objects
-Pangodream_18650_CL BL(BOARD_BAT_ADC, CONV_FACTOR, READS);
-TFT_eSPI tft = TFT_eSPI();
-WiFiClient* client;
-
-// Memory vectors & maps
-std::map<String, uint32_t> nickColors;
-std::vector<String> lines; // Possible rename to bufferLines ?
-std::vector<bool> mentions;
-std::vector<WiFiNetwork> wifiNetworks;
-String inputBuffer = "";
-
-// Leftover crack variables (will be removed when preferences are done)
-const char* channel = "#comms";
-
-// Timing variables
-unsigned long infoScreenStartTime = 0;
-unsigned long configScreenStartTime = 0;
-unsigned long joinChannelTime = 0;
-unsigned long lastStatusUpdateTime = 0;
-unsigned long lastActivityTime = 0;
+#include "Utilities.h"
+#include "Display.h"
+#include "Network.h"
+#include "IRC.h"
 
 // Timing constants
 const unsigned long STATUS_UPDATE_INTERVAL = 15000; // 15 seconds
 const unsigned long INACTIVITY_TIMEOUT = 30000;     // 30 seconds
 
-// Dynamic variables
-bool infoScreen = false;
-bool configScreen = false;
-bool readyToJoinChannel = false;
-bool screenOn = true;
-int selectedNetworkIndex = 0;
-
-static WireGuard wg;
-
-
 
 // Main functions ---------------------------------------------------------------------------------
-void displayXBM() {
-    tft.fillScreen(TFT_BLACK);
-
-    // Get the center position for the logo
-    int x = (SCREEN_WIDTH - logo_width) / 2;
-    int y = (SCREEN_HEIGHT - logo_height) / 2;
-
-    tft.drawXBitmap(x, y, logo_bits, logo_width, logo_height, TFT_GREEN);
-
-    // Time handling
-    unsigned long startTime = millis();
-    bool wipeInitiated = false;
-
-    // Check for 'w' key press during the logo display time
-    while (millis() - startTime < 3000) {
-        if (getKeyboardInput() == 'w') {
-            wipeNVS();
-            tft.fillScreen(TFT_BLACK);
-            displayCenteredText("NVS WIPED");
-            delay(2000);
-            wipeInitiated = true;
-            break;
-        }
-    }
-}
-
-
-void wgConnect(const IPAddress& localIp, const char* privateKey, const char* endpointAddress, const char* publicKey, uint16_t endpointPort) {
-    //IPAddress localIp(192, 168, 1, 100);
-    //IPAddress endpointIp(192, 168, 1, 1);
-    wg.begin(localIp, privateKey, endpointAddress, publicKey, endpointPort);
-}
-
-
 void setup() {
     // Initialize serial communication
     Serial.begin(115200);
@@ -245,1169 +153,4 @@ void loop() {
                 turnOffScreen();
         }
     }
-}
-
-// ------------------------------------------------------------------------------------------------
-
-
-
-// WiFi functions ---------------------------------------------------------------------------------
-void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
-    switch (event) {
-        case SYSTEM_EVENT_STA_CONNECTED:
-            Serial.println("WiFi connected");
-            break;
-        case SYSTEM_EVENT_STA_DISCONNECTED:
-            Serial.println("WiFi disconnected");
-            break;
-        case SYSTEM_EVENT_STA_GOT_IP:
-            Serial.println("WiFi got IP address: " + WiFi.localIP().toString());
-            break;
-        case SYSTEM_EVENT_STA_LOST_IP:
-            Serial.println("WiFi lost IP address");
-            break;
-        default:
-            break;
-    }
-}
-
-
-void connectToWiFi(String ssid, String password) {
-    Serial.println("Connecting to WiFi network: " + ssid);
-    WiFi.begin(ssid.c_str(), password.c_str());
-
-    // Wait for the WiFi connection to complete (or timeout after 10 seconds)
-    int attempts = 0;
-    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
-        delay(500);
-        displayCenteredText("CONNECTING TO " + ssid);
-        attempts++;
-    }
-
-    // Handle the connection result
-    if (WiFi.status() == WL_CONNECTED) {
-        displayCenteredText("CONNECTED TO " + ssid);
-
-        // Sync time with NTP server
-        updateTimeFromNTP();
-
-        // Store the WiFi credentials upon successful connection
-        preferences.begin("config", false);
-        preferences.putString("wifi_ssid", ssid);
-        preferences.putString("wifi_password", password);
-        preferences.end();
-        Serial.println("Stored WiFi credentials updated");
-
-        // Cache the WiFi credentials
-        wifi_ssid = ssid;
-        wifi_password = password;
-    } else {
-        Serial.println("Failed to connect to WiFi network: " + ssid);
-        displayCenteredText("WIFI CONNECTION FAILED");
-        
-        // Clear stored credentials on failure
-        preferences.begin("config", false);
-        preferences.putString("wifi_ssid", "");
-        preferences.putString("wifi_password", "");
-        preferences.end();
-        Serial.println("Stored WiFi credentials removed");\
-
-        // Clear the cached WiFi credentials
-        wifi_ssid = "";
-        wifi_password = "";
-
-        scanWiFiNetworks(); // Rescan for networks
-    }
-}
-
-
-void randomizeMacAddress() {
-    Serial.println("Current MAC Address: " + WiFi.macAddress());
-
-    uint8_t new_mac[6];
-
-    for (int i = 0; i < 6; ++i)
-        new_mac[i] = random(0x00, 0xFF);
-
-    if (esp_wifi_set_mac(WIFI_IF_STA, new_mac) == ESP_OK)
-        Serial.println("New MAC Address: " + WiFi.macAddress());
-    else
-        Serial.print("Failed to set new MAC Address");
-}
-
-
-// Need to utilize this function still
-String getEncryptionType(wifi_auth_mode_t encryptionType) {
-    switch (encryptionType) {
-        case (WIFI_AUTH_OPEN):
-            return "Open";
-        case (WIFI_AUTH_WEP):
-            return "WEP";
-        case (WIFI_AUTH_WPA_PSK):
-            return "WPA_PSK";
-        case (WIFI_AUTH_WPA2_PSK):
-            return "WPA2_PSK";
-        case (WIFI_AUTH_WPA_WPA2_PSK):
-            return "WPA_WPA2_PSK";
-        case (WIFI_AUTH_WPA2_ENTERPRISE):
-            return "WPA2_ENTERPRISE";
-        default:
-            return "Unknown";
-    }
-}
-
-
-void scanWiFiNetworks() {
-    Serial.println("Scanning for WiFi networks...");
-    displayCenteredText("SCANNING WIFI");
-    delay(1000); // Do we need this delay?
-
-    int n = WiFi.scanNetworks();
-
-    if (n == 0) {
-        Serial.println("No WiFi networks found");
-        displayCenteredText("NO NETWORKS FOUND");
-        delay(2000);
-        scanWiFiNetworks();
-        return;
-    }
-    
-    Serial.print("Total number of networks found: ");
-    Serial.println(n);
-
-    // Loop through the networks and store them in the wifiNetworks vector
-    for (int i = 0; i < n && i < 100; i++) {
-        WiFiNetwork net;
-        net.index = i + 1;
-        net.channel = WiFi.channel(i);
-        net.rssi = WiFi.RSSI(i);
-        net.encryption = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "Open" : "Secured";
-        String ssid = WiFi.SSID(i).substring(0, 32); // WiFi SSIDs are limited to 32 characters
-        net.ssid = ssid;
-        wifiNetworks.push_back(net);
-    }
-
-    displayWiFiNetworks(); // Display the scanned networks
-}
-
-
-void handlePasswordInput(char key) {
-    if (key == '\n' || key == '\r') { // Enter
-        wifi_password = inputBuffer;
-        inputBuffer = "";
-        connectToWiFi(wifi_ssid, wifi_password);
-    } else if (key == '\b') { // Backspace
-        if (inputBuffer.length() > 0) {
-            inputBuffer.remove(inputBuffer.length() - 1);
-            displayPasswordInputLine();
-        }
-    } else if (inputBuffer.length() < 63) { // WiFi passwords are limited to 63 characters
-        inputBuffer += key;
-        displayPasswordInputLine();
-    }
-}
-
-
-void displayPasswordInputLine() {
-    tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
-    tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
-    tft.setTextColor(TFT_WHITE);
-    tft.setTextSize(1);
-    tft.print("> " + inputBuffer);
-}
-
-
-void updateSelectedNetwork(int delta) {
-    int newIndex = selectedNetworkIndex + delta;
-    if (newIndex >= 0 && newIndex < wifiNetworks.size()) {
-        selectedNetworkIndex = newIndex;
-        displayWiFiNetworks();
-    }
-}
-
-
-void displayWiFiNetworks() {
-    tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT, TFT_BLACK); // Clear the screen (except the status bar area)
-
-    // Set the header for the WiFi networks
-    tft.setTextSize(1);
-    tft.setTextColor(TFT_CYAN);
-    tft.setCursor(0, STATUS_BAR_HEIGHT);
-    tft.printf("#  CH  RSSI  ENC      SSID\n");
-    tft.setTextColor(TFT_WHITE);
-
-    // Calculate the starting index and max displayed lines
-    int maxDisplayedLines = (SCREEN_HEIGHT - STATUS_BAR_HEIGHT - CHAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING);
-    int startIdx = selectedNetworkIndex >= maxDisplayedLines ? selectedNetworkIndex - maxDisplayedLines + 1 : 0;
-
-    // Display the WiFi networks on the screen
-    for (int i = startIdx; i < wifiNetworks.size() && i < startIdx + maxDisplayedLines; ++i) {
-        displayWiFiNetwork(i, i - startIdx + 1); // +1 to account for the header
-    }
-}
-
-
-void displayWiFiNetwork(int index, int displayIndex) {
-    int y = STATUS_BAR_HEIGHT + displayIndex * (CHAR_HEIGHT + LINE_SPACING);
-    tft.setCursor(0, y);
-
-    // Set the text color based on the selected network
-    if (index == selectedNetworkIndex)
-        tft.setTextColor(TFT_GREEN, TFT_BLACK);
-    else
-        tft.setTextColor(TFT_WHITE, TFT_BLACK);
-
-    WiFiNetwork net = wifiNetworks[index];
-
-    // Index and channel number
-    uint16_t rssiColor = getColorFromPercentage((int32_t)net.rssi);
-    tft.printf("%-2d %-3d ", net.index, net.channel);
-
-    // RSSI
-    tft.setTextColor(rssiColor, TFT_BLACK);
-    tft.printf("%-5d ", net.rssi);
-
-    // Encryption and SSID
-    tft.setTextColor(index == selectedNetworkIndex ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
-    tft.printf("%-8s %s", net.encryption.c_str(), net.ssid.c_str());
-}
-
-
-void handleWiFiSelection(char key) {
-    if (key == 'u') {
-        updateSelectedNetwork(-1);
-    } else if (key == 'd') {
-        updateSelectedNetwork(1);
-    } else if (key == '\n' || key == '\r') {
-        wifi_ssid = wifiNetworks[selectedNetworkIndex].ssid;
-        if (wifiNetworks[selectedNetworkIndex].encryption == "Secured") {
-            inputBuffer = "";
-            tft.fillScreen(TFT_BLACK);
-            tft.setTextSize(1);
-            tft.setTextColor(TFT_WHITE);
-            tft.setCursor(0, STATUS_BAR_HEIGHT);
-            tft.println("ENTER PASSWORD:");
-            displayPasswordInputLine();
-            // Switch to password input mode
-            while (true) {
-                char incoming = getKeyboardInput();
-                if (incoming != 0) {
-                    handlePasswordInput(incoming);
-                    if (incoming == '\n' || incoming == '\r') {
-                        break;
-                    }
-                }
-            }
-        } else {
-            wifi_password = "";
-            connectToWiFi(wifi_ssid, wifi_password);
-        }
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-
-
-
-// IRC functions ----------------------------------------------------------------------------------
-bool connectToIRC() {
-    if (irc_tls) {
-        Serial.println("Connecting to IRC with TLS: " + String(irc_server) + ":" + String(irc_port));
-        client = new WiFiClientSecure();
-        static_cast<WiFiClientSecure*>(client)->setInsecure();
-        return static_cast<WiFiClientSecure*>(client)->connect(irc_server.c_str(), irc_port);
-    } else {
-        Serial.println("Connecting to IRC: " + String(irc_server) + ":" + String(irc_port));
-        client = new WiFiClient();
-        return client->connect(irc_server.c_str(), irc_port);
-    }
-}
-
-
-void sendIRC(String command) {
-    if (command.length() > 510) {
-        Serial.println("Failed to send: Command too long");
-        return;
-    }
-
-    if (client->connected()) {
-        if (client->println(command)) {
-            Serial.println("IRC: >>> " + command);
-        } else {
-            Serial.println("Failed to send: " + command);
-        }
-    } else {
-        Serial.println("Failed to send: Not connected to IRC");
-    }
-}
-
-
-void handleIRC() {
-    while (client->available()) {
-        String line = client->readStringUntil('\n');
-
-        // This is an anomaly, but it can happen and I wanted debug output for if it does
-        if (line.length() > 512) {
-            Serial.println("WARNING: IRC line length exceeds 512 characters!");
-            line = line.substring(0, 512); // Truncate the line to 512 characters anyways
-        }
-
-        Serial.println("IRC: " + line);
-
-        int firstSpace = line.indexOf(' ');
-        int secondSpace = line.indexOf(' ', firstSpace + 1);
-
-        // Ensure the spaces are found and prevent substring from going out of bounds
-        if (firstSpace != -1 && secondSpace != -1) {
-            String prefix = line.substring(0, firstSpace);
-            String command = line.substring(firstSpace + 1, secondSpace);
-
-            // RPL_WELCOME
-            if (command == "001") {
-                joinChannelTime = millis() + 2500;
-                readyToJoinChannel = true;
-            }
-        }
-
-        if (line.startsWith("PING")) {
-            String pingResponse = "PONG " + line.substring(line.indexOf(' ') + 1);
-            sendIRC(pingResponse);
-        } else {
-            parseAndDisplay(line);
-            lastActivityTime = millis();
-        }
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-
-
-
-// Indepentent functions (does not rely on any other functions) -----------------------------------
-int calculateLinesRequired(String message) {
-    int linesRequired = 1;
-    int lineWidth = 0;
-
-    for (unsigned int i = 0; i < message.length(); i++) {
-        char c = message[i];
-        if (c == '\x03') {
-            // Check for foreground color
-            if (i + 1 < message.length() && isdigit(message[i + 1])) {
-                i++;
-                if (i + 1 < message.length() && isdigit(message[i + 1])) {
-                    i++;
-                }
-            }
-            // Check for background color
-            if (i + 1 < message.length() && message[i + 1] == ',' && isdigit(message[i + 2])) {
-                i += 2; // Skip the comma
-                if (i + 1 < message.length() && isdigit(message[i + 1])) {
-                    i++;
-                }
-            }
-        } else if (c != '\x02' && c != '\x0F' && c != '\x1F') { // Ignore other formatting codes as they are not as prevalent
-            lineWidth += tft.textWidth(String(c));
-            if (lineWidth > SCREEN_WIDTH) {
-                linesRequired++;
-                lineWidth = tft.textWidth(String(c));
-            }
-        }
-    }
-
-    return linesRequired;
-}
-
-
-void displayCenteredText(String text) {
-    tft.fillScreen(TFT_BLACK);
-    tft.setTextDatum(MC_DATUM);
-    tft.setTextColor(TFT_GREEN, TFT_BLACK);
-    tft.drawString(text, SCREEN_WIDTH / 2, (SCREEN_HEIGHT + STATUS_BAR_HEIGHT) / 2);
-}
-
-
-String formatBytes(size_t bytes) {
-    if (bytes < 1024)
-        return String(bytes) + " B";
-    else if (bytes < (1024 * 1024))
-        return String(bytes / 1024.0, 2) + " KB";
-    else if (bytes < (1024 * 1024 * 1024))
-        return String(bytes / 1024.0 / 1024.0, 2) + " MB";
-    else
-        return String(bytes / 1024.0 / 1024.0 / 1024.0, 2) + " GB";
-}
-
-
-uint32_t generateRandomColor() {
-    return tft.color565(random(0, 255), random(0, 255), random(0, 255));
-}
-
-
-uint16_t getColorFromPercentage(int percentage) {
-    if      (percentage > 75) return TFT_GREEN;
-    else if (percentage > 50) return TFT_YELLOW;
-    else if (percentage > 25) return TFT_ORANGE;
-    else                      return TFT_RED;
-}
-
-
-char getKeyboardInput() {
-    char incoming = 0;
-    Wire.requestFrom(LILYGO_KB_SLAVE_ADDRESS, 1);
-    if (Wire.available()) {
-        incoming = Wire.read();
-        if (incoming != (char)0x00) {
-            return incoming;
-        }
-    }
-    return 0;
-}
-
-
-// Cheers to e for hand typing these color codes
-uint16_t getColorFromCode(int colorCode) {
-    switch (colorCode) {
-        case 0: return TFT_WHITE;
-        case 1: return TFT_BLACK;
-        case 2: return tft.color565(0, 0, 128); // Dark Blue (Navy)
-        case 3: return TFT_GREEN;
-        case 4: return TFT_RED;
-        case 5: return tft.color565(128, 0, 0); // Brown (Maroon)
-        case 6: return tft.color565(128, 0, 128); // Purple
-        case 7: return tft.color565(255, 165, 0); // Orange
-        case 8: return TFT_YELLOW;
-        case 9: return tft.color565(144, 238, 144); // Light Green
-        case 10: return tft.color565(0, 255, 255); // Cyan (Light Blue)
-        case 11: return tft.color565(224, 255, 255); // Light Cyan (Aqua)
-        case 12: return TFT_BLUE;
-        case 13: return tft.color565(255, 192, 203); // Pink (Light Purple)
-        case 14: return tft.color565(128, 128, 128); // Grey
-        case 15: return tft.color565(211, 211, 211); // Light Grey
-        case 16: return 0x4000;
-        case 17: return 0x4100;
-        case 18: return 0x4220;
-        case 19: return 0x3220;
-        case 20: return 0x0220;
-        case 21: return 0x0225;
-        case 22: return 0x0228;
-        case 23: return 0x0128;
-        case 24: return 0x0008;
-        case 25: return 0x2808;
-        case 26: return 0x4008;
-        case 27: return 0x4005;
-        case 28: return 0x7000;
-        case 29: return 0x71C0;
-        case 30: return 0x73A0;
-        case 31: return 0x53A0;
-        case 32: return 0x03A0;
-        case 33: return 0x03A9;
-        case 34: return 0x03AE;
-        case 35: return 0x020E;
-        case 36: return 0x000E;
-        case 37: return 0x480E;
-        case 38: return 0x700E;
-        case 39: return 0x7008;
-        case 40: return 0xB000;
-        case 41: return 0xB300;
-        case 42: return 0xB5A0;
-        case 43: return 0x7DA0;
-        case 44: return 0x05A0;
-        case 45: return 0x05AE;
-        case 46: return 0x05B6;
-        case 47: return 0x0316;
-        case 48: return 0x0016;
-        case 49: return 0x7016;
-        case 50: return 0xB016;
-        case 51: return 0xB00D;
-        case 52: return 0xF800;
-        case 53: return 0xFC60;
-        case 54: return 0xFFE0;
-        case 55: return 0xB7E0;
-        case 56: return 0x07E0;
-        case 57: return 0x07F4;
-        case 58: return 0x07FF;
-        case 59: return 0x047F;
-        case 60: return 0x001F;
-        case 61: return 0xA01F;
-        case 62: return 0xF81F;
-        case 63: return 0xF813;
-        case 64: return 0xFACB;
-        case 65: return 0xFDAB;
-        case 66: return 0xFFEE;
-        case 67: return 0xCFEC;
-        case 68: return 0x6FED;
-        case 69: return 0x67F9;
-        case 70: return 0x6FFF;
-        case 71: return 0x5DBF;
-        case 72: return 0x5ADF;
-        case 73: return 0xC2DF;
-        case 74: return 0xFB3F;
-        case 75: return 0xFAD7;
-        case 76: return 0xFCF3;
-        case 77: return 0xFE93;
-        case 78: return 0xFFF3;
-        case 79: return 0xE7F3;
-        case 80: return 0x9FF3;
-        case 81: return 0x9FFB;
-        case 82: return 0x9FFF;
-        case 83: return 0x9E9F;
-        case 84: return 0x9CFF;
-        case 85: return 0xDCFF;
-        case 86: return 0xFCFF;
-        case 87: return 0xFCBA;
-        case 88: return 0x0000;
-        case 89: return 0x1082;
-        case 90: return 0x2945;
-        case 91: return 0x31A6;
-        case 92: return 0x4A69;
-        case 93: return 0x632C;
-        case 94: return 0x8410;
-        case 95: return 0x9CF3;
-        case 96: return 0xBDF7;
-        case 97: return 0xE71C;
-        case 98: return 0xFFFF;
-        default: return TFT_WHITE;
-    }
-}
-
-
-void setBrightness(uint8_t value) {
-    static uint8_t level = 16;
-    static uint8_t steps = 16;
-    value = constrain(value, 0, steps); // Ensure the brightness value is within the valid range
-
-    if (value == 0) {
-        digitalWrite(BOARD_BL_PIN, 0);
-        delay(3);
-        level = 0;
-        return;
-    }
-
-    if (level == 0) {
-        digitalWrite(BOARD_BL_PIN, 1);
-        level = steps;
-        delayMicroseconds(30);
-    }
-
-    int from = steps - level;
-    int to = steps - value;
-    int num = (steps + to - from) % steps;
-    for (int i = 0; i < num; i++) {
-        digitalWrite(BOARD_BL_PIN, 0);
-        digitalWrite(BOARD_BL_PIN, 1);
-    }
-
-    level = value;
-}
-
-
-void turnOffScreen() {
-    Serial.println("Screen turned off");
-    tft.writecommand(TFT_DISPOFF);
-    tft.writecommand(TFT_SLPIN);
-    digitalWrite(TFT_BL, LOW);
-    screenOn = false;
-}
-
-
-void turnOnScreen() {
-    Serial.println("Screen turned on");
-    digitalWrite(TFT_BL, HIGH);
-    tft.writecommand(TFT_SLPOUT);
-    tft.writecommand(TFT_DISPON);
-    screenOn = true;
-}
-
-
-void updateTimeFromNTP() {
-    Serial.println("Syncing time with NTP server...");
-    configTime(-5 * 3600, 3600, "pool.ntp.org", "north-america.pool.ntp.org", "time.nist.gov");
-
-    for (int i = 0; i < 10; ++i) { // Try up to 10 times
-        delay(2000);
-        struct tm timeinfo;
-        if (getLocalTime(&timeinfo)) {
-            Serial.println(&timeinfo, "Time synchronized: %A, %B %d %Y %H:%M:%S");
-            return;
-        } else {
-            Serial.println("Failed to synchronize time, retrying...");
-        }
-    }
-
-    Serial.println("Failed to synchronize time after multiple attempts."); // What do we do if we can't sync with the NTP server?
-}
-
-// ------------------------------------------------------------------------------------------------
-
-
-
-// Logic and Interface functions ------------------------------------------------------------------
-int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick = false) {
-    uint16_t fgColor = TFT_WHITE;
-    uint16_t bgColor = TFT_BLACK;
-    bool bold = false;
-    bool underline = false;
-    bool nickHighlighted = false; // Track if the nick has been highlighted
-
-    int nickPos = -1;
-    if (highlightNick) {
-        nickPos = message.indexOf(irc_nickname);
-    }
-
-    for (unsigned int i = 0; i < message.length(); i++) {
-        char c = message[i];
-        if (c == '\x02') { // Bold
-            bold = !bold;
-
-            // Bold text is not supported by the current font...it makes the entire screen bold
-            //tft.setTextFont(bold ? 2 : 1);
-        } else if (c == '\x1F') { // Underline
-            underline = !underline;
-            // need to add this still
-        } else if (c == '\x03') { // Color
-            fgColor = TFT_WHITE;
-            bgColor = TFT_BLACK;
-
-            if (i + 1 < message.length() && (isdigit(message[i + 1]) || message[i + 1] == ',')) {
-                int colorCode = -1;
-                if (isdigit(message[i + 1])) {
-                    colorCode = message[++i] - '0';
-                    if (i + 1 < message.length() && isdigit(message[i + 1]))
-                        colorCode = colorCode * 10 + (message[++i] - '0');
-                }
-
-                if (colorCode != -1)
-                    fgColor = getColorFromCode(colorCode);
-
-                if (i + 1 < message.length() && message[i + 1] == ',') {
-                    i++;
-                    int bgColorCode = -1;
-                    if (isdigit(message[i + 1])) {
-                        bgColorCode = message[++i] - '0';
-                        if (i + 1 < message.length() && isdigit(message[i + 1]))
-                            bgColorCode = bgColorCode * 10 + (message[++i] - '0');
-                    }
-
-                    if (bgColorCode != -1)
-                        bgColor = getColorFromCode(bgColorCode);
-
-                }
-
-                tft.setTextColor(fgColor, bgColor);
-            }
-        } else if (c == '\x0F') { // Reset
-            fgColor = TFT_WHITE;
-            bgColor = TFT_BLACK;
-            bold = false;
-            underline = false;
-            tft.setTextColor(fgColor, bgColor);
-            tft.setTextFont(1);
-        } else {
-            if (highlightNick && !nickHighlighted && nickPos != -1 && i == nickPos) {
-                tft.setTextColor(TFT_YELLOW, bgColor); // Set both foreground and background color
-                for (char nc : irc_nickname) {
-                    tft.print(nc);
-                    i++;
-                }
-                i--; // Adjust for the loop increment
-                tft.setTextColor(TFT_WHITE, bgColor); // Reset to white foreground with current background
-                nickHighlighted = true;
-            } else {
-                if (tft.getCursorX() + tft.textWidth(String(c)) > SCREEN_WIDTH) {
-                    cursorY += lineHeight;
-                    tft.setCursor(0, cursorY);
-                }
-                if (c == ' ') { // Handle spaces separately to ensure background color is applied (do we need this anyhmore since .trim() was removed?)
-                    int spaceWidth = tft.textWidth(" ");
-                    tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
-                    tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
-                } else {
-                    tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.textWidth(String(c)), lineHeight, bgColor);
-                    tft.setTextColor(fgColor, bgColor);
-                    tft.print(c);
-                }
-            }
-        }
-    }
-
-    // Ensure trailing spaces are displayed with background color (do we need this anymore since .trim() was removed?)
-    if (message.endsWith(" ")) {
-        int trailingSpaces = 0;
-        for (int i = message.length() - 1; i >= 0 && message[i] == ' '; i--) {
-            trailingSpaces++;
-        }
-        for (int i = 0; i < trailingSpaces; i++) {
-            int spaceWidth = tft.textWidth(" ");
-            tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
-            tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
-        }
-    }
-
-    cursorY += lineHeight; // Add line height after printing the message
-    return cursorY; // Return the new cursor Y position for the next line
-}
-
-
-
-void displayLines() {
-    tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT, TFT_BLACK);
-
-    int cursorY = STATUS_BAR_HEIGHT;
-    int totalLinesHeight = 0;
-    std::vector<int> lineHeights;
-
-    // Calculate total height needed for all lines
-    for (const String& line : lines) {
-        int lineHeight = calculateLinesRequired(line) * CHAR_HEIGHT;
-        lineHeights.push_back(lineHeight);
-        totalLinesHeight += lineHeight;
-    }
-
-    // Remove lines from the top if they exceed the screen height
-    while (totalLinesHeight > SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT) {
-        totalLinesHeight -= lineHeights.front();
-        lines.erase(lines.begin());
-        mentions.erase(mentions.begin());
-        lineHeights.erase(lineHeights.begin());
-    }
-
-    // Render each line
-    for (size_t i = 0; i < lines.size(); ++i) {
-        const String& line = lines[i];
-        bool mention = mentions[i];
-
-        tft.setCursor(0, cursorY);
-
-        if (line.startsWith("JOIN ")) {
-            tft.setTextColor(TFT_GREEN);
-            tft.print("JOIN ");
-            int startIndex = 5;
-            int endIndex = line.indexOf(" has joined ");
-            String senderNick = line.substring(startIndex, endIndex);
-            tft.setTextColor(nickColors[senderNick]);
-            tft.print(senderNick);
-            tft.setTextColor(TFT_WHITE);
-            tft.print(" has joined ");
-            tft.setTextColor(TFT_CYAN);
-            tft.print(channel);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("PART ")) {
-            tft.setTextColor(TFT_RED);
-            tft.print("PART ");
-            int startIndex = 5;
-            int endIndex = line.indexOf(" has EMO-QUIT ");
-            String senderNick = line.substring(startIndex, endIndex);
-            tft.setTextColor(nickColors[senderNick]);
-            tft.print(senderNick);
-            tft.setTextColor(TFT_WHITE);
-            tft.print(" has EMO-QUIT ");
-            tft.setTextColor(TFT_CYAN);
-            tft.print(channel);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("QUIT ")) {
-            tft.setTextColor(TFT_RED);
-            tft.print("QUIT ");
-            String senderNick = line.substring(5);
-            tft.setTextColor(nickColors[senderNick]);
-            tft.print(senderNick);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("NICK ")) {
-            tft.setTextColor(TFT_BLUE);
-            tft.print("NICK ");
-            int startIndex = 5;
-            int endIndex = line.indexOf(" -> ");
-            String oldNick = line.substring(startIndex, endIndex);
-            String newNick = line.substring(endIndex + 4);
-            tft.setTextColor(nickColors[oldNick]);
-            tft.print(oldNick);
-            tft.setTextColor(TFT_WHITE);
-            tft.print(" -> ");
-            tft.setTextColor(nickColors[newNick]);
-            tft.print(newNick);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("KICK ")) {
-            tft.setTextColor(TFT_RED);
-            tft.print("KICK ");
-            int startIndex = 5;
-            int endIndex = line.indexOf(" by ");
-            String kickedNick = line.substring(startIndex, endIndex);
-            String kicker = line.substring(endIndex + 4);
-            tft.setTextColor(nickColors[kickedNick]);
-            tft.print(kickedNick);
-            tft.setTextColor(TFT_WHITE);
-            tft.print(" by ");
-            tft.setTextColor(nickColors[kicker]);
-            tft.print(kicker);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("MODE ")) {
-            tft.setTextColor(TFT_BLUE);
-            tft.print("MODE ");
-            String modeChange = line.substring(5);
-            tft.setTextColor(TFT_WHITE);
-            tft.print(modeChange);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("ERROR ")) {
-            tft.setTextColor(TFT_RED);
-            tft.print("ERROR ");
-            String errorReason = line.substring(6);
-            tft.setTextColor(TFT_DARKGREY);
-            tft.print(errorReason);
-            cursorY += CHAR_HEIGHT;
-        } else if (line.startsWith("* ")) {
-            tft.setTextColor(TFT_MAGENTA);
-            tft.print("* ");
-            int startIndex = 2;
-            int endIndex = line.indexOf(' ', startIndex);
-            String senderNick = line.substring(startIndex, endIndex);
-            String actionMessage = line.substring(endIndex + 1);
-            tft.setTextColor(nickColors[senderNick]);
-            tft.print(senderNick);
-            tft.setTextColor(TFT_MAGENTA);
-            tft.print(" " + actionMessage);
-            cursorY += CHAR_HEIGHT;
-        } else {
-            int colonIndex = line.indexOf(':');
-            String senderNick = line.substring(0, colonIndex);
-            String message = line.substring(colonIndex + 1);
-
-            tft.setTextColor(nickColors[senderNick]);
-            tft.print(senderNick);
-            tft.setTextColor(TFT_WHITE);
-            cursorY = renderFormattedMessage(":" + message, cursorY, CHAR_HEIGHT, mention);
-        }
-    }
-
-    displayInputLine();
-}
-
-
-void addLine(String senderNick, String message, String type, bool mention = false, uint16_t errorColor = TFT_WHITE, uint16_t reasonColor = TFT_WHITE) {
-    if (type != "error" && nickColors.find(senderNick) == nickColors.end())
-        nickColors[senderNick] = generateRandomColor();
-
-    String formattedMessage;
-    if (type == "join") {
-        formattedMessage = "JOIN " + senderNick + " has joined " + String(channel);
-    } else if (type == "part") {
-        formattedMessage = "PART " + senderNick + " has EMO-QUIT " + String(channel);
-    } else if (type == "quit") {
-        formattedMessage = "QUIT " + senderNick;
-    } else if (type == "nick") {
-        int arrowPos = message.indexOf(" -> ");
-        String oldNick = senderNick;
-        String newNick = message.substring(arrowPos + 4);
-        if (nickColors.find(newNick) == nickColors.end()) {
-            nickColors[newNick] = generateRandomColor();
-        }
-        formattedMessage = "NICK " + oldNick + " -> " + newNick;
-    } else if (type == "kick") {
-        formattedMessage = "KICK " + senderNick + message;
-    } else if (type == "mode") {
-        formattedMessage = "MODE " + message;
-        tft.setTextColor(TFT_BLUE);
-    } else if (type == "action") {
-        formattedMessage = "* " + senderNick + " " + message;
-    } else if (type == "error") {
-        formattedMessage = "ERROR " + message;
-        senderNick = "ERROR"; // Probably a better way to handle this than emulating a NICK
-    } else {
-        formattedMessage = senderNick + ": " + message;
-    }
-
-    int linesRequired = calculateLinesRequired(formattedMessage);
-
-    while (lines.size() + linesRequired > MAX_LINES) {
-        lines.erase(lines.begin());
-        mentions.erase(mentions.begin());
-    }
-
-    if (type == "error") {
-        lines.push_back("ERROR " + message);
-        mentions.push_back(false);
-    } else {
-        lines.push_back(formattedMessage);
-        mentions.push_back(mention);
-    }
-
-    displayLines();
-}
-
-
-void parseAndDisplay(String line) {
-    int firstSpace = line.indexOf(' ');
-    int secondSpace = line.indexOf(' ', firstSpace + 1);
-
-    if (firstSpace != -1 && secondSpace != -1) {
-        String command = line.substring(firstSpace + 1, secondSpace);
-
-        if (command == "PRIVMSG") {
-            int thirdSpace = line.indexOf(' ', secondSpace + 1);
-            String target = line.substring(secondSpace + 1, thirdSpace);
-            if (target == String(channel)) {
-                int colonPos = line.indexOf(':', thirdSpace);
-                String message = line.substring(colonPos + 1);
-                String senderNick = line.substring(1, line.indexOf('!'));
-                bool mention = message.indexOf(irc_nickname) != -1;
-
-                if (mention)
-                    playNotificationSound(); // Will need a check here in the future when we have the ability to turn on/off notification sounds...
-
-                if (message.startsWith(String("\x01") + "ACTION ") && message.endsWith("\x01")) {
-                    String actionMessage = message.substring(8, message.length() - 1);
-                    addLine(senderNick, actionMessage, "action");
-                } else {
-                    addLine(senderNick, message, "message", mention);
-                }
-            }
-        } else if (command == "JOIN" && line.indexOf(channel) != -1) {
-            String senderNick = line.substring(1, line.indexOf('!'));
-            addLine(senderNick, " has joined " + String(channel), "join");
-        } else if (command == "PART" && line.indexOf(channel) != -1) {
-            String senderNick = line.substring(1, line.indexOf('!'));
-            addLine(senderNick, " has EMO-QUIT " + String(channel), "part");
-        } else if (command == "QUIT") {
-            String senderNick = line.substring(1, line.indexOf('!'));
-            addLine(senderNick, "", "quit");
-        } else if (command == "NICK") {
-            String prefix = line.startsWith(":") ? line.substring(1, firstSpace) : "";
-            String newNick = line.substring(line.lastIndexOf(':') + 1);
-
-            if (prefix.indexOf('!') == -1) { // Our own NICK changes
-                addLine(irc_nickname, " -> " + newNick, "nick");
-                irc_nickname = newNick; // Update global nick when we get confirmation
-            } else { // Other peoples NICK change
-                String oldNick = prefix.substring(0, prefix.indexOf('!'));
-                addLine(oldNick, " -> " + newNick, "nick");
-                if (oldNick == irc_nickname) {
-                    irc_nickname = newNick; // Update global nick when we get confirmation
-                }
-            }
-        } else if (command == "KICK") {
-            int thirdSpace = line.indexOf(' ', secondSpace + 1);
-            int fourthSpace = line.indexOf(' ', thirdSpace + 1);
-            String kicker = line.substring(1, line.indexOf('!'));
-            String kicked = line.substring(thirdSpace + 1, fourthSpace);
-            addLine(kicked, " by " + kicker, "kick");
-        } else if (command == "MODE") {
-            String modeChange = line.substring(secondSpace + 1);
-            addLine("", modeChange, "mode");
-        } else if (command == "432") { // ERR_ERRONEUSNICKNAME
-            addLine("ERROR", "ERR_ERRONEUSNICKNAME", "error", TFT_RED, TFT_DARKGREY);
-        } else if (command == "433") { // ERR_NICKNAMEINUSE
-            addLine("ERROR", "ERR_NICKNAMEINUSE", "error", TFT_RED, TFT_DARKGREY);
-            irc_nickname = "ACID_" + String(random(1000, 9999)); // Generate a random nickname
-            sendIRC("NICK " + irc_nickname);                     // Attempt to change to the random nickname
-        }
-    }
-}
-
-
-void handleKeyboardInput(char key) {
-    lastActivityTime = millis(); // Update last activity time to reset the inactivity timer
-
-    if (!screenOn) {
-        turnOnScreen();
-        screenOn = true;
-        return;
-    }
-
-    static bool altPressed = false;
-
-    if (key == '\n' || key == '\r') { // Enter
-        if (inputBuffer.startsWith("/nick ")) {
-            String newNick = inputBuffer.substring(6);
-            sendIRC("NICK " + newNick);
-            inputBuffer = "";
-        } else if (inputBuffer.startsWith("/config")) {
-            configScreen = true;
-            configScreenStartTime = millis();
-            inputBuffer = "";
-        } else if (inputBuffer.startsWith("/info")) {
-            infoScreen = true;
-            infoScreenStartTime = millis();
-            printDeviceInfo();
-            inputBuffer = "";
-        } else if (inputBuffer.startsWith("/raw ")) {
-            String rawCommand = inputBuffer.substring(5);
-            sendIRC(rawCommand);
-        } else if (inputBuffer.startsWith("/me ")) {
-            String actionMessage = inputBuffer.substring(4);
-            sendIRC("PRIVMSG " + String(channel) + " :\001ACTION " + actionMessage + "\001");
-            addLine(irc_nickname, actionMessage, "action");
-            inputBuffer = "";
-        } else {
-            sendIRC("PRIVMSG " + String(channel) + " :" + inputBuffer);
-            addLine(irc_nickname, inputBuffer, "message");
-        }
-        inputBuffer = "";
-        displayInputLine();
-    } else if (key == '\b') { // Backspace
-        if (inputBuffer.length() > 0) {
-            inputBuffer.remove(inputBuffer.length() - 1);
-            displayInputLine();
-        }
-    } else {
-        inputBuffer += key;
-        displayInputLine();
-    }
-}
-
-
-void displayInputLine() {
-    tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
-    tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
-    tft.setTextColor(TFT_WHITE);
-    tft.setTextSize(1);
-
-    String displayInput = inputBuffer;
-    int displayWidth = tft.textWidth(displayInput);
-    int inputWidth = SCREEN_WIDTH - tft.textWidth("> ");
-    
-    // Allow the input to scroll if it exceeds the screen width
-    while (displayWidth > inputWidth) {
-        displayInput = displayInput.substring(1);
-        displayWidth = tft.textWidth(displayInput);
-    }
-
-    // Add a visual indicator if the max input length is reached
-    if (inputBuffer.length() >= 510)
-        tft.setTextColor(TFT_RED);
-
-    tft.print("> " + displayInput);
-    tft.setTextColor(TFT_WHITE); // Reset text color
-}
-
-
-void updateStatusBar() {
-    Serial.println("Updating status bar...");
-    uint16_t darkerGrey = tft.color565(25, 25, 25);
-    tft.fillRect(0, 0, SCREEN_WIDTH, STATUS_BAR_HEIGHT, darkerGrey);
-
-    // Display the time
-    struct tm timeinfo;
-    char timeStr[9];
-    if (!getLocalTime(&timeinfo)) {
-        sprintf(timeStr, "12:00 AM"); // Default time if NTP sync fails
-    } else {
-        int hour = timeinfo.tm_hour;
-        char ampm[] = "AM";
-        if (hour == 0) {
-            hour = 12;
-        } else if (hour >= 12) {
-            if (hour > 12)
-                hour -= 12;
-            strcpy(ampm, "PM"); // DREADED STRCPY X_X
-        }
-        sprintf(timeStr, "%02d:%02d %s", hour, timeinfo.tm_min, ampm);
-    }
-    tft.setTextDatum(ML_DATUM);
-    tft.setTextColor(TFT_WHITE, darkerGrey);
-    tft.drawString(timeStr, 0, STATUS_BAR_HEIGHT / 2);
-
-    // Display the WiFi signal strength
-    char wifiStr[15];
-    int wifiSignal = 0;
-    if (WiFi.status() != WL_CONNECTED) {
-        sprintf(wifiStr, "WiFi: N/A");
-        tft.setTextColor(TFT_PINK, darkerGrey);
-        tft.setTextDatum(MR_DATUM);
-        tft.drawString(wifiStr, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
-    } else {
-        int32_t rssi = WiFi.RSSI();
-        if (rssi > -50) wifiSignal = 100;
-        else if (rssi > -60) wifiSignal = 80;
-        else if (rssi > -70) wifiSignal = 60;
-        else if (rssi > -80) wifiSignal = 40;
-        else if (rssi > -90) wifiSignal = 20;
-        else wifiSignal = 0;
-
-        sprintf(wifiStr, "WiFi: %d%%", wifiSignal);
-        tft.setTextDatum(MR_DATUM);
-        tft.setTextColor(TFT_PINK, darkerGrey);
-        tft.drawString("WiFi:", SCREEN_WIDTH - 120, STATUS_BAR_HEIGHT / 2);
-        tft.setTextColor(getColorFromPercentage(wifiSignal), darkerGrey);
-        tft.drawString(wifiStr + 6, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
-    }
-
-    // Display the battery level
-    int batteryLevel = BL.getBatteryChargeLevel();
-    char batteryStr[15];
-    sprintf(batteryStr, "Batt: %d%%", batteryLevel);
-    tft.setTextDatum(MR_DATUM);
-    tft.setTextColor(TFT_CYAN, darkerGrey);
-    tft.drawString("Batt:", SCREEN_WIDTH - 40, STATUS_BAR_HEIGHT / 2);
-    tft.setTextColor(getColorFromPercentage(batteryLevel), darkerGrey);
-    tft.drawString(batteryStr + 5, SCREEN_WIDTH - 5, STATUS_BAR_HEIGHT / 2);
-}
-
-
-void printDeviceInfo() {
-    Serial.println("Gathering device info...");
-    tft.fillScreen(TFT_BLACK);
-
-    // Get MAC Address
-    uint8_t mac[6];
-    esp_efuse_mac_get_default(mac);
-    String macAddress = String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX);
-
-    // Get Chip Info
-    uint32_t chipId = ESP.getEfuseMac();
-    esp_chip_info_t chip_info;
-    esp_chip_info(&chip_info);
-    String chipInfo = String(chip_info.model) + " Rev " + String(chip_info.revision) + ", " + String(chip_info.cores) + " cores, " +  String(ESP.getCpuFreqMHz()) + " MHz";
-
-    // Get Flash Info
-    size_t flashSize = spi_flash_get_chip_size();
-    size_t flashUsed = ESP.getFlashChipSize() - ESP.getFreeSketchSpace();
-    String flashInfo = formatBytes(flashUsed) + " / " + formatBytes(flashSize);
-    
-    // Get PSRAM Info
-    size_t total_psram = ESP.getPsramSize();
-    size_t free_psram = ESP.getFreePsram();
-    String psramInfo = formatBytes(total_psram - free_psram) + " / " + formatBytes(total_psram);
-
-    // Get Heap Info
-    size_t total_heap = ESP.getHeapSize();
-    size_t free_heap = ESP.getFreeHeap();
-    String heapInfo = formatBytes(total_heap - free_heap) + " / " + formatBytes(total_heap);
-
-    // Get WiFi Info
-    String wifiInfo = "Not connected";
-    String wifiSSID = "";
-    String wifiChannel = "";
-    String wifiSignal = "";
-    String wifiLocalIP = "";
-    String wifiGatewayIP = "";
-    if (WiFi.status() == WL_CONNECTED) {
-        wifiSSID = WiFi.SSID();
-        wifiChannel = String(WiFi.channel());
-        wifiSignal = String(WiFi.RSSI()) + " dBm";
-        wifiLocalIP = WiFi.localIP().toString();
-        wifiGatewayIP = WiFi.gatewayIP().toString();
-    }
-
-    // Print to Serial Monitor
-    Serial.println("MCU: ESP32-S3FN16R8");
-    Serial.println("LoRa Tranciever: Semtech SX1262 (915 MHz)"); // Need to set the frequency in pins.h
-    Serial.println("LCD: ST7789 SPI"); // Update this
-    Serial.println("Chip ID: " + String(chipId, HEX));
-    Serial.println("MAC Address: " + macAddress);
-    Serial.println("Chip Info: " + chipInfo);
-    Serial.println("Battery:");
-    Serial.println("  Pin Value: " + String(analogRead(34)));
-    Serial.println("  Average Pin Value: " + String(BL.pinRead()));
-    Serial.println("  Volts: " + String(BL.getBatteryVolts()));
-    Serial.println("  Charge Level: " + String(BL.getBatteryChargeLevel()) + "%");
-    Serial.println("Memory:");
-    Serial.println("  Flash: " + flashInfo);
-    Serial.println("  PSRAM: " + psramInfo);
-    Serial.println("  Heap: " + heapInfo);
-    Serial.println("WiFi Info: " + wifiInfo);
-    if (WiFi.status() == WL_CONNECTED) {
-        Serial.println("  SSID: " + wifiSSID);
-        Serial.println("  Channel: " + wifiChannel);
-        Serial.println("  Signal: " + wifiSignal);
-        Serial.println("  Local IP: " + wifiLocalIP);
-        Serial.println("  Gateway IP: " + wifiGatewayIP);
-    }
 }
 \ No newline at end of file