Refactor layout of activities
This commit is contained in:
parent
e2dab0d811
commit
804db098d1
@ -6,7 +6,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int menuItemCount = 2;
|
constexpr int menuItemCount = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeActivity::taskTrampoline(void* param) {
|
void HomeActivity::taskTrampoline(void* param) {
|
||||||
@ -51,6 +51,8 @@ void HomeActivity::loop() {
|
|||||||
if (selectorIndex == 0) {
|
if (selectorIndex == 0) {
|
||||||
onReaderOpen();
|
onReaderOpen();
|
||||||
} else if (selectorIndex == 1) {
|
} else if (selectorIndex == 1) {
|
||||||
|
onFileTransferOpen();
|
||||||
|
} else if (selectorIndex == 2) {
|
||||||
onSettingsOpen();
|
onSettingsOpen();
|
||||||
}
|
}
|
||||||
} else if (prevPressed) {
|
} else if (prevPressed) {
|
||||||
@ -84,7 +86,8 @@ void HomeActivity::render() const {
|
|||||||
// Draw selection
|
// Draw selection
|
||||||
renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
||||||
renderer.drawText(UI_FONT_ID, 20, 60, "Read", selectorIndex != 0);
|
renderer.drawText(UI_FONT_ID, 20, 60, "Read", selectorIndex != 0);
|
||||||
renderer.drawText(UI_FONT_ID, 20, 90, "Settings", selectorIndex != 1);
|
renderer.drawText(UI_FONT_ID, 20, 90, "File transfer", selectorIndex != 1);
|
||||||
|
renderer.drawText(UI_FONT_ID, 20, 120, "Settings", selectorIndex != 2);
|
||||||
|
|
||||||
renderer.drawRect(25, pageHeight - 40, 106, 40);
|
renderer.drawRect(25, pageHeight - 40, 106, 40);
|
||||||
renderer.drawText(UI_FONT_ID, 25 + (105 - renderer.getTextWidth(UI_FONT_ID, "Back")) / 2, pageHeight - 35, "Back");
|
renderer.drawText(UI_FONT_ID, 25 + (105 - renderer.getTextWidth(UI_FONT_ID, "Back")) / 2, pageHeight - 35, "Back");
|
||||||
|
|||||||
@ -14,6 +14,7 @@ class HomeActivity final : public Activity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void()> onReaderOpen;
|
const std::function<void()> onReaderOpen;
|
||||||
const std::function<void()> onSettingsOpen;
|
const std::function<void()> onSettingsOpen;
|
||||||
|
const std::function<void()> onFileTransferOpen;
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
@ -21,8 +22,8 @@ class HomeActivity final : public Activity {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onReaderOpen,
|
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onReaderOpen,
|
||||||
const std::function<void()>& onSettingsOpen)
|
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen)
|
||||||
: Activity(renderer, inputManager), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen) {}
|
: Activity(renderer, inputManager), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
234
src/activities/network/CrossPointWebServerActivity.cpp
Normal file
234
src/activities/network/CrossPointWebServerActivity.cpp
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#include "CrossPointWebServerActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
#include "CrossPointWebServer.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::taskTrampoline(void* param) {
|
||||||
|
auto* self = static_cast<CrossPointWebServerActivity*>(param);
|
||||||
|
self->displayTaskLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::onEnter() {
|
||||||
|
Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onEnter ==========\n", millis());
|
||||||
|
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
|
// Reset state
|
||||||
|
state = WebServerActivityState::WIFI_SELECTION;
|
||||||
|
connectedIP.clear();
|
||||||
|
connectedSSID.clear();
|
||||||
|
lastHandleClientTime = 0;
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
|
xTaskCreate(&CrossPointWebServerActivity::taskTrampoline, "WebServerActivityTask",
|
||||||
|
2048, // Stack size
|
||||||
|
this, // Parameters
|
||||||
|
1, // Priority
|
||||||
|
&displayTaskHandle // Task handle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Turn on WiFi immediately
|
||||||
|
Serial.printf("[%lu] [WEBACT] Turning on WiFi...\n", millis());
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
|
||||||
|
// Launch WiFi selection subactivity
|
||||||
|
Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis());
|
||||||
|
wifiSelection.reset(new WifiSelectionActivity(renderer, inputManager, [this](bool connected) {
|
||||||
|
onWifiSelectionComplete(connected);
|
||||||
|
}));
|
||||||
|
wifiSelection->onEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::onExit() {
|
||||||
|
Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onExit START ==========\n", millis());
|
||||||
|
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
|
state = WebServerActivityState::SHUTTING_DOWN;
|
||||||
|
|
||||||
|
// Stop the web server first (before disconnecting WiFi)
|
||||||
|
stopWebServer();
|
||||||
|
|
||||||
|
// Exit WiFi selection subactivity if still active
|
||||||
|
if (wifiSelection) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] Exiting WifiSelectionActivity...\n", millis());
|
||||||
|
wifiSelection->onExit();
|
||||||
|
wifiSelection.reset();
|
||||||
|
Serial.printf("[%lu] [WEBACT] WifiSelectionActivity exited\n", millis());
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Wait for LWIP stack to flush any pending packets
|
||||||
|
Serial.printf("[%lu] [WEBACT] Waiting 500ms for network stack to flush pending packets...\n", millis());
|
||||||
|
delay(500);
|
||||||
|
|
||||||
|
// Disconnect WiFi gracefully
|
||||||
|
Serial.printf("[%lu] [WEBACT] Disconnecting WiFi (graceful)...\n", millis());
|
||||||
|
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
||||||
|
delay(100); // Allow disconnect frame to be sent
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis());
|
||||||
|
WiFi.mode(WIFI_OFF);
|
||||||
|
delay(100); // Allow WiFi hardware to fully power down
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [WEBACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
|
// Acquire mutex before deleting task
|
||||||
|
Serial.printf("[%lu] [WEBACT] Acquiring rendering mutex before task deletion...\n", millis());
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// Delete the display task
|
||||||
|
Serial.printf("[%lu] [WEBACT] Deleting display task...\n", millis());
|
||||||
|
if (displayTaskHandle) {
|
||||||
|
vTaskDelete(displayTaskHandle);
|
||||||
|
displayTaskHandle = nullptr;
|
||||||
|
Serial.printf("[%lu] [WEBACT] Display task deleted\n", millis());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the mutex
|
||||||
|
Serial.printf("[%lu] [WEBACT] Deleting mutex...\n", millis());
|
||||||
|
vSemaphoreDelete(renderingMutex);
|
||||||
|
renderingMutex = nullptr;
|
||||||
|
Serial.printf("[%lu] [WEBACT] Mutex deleted\n", millis());
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onExit COMPLETE ==========\n", millis());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::onWifiSelectionComplete(bool connected) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected);
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
// Get connection info before exiting subactivity
|
||||||
|
connectedIP = wifiSelection->getConnectedIP();
|
||||||
|
connectedSSID = WiFi.SSID().c_str();
|
||||||
|
|
||||||
|
// Exit the wifi selection subactivity
|
||||||
|
wifiSelection->onExit();
|
||||||
|
wifiSelection.reset();
|
||||||
|
|
||||||
|
// Start the web server
|
||||||
|
startWebServer();
|
||||||
|
} else {
|
||||||
|
// User cancelled - go back
|
||||||
|
onGoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::startWebServer() {
|
||||||
|
Serial.printf("[%lu] [WEBACT] Starting web server...\n", millis());
|
||||||
|
|
||||||
|
crossPointWebServer.begin();
|
||||||
|
|
||||||
|
if (crossPointWebServer.isRunning()) {
|
||||||
|
state = WebServerActivityState::SERVER_RUNNING;
|
||||||
|
Serial.printf("[%lu] [WEBACT] Web server started successfully\n", millis());
|
||||||
|
|
||||||
|
// Force an immediate render since we're transitioning from a subactivity
|
||||||
|
// that had its own rendering task. We need to make sure our display is shown.
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render();
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
Serial.printf("[%lu] [WEBACT] Rendered File Transfer screen\n", millis());
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%lu] [WEBACT] ERROR: Failed to start web server!\n", millis());
|
||||||
|
// Go back on error
|
||||||
|
onGoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::stopWebServer() {
|
||||||
|
if (crossPointWebServer.isRunning()) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] Stopping web server...\n", millis());
|
||||||
|
crossPointWebServer.stop();
|
||||||
|
Serial.printf("[%lu] [WEBACT] Web server stopped\n", millis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::loop() {
|
||||||
|
// Handle different states
|
||||||
|
switch (state) {
|
||||||
|
case WebServerActivityState::WIFI_SELECTION:
|
||||||
|
// Forward loop to WiFi selection subactivity
|
||||||
|
if (wifiSelection) {
|
||||||
|
wifiSelection->loop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WebServerActivityState::SERVER_RUNNING:
|
||||||
|
// Handle web server requests
|
||||||
|
if (crossPointWebServer.isRunning()) {
|
||||||
|
unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
||||||
|
|
||||||
|
// Log if there's a significant gap between handleClient calls (>100ms)
|
||||||
|
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] WARNING: %lu ms gap since last handleClient\n", millis(),
|
||||||
|
timeSinceLastHandleClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
crossPointWebServer.handleClient();
|
||||||
|
lastHandleClientTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle exit on Back button
|
||||||
|
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
|
onGoBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WebServerActivityState::SHUTTING_DOWN:
|
||||||
|
// Do nothing - waiting for cleanup
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::displayTaskLoop() {
|
||||||
|
while (true) {
|
||||||
|
if (updateRequired) {
|
||||||
|
updateRequired = false;
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render();
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::render() const {
|
||||||
|
// Only render our own UI when server is running
|
||||||
|
// WiFi selection handles its own rendering
|
||||||
|
if (state == WebServerActivityState::SERVER_RUNNING) {
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderServerRunning();
|
||||||
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServerActivity::renderServerRunning() const {
|
||||||
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
|
const auto top = (pageHeight - height * 5) / 2;
|
||||||
|
|
||||||
|
renderer.drawCenteredText(READER_FONT_ID, top - 30, "File Transfer", true, BOLD);
|
||||||
|
|
||||||
|
std::string ssidInfo = "Network: " + connectedSSID;
|
||||||
|
if (ssidInfo.length() > 28) {
|
||||||
|
ssidInfo = ssidInfo.substr(0, 25) + "...";
|
||||||
|
}
|
||||||
|
renderer.drawCenteredText(UI_FONT_ID, top + 10, ssidInfo.c_str(), true, REGULAR);
|
||||||
|
|
||||||
|
std::string ipInfo = "IP Address: " + connectedIP;
|
||||||
|
renderer.drawCenteredText(UI_FONT_ID, top + 40, ipInfo.c_str(), true, REGULAR);
|
||||||
|
|
||||||
|
// Show web server URL prominently
|
||||||
|
std::string webInfo = "http://" + connectedIP + "/";
|
||||||
|
renderer.drawCenteredText(UI_FONT_ID, top + 70, webInfo.c_str(), true, BOLD);
|
||||||
|
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, top + 110, "Open this URL in your browser", true, REGULAR);
|
||||||
|
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press BACK to exit", true, REGULAR);
|
||||||
|
}
|
||||||
61
src/activities/network/CrossPointWebServerActivity.h
Normal file
61
src/activities/network/CrossPointWebServerActivity.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
#include "WifiSelectionActivity.h"
|
||||||
|
|
||||||
|
// Web server activity states
|
||||||
|
enum class WebServerActivityState {
|
||||||
|
WIFI_SELECTION, // WiFi selection subactivity is active
|
||||||
|
SERVER_RUNNING, // Web server is running and handling requests
|
||||||
|
SHUTTING_DOWN // Shutting down server and WiFi
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CrossPointWebServerActivity is the entry point for file transfer functionality.
|
||||||
|
* It:
|
||||||
|
* - Immediately turns on WiFi and launches WifiSelectionActivity on enter
|
||||||
|
* - When WifiSelectionActivity completes successfully, starts the CrossPointWebServer
|
||||||
|
* - Handles client requests in its loop() function
|
||||||
|
* - Cleans up the server and shuts down WiFi on exit
|
||||||
|
*/
|
||||||
|
class CrossPointWebServerActivity final : public Activity {
|
||||||
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
bool updateRequired = false;
|
||||||
|
WebServerActivityState state = WebServerActivityState::WIFI_SELECTION;
|
||||||
|
const std::function<void()> onGoBack;
|
||||||
|
|
||||||
|
// WiFi selection subactivity
|
||||||
|
std::unique_ptr<WifiSelectionActivity> wifiSelection;
|
||||||
|
|
||||||
|
// Server status
|
||||||
|
std::string connectedIP;
|
||||||
|
std::string connectedSSID;
|
||||||
|
|
||||||
|
// Performance monitoring
|
||||||
|
unsigned long lastHandleClientTime = 0;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* param);
|
||||||
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
void render() const;
|
||||||
|
void renderServerRunning() const;
|
||||||
|
|
||||||
|
void onWifiSelectionComplete(bool connected);
|
||||||
|
void startWebServer();
|
||||||
|
void stopWebServer();
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CrossPointWebServerActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
|
const std::function<void()>& onGoBack)
|
||||||
|
: Activity(renderer, inputManager), onGoBack(onGoBack) {}
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
};
|
||||||
@ -1,20 +1,19 @@
|
|||||||
#include "WifiScreen.h"
|
#include "WifiSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "CrossPointWebServer.h"
|
|
||||||
#include "WifiCredentialStore.h"
|
#include "WifiCredentialStore.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void WifiScreen::taskTrampoline(void* param) {
|
void WifiSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<WifiScreen*>(param);
|
auto* self = static_cast<WifiSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::onEnter() {
|
void WifiSelectionActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
// Load saved WiFi credentials
|
// Load saved WiFi credentials
|
||||||
@ -23,7 +22,7 @@ void WifiScreen::onEnter() {
|
|||||||
// Reset state
|
// Reset state
|
||||||
selectedNetworkIndex = 0;
|
selectedNetworkIndex = 0;
|
||||||
networks.clear();
|
networks.clear();
|
||||||
state = WifiScreenState::SCANNING;
|
state = WifiSelectionState::SCANNING;
|
||||||
selectedSSID.clear();
|
selectedSSID.clear();
|
||||||
connectedIP.clear();
|
connectedIP.clear();
|
||||||
connectionError.clear();
|
connectionError.clear();
|
||||||
@ -36,7 +35,7 @@ void WifiScreen::onEnter() {
|
|||||||
// Trigger first update to show scanning message
|
// Trigger first update to show scanning message
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&WifiScreen::taskTrampoline, "WifiScreenTask",
|
xTaskCreate(&WifiSelectionActivity::taskTrampoline, "WifiSelectionTask",
|
||||||
4096, // Stack size (larger for WiFi operations)
|
4096, // Stack size (larger for WiFi operations)
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -47,8 +46,8 @@ void WifiScreen::onEnter() {
|
|||||||
startWifiScan();
|
startWifiScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::onExit() {
|
void WifiSelectionActivity::onExit() {
|
||||||
Serial.printf("[%lu] [WIFI] ========== onExit START ==========\n", millis());
|
Serial.printf("[%lu] [WIFI] ========== WifiSelectionActivity onExit START ==========\n", millis());
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
// Stop any ongoing WiFi scan
|
// Stop any ongoing WiFi scan
|
||||||
@ -56,28 +55,8 @@ void WifiScreen::onExit() {
|
|||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap after scanDelete: %d bytes\n", millis(), ESP.getFreeHeap());
|
Serial.printf("[%lu] [WIFI] [MEM] Free heap after scanDelete: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
// CRITICAL: Stop the web server FIRST to prevent new packets from being queued
|
// Note: We do NOT disconnect WiFi here - the parent activity (CrossPointWebServerActivity)
|
||||||
Serial.printf("[%lu] [WIFI] Stopping web server...\n", millis());
|
// manages WiFi connection state. We just clean up the scan and task.
|
||||||
crossPointWebServer.stop();
|
|
||||||
Serial.printf("[%lu] [WIFI] Web server stopped successfully\n", millis());
|
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap after webserver stop: %d bytes\n", millis());
|
|
||||||
|
|
||||||
// CRITICAL: Wait for LWIP stack to flush any pending packets
|
|
||||||
// The crash occurs because WiFi.disconnect() tears down the interface while
|
|
||||||
// packets are still queued in the LWIP stack (ethernet.c, etharp.c, wlanif.c)
|
|
||||||
Serial.printf("[%lu] [WIFI] Waiting 500ms for network stack to flush pending packets...\n", millis());
|
|
||||||
delay(500);
|
|
||||||
|
|
||||||
// Disconnect WiFi gracefully - use disconnect(false) first to send disconnect frame
|
|
||||||
Serial.printf("[%lu] [WIFI] Disconnecting WiFi (graceful)...\n", millis());
|
|
||||||
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
|
||||||
delay(100); // Allow disconnect frame to be sent
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [WIFI] Setting WiFi mode OFF...\n", millis());
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
delay(100); // Allow WiFi hardware to fully power down
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
|
|
||||||
|
|
||||||
// Acquire mutex before deleting task to ensure task isn't using it
|
// Acquire mutex before deleting task to ensure task isn't using it
|
||||||
// This prevents hangs/crashes if the task holds the mutex when deleted
|
// This prevents hangs/crashes if the task holds the mutex when deleted
|
||||||
@ -99,11 +78,11 @@ void WifiScreen::onExit() {
|
|||||||
Serial.printf("[%lu] [WIFI] Mutex deleted\n", millis());
|
Serial.printf("[%lu] [WIFI] Mutex deleted\n", millis());
|
||||||
|
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
Serial.printf("[%lu] [WIFI] ========== onExit COMPLETE ==========\n", millis());
|
Serial.printf("[%lu] [WIFI] ========== WifiSelectionActivity onExit COMPLETE ==========\n", millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::startWifiScan() {
|
void WifiSelectionActivity::startWifiScan() {
|
||||||
state = WifiScreenState::SCANNING;
|
state = WifiSelectionState::SCANNING;
|
||||||
networks.clear();
|
networks.clear();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
@ -116,7 +95,7 @@ void WifiScreen::startWifiScan() {
|
|||||||
WiFi.scanNetworks(true); // true = async scan
|
WiFi.scanNetworks(true); // true = async scan
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::processWifiScanResults() {
|
void WifiSelectionActivity::processWifiScanResults() {
|
||||||
int16_t scanResult = WiFi.scanComplete();
|
int16_t scanResult = WiFi.scanComplete();
|
||||||
|
|
||||||
if (scanResult == WIFI_SCAN_RUNNING) {
|
if (scanResult == WIFI_SCAN_RUNNING) {
|
||||||
@ -125,7 +104,7 @@ void WifiScreen::processWifiScanResults() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scanResult == WIFI_SCAN_FAILED) {
|
if (scanResult == WIFI_SCAN_FAILED) {
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -167,12 +146,12 @@ void WifiScreen::processWifiScanResults() {
|
|||||||
[](const WifiNetworkInfo& a, const WifiNetworkInfo& b) { return a.rssi > b.rssi; });
|
[](const WifiNetworkInfo& a, const WifiNetworkInfo& b) { return a.rssi > b.rssi; });
|
||||||
|
|
||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
selectedNetworkIndex = 0;
|
selectedNetworkIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::selectNetwork(int index) {
|
void WifiSelectionActivity::selectNetwork(int index) {
|
||||||
if (index < 0 || index >= static_cast<int>(networks.size())) {
|
if (index < 0 || index >= static_cast<int>(networks.size())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -197,7 +176,7 @@ void WifiScreen::selectNetwork(int index) {
|
|||||||
|
|
||||||
if (selectedRequiresPassword) {
|
if (selectedRequiresPassword) {
|
||||||
// Show password entry
|
// Show password entry
|
||||||
state = WifiScreenState::PASSWORD_ENTRY;
|
state = WifiSelectionState::PASSWORD_ENTRY;
|
||||||
keyboard.reset(new KeyboardEntryActivity(renderer, inputManager, "Enter WiFi Password",
|
keyboard.reset(new KeyboardEntryActivity(renderer, inputManager, "Enter WiFi Password",
|
||||||
"", // No initial text
|
"", // No initial text
|
||||||
64, // Max password length
|
64, // Max password length
|
||||||
@ -210,8 +189,8 @@ void WifiScreen::selectNetwork(int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::attemptConnection() {
|
void WifiSelectionActivity::attemptConnection() {
|
||||||
state = WifiScreenState::CONNECTING;
|
state = WifiSelectionState::CONNECTING;
|
||||||
connectionStartTime = millis();
|
connectionStartTime = millis();
|
||||||
connectedIP.clear();
|
connectedIP.clear();
|
||||||
connectionError.clear();
|
connectionError.clear();
|
||||||
@ -231,8 +210,8 @@ void WifiScreen::attemptConnection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::checkConnectionStatus() {
|
void WifiSelectionActivity::checkConnectionStatus() {
|
||||||
if (state != WifiScreenState::CONNECTING) {
|
if (state != WifiSelectionState::CONNECTING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,18 +224,17 @@ void WifiScreen::checkConnectionStatus() {
|
|||||||
snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||||
connectedIP = ipStr;
|
connectedIP = ipStr;
|
||||||
|
|
||||||
// Start the web server
|
|
||||||
crossPointWebServer.begin();
|
|
||||||
|
|
||||||
// If we used a saved password, go directly to connected screen
|
|
||||||
// If we entered a new password, ask if user wants to save it
|
// If we entered a new password, ask if user wants to save it
|
||||||
if (usedSavedPassword || enteredPassword.empty()) {
|
// Otherwise, immediately complete so parent can start web server
|
||||||
state = WifiScreenState::CONNECTED;
|
if (!usedSavedPassword && !enteredPassword.empty()) {
|
||||||
} else {
|
state = WifiSelectionState::SAVE_PROMPT;
|
||||||
state = WifiScreenState::SAVE_PROMPT;
|
|
||||||
savePromptSelection = 0; // Default to "Yes"
|
savePromptSelection = 0; // Default to "Yes"
|
||||||
|
updateRequired = true;
|
||||||
|
} else {
|
||||||
|
// Using saved password or open network - complete immediately
|
||||||
|
Serial.printf("[%lu] [WIFI] Connected with saved/open credentials, completing immediately\n", millis());
|
||||||
|
onComplete(true);
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +243,7 @@ void WifiScreen::checkConnectionStatus() {
|
|||||||
if (status == WL_NO_SSID_AVAIL) {
|
if (status == WL_NO_SSID_AVAIL) {
|
||||||
connectionError = "Network not found";
|
connectionError = "Network not found";
|
||||||
}
|
}
|
||||||
state = WifiScreenState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -274,27 +252,27 @@ void WifiScreen::checkConnectionStatus() {
|
|||||||
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
connectionError = "Connection timeout";
|
connectionError = "Connection timeout";
|
||||||
state = WifiScreenState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::loop() {
|
void WifiSelectionActivity::loop() {
|
||||||
// Check scan progress
|
// Check scan progress
|
||||||
if (state == WifiScreenState::SCANNING) {
|
if (state == WifiSelectionState::SCANNING) {
|
||||||
processWifiScanResults();
|
processWifiScanResults();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check connection progress
|
// Check connection progress
|
||||||
if (state == WifiScreenState::CONNECTING) {
|
if (state == WifiSelectionState::CONNECTING) {
|
||||||
checkConnectionStatus();
|
checkConnectionStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle password entry state
|
// Handle password entry state
|
||||||
if (state == WifiScreenState::PASSWORD_ENTRY && keyboard) {
|
if (state == WifiSelectionState::PASSWORD_ENTRY && keyboard) {
|
||||||
keyboard->handleInput();
|
keyboard->handleInput();
|
||||||
|
|
||||||
if (keyboard->isComplete()) {
|
if (keyboard->isComplete()) {
|
||||||
@ -303,7 +281,7 @@ void WifiScreen::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keyboard->isCancelled()) {
|
if (keyboard->isCancelled()) {
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
keyboard.reset();
|
keyboard.reset();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
@ -314,7 +292,7 @@ void WifiScreen::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle save prompt state
|
// Handle save prompt state
|
||||||
if (state == WifiScreenState::SAVE_PROMPT) {
|
if (state == WifiSelectionState::SAVE_PROMPT) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
||||||
if (savePromptSelection > 0) {
|
if (savePromptSelection > 0) {
|
||||||
savePromptSelection--;
|
savePromptSelection--;
|
||||||
@ -330,19 +308,17 @@ void WifiScreen::loop() {
|
|||||||
// User chose "Yes" - save the password
|
// User chose "Yes" - save the password
|
||||||
WIFI_STORE.addCredential(selectedSSID, enteredPassword);
|
WIFI_STORE.addCredential(selectedSSID, enteredPassword);
|
||||||
}
|
}
|
||||||
// Move to connected screen
|
// Complete - parent will start web server
|
||||||
state = WifiScreenState::CONNECTED;
|
onComplete(true);
|
||||||
updateRequired = true;
|
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
// Skip saving, go to connected screen
|
// Skip saving, complete anyway
|
||||||
state = WifiScreenState::CONNECTED;
|
onComplete(true);
|
||||||
updateRequired = true;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle forget prompt state (connection failed with saved credentials)
|
// Handle forget prompt state (connection failed with saved credentials)
|
||||||
if (state == WifiScreenState::FORGET_PROMPT) {
|
if (state == WifiSelectionState::FORGET_PROMPT) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
||||||
if (forgetPromptSelection > 0) {
|
if (forgetPromptSelection > 0) {
|
||||||
forgetPromptSelection--;
|
forgetPromptSelection--;
|
||||||
@ -366,35 +342,33 @@ void WifiScreen::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Go back to network list
|
// Go back to network list
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
// Skip forgetting, go back to network list
|
// Skip forgetting, go back to network list
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle connected state
|
// Handle connected state (should not normally be reached - connection completes immediately)
|
||||||
if (state == WifiScreenState::CONNECTED) {
|
if (state == WifiSelectionState::CONNECTED) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK) || inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
// Safety fallback - immediately complete
|
||||||
// Exit screen on success
|
onComplete(true);
|
||||||
onGoBack();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle connection failed state
|
// Handle connection failed state
|
||||||
if (state == WifiScreenState::CONNECTION_FAILED) {
|
if (state == WifiSelectionState::CONNECTION_FAILED) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK) || inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_BACK) || inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
// If we used saved credentials, offer to forget the network
|
// If we used saved credentials, offer to forget the network
|
||||||
if (usedSavedPassword) {
|
if (usedSavedPassword) {
|
||||||
state = WifiScreenState::FORGET_PROMPT;
|
state = WifiSelectionState::FORGET_PROMPT;
|
||||||
forgetPromptSelection = 0; // Default to "Yes"
|
forgetPromptSelection = 0; // Default to "Yes"
|
||||||
} else {
|
} else {
|
||||||
// Go back to network list on failure
|
// Go back to network list on failure
|
||||||
state = WifiScreenState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
@ -402,10 +376,10 @@ void WifiScreen::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle network list state
|
// Handle network list state
|
||||||
if (state == WifiScreenState::NETWORK_LIST) {
|
if (state == WifiSelectionState::NETWORK_LIST) {
|
||||||
// Check for Back button to exit
|
// Check for Back button to exit (cancel)
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
onGoBack();
|
onComplete(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,7 +408,7 @@ void WifiScreen::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WifiScreen::getSignalStrengthIndicator(int32_t rssi) const {
|
std::string WifiSelectionActivity::getSignalStrengthIndicator(int32_t rssi) const {
|
||||||
// Convert RSSI to signal bars representation
|
// Convert RSSI to signal bars representation
|
||||||
if (rssi >= -50) {
|
if (rssi >= -50) {
|
||||||
return "||||"; // Excellent
|
return "||||"; // Excellent
|
||||||
@ -448,7 +422,7 @@ std::string WifiScreen::getSignalStrengthIndicator(int32_t rssi) const {
|
|||||||
return " "; // Very weak
|
return " "; // Very weak
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::displayTaskLoop() {
|
void WifiSelectionActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -460,32 +434,32 @@ void WifiScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::render() const {
|
void WifiSelectionActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case WifiScreenState::SCANNING:
|
case WifiSelectionState::SCANNING:
|
||||||
renderConnecting(); // Reuse connecting screen with different message
|
renderConnecting(); // Reuse connecting screen with different message
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::NETWORK_LIST:
|
case WifiSelectionState::NETWORK_LIST:
|
||||||
renderNetworkList();
|
renderNetworkList();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::PASSWORD_ENTRY:
|
case WifiSelectionState::PASSWORD_ENTRY:
|
||||||
renderPasswordEntry();
|
renderPasswordEntry();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::CONNECTING:
|
case WifiSelectionState::CONNECTING:
|
||||||
renderConnecting();
|
renderConnecting();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::CONNECTED:
|
case WifiSelectionState::CONNECTED:
|
||||||
renderConnected();
|
renderConnected();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::SAVE_PROMPT:
|
case WifiSelectionState::SAVE_PROMPT:
|
||||||
renderSavePrompt();
|
renderSavePrompt();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::CONNECTION_FAILED:
|
case WifiSelectionState::CONNECTION_FAILED:
|
||||||
renderConnectionFailed();
|
renderConnectionFailed();
|
||||||
break;
|
break;
|
||||||
case WifiScreenState::FORGET_PROMPT:
|
case WifiSelectionState::FORGET_PROMPT:
|
||||||
renderForgetPrompt();
|
renderForgetPrompt();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -493,7 +467,7 @@ void WifiScreen::render() const {
|
|||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderNetworkList() const {
|
void WifiSelectionActivity::renderNetworkList() const {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
@ -569,7 +543,7 @@ void WifiScreen::renderNetworkList() const {
|
|||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 30, "OK: Connect | * = Encrypted | + = Saved");
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 30, "OK: Connect | * = Encrypted | + = Saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderPasswordEntry() const {
|
void WifiSelectionActivity::renderPasswordEntry() const {
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
@ -588,12 +562,12 @@ void WifiScreen::renderPasswordEntry() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderConnecting() const {
|
void WifiSelectionActivity::renderConnecting() const {
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto top = (pageHeight - height) / 2;
|
const auto top = (pageHeight - height) / 2;
|
||||||
|
|
||||||
if (state == WifiScreenState::SCANNING) {
|
if (state == WifiSelectionState::SCANNING) {
|
||||||
renderer.drawCenteredText(UI_FONT_ID, top, "Scanning...", true, REGULAR);
|
renderer.drawCenteredText(UI_FONT_ID, top, "Scanning...", true, REGULAR);
|
||||||
} else {
|
} else {
|
||||||
renderer.drawCenteredText(READER_FONT_ID, top - 30, "Connecting...", true, BOLD);
|
renderer.drawCenteredText(READER_FONT_ID, top - 30, "Connecting...", true, BOLD);
|
||||||
@ -606,7 +580,7 @@ void WifiScreen::renderConnecting() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderConnected() const {
|
void WifiSelectionActivity::renderConnected() const {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
@ -623,14 +597,10 @@ void WifiScreen::renderConnected() const {
|
|||||||
std::string ipInfo = "IP Address: " + connectedIP;
|
std::string ipInfo = "IP Address: " + connectedIP;
|
||||||
renderer.drawCenteredText(UI_FONT_ID, top + 40, ipInfo.c_str(), true, REGULAR);
|
renderer.drawCenteredText(UI_FONT_ID, top + 40, ipInfo.c_str(), true, REGULAR);
|
||||||
|
|
||||||
// Show web server info
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue", true, REGULAR);
|
||||||
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 exit", true, REGULAR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderSavePrompt() const {
|
void WifiSelectionActivity::renderSavePrompt() const {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
@ -670,7 +640,7 @@ void WifiScreen::renderSavePrompt() const {
|
|||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm", true, REGULAR);
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm", true, REGULAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderConnectionFailed() const {
|
void WifiSelectionActivity::renderConnectionFailed() const {
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto top = (pageHeight - height * 2) / 2;
|
const auto top = (pageHeight - height * 2) / 2;
|
||||||
@ -680,7 +650,7 @@ void WifiScreen::renderConnectionFailed() const {
|
|||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue", true, REGULAR);
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue", true, REGULAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiScreen::renderForgetPrompt() const {
|
void WifiSelectionActivity::renderForgetPrompt() const {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
@ -20,26 +20,37 @@ struct WifiNetworkInfo {
|
|||||||
bool hasSavedPassword; // Whether we have saved credentials for this network
|
bool hasSavedPassword; // Whether we have saved credentials for this network
|
||||||
};
|
};
|
||||||
|
|
||||||
// WiFi screen states
|
// WiFi selection states
|
||||||
enum class WifiScreenState {
|
enum class WifiSelectionState {
|
||||||
SCANNING, // Scanning for networks
|
SCANNING, // Scanning for networks
|
||||||
NETWORK_LIST, // Displaying available networks
|
NETWORK_LIST, // Displaying available networks
|
||||||
PASSWORD_ENTRY, // Entering password for selected network
|
PASSWORD_ENTRY, // Entering password for selected network
|
||||||
CONNECTING, // Attempting to connect
|
CONNECTING, // Attempting to connect
|
||||||
CONNECTED, // Successfully connected, showing IP
|
CONNECTED, // Successfully connected
|
||||||
SAVE_PROMPT, // Asking user if they want to save the password
|
SAVE_PROMPT, // Asking user if they want to save the password
|
||||||
CONNECTION_FAILED, // Connection failed
|
CONNECTION_FAILED, // Connection failed
|
||||||
FORGET_PROMPT // Asking user if they want to forget the network
|
FORGET_PROMPT // Asking user if they want to forget the network
|
||||||
};
|
};
|
||||||
|
|
||||||
class WifiScreen final : public Activity {
|
/**
|
||||||
|
* WifiSelectionActivity is responsible for scanning WiFi APs and connecting to them.
|
||||||
|
* It will:
|
||||||
|
* - Enter scanning mode on entry
|
||||||
|
* - List available WiFi networks
|
||||||
|
* - Allow selection and launch KeyboardEntryActivity for password if needed
|
||||||
|
* - Save the password if requested
|
||||||
|
* - Call onComplete callback when connected or cancelled
|
||||||
|
*
|
||||||
|
* The onComplete callback receives true if connected successfully, false if cancelled.
|
||||||
|
*/
|
||||||
|
class WifiSelectionActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
WifiScreenState state = WifiScreenState::SCANNING;
|
WifiSelectionState state = WifiSelectionState::SCANNING;
|
||||||
int selectedNetworkIndex = 0;
|
int selectedNetworkIndex = 0;
|
||||||
std::vector<WifiNetworkInfo> networks;
|
std::vector<WifiNetworkInfo> networks;
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void(bool connected)> onComplete;
|
||||||
|
|
||||||
// Selected network for connection
|
// Selected network for connection
|
||||||
std::string selectedSSID;
|
std::string selectedSSID;
|
||||||
@ -85,9 +96,13 @@ class WifiScreen final : public Activity {
|
|||||||
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WifiScreen(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoBack)
|
explicit WifiSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
: Activity(renderer, inputManager), onGoBack(onGoBack) {}
|
const std::function<void(bool connected)>& onComplete)
|
||||||
|
: Activity(renderer, inputManager), onComplete(onComplete) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
// Get the IP address after successful connection
|
||||||
|
const std::string& getConnectedIP() const { return connectedIP; }
|
||||||
};
|
};
|
||||||
@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
||||||
{"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen},
|
{"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen},
|
||||||
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing},
|
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing}};
|
||||||
{"WiFi", SettingType::ACTION, nullptr}};
|
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<SettingsActivity*>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
@ -48,7 +47,7 @@ void SettingsActivity::onExit() {
|
|||||||
void SettingsActivity::loop() {
|
void SettingsActivity::loop() {
|
||||||
// Handle actions with early return
|
// Handle actions with early return
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
activateCurrentSetting();
|
toggleCurrentSetting();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -73,26 +72,6 @@ void SettingsActivity::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsActivity::activateCurrentSetting() {
|
|
||||||
// Validate index
|
|
||||||
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& setting = settingsList[selectedSettingIndex];
|
|
||||||
|
|
||||||
if (setting.type == SettingType::TOGGLE) {
|
|
||||||
toggleCurrentSetting();
|
|
||||||
// Trigger a redraw of the entire screen
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (setting.type == SettingType::ACTION) {
|
|
||||||
// Handle action settings
|
|
||||||
if (std::string(setting.name) == "WiFi") {
|
|
||||||
onGoWifi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsActivity::toggleCurrentSetting() {
|
void SettingsActivity::toggleCurrentSetting() {
|
||||||
// Validate index
|
// Validate index
|
||||||
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
||||||
@ -135,8 +114,6 @@ void SettingsActivity::render() const {
|
|||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(READER_FONT_ID, 10, "Settings", true, BOLD);
|
renderer.drawCenteredText(READER_FONT_ID, 10, "Settings", true, BOLD);
|
||||||
|
|
||||||
// We always have at least one setting
|
|
||||||
|
|
||||||
// Draw all settings
|
// Draw all settings
|
||||||
for (int i = 0; i < settingsCount; i++) {
|
for (int i = 0; i < settingsCount; i++) {
|
||||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||||
@ -153,13 +130,11 @@ void SettingsActivity::render() const {
|
|||||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||||
bool value = SETTINGS.*(settingsList[i].valuePtr);
|
bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||||
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF");
|
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF");
|
||||||
} else if (settingsList[i].type == SettingType::ACTION) {
|
|
||||||
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, ">");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 30, "Press OK to select, BACK to save & exit");
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 30, "Press OK to toggle, BACK to save & exit");
|
||||||
|
|
||||||
// Always use standard refresh for settings screen
|
// Always use standard refresh for settings screen
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
class CrossPointSettings;
|
class CrossPointSettings;
|
||||||
|
|
||||||
enum class SettingType { TOGGLE, ACTION };
|
enum class SettingType { TOGGLE };
|
||||||
|
|
||||||
// Structure to hold setting information
|
// Structure to hold setting information
|
||||||
struct SettingInfo {
|
struct SettingInfo {
|
||||||
@ -27,22 +27,19 @@ class SettingsActivity final : public Activity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
int selectedSettingIndex = 0; // Currently selected setting
|
int selectedSettingIndex = 0; // Currently selected setting
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
const std::function<void()> onGoWifi;
|
|
||||||
|
|
||||||
// Static settings list
|
// Static settings list
|
||||||
static constexpr int settingsCount = 3; // Number of settings
|
static constexpr int settingsCount = 2; // Number of settings
|
||||||
static const SettingInfo settingsList[settingsCount];
|
static const SettingInfo settingsList[settingsCount];
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void render() const;
|
void render() const;
|
||||||
void toggleCurrentSetting();
|
void toggleCurrentSetting();
|
||||||
void activateCurrentSetting();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome,
|
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
||||||
const std::function<void()>& onGoWifi)
|
: Activity(renderer, inputManager), onGoHome(onGoHome) {}
|
||||||
: Activity(renderer, inputManager), onGoHome(onGoHome), onGoWifi(onGoWifi) {}
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
28
src/main.cpp
28
src/main.cpp
@ -21,7 +21,7 @@
|
|||||||
#include "activities/boot_sleep/BootActivity.h"
|
#include "activities/boot_sleep/BootActivity.h"
|
||||||
#include "activities/boot_sleep/SleepActivity.h"
|
#include "activities/boot_sleep/SleepActivity.h"
|
||||||
#include "activities/home/HomeActivity.h"
|
#include "activities/home/HomeActivity.h"
|
||||||
#include "activities/network/WifiScreen.h"
|
#include "activities/network/CrossPointWebServerActivity.h"
|
||||||
#include "activities/reader/ReaderActivity.h"
|
#include "activities/reader/ReaderActivity.h"
|
||||||
#include "activities/settings/SettingsActivity.h"
|
#include "activities/settings/SettingsActivity.h"
|
||||||
#include "activities/util/FullScreenMessageActivity.h"
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
@ -143,21 +143,19 @@ void onGoToReader(const std::string& initialEpubPath) {
|
|||||||
}
|
}
|
||||||
void onGoToReaderHome() { onGoToReader(std::string()); }
|
void onGoToReaderHome() { onGoToReader(std::string()); }
|
||||||
|
|
||||||
void onGoToSettings();
|
void onGoToFileTransfer() {
|
||||||
|
|
||||||
void onGoToWifi() {
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new WifiScreen(renderer, inputManager, onGoToSettings));
|
enterNewActivity(new CrossPointWebServerActivity(renderer, inputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToSettings() {
|
void onGoToSettings() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome, onGoToWifi));
|
enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings));
|
enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings, onGoToFileTransfer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
@ -204,10 +202,8 @@ void setup() {
|
|||||||
void loop() {
|
void loop() {
|
||||||
static unsigned long lastLoopTime = 0;
|
static unsigned long lastLoopTime = 0;
|
||||||
static unsigned long maxLoopDuration = 0;
|
static unsigned long maxLoopDuration = 0;
|
||||||
static unsigned long lastHandleClientTime = 0;
|
|
||||||
|
|
||||||
unsigned long loopStartTime = millis();
|
unsigned long loopStartTime = millis();
|
||||||
unsigned long timeSinceLastLoop = loopStartTime - lastLoopTime;
|
|
||||||
|
|
||||||
// Reduce delay when webserver is running to allow faster handleClient() calls
|
// Reduce delay when webserver is running to allow faster handleClient() calls
|
||||||
// This is critical for upload performance and preventing TCP timeouts
|
// This is critical for upload performance and preventing TCP timeouts
|
||||||
@ -251,20 +247,6 @@ void loop() {
|
|||||||
}
|
}
|
||||||
unsigned long activityDuration = millis() - activityStartTime;
|
unsigned long activityDuration = millis() - activityStartTime;
|
||||||
|
|
||||||
// Handle web server requests if running
|
|
||||||
if (crossPointWebServer.isRunning()) {
|
|
||||||
unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
|
||||||
|
|
||||||
// Log if there's a significant gap between handleClient calls (>100ms)
|
|
||||||
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
|
||||||
Serial.printf("[%lu] [LOOP] WARNING: %lu ms gap since last handleClient (activity took %lu ms)\n", millis(),
|
|
||||||
timeSinceLastHandleClient, activityDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
crossPointWebServer.handleClient();
|
|
||||||
lastHandleClientTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long loopDuration = millis() - loopStartTime;
|
unsigned long loopDuration = millis() - loopStartTime;
|
||||||
if (loopDuration > maxLoopDuration) {
|
if (loopDuration > maxLoopDuration) {
|
||||||
maxLoopDuration = loopDuration;
|
maxLoopDuration = loopDuration;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user