Compare commits

..

6 Commits

Author SHA1 Message Date
Dave Allie 6fe28da41b Cut release 0.8.1 2025-12-22 03:20:22 +11:00
Dave Allie 689b539c6b Stream CrossPointWebServer data over JSON APIs (#97)
## Summary

* HTML files are now static, streamed directly to the client without
modification
* For any dynamic values, load via JSON APIs
* For files page, we stream the JSON content as we scan the directory to
avoid holding onto too much data

## Additional details

* We were previously building up a very large string all generated on
the X4 directly, we should be leveraging the browser
* Fixes https://github.com/daveallie/crosspoint-reader/issues/94
2025-12-22 03:19:49 +11:00
Jonas Diemer ce37c80c2d Improve power button hold measurement for boot (#95)
Improves the duration for which the power button needs to be held - see
#53.

I left the measurement code for the calibration value in, as it will
likely change if we move the settings to NVS.
2025-12-22 00:53:55 +11:00
Dave Allie b39ce22e54 Cleanup of activities 2025-12-22 00:48:16 +11:00
Dave Allie 77c655fcf5 Give activities names and log when entering and exiting them (#92)
## Summary

* Give activities name and log when entering and exiting them
* Clearer logs when attempting to debug, knowing where users are coming
from/going to helps
2025-12-21 21:17:00 +11:00
Dave Allie 246afae6ef Start power off sequence as soon as hold duration for the power button is reached (#93)
## Summary

* Swap from `wasReleased` to `isPressed` when checking power button
duration
  * In practice it makes the power down experience feel a lot snappier
* Remove the unnecessary 1000ms delay when powering off

## Additional Context

* A little discussion in here:
https://github.com/daveallie/crosspoint-reader/discussions/53#discussioncomment-15309707
2025-12-21 21:16:41 +11:00
35 changed files with 1167 additions and 1153 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
[platformio]
crosspoint_version = 0.8.0
crosspoint_version = 0.8.1
default_envs = default
[base]
@@ -9,7 +9,7 @@ framework = arduino
monitor_speed = 115200
upload_speed = 921600
check_tool = cppcheck
check_flags = --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --suppress=unmatchedSuppression --inline-suppr
check_flags = --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --suppress=unmatchedSuppression --suppress=*:*/.pio/* --inline-suppr
check_skip_packages = yes
board_upload.flash_size = 16MB
@@ -39,6 +39,7 @@ lib_deps =
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
ArduinoJson @ 7.4.2
[env:default]
extends = base
+11 -5
View File
@@ -1,19 +1,25 @@
#pragma once
#include <InputManager.h>
#include <HardwareSerial.h>
#include <string>
#include <utility>
class InputManager;
class GfxRenderer;
class Activity {
protected:
std::string name;
GfxRenderer& renderer;
InputManager& inputManager;
public:
explicit Activity(GfxRenderer& renderer, InputManager& inputManager)
: renderer(renderer), inputManager(inputManager) {}
explicit Activity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
: name(std::move(name)), renderer(renderer), inputManager(inputManager) {}
virtual ~Activity() = default;
virtual void onEnter() {}
virtual void onExit() {}
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
virtual void loop() {}
virtual bool skipLoopDelay() { return false; }
};
+4 -1
View File
@@ -18,4 +18,7 @@ void ActivityWithSubactivity::loop() {
}
}
void ActivityWithSubactivity::onExit() { exitActivity(); }
void ActivityWithSubactivity::onExit() {
Activity::onExit();
exitActivity();
}
+2 -2
View File
@@ -10,8 +10,8 @@ class ActivityWithSubactivity : public Activity {
void enterNewActivity(Activity* activity);
public:
explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager)
: Activity(renderer, inputManager) {}
explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
: Activity(std::move(name), renderer, inputManager) {}
void loop() override;
void onExit() override;
};
@@ -6,6 +6,8 @@
#include "images/CrossLarge.h"
void BootActivity::onEnter() {
Activity::onEnter();
const auto pageWidth = GfxRenderer::getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight();
+1 -1
View File
@@ -3,6 +3,6 @@
class BootActivity final : public Activity {
public:
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity("Boot", renderer, inputManager) {}
void onEnter() override;
};
@@ -12,6 +12,7 @@
#include "images/CrossLarge.h"
void SleepActivity::onEnter() {
Activity::onEnter();
renderPopup("Entering Sleep...");
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
@@ -170,11 +171,16 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
}
void SleepActivity::renderCoverSleepScreen() const {
if (APP_STATE.openEpubPath.empty()) {
return renderDefaultSleepScreen();
}
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
if (!lastEpub.load()) {
Serial.println("[SLP] Failed to load last epub");
return renderDefaultSleepScreen();
}
if (!lastEpub.generateCoverBmp()) {
Serial.println("[SLP] Failed to generate cover bmp");
return renderDefaultSleepScreen();
+2 -1
View File
@@ -5,7 +5,8 @@ class Bitmap;
class SleepActivity final : public Activity {
public:
explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager)
: Activity("Sleep", renderer, inputManager) {}
void onEnter() override;
private:
+7 -2
View File
@@ -1,6 +1,7 @@
#include "HomeActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "config.h"
@@ -15,6 +16,8 @@ void HomeActivity::taskTrampoline(void* param) {
}
void HomeActivity::onEnter() {
Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
selectorIndex = 0;
@@ -31,6 +34,8 @@ void HomeActivity::onEnter() {
}
void HomeActivity::onExit() {
Activity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -79,8 +84,8 @@ void HomeActivity::displayTaskLoop() {
void HomeActivity::render() const {
renderer.clearScreen();
const auto pageWidth = GfxRenderer::getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
// Draw selection
+1 -1
View File
@@ -23,7 +23,7 @@ class HomeActivity final : public Activity {
public:
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onReaderOpen,
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen)
: Activity(renderer, inputManager),
: Activity("Home", renderer, inputManager),
onReaderOpen(onReaderOpen),
onSettingsOpen(onSettingsOpen),
onFileTransferOpen(onFileTransferOpen) {}
@@ -1,8 +1,10 @@
#include "CrossPointWebServerActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <WiFi.h>
#include "WifiSelectionActivity.h"
#include "config.h"
void CrossPointWebServerActivity::taskTrampoline(void* param) {
@@ -11,7 +13,8 @@ void CrossPointWebServerActivity::taskTrampoline(void* param) {
}
void CrossPointWebServerActivity::onEnter() {
Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onEnter ==========\n", millis());
ActivityWithSubactivity::onEnter();
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap());
renderingMutex = xSemaphoreCreateMutex();
@@ -36,13 +39,13 @@ void CrossPointWebServerActivity::onEnter() {
// 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();
enterNewActivity(new WifiSelectionActivity(renderer, inputManager,
[this](const bool connected) { onWifiSelectionComplete(connected); }));
}
void CrossPointWebServerActivity::onExit() {
Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onExit START ==========\n", millis());
ActivityWithSubactivity::onExit();
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
state = WebServerActivityState::SHUTTING_DOWN;
@@ -50,14 +53,6 @@ void CrossPointWebServerActivity::onExit() {
// 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);
@@ -92,20 +87,17 @@ void CrossPointWebServerActivity::onExit() {
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) {
void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) {
Serial.printf("[%lu] [WEBACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected);
if (connected) {
// Get connection info before exiting subactivity
connectedIP = wifiSelection->getConnectedIP();
connectedIP = static_cast<WifiSelectionActivity*>(subActivity.get())->getConnectedIP();
connectedSSID = WiFi.SSID().c_str();
// Exit the wifi selection subactivity
wifiSelection->onExit();
wifiSelection.reset();
exitActivity();
// Start the web server
startWebServer();
@@ -150,47 +142,40 @@ void CrossPointWebServerActivity::stopWebServer() {
}
void CrossPointWebServerActivity::loop() {
if (subActivity) {
// Forward loop to subactivity
subActivity->loop();
return;
}
// Handle different states
switch (state) {
case WebServerActivityState::WIFI_SELECTION:
// Forward loop to WiFi selection subactivity
if (wifiSelection) {
wifiSelection->loop();
}
break;
if (state == WebServerActivityState::SERVER_RUNNING) {
// Handle web server requests - call handleClient multiple times per loop
// to improve responsiveness and upload throughput
if (webServer && webServer->isRunning()) {
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
case WebServerActivityState::SERVER_RUNNING:
// Handle web server requests - call handleClient multiple times per loop
// to improve responsiveness and upload throughput
if (webServer && webServer->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);
}
// Call handleClient multiple times to process pending requests faster
// This is critical for upload performance - HTTP file uploads send data
// in chunks and each handleClient() call processes incoming data
constexpr int HANDLE_CLIENT_ITERATIONS = 10;
for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
webServer->handleClient();
}
lastHandleClientTime = millis();
// 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);
}
// Handle exit on Back button
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
onGoBack();
return;
// Call handleClient multiple times to process pending requests faster
// This is critical for upload performance - HTTP file uploads send data
// in chunks and each handleClient() call processes incoming data
constexpr int HANDLE_CLIENT_ITERATIONS = 10;
for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
webServer->handleClient();
}
break;
lastHandleClientTime = millis();
}
case WebServerActivityState::SHUTTING_DOWN:
// Do nothing - waiting for cleanup
break;
// Handle exit on Back button
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
onGoBack();
return;
}
}
}
@@ -7,9 +7,8 @@
#include <memory>
#include <string>
#include "../Activity.h"
#include "WifiSelectionActivity.h"
#include "server/CrossPointWebServer.h"
#include "activities/ActivityWithSubactivity.h"
#include "network/CrossPointWebServer.h"
// Web server activity states
enum class WebServerActivityState {
@@ -26,16 +25,13 @@ enum class WebServerActivityState {
* - Handles client requests in its loop() function
* - Cleans up the server and shuts down WiFi on exit
*/
class CrossPointWebServerActivity final : public Activity {
class CrossPointWebServerActivity final : public ActivityWithSubactivity {
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;
// Web server - owned by this activity
std::unique_ptr<CrossPointWebServer> webServer;
@@ -58,7 +54,7 @@ class CrossPointWebServerActivity final : public Activity {
public:
explicit CrossPointWebServerActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void()>& onGoBack)
: Activity(renderer, inputManager), onGoBack(onGoBack) {}
: ActivityWithSubactivity("CrossPointWebServer", renderer, inputManager), onGoBack(onGoBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
@@ -6,6 +6,7 @@
#include <map>
#include "WifiCredentialStore.h"
#include "activities/util/KeyboardEntryActivity.h"
#include "config.h"
void WifiSelectionActivity::taskTrampoline(void* param) {
@@ -14,10 +15,14 @@ void WifiSelectionActivity::taskTrampoline(void* param) {
}
void WifiSelectionActivity::onEnter() {
Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
// Load saved WiFi credentials
// Load saved WiFi credentials - SD card operations need lock as we use SPI for both
xSemaphoreTake(renderingMutex, portMAX_DELAY);
WIFI_STORE.loadFromFile();
xSemaphoreGive(renderingMutex);
// Reset state
selectedNetworkIndex = 0;
@@ -30,7 +35,6 @@ void WifiSelectionActivity::onEnter() {
usedSavedPassword = false;
savePromptSelection = 0;
forgetPromptSelection = 0;
keyboard.reset();
// Trigger first update to show scanning message
updateRequired = true;
@@ -47,7 +51,8 @@ void WifiSelectionActivity::onEnter() {
}
void WifiSelectionActivity::onExit() {
Serial.printf("[%lu] [WIFI] ========== WifiSelectionActivity onExit START ==========\n", millis());
Activity::onExit();
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
// Stop any ongoing WiFi scan
@@ -78,7 +83,6 @@ void WifiSelectionActivity::onExit() {
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] ========== WifiSelectionActivity onExit COMPLETE ==========\n", millis());
}
void WifiSelectionActivity::startWifiScan() {
@@ -96,7 +100,7 @@ void WifiSelectionActivity::startWifiScan() {
}
void WifiSelectionActivity::processWifiScanResults() {
int16_t scanResult = WiFi.scanComplete();
const int16_t scanResult = WiFi.scanComplete();
if (scanResult == WIFI_SCAN_RUNNING) {
// Scan still in progress
@@ -115,7 +119,7 @@ void WifiSelectionActivity::processWifiScanResults() {
for (int i = 0; i < scanResult; i++) {
std::string ssid = WiFi.SSID(i).c_str();
int32_t rssi = WiFi.RSSI(i);
const int32_t rssi = WiFi.RSSI(i);
// Skip hidden networks (empty SSID)
if (ssid.empty()) {
@@ -152,7 +156,7 @@ void WifiSelectionActivity::processWifiScanResults() {
updateRequired = true;
}
void WifiSelectionActivity::selectNetwork(int index) {
void WifiSelectionActivity::selectNetwork(const int index) {
if (index < 0 || index >= static_cast<int>(networks.size())) {
return;
}
@@ -178,11 +182,11 @@ void WifiSelectionActivity::selectNetwork(int index) {
if (selectedRequiresPassword) {
// Show password entry
state = WifiSelectionState::PASSWORD_ENTRY;
keyboard.reset(new KeyboardEntryActivity(renderer, inputManager, "Enter WiFi Password",
"", // No initial text
64, // Max password length
false // Show password by default (hard keyboard to use)
));
enterNewActivity(new KeyboardEntryActivity(renderer, inputManager, "Enter WiFi Password",
"", // No initial text
64, // Max password length
false // Show password by default (hard keyboard to use)
));
updateRequired = true;
} else {
// Connect directly for open networks
@@ -200,8 +204,8 @@ void WifiSelectionActivity::attemptConnection() {
WiFi.mode(WIFI_STA);
// Get password from keyboard if we just entered it
if (keyboard && !usedSavedPassword) {
enteredPassword = keyboard->getText();
if (subActivity && !usedSavedPassword) {
enteredPassword = static_cast<KeyboardEntryActivity*>(subActivity.get())->getText();
}
if (selectedRequiresPassword && !enteredPassword.empty()) {
@@ -216,7 +220,7 @@ void WifiSelectionActivity::checkConnectionStatus() {
return;
}
wl_status_t status = WiFi.status();
const wl_status_t status = WiFi.status();
if (status == WL_CONNECTED) {
// Successfully connected
@@ -273,7 +277,8 @@ void WifiSelectionActivity::loop() {
}
// Handle password entry state
if (state == WifiSelectionState::PASSWORD_ENTRY && keyboard) {
if (state == WifiSelectionState::PASSWORD_ENTRY && subActivity) {
const auto keyboard = static_cast<KeyboardEntryActivity*>(subActivity.get());
keyboard->handleInput();
if (keyboard->isComplete()) {
@@ -283,7 +288,7 @@ void WifiSelectionActivity::loop() {
if (keyboard->isCancelled()) {
state = WifiSelectionState::NETWORK_LIST;
keyboard.reset();
exitActivity();
updateRequired = true;
return;
}
@@ -307,7 +312,9 @@ void WifiSelectionActivity::loop() {
} else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
if (savePromptSelection == 0) {
// User chose "Yes" - save the password
xSemaphoreTake(renderingMutex, portMAX_DELAY);
WIFI_STORE.addCredential(selectedSSID, enteredPassword);
xSemaphoreGive(renderingMutex);
}
// Complete - parent will start web server
onComplete(true);
@@ -333,7 +340,9 @@ void WifiSelectionActivity::loop() {
} else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
if (forgetPromptSelection == 0) {
// User chose "Yes" - forget the network
xSemaphoreTake(renderingMutex, portMAX_DELAY);
WIFI_STORE.removeCredential(selectedSSID);
xSemaphoreGive(renderingMutex);
// Update the network list to reflect the change
const auto network = find_if(networks.begin(), networks.end(),
[this](const WifiNetworkInfo& net) { return net.ssid == selectedSSID; });
@@ -408,15 +417,18 @@ void WifiSelectionActivity::loop() {
}
}
std::string WifiSelectionActivity::getSignalStrengthIndicator(int32_t rssi) const {
std::string WifiSelectionActivity::getSignalStrengthIndicator(const int32_t rssi) const {
// Convert RSSI to signal bars representation
if (rssi >= -50) {
return "||||"; // Excellent
} else if (rssi >= -60) {
}
if (rssi >= -60) {
return "||| "; // Good
} else if (rssi >= -70) {
}
if (rssi >= -70) {
return "|| "; // Fair
} else if (rssi >= -80) {
}
if (rssi >= -80) {
return "| "; // Weak
}
return " "; // Very weak
@@ -482,8 +494,8 @@ void WifiSelectionActivity::renderNetworkList() const {
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again", true, REGULAR);
} else {
// Calculate how many networks we can display
const int startY = 60;
const int lineHeight = 25;
constexpr int startY = 60;
constexpr int lineHeight = 25;
const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight;
// Calculate scroll offset to keep selected item visible
@@ -555,8 +567,8 @@ void WifiSelectionActivity::renderPasswordEntry() const {
renderer.drawCenteredText(UI_FONT_ID, 38, networkInfo.c_str(), true, REGULAR);
// Draw keyboard
if (keyboard) {
keyboard->render(58);
if (subActivity) {
static_cast<KeyboardEntryActivity*>(subActivity.get())->render(58);
}
}
@@ -591,7 +603,7 @@ void WifiSelectionActivity::renderConnected() const {
}
renderer.drawCenteredText(UI_FONT_ID, top + 10, ssidInfo.c_str(), true, REGULAR);
std::string ipInfo = "IP Address: " + connectedIP;
const std::string ipInfo = "IP Address: " + connectedIP;
renderer.drawCenteredText(UI_FONT_ID, top + 40, ipInfo.c_str(), true, REGULAR);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue", true, REGULAR);
@@ -615,9 +627,9 @@ void WifiSelectionActivity::renderSavePrompt() const {
// Draw Yes/No buttons
const int buttonY = top + 80;
const int buttonWidth = 60;
const int buttonSpacing = 30;
const int totalWidth = buttonWidth * 2 + buttonSpacing;
constexpr int buttonWidth = 60;
constexpr int buttonSpacing = 30;
constexpr int totalWidth = buttonWidth * 2 + buttonSpacing;
const int startX = (pageWidth - totalWidth) / 2;
// Draw "Yes" button
@@ -665,9 +677,9 @@ void WifiSelectionActivity::renderForgetPrompt() const {
// Draw Yes/No buttons
const int buttonY = top + 80;
const int buttonWidth = 60;
const int buttonSpacing = 30;
const int totalWidth = buttonWidth * 2 + buttonSpacing;
constexpr int buttonWidth = 60;
constexpr int buttonSpacing = 30;
constexpr int totalWidth = buttonWidth * 2 + buttonSpacing;
const int startX = (pageWidth - totalWidth) / 2;
// Draw "Yes" button
@@ -9,8 +9,7 @@
#include <string>
#include <vector>
#include "../Activity.h"
#include "../util/KeyboardEntryActivity.h"
#include "activities/ActivityWithSubactivity.h"
// Structure to hold WiFi network information
struct WifiNetworkInfo {
@@ -43,7 +42,7 @@ enum class WifiSelectionState {
*
* The onComplete callback receives true if connected successfully, false if cancelled.
*/
class WifiSelectionActivity final : public Activity {
class WifiSelectionActivity final : public ActivityWithSubactivity {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
bool updateRequired = false;
@@ -56,9 +55,6 @@ class WifiSelectionActivity final : public Activity {
std::string selectedSSID;
bool selectedRequiresPassword = false;
// On-screen keyboard for password entry
std::unique_ptr<KeyboardEntryActivity> keyboard;
// Connection result
std::string connectedIP;
std::string connectionError;
@@ -98,7 +94,7 @@ class WifiSelectionActivity final : public Activity {
public:
explicit WifiSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void(bool connected)>& onComplete)
: Activity(renderer, inputManager), onComplete(onComplete) {}
: ActivityWithSubactivity("WifiSelection", renderer, inputManager), onComplete(onComplete) {}
void onEnter() override;
void onExit() override;
void loop() override;
+13 -10
View File
@@ -2,6 +2,7 @@
#include <Epub/Page.h>
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "Battery.h"
@@ -26,6 +27,8 @@ void EpubReaderActivity::taskTrampoline(void* param) {
}
void EpubReaderActivity::onEnter() {
ActivityWithSubactivity::onEnter();
if (!epub) {
return;
}
@@ -61,6 +64,8 @@ void EpubReaderActivity::onEnter() {
}
void EpubReaderActivity::onExit() {
ActivityWithSubactivity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -75,8 +80,8 @@ void EpubReaderActivity::onExit() {
void EpubReaderActivity::loop() {
// Pass input responsibility to sub activity if exists
if (subAcitivity) {
subAcitivity->loop();
if (subActivity) {
subActivity->loop();
return;
}
@@ -84,11 +89,11 @@ void EpubReaderActivity::loop() {
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
// Don't start activity transition while rendering
xSemaphoreTake(renderingMutex, portMAX_DELAY);
subAcitivity.reset(new EpubReaderChapterSelectionActivity(
exitActivity();
enterNewActivity(new EpubReaderChapterSelectionActivity(
this->renderer, this->inputManager, epub, currentSpineIndex,
[this] {
subAcitivity->onExit();
subAcitivity.reset();
exitActivity();
updateRequired = true;
},
[this](const int newSpineIndex) {
@@ -97,11 +102,9 @@ void EpubReaderActivity::loop() {
nextPageNumber = 0;
section.reset();
}
subAcitivity->onExit();
subAcitivity.reset();
exitActivity();
updateRequired = true;
}));
subAcitivity->onEnter();
xSemaphoreGive(renderingMutex);
}
@@ -330,8 +333,8 @@ void EpubReaderActivity::renderStatusBar() const {
constexpr auto textY = 776;
// Calculate progress in book
float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
const float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
// Right aligned text for progress counter
const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) +
+3 -4
View File
@@ -5,14 +5,13 @@
#include <freertos/semphr.h>
#include <freertos/task.h>
#include "../Activity.h"
#include "activities/ActivityWithSubactivity.h"
class EpubReaderActivity final : public Activity {
class EpubReaderActivity final : public ActivityWithSubactivity {
std::shared_ptr<Epub> epub;
std::unique_ptr<Section> section = nullptr;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
std::unique_ptr<Activity> subAcitivity = nullptr;
int currentSpineIndex = 0;
int nextPageNumber = 0;
int pagesUntilFullRefresh = 0;
@@ -28,7 +27,7 @@ class EpubReaderActivity final : public Activity {
public:
explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
const std::function<void()>& onGoBack)
: Activity(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
: ActivityWithSubactivity("EpubReader", renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
@@ -1,6 +1,7 @@
#include "EpubReaderChapterSelectionActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "config.h"
@@ -16,6 +17,8 @@ void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
}
void EpubReaderChapterSelectionActivity::onEnter() {
Activity::onEnter();
if (!epub) {
return;
}
@@ -34,6 +37,8 @@ void EpubReaderChapterSelectionActivity::onEnter() {
}
void EpubReaderChapterSelectionActivity::onExit() {
Activity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -27,7 +27,7 @@ class EpubReaderChapterSelectionActivity final : public Activity {
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
const std::function<void()>& onGoBack,
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
: Activity(renderer, inputManager),
: Activity("EpubReaderChapterSelection", renderer, inputManager),
epub(epub),
currentSpineIndex(currentSpineIndex),
onGoBack(onGoBack),
@@ -1,6 +1,7 @@
#include "FileSelectionActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "config.h"
@@ -48,6 +49,8 @@ void FileSelectionActivity::loadFiles() {
}
void FileSelectionActivity::onEnter() {
Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
basepath = "/";
@@ -66,6 +69,8 @@ void FileSelectionActivity::onEnter() {
}
void FileSelectionActivity::onExit() {
Activity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -28,7 +28,7 @@ class FileSelectionActivity final : public Activity {
explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void(const std::string&)>& onSelect,
const std::function<void()>& onGoHome)
: Activity(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
: Activity("FileSelection", renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
void onEnter() override;
void onExit() override;
void loop() override;

Some files were not shown because too many files have changed in this diff Show More