From e4f7327719baa1bcb6ff83439b799a858df1a13a Mon Sep 17 00:00:00 2001 From: Brendan O'Leary Date: Mon, 15 Dec 2025 21:23:21 -0500 Subject: [PATCH] Add basic webserver --- src/CrossPointWebServer.cpp | 199 ++++++++++++++++++++++++++++++++++++ src/CrossPointWebServer.h | 39 +++++++ src/main.cpp | 7 ++ src/screens/WifiScreen.cpp | 17 ++- 4 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 src/CrossPointWebServer.cpp create mode 100644 src/CrossPointWebServer.h diff --git a/src/CrossPointWebServer.cpp b/src/CrossPointWebServer.cpp new file mode 100644 index 0000000..1e20fe8 --- /dev/null +++ b/src/CrossPointWebServer.cpp @@ -0,0 +1,199 @@ +#include "CrossPointWebServer.h" + +#include + +#include "config.h" + +// Global instance +CrossPointWebServer crossPointWebServer; + +// HTML page template +static const char* HTML_PAGE = R"rawliteral( + + + + + + CrossPoint Reader + + + +

📚 CrossPoint Reader

+ +
+

Device Status

+
+ Version + %VERSION% +
+
+ WiFi Status + Connected +
+
+ IP Address + %IP_ADDRESS% +
+
+ Free Memory + %FREE_HEAP% bytes +
+
+ +
+

File Management

+

📁 File upload functionality coming soon...

+
+ +
+

+ CrossPoint E-Reader • Open Source +

+
+ + +)rawliteral"; + +CrossPointWebServer::CrossPointWebServer() {} + +CrossPointWebServer::~CrossPointWebServer() { + stop(); +} + +void CrossPointWebServer::begin() { + if (running) { + Serial.printf("[%lu] [WEB] Web server already running\n", millis()); + return; + } + + if (WiFi.status() != WL_CONNECTED) { + Serial.printf("[%lu] [WEB] Cannot start webserver - WiFi not connected\n", millis()); + return; + } + + Serial.printf("[%lu] [WEB] Creating web server on port %d...\n", millis(), port); + server = new WebServer(port); + + if (!server) { + Serial.printf("[%lu] [WEB] Failed to create WebServer!\n", millis()); + return; + } + + // Setup routes + Serial.printf("[%lu] [WEB] Setting up routes...\n", millis()); + server->on("/", HTTP_GET, [this]() { handleRoot(); }); + server->on("/status", HTTP_GET, [this]() { handleStatus(); }); + server->onNotFound([this]() { handleNotFound(); }); + + server->begin(); + running = true; + + Serial.printf("[%lu] [WEB] Web server started on port %d\n", millis(), port); + Serial.printf("[%lu] [WEB] Access at http://%s/\n", millis(), WiFi.localIP().toString().c_str()); +} + +void CrossPointWebServer::stop() { + if (!running || !server) { + return; + } + + server->stop(); + delete server; + server = nullptr; + running = false; + + Serial.printf("[%lu] [WEB] Web server stopped\n", millis()); +} + +void CrossPointWebServer::handleClient() { + static unsigned long lastDebugPrint = 0; + if (running && server) { + // Print debug every 10 seconds to confirm handleClient is being called + if (millis() - lastDebugPrint > 10000) { + Serial.printf("[%lu] [WEB] handleClient active, server running on port %d\n", millis(), port); + lastDebugPrint = millis(); + } + server->handleClient(); + } +} + +void CrossPointWebServer::handleRoot() { + String html = HTML_PAGE; + + // Replace placeholders with actual values + html.replace("%VERSION%", CROSSPOINT_VERSION); + html.replace("%IP_ADDRESS%", WiFi.localIP().toString()); + html.replace("%FREE_HEAP%", String(ESP.getFreeHeap())); + + server->send(200, "text/html", html); + Serial.printf("[%lu] [WEB] Served root page\n", millis()); +} + +void CrossPointWebServer::handleNotFound() { + String message = "404 Not Found\n\n"; + message += "URI: " + server->uri() + "\n"; + server->send(404, "text/plain", message); +} + +void CrossPointWebServer::handleStatus() { + String json = "{"; + json += "\"version\":\"" + String(CROSSPOINT_VERSION) + "\","; + json += "\"ip\":\"" + WiFi.localIP().toString() + "\","; + json += "\"rssi\":" + String(WiFi.RSSI()) + ","; + json += "\"freeHeap\":" + String(ESP.getFreeHeap()) + ","; + json += "\"uptime\":" + String(millis() / 1000); + json += "}"; + + server->send(200, "application/json", json); +} diff --git a/src/CrossPointWebServer.h b/src/CrossPointWebServer.h new file mode 100644 index 0000000..531e9ff --- /dev/null +++ b/src/CrossPointWebServer.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +class CrossPointWebServer { + public: + CrossPointWebServer(); + ~CrossPointWebServer(); + + // Start the web server (call after WiFi is connected) + void begin(); + + // Stop the web server + void stop(); + + // Call this periodically to handle client requests + void handleClient(); + + // Check if server is running + bool isRunning() const { return running; } + + // Get the port number + uint16_t getPort() const { return port; } + + private: + WebServer* server = nullptr; + bool running = false; + uint16_t port = 80; + + // Request handlers + void handleRoot(); + void handleNotFound(); + void handleStatus(); +}; + +// Global instance +extern CrossPointWebServer crossPointWebServer; diff --git a/src/main.cpp b/src/main.cpp index 27dd68a..71267a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "CrossPointWebServer.h" #include "config.h" #include "screens/BootLogoScreen.h" #include "screens/EpubReaderScreen.h" @@ -244,6 +246,11 @@ void loop() { lastMemPrint = millis(); } + // Handle web server clients if WiFi is connected + if (WiFi.status() == WL_CONNECTED) { + crossPointWebServer.handleClient(); + } + inputManager.update(); if (inputManager.wasReleased(InputManager::BTN_POWER) && inputManager.getHeldTime() > POWER_BUTTON_WAKEUP_MS) { enterDeepSleep(); diff --git a/src/screens/WifiScreen.cpp b/src/screens/WifiScreen.cpp index eaad0eb..1912bd6 100644 --- a/src/screens/WifiScreen.cpp +++ b/src/screens/WifiScreen.cpp @@ -3,6 +3,7 @@ #include #include +#include "CrossPointWebServer.h" #include "config.h" void WifiScreen::taskTrampoline(void* param) { @@ -164,6 +165,10 @@ void WifiScreen::checkConnectionStatus() { connectedIP = ipStr; state = WifiScreenState::CONNECTED; updateRequired = true; + + // Start the web server + crossPointWebServer.begin(); + return; } @@ -434,18 +439,22 @@ void WifiScreen::renderConnected() const { const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageHeight = GfxRenderer::getScreenHeight(); const auto height = renderer.getLineHeight(UI_FONT_ID); - const auto top = (pageHeight - height * 3) / 2; + const auto top = (pageHeight - height * 4) / 2; - renderer.drawCenteredText(READER_FONT_ID, top - 20, "Connected!", true, BOLD); + renderer.drawCenteredText(READER_FONT_ID, top - 30, "Connected!", true, BOLD); std::string ssidInfo = "Network: " + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo = ssidInfo.substr(0, 25) + "..."; } - renderer.drawCenteredText(UI_FONT_ID, top + 20, ssidInfo.c_str(), true, REGULAR); + renderer.drawCenteredText(UI_FONT_ID, top + 10, ssidInfo.c_str(), true, REGULAR); std::string ipInfo = "IP Address: " + connectedIP; - renderer.drawCenteredText(UI_FONT_ID, top + 50, ipInfo.c_str(), true, REGULAR); + renderer.drawCenteredText(UI_FONT_ID, top + 40, ipInfo.c_str(), true, REGULAR); + + // Show web server info + std::string webInfo = "Web: http://" + connectedIP + "/"; + renderer.drawCenteredText(UI_FONT_ID, top + 70, webInfo.c_str(), true, REGULAR); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue", true, REGULAR); }