2024-05-23 22:42:03 +00:00
|
|
|
#include <TFT_eSPI.h>
|
|
|
|
#include <WiFi.h>
|
|
|
|
#include <WiFiClientSecure.h>
|
|
|
|
#include <Wire.h>
|
2024-05-25 01:04:58 +00:00
|
|
|
#include <Pangodream_18650_CL.h> // POWER MANAGEMENT
|
2024-05-23 22:42:03 +00:00
|
|
|
#include <map>
|
|
|
|
#include <vector>
|
2024-05-25 01:04:58 +00:00
|
|
|
#include <time.h>
|
2024-05-23 22:42:03 +00:00
|
|
|
|
|
|
|
#include "boot_screen.h"
|
|
|
|
#include "pins.h"
|
|
|
|
|
|
|
|
#define SCREEN_WIDTH 320
|
|
|
|
#define SCREEN_HEIGHT 240
|
|
|
|
|
|
|
|
#define CHAR_HEIGHT 10
|
|
|
|
#define LINE_SPACING 0
|
|
|
|
|
|
|
|
#define INPUT_LINE_HEIGHT (CHAR_HEIGHT + LINE_SPACING)
|
2024-05-25 01:04:58 +00:00
|
|
|
#define STATUS_BAR_HEIGHT 10
|
|
|
|
#define MAX_LINES ((SCREEN_HEIGHT - INPUT_LINE_HEIGHT - STATUS_BAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING))
|
2024-05-23 22:42:03 +00:00
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
#define BOARD_BAT_ADC 4 // Define the ADC pin used for battery reading
|
2024-05-27 06:32:40 +00:00
|
|
|
#define CONV_FACTOR 1.8 // Conversion factor for the ADC to voltage conversion
|
|
|
|
#define READS 20 // Number of readings for averaging
|
2024-05-25 01:04:58 +00:00
|
|
|
Pangodream_18650_CL BL(BOARD_BAT_ADC, CONV_FACTOR, READS);
|
2024-05-23 22:42:03 +00:00
|
|
|
|
|
|
|
TFT_eSPI tft = TFT_eSPI();
|
|
|
|
WiFiClientSecure client;
|
|
|
|
|
|
|
|
std::map<String, uint32_t> nickColors;
|
|
|
|
std::vector<String> lines;
|
2024-05-26 03:39:20 +00:00
|
|
|
std::vector<bool> mentions;
|
2024-05-23 22:42:03 +00:00
|
|
|
String inputBuffer = "";
|
|
|
|
|
|
|
|
// WiFi credentials
|
2024-05-25 01:04:58 +00:00
|
|
|
String ssid = "";
|
|
|
|
String password = "";
|
2024-05-26 02:59:46 +00:00
|
|
|
String nick = "";
|
2024-05-23 22:42:03 +00:00
|
|
|
|
2024-05-27 04:05:43 +00:00
|
|
|
bool debugMode = false;
|
|
|
|
unsigned long debugStartTime = 0;
|
|
|
|
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
// IRC connection
|
|
|
|
const char* server = "irc.supernets.org";
|
|
|
|
const int port = 6697;
|
|
|
|
bool useSSL = true;
|
|
|
|
const char* channel = "#comms";
|
|
|
|
|
|
|
|
// IRC identity
|
|
|
|
const char* user = "tdeck";
|
2024-05-25 01:04:58 +00:00
|
|
|
const char* realname = "ACID DROP Firmware 1.0.0"; // Need to eventually set this up to use a variable
|
2024-05-23 22:42:03 +00:00
|
|
|
|
|
|
|
unsigned long joinChannelTime = 0;
|
|
|
|
bool readyToJoinChannel = false;
|
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
unsigned long lastStatusUpdateTime = 0;
|
|
|
|
const unsigned long STATUS_UPDATE_INTERVAL = 15000;
|
|
|
|
|
2024-05-26 04:39:05 +00:00
|
|
|
unsigned long lastActivityTime = 0;
|
|
|
|
const unsigned long INACTIVITY_TIMEOUT = 30000; // 30 seconds
|
|
|
|
bool screenOn = true;
|
|
|
|
|
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
struct WiFiNetwork {
|
|
|
|
int index;
|
|
|
|
int channel;
|
|
|
|
int rssi;
|
|
|
|
String encryption;
|
|
|
|
String ssid;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<WiFiNetwork> wifiNetworks;
|
|
|
|
int selectedNetworkIndex = 0;
|
2024-05-23 22:42:03 +00:00
|
|
|
|
|
|
|
void setup() {
|
|
|
|
Serial.begin(115200);
|
|
|
|
Serial.println("Booting device...");
|
|
|
|
|
|
|
|
pinMode(BOARD_POWERON, OUTPUT);
|
|
|
|
digitalWrite(BOARD_POWERON, HIGH);
|
|
|
|
|
2024-05-26 04:39:05 +00:00
|
|
|
pinMode(TFT_BL, OUTPUT);
|
|
|
|
digitalWrite(TFT_BL, HIGH); // Turn on the backlight initially
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
Wire.begin(BOARD_I2C_SDA, BOARD_I2C_SCL);
|
2024-05-26 23:27:12 +00:00
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
tft.begin();
|
|
|
|
tft.setRotation(1);
|
|
|
|
tft.invertDisplay(1);
|
|
|
|
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("TFT initialized");
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
displayXBM();
|
|
|
|
delay(3000);
|
2024-05-25 01:04:58 +00:00
|
|
|
displayCenteredText("SCANNING WIFI");
|
|
|
|
delay(1000);
|
|
|
|
scanWiFiNetworks();
|
|
|
|
displayWiFiNetworks();
|
2024-05-26 02:59:46 +00:00
|
|
|
|
|
|
|
randomSeed(analogRead(0));
|
|
|
|
int randomNum = random(1000, 10000);
|
|
|
|
nick = "ACID_" + String(randomNum);
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
|
2024-05-26 04:39:05 +00:00
|
|
|
|
2024-05-26 03:49:13 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < message.length(); i++) {
|
|
|
|
char c = message[i];
|
|
|
|
if (c == '\x02') { // Bold
|
|
|
|
bold = !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 && message.substring(i).startsWith(nick)) {
|
2024-05-27 00:21:17 +00:00
|
|
|
tft.setTextColor(TFT_YELLOW, bgColor); // Set both foreground and background color
|
2024-05-26 03:49:13 +00:00
|
|
|
for (char nc : nick) {
|
|
|
|
tft.print(nc);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
i--; // Adjust for the loop increment
|
2024-05-27 00:21:17 +00:00
|
|
|
tft.setTextColor(TFT_WHITE, bgColor); // Reset to white foreground with current background
|
2024-05-26 03:49:13 +00:00
|
|
|
nickHighlighted = true;
|
|
|
|
} else {
|
|
|
|
if (tft.getCursorX() + tft.textWidth(String(c)) > SCREEN_WIDTH) {
|
|
|
|
cursorY += lineHeight;
|
|
|
|
tft.setCursor(0, cursorY);
|
|
|
|
}
|
2024-05-27 00:11:57 +00:00
|
|
|
if (c == ' ') {
|
|
|
|
// Draw background for spaces
|
|
|
|
int spaceWidth = tft.textWidth(" ");
|
|
|
|
tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
|
|
|
|
tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
|
|
|
|
} else {
|
|
|
|
// Ensure that background color is applied to characters
|
2024-05-27 00:21:17 +00:00
|
|
|
tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.textWidth(String(c)), lineHeight, bgColor);
|
2024-05-27 00:11:57 +00:00
|
|
|
tft.setTextColor(fgColor, bgColor);
|
|
|
|
tft.print(c);
|
|
|
|
}
|
2024-05-26 03:49:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 00:11:57 +00:00
|
|
|
// Ensure trailing spaces are displayed with background color
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-26 03:49:13 +00:00
|
|
|
cursorY += lineHeight; // Add line height after printing the message
|
|
|
|
return cursorY; // Return the new cursor Y position for the next line
|
|
|
|
}
|
|
|
|
|
2024-05-26 04:39:05 +00:00
|
|
|
void turnOffScreen() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Screen turned off");
|
2024-05-26 04:39:05 +00:00
|
|
|
tft.writecommand(TFT_DISPOFF); // Turn off display
|
|
|
|
tft.writecommand(TFT_SLPIN); // Put display into sleep mode
|
2024-05-27 20:49:03 +00:00
|
|
|
digitalWrite(TFT_BL, LOW); // Turn off the backlight
|
2024-05-26 04:39:05 +00:00
|
|
|
screenOn = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void turnOnScreen() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Screen turned on");
|
2024-05-27 20:49:03 +00:00
|
|
|
digitalWrite(TFT_BL, HIGH); // Turn on the backlight
|
2024-05-26 04:39:05 +00:00
|
|
|
tft.writecommand(TFT_SLPOUT); // Wake up display from sleep mode
|
|
|
|
tft.writecommand(TFT_DISPON); // Turn on display
|
|
|
|
screenOn = true;
|
|
|
|
}
|
|
|
|
|
2024-05-26 03:39:20 +00:00
|
|
|
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;
|
|
|
|
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 ")) {
|
2024-05-27 19:57:23 +00:00
|
|
|
tft.setTextColor(TFT_YELLOW);
|
2024-05-26 03:39:20 +00:00
|
|
|
tft.print("MODE ");
|
|
|
|
String modeChange = line.substring(5);
|
|
|
|
tft.setTextColor(TFT_WHITE);
|
|
|
|
tft.print(modeChange);
|
2024-05-27 05:54:38 +00:00
|
|
|
cursorY += CHAR_HEIGHT;
|
2024-05-27 19:57:23 +00:00
|
|
|
} 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);
|
2024-05-27 05:54:38 +00:00
|
|
|
tft.print("* ");
|
2024-05-27 19:57:23 +00:00
|
|
|
int startIndex = 2;
|
|
|
|
int endIndex = line.indexOf(' ', startIndex);
|
|
|
|
String senderNick = line.substring(startIndex, endIndex);
|
|
|
|
String actionMessage = line.substring(endIndex + 1);
|
2024-05-27 05:54:38 +00:00
|
|
|
tft.setTextColor(nickColors[senderNick]);
|
2024-05-27 19:57:23 +00:00
|
|
|
tft.print(senderNick);
|
|
|
|
tft.setTextColor(TFT_MAGENTA);
|
|
|
|
tft.print(" " + actionMessage);
|
2024-05-26 03:39:20 +00:00
|
|
|
cursorY += CHAR_HEIGHT;
|
|
|
|
} else {
|
2024-05-27 19:57:23 +00:00
|
|
|
int colonIndex = line.indexOf(':');
|
|
|
|
String senderNick = line.substring(0, colonIndex);
|
|
|
|
String message = line.substring(colonIndex + 1);
|
2024-05-26 03:39:20 +00:00
|
|
|
|
2024-05-27 20:03:40 +00:00
|
|
|
tft.setTextColor(nickColors[senderNick]);
|
2024-05-27 19:57:23 +00:00
|
|
|
tft.print(senderNick);
|
|
|
|
tft.setTextColor(TFT_WHITE);
|
2024-05-27 20:03:40 +00:00
|
|
|
cursorY = renderFormattedMessage(":" + message, cursorY, CHAR_HEIGHT, mention);
|
2024-05-26 03:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-27 20:03:40 +00:00
|
|
|
|
|
|
|
displayInputLine();
|
2024-05-26 03:39:20 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 20:03:40 +00:00
|
|
|
|
2024-05-27 19:57:23 +00:00
|
|
|
void addLine(String senderNick, String message, String type, uint16_t errorColor = TFT_WHITE, uint16_t reasonColor = TFT_WHITE) {
|
|
|
|
if (type != "error" && nickColors.find(senderNick) == nickColors.end())
|
2024-05-26 03:39:20 +00:00
|
|
|
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;
|
2024-05-27 05:54:38 +00:00
|
|
|
} else if (type == "action") {
|
|
|
|
formattedMessage = "* " + senderNick + " " + message;
|
2024-05-27 19:57:23 +00:00
|
|
|
} else if (type == "error") {
|
|
|
|
formattedMessage = "ERROR " + message;
|
|
|
|
senderNick = "ERROR"; // Use ERROR as senderNick to highlight it in red
|
2024-05-26 03:39:20 +00:00
|
|
|
} else {
|
|
|
|
formattedMessage = senderNick + ": " + message;
|
|
|
|
}
|
|
|
|
|
|
|
|
int linesRequired = calculateLinesRequired(formattedMessage);
|
|
|
|
|
|
|
|
while (lines.size() + linesRequired > MAX_LINES) {
|
|
|
|
lines.erase(lines.begin());
|
|
|
|
mentions.erase(mentions.begin());
|
|
|
|
}
|
|
|
|
|
2024-05-27 19:57:23 +00:00
|
|
|
if (type == "error") {
|
|
|
|
lines.push_back("ERROR " + message);
|
|
|
|
mentions.push_back(false);
|
|
|
|
} else {
|
|
|
|
lines.push_back(formattedMessage);
|
|
|
|
mentions.push_back(false);
|
|
|
|
}
|
2024-05-26 03:39:20 +00:00
|
|
|
|
|
|
|
displayLines();
|
|
|
|
}
|
|
|
|
|
2024-05-27 04:05:43 +00:00
|
|
|
void displayDeviceInfo() {
|
|
|
|
tft.fillScreen(TFT_BLACK);
|
|
|
|
printDeviceInfo();
|
|
|
|
}
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
void loop() {
|
2024-05-27 04:05:43 +00:00
|
|
|
if (debugMode) {
|
|
|
|
if (millis() - debugStartTime > 10000) { // 10 seconds
|
|
|
|
debugMode = false;
|
|
|
|
// Clear the screen and return to the IRC interface
|
|
|
|
tft.fillScreen(TFT_BLACK);
|
|
|
|
displayLines();
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
} else {
|
2024-05-27 04:05:43 +00:00
|
|
|
if (ssid.isEmpty()) {
|
|
|
|
char incoming = getKeyboardInput();
|
|
|
|
if (incoming != 0) {
|
|
|
|
handleWiFiSelection(incoming);
|
|
|
|
lastActivityTime = millis(); // Reset activity timer
|
|
|
|
}
|
2024-05-25 01:04:58 +00:00
|
|
|
} else {
|
2024-05-27 04:05:43 +00:00
|
|
|
if (millis() - lastStatusUpdateTime > STATUS_UPDATE_INTERVAL) {
|
|
|
|
updateStatusBar();
|
|
|
|
lastStatusUpdateTime = millis();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (client.connected()) {
|
|
|
|
handleIRC();
|
|
|
|
} else {
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
|
|
displayCenteredText("CONNECTING TO " + String(server));
|
|
|
|
if (connectToIRC()) {
|
|
|
|
displayCenteredText("CONNECTED TO " + String(server));
|
|
|
|
sendIRC("NICK " + String(nick));
|
|
|
|
sendIRC("USER " + String(user) + " 0 * :" + String(realname));
|
|
|
|
} else {
|
|
|
|
displayCenteredText("CONNECTION FAILED");
|
|
|
|
delay(1000);
|
|
|
|
}
|
2024-05-25 01:04:58 +00:00
|
|
|
} else {
|
2024-05-27 04:05:43 +00:00
|
|
|
displayCenteredText("RECONNECTING TO WIFI");
|
|
|
|
WiFi.begin(ssid.c_str(), password.c_str());
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
|
2024-05-27 04:05:43 +00:00
|
|
|
if (readyToJoinChannel && millis() >= joinChannelTime) {
|
|
|
|
tft.fillScreen(TFT_BLACK);
|
|
|
|
updateStatusBar();
|
|
|
|
sendIRC("JOIN " + String(channel));
|
|
|
|
readyToJoinChannel = false;
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
|
2024-05-27 04:05:43 +00:00
|
|
|
char incoming = getKeyboardInput();
|
|
|
|
if (incoming != 0) {
|
|
|
|
handleKeyboardInput(incoming);
|
|
|
|
lastActivityTime = millis(); // Reset activity timer
|
|
|
|
}
|
2024-05-26 04:39:05 +00:00
|
|
|
|
2024-05-27 04:05:43 +00:00
|
|
|
// Check for inactivity
|
|
|
|
if (screenOn && millis() - lastActivityTime > INACTIVITY_TIMEOUT) {
|
|
|
|
turnOffScreen(); // Turn off screen and backlight
|
|
|
|
}
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool connectToIRC() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Connecting to IRC...");
|
2024-05-23 22:42:03 +00:00
|
|
|
if (useSSL) {
|
|
|
|
client.setInsecure();
|
|
|
|
return client.connect(server, port);
|
|
|
|
} else {
|
|
|
|
WiFiClient nonSecureClient;
|
|
|
|
return nonSecureClient.connect(server, port);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void connectToWiFi() {
|
2024-05-25 01:04:58 +00:00
|
|
|
WiFi.begin(ssid.c_str(), password.c_str());
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Connecting to WiFi...");
|
2024-05-26 04:57:34 +00:00
|
|
|
int attempts = 0;
|
|
|
|
while (WiFi.status() != WL_CONNECTED && attempts < 10) { // Try to connect for up to 10 seconds
|
2024-05-23 22:42:03 +00:00
|
|
|
delay(500);
|
2024-05-25 01:04:58 +00:00
|
|
|
displayCenteredText("CONNECTING TO " + ssid);
|
2024-05-26 04:57:34 +00:00
|
|
|
attempts++;
|
|
|
|
}
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Connected to WiFi network: " + ssid);
|
2024-05-26 04:57:34 +00:00
|
|
|
displayCenteredText("CONNECTED TO " + ssid);
|
|
|
|
delay(1000);
|
|
|
|
updateTimeFromNTP();
|
|
|
|
} else {
|
|
|
|
displayCenteredText("WIFI CONNECTION FAILED");
|
|
|
|
Serial.println("Failed to connect to WiFi.");
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void sendIRC(String command) {
|
|
|
|
if (client.println(command))
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("IRC: >>> " + command);
|
2024-05-23 22:42:03 +00:00
|
|
|
else
|
|
|
|
Serial.println("Failed to send: " + command);
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleIRC() {
|
|
|
|
while (client.available()) {
|
|
|
|
String line = client.readStringUntil('\n');
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("IRC: " + line);
|
2024-05-23 22:42:03 +00:00
|
|
|
|
|
|
|
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);
|
2024-05-26 23:27:12 +00:00
|
|
|
lastActivityTime = millis(); // Reset activity timer
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-05-26 03:39:20 +00:00
|
|
|
String senderNick = line.substring(1, line.indexOf('!'));
|
|
|
|
bool mention = message.indexOf(nick) != -1;
|
2024-05-27 05:54:38 +00:00
|
|
|
|
|
|
|
if (message.startsWith("\x01ACTION ") && message.endsWith("\x01")) {
|
|
|
|
String actionMessage = message.substring(8, message.length() - 1);
|
|
|
|
addLine(senderNick, actionMessage, "action");
|
|
|
|
} else {
|
|
|
|
addLine(senderNick, message, "message", mention);
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
} else if (command == "JOIN" && line.indexOf(channel) != -1) {
|
2024-05-26 03:39:20 +00:00
|
|
|
String senderNick = line.substring(1, line.indexOf('!'));
|
|
|
|
addLine(senderNick, " has joined " + String(channel), "join");
|
2024-05-23 22:42:03 +00:00
|
|
|
} else if (command == "PART" && line.indexOf(channel) != -1) {
|
2024-05-26 03:39:20 +00:00
|
|
|
String senderNick = line.substring(1, line.indexOf('!'));
|
|
|
|
addLine(senderNick, " has EMO-QUIT " + String(channel), "part");
|
2024-05-23 22:42:03 +00:00
|
|
|
} else if (command == "QUIT" && line.indexOf(channel) != -1) {
|
2024-05-26 03:39:20 +00:00
|
|
|
String senderNick = line.substring(1, line.indexOf('!'));
|
|
|
|
addLine(senderNick, "", "quit");
|
2024-05-23 22:42:03 +00:00
|
|
|
} else if (command == "NICK") {
|
2024-05-27 19:57:23 +00:00
|
|
|
String prefix = line.startsWith(":") ? line.substring(1, firstSpace) : "";
|
2024-05-23 22:42:03 +00:00
|
|
|
String newNick = line.substring(line.lastIndexOf(':') + 1);
|
2024-05-27 19:57:23 +00:00
|
|
|
|
|
|
|
if (prefix == "") { // Our own NICK changes
|
|
|
|
addLine(nick, " -> " + newNick, "nick");
|
|
|
|
nick = newNick;
|
|
|
|
} else { // Other peoples NICK change
|
|
|
|
String oldNick = prefix.substring(0, prefix.indexOf('!'));
|
|
|
|
if (oldNick == nick) {
|
|
|
|
nick = newNick; // Update global nick when we get confirmation
|
|
|
|
}
|
|
|
|
addLine(oldNick, " -> " + newNick, "nick");
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
} 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");
|
2024-05-27 19:57:23 +00:00
|
|
|
} 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);
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 05:54:38 +00:00
|
|
|
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
void handleKeyboardInput(char key) {
|
|
|
|
if (key == '\n' || key == '\r') { // Enter
|
2024-05-27 19:57:23 +00:00
|
|
|
if (inputBuffer.startsWith("/nick ")) {
|
|
|
|
String newNick = inputBuffer.substring(6);
|
|
|
|
sendIRC("NICK " + newNick);
|
|
|
|
inputBuffer = "";
|
|
|
|
displayInputLine();
|
|
|
|
} else if (inputBuffer.startsWith("/debug")) {
|
2024-05-27 04:05:43 +00:00
|
|
|
debugMode = true;
|
|
|
|
debugStartTime = millis();
|
|
|
|
displayDeviceInfo();
|
|
|
|
inputBuffer = "";
|
|
|
|
} else if (inputBuffer.startsWith("/raw ")) {
|
2024-05-26 04:39:05 +00:00
|
|
|
String rawCommand = inputBuffer.substring(5);
|
2024-05-25 23:56:17 +00:00
|
|
|
sendRawCommand(rawCommand);
|
2024-05-27 05:54:38 +00:00
|
|
|
} else if (inputBuffer.startsWith("/me ")) {
|
|
|
|
String actionMessage = inputBuffer.substring(4);
|
|
|
|
sendIRC("PRIVMSG " + String(channel) + " :\001ACTION " + actionMessage + "\001");
|
|
|
|
addLine(nick, actionMessage, "action");
|
|
|
|
inputBuffer = "";
|
2024-05-25 23:56:17 +00:00
|
|
|
} else {
|
|
|
|
sendIRC("PRIVMSG " + String(channel) + " :" + inputBuffer);
|
|
|
|
addLine(nick, inputBuffer, "message");
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
inputBuffer = "";
|
|
|
|
displayInputLine();
|
2024-05-27 19:57:23 +00:00
|
|
|
lastActivityTime = millis();
|
2024-05-26 04:39:05 +00:00
|
|
|
if (!screenOn) {
|
2024-05-27 19:57:23 +00:00
|
|
|
turnOnScreen();
|
2024-05-26 04:39:05 +00:00
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
} else if (key == '\b') { // Backspace
|
|
|
|
if (inputBuffer.length() > 0) {
|
|
|
|
inputBuffer.remove(inputBuffer.length() - 1);
|
|
|
|
displayInputLine();
|
2024-05-27 19:57:23 +00:00
|
|
|
lastActivityTime = millis();
|
2024-05-26 04:39:05 +00:00
|
|
|
if (!screenOn) {
|
2024-05-27 19:57:23 +00:00
|
|
|
turnOnScreen();
|
2024-05-26 04:39:05 +00:00
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
inputBuffer += key;
|
|
|
|
displayInputLine();
|
2024-05-27 19:57:23 +00:00
|
|
|
lastActivityTime = millis();
|
2024-05-26 04:39:05 +00:00
|
|
|
if (!screenOn) {
|
2024-05-27 19:57:23 +00:00
|
|
|
turnOnScreen();
|
2024-05-26 04:39:05 +00:00
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-25 23:56:17 +00:00
|
|
|
void sendRawCommand(String command) {
|
|
|
|
if (client.connected()) {
|
|
|
|
sendIRC(command);
|
|
|
|
Serial.println("Sent raw command: " + command);
|
|
|
|
} else {
|
|
|
|
Serial.println("Failed to send raw command: Not connected to IRC");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 22:42:03 +00:00
|
|
|
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 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void displayInputLine() {
|
2024-05-25 01:04:58 +00:00
|
|
|
tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
|
2024-05-23 22:42:03 +00:00
|
|
|
tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
|
2024-05-25 01:04:58 +00:00
|
|
|
tft.setTextColor(TFT_WHITE);
|
|
|
|
tft.setTextSize(1);
|
2024-05-27 20:49:03 +00:00
|
|
|
|
|
|
|
int inputWidth = SCREEN_WIDTH - tft.textWidth("> ");
|
|
|
|
String displayInput = inputBuffer;
|
|
|
|
int displayWidth = tft.textWidth(displayInput);
|
|
|
|
|
|
|
|
// Scrolling input text
|
|
|
|
while (displayWidth > inputWidth) {
|
|
|
|
displayInput = displayInput.substring(1);
|
|
|
|
displayWidth = tft.textWidth(displayInput);
|
|
|
|
}
|
|
|
|
|
|
|
|
tft.print("> " + displayInput);
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 20:49:03 +00:00
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
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);
|
2024-05-23 22:42:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int calculateLinesRequired(String message) {
|
|
|
|
int linesRequired = 1;
|
|
|
|
int lineWidth = 0;
|
|
|
|
|
|
|
|
for (char c : message) {
|
|
|
|
lineWidth += tft.textWidth(String(c));
|
|
|
|
if (lineWidth > SCREEN_WIDTH) {
|
|
|
|
linesRequired++;
|
|
|
|
lineWidth = tft.textWidth(String(c));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return linesRequired;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2024-05-27 06:32:40 +00:00
|
|
|
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;
|
2024-05-27 06:57:59 +00:00
|
|
|
case 38: return 0x700E;
|
2024-05-27 06:32:40 +00:00
|
|
|
case 39: return 0x7008;
|
2024-05-27 06:57:59 +00:00
|
|
|
case 40: return 0xB000;
|
2024-05-27 06:32:40 +00:00
|
|
|
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;
|
2024-05-27 06:57:59 +00:00
|
|
|
case 50: return 0xB016;
|
2024-05-27 06:32:40 +00:00
|
|
|
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;
|
2024-05-23 22:42:03 +00:00
|
|
|
default: return TFT_WHITE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 06:32:40 +00:00
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
uint32_t generateRandomColor() {
|
|
|
|
return tft.color565(random(0, 255), random(0, 255), random(0, 255));
|
|
|
|
}
|
2024-05-23 22:42:03 +00:00
|
|
|
|
2024-05-25 01:04:58 +00:00
|
|
|
void handlePasswordInput(char key) {
|
|
|
|
if (key == '\n' || key == '\r') { // Enter
|
|
|
|
password = inputBuffer;
|
|
|
|
inputBuffer = "";
|
|
|
|
connectToWiFi();
|
|
|
|
} else if (key == '\b') { // Backspace
|
|
|
|
if (inputBuffer.length() > 0) {
|
|
|
|
inputBuffer.remove(inputBuffer.length() - 1);
|
|
|
|
displayPasswordInputLine();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
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 scanWiFiNetworks() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Scanning for WiFi networks...");
|
2024-05-25 01:04:58 +00:00
|
|
|
int n = WiFi.scanNetworks();
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.print("Total number of networks found: ");
|
|
|
|
Serial.println(n);
|
2024-05-25 01:04:58 +00:00
|
|
|
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";
|
|
|
|
net.ssid = WiFi.SSID(i);
|
|
|
|
wifiNetworks.push_back(net);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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); // +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);
|
|
|
|
|
|
|
|
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); // Set RSSI color
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleWiFiSelection(char key) {
|
|
|
|
if (key == 'u') {
|
|
|
|
updateSelectedNetwork(-1);
|
|
|
|
} else if (key == 'd') {
|
|
|
|
updateSelectedNetwork(1);
|
|
|
|
} else if (key == '\n' || key == '\r') {
|
|
|
|
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 {
|
|
|
|
connectToWiFi();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateStatusBar() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Updating status bar...");
|
2024-05-25 01:04:58 +00:00
|
|
|
uint16_t darkerGrey = tft.color565(25, 25, 25);
|
|
|
|
tft.fillRect(0, 0, SCREEN_WIDTH, STATUS_BAR_HEIGHT, darkerGrey);
|
|
|
|
|
|
|
|
// Display time
|
|
|
|
struct tm timeinfo;
|
|
|
|
char timeStr[9];
|
|
|
|
if (!getLocalTime(&timeinfo)) {
|
|
|
|
// Default time if NTP sync fails
|
|
|
|
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);
|
2024-05-26 03:39:20 +00:00
|
|
|
tft.drawString(wifiStr + 6, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-05-26 03:39:20 +00:00
|
|
|
tft.drawString(batteryStr + 5, SCREEN_WIDTH - 5, STATUS_BAR_HEIGHT / 2);
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t getColorFromPercentage(int rssi) {
|
|
|
|
if (rssi > -50) return TFT_GREEN;
|
|
|
|
else if (rssi > -60) return TFT_YELLOW;
|
|
|
|
else if (rssi > -70) return TFT_ORANGE;
|
|
|
|
else return TFT_RED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateTimeFromNTP() {
|
2024-05-26 23:27:12 +00:00
|
|
|
Serial.println("Syncing time with NTP server...");
|
2024-05-25 01:04:58 +00:00
|
|
|
configTime(-5 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
2024-05-26 04:57:34 +00:00
|
|
|
|
|
|
|
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...");
|
|
|
|
}
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
2024-05-26 04:57:34 +00:00
|
|
|
|
|
|
|
Serial.println("Failed to synchronize time after multiple attempts.");
|
2024-05-25 01:04:58 +00:00
|
|
|
}
|
2024-05-27 04:05:43 +00:00
|
|
|
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void printDeviceInfo() {
|
|
|
|
// 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(); // Unique ID
|
|
|
|
esp_chip_info_t chip_info;
|
|
|
|
esp_chip_info(&chip_info);
|
2024-05-27 05:54:38 +00:00
|
|
|
String chipInfo = String(chip_info.model) + " Rev " + String(chip_info.revision) + ", " + String(chip_info.cores) + " cores, " + String(ESP.getCpuFreqMHz()) + " MHz";
|
2024-05-27 04:05:43 +00:00
|
|
|
|
|
|
|
// 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("Chip ID: " + String(chipId, HEX));
|
|
|
|
Serial.println("MAC Address: " + macAddress);
|
|
|
|
Serial.println("Chip Info: " + chipInfo);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display on TFT
|
|
|
|
tft.fillScreen(TFT_BLACK);
|
|
|
|
int line = 0;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print("Chip ID: "); tft.setTextColor(TFT_WHITE); tft.println(String(chipId, HEX)); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print("MAC Address: "); tft.setTextColor(TFT_WHITE); tft.println(macAddress); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print("Chip Info: "); tft.setTextColor(TFT_WHITE); tft.println(chipInfo); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_CYAN); tft.print("Memory: "); tft.setTextColor(TFT_WHITE); tft.println(""); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Flash: "); tft.setTextColor(TFT_WHITE); tft.println(flashInfo); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" PSRAM: "); tft.setTextColor(TFT_WHITE); tft.println(psramInfo); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Heap: "); tft.setTextColor(TFT_WHITE); tft.println(heapInfo); line++;
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_CYAN); tft.print("WiFi Info: "); tft.setTextColor(TFT_WHITE); tft.println(""); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" SSID: "); tft.setTextColor(TFT_WHITE); tft.println(wifiSSID); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Channel: "); tft.setTextColor(TFT_WHITE); tft.println(wifiChannel); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Signal: "); tft.setTextColor(TFT_WHITE); tft.println(wifiSignal); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Local IP: "); tft.setTextColor(TFT_WHITE); tft.println(wifiLocalIP); line++;
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_YELLOW); tft.print(" Gateway IP: "); tft.setTextColor(TFT_WHITE); tft.println(wifiGatewayIP); line++;
|
|
|
|
} else {
|
|
|
|
tft.setCursor(0, line * 16); tft.setTextColor(TFT_CYAN); tft.print("WiFi Info: "); tft.setTextColor(TFT_WHITE); tft.println("Not connected"); line++;
|
|
|
|
}
|
|
|
|
}
|