From a5304929373c5a2939c49a4187769ec0250ecf12 Mon Sep 17 00:00:00 2001 From: acidvegas Date: Fri, 7 Jun 2024 23:58:03 -0400 Subject: [PATCH] Finally got the code split up into multiple files for organization --- src/Display.cpp | 694 +++++++++++++++++++++++++ src/Display.h | 48 ++ src/IRC.cpp | 76 +++ src/IRC.h | 14 + src/Lora.cpp | 63 +-- src/Lora.h | 3 +- src/Network.cpp | 250 +++++++++ src/Network.h | 36 ++ src/Speaker.cpp | 94 ++-- src/Speaker.h | 6 +- src/Storage.cpp | 5 +- src/Storage.h | 6 +- src/Utilities.cpp | 148 ++++++ src/Utilities.h | 17 + src/bootScreen.h | 3 +- src/main.ino | 1265 +-------------------------------------------- 16 files changed, 1379 insertions(+), 1349 deletions(-) create mode 100644 src/Display.cpp create mode 100644 src/Display.h create mode 100644 src/IRC.cpp create mode 100644 src/IRC.h create mode 100644 src/Network.cpp create mode 100644 src/Network.h create mode 100644 src/Utilities.cpp create mode 100644 src/Utilities.h diff --git a/src/Display.cpp b/src/Display.cpp new file mode 100644 index 0000000..dbbf74e --- /dev/null +++ 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 lines; +std::vector mentions; +std::map 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 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 new file mode 100644 index 0000000..304b94e --- /dev/null +++ b/src/Display.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include + +// 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 lines; +extern std::vector mentions; +extern std::map 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 new file mode 100644 index 0000000..c74177d --- /dev/null +++ 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(client)->setInsecure(); + return static_cast(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 new file mode 100644 index 0000000..4479fe8 --- /dev/null +++ b/src/IRC.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#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 index b07ea4f..964da98 100644 --- 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 index 684c3de..b45495e 100644 --- a/src/Lora.h +++ b/src/Lora.h @@ -3,9 +3,10 @@ #include #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 new file mode 100644 index 0000000..3af164a --- /dev/null +++ b/src/Network.cpp @@ -0,0 +1,250 @@ +#include "Network.h" + + +std::vector 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 new file mode 100644 index 0000000..6ec0aeb --- /dev/null +++ b/src/Network.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include +#include + +#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 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 index 8afa617..6f24834 100644 --- a/src/Speaker.cpp +++ b/src/Speaker.cpp @@ -1,6 +1,52 @@ #include "Speaker.h" +void playNotificationSound() { + playTone(1000, 50); + delay(100); + playTone(1500, 50); + delay(100); + playTone(2000, 50); + delay(100); + playTone(500, 50); +} + + +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 playTone(float frequency, int duration, int volume) { + volume = constrain(volume, 0, 32767); + const int wave_period = SAMPLE_RATE / frequency; + int16_t sample_buffer[wave_period]; + + for (int i = 0; i < wave_period; ++i) + sample_buffer[i] = (i < wave_period / 2) ? volume : -volume; + + int total_samples = SAMPLE_RATE * duration / 1000; + int samples_written = 0; + + while (samples_written < total_samples) { + int to_write = min(wave_period, total_samples - samples_written); + i2s_write(BOARD_I2S_PORT, sample_buffer, to_write * sizeof(int16_t), (size_t *)&to_write, portMAX_DELAY); + samples_written += to_write; + } +} + + void setupI2S() { i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), @@ -26,50 +72,4 @@ void setupI2S() { 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 playTone(float frequency, int duration, int volume) { - volume = constrain(volume, 0, 32767); - const int wave_period = SAMPLE_RATE / frequency; - int16_t sample_buffer[wave_period]; - - for (int i = 0; i < wave_period; ++i) - sample_buffer[i] = (i < wave_period / 2) ? volume : -volume; - - int total_samples = SAMPLE_RATE * duration / 1000; - int samples_written = 0; - - while (samples_written < total_samples) { - int to_write = min(wave_period, total_samples - samples_written); - i2s_write(BOARD_I2S_PORT, sample_buffer, to_write * sizeof(int16_t), (size_t *)&to_write, portMAX_DELAY); - samples_written += to_write; - } -} - - -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 playNotificationSound() { - playTone(1000, 150); - delay(100); - playTone(1500, 150); - delay(100); - playTone(2000, 150); - delay(100); - playTone(500, 150); -} +} \ No newline at end of file diff --git a/src/Speaker.h b/src/Speaker.h index d99afc8..1bfd05e 100644 --- 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 index d23e505..73d1659 100644 --- 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 index a53da7c..d651a8b 100644 --- a/src/Storage.h +++ b/src/Storage.h @@ -1,8 +1,8 @@ #pragma once #include -#include #include "nvs_flash.h" +#include #include #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 new file mode 100644 index 0000000..234cd22 --- /dev/null +++ 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 new file mode 100644 index 0000000..cb6bd26 --- /dev/null +++ b/src/Utilities.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Arduino.h" +#include "WiFi.h" +#include "Wire.h" +#include // 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 index b3ff261..5d30696 100644 --- 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 index cc08f60..5ee981e 100644 --- a/src/main.ino +++ b/src/main.ino @@ -1,18 +1,5 @@ // Standard includes -#include #include -#include - -// Aurduino includes -#include // Needed for Mac spoofing -#include // Power management -#include -#include -#include -#include -#include -#include -#include // 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 nickColors; -std::vector lines; // Possible rename to bufferLines ? -std::vector mentions; -std::vector 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(client)->setInsecure(); - return static_cast(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 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