2025-12-03 22:00:29 +11:00
|
|
|
#include <Arduino.h>
|
2025-12-08 19:48:49 +11:00
|
|
|
#include <EInkDisplay.h>
|
2025-12-03 22:00:29 +11:00
|
|
|
#include <Epub.h>
|
2025-12-08 22:06:09 +11:00
|
|
|
#include <GfxRenderer.h>
|
2025-12-06 12:35:41 +11:00
|
|
|
#include <InputManager.h>
|
2025-12-03 22:00:29 +11:00
|
|
|
#include <SD.h>
|
|
|
|
|
#include <SPI.h>
|
2025-12-13 00:16:10 +11:00
|
|
|
#include <builtinFonts/bookerly_2b.h>
|
|
|
|
|
#include <builtinFonts/bookerly_bold_2b.h>
|
|
|
|
|
#include <builtinFonts/bookerly_bold_italic_2b.h>
|
|
|
|
|
#include <builtinFonts/bookerly_italic_2b.h>
|
|
|
|
|
#include <builtinFonts/pixelarial14.h>
|
|
|
|
|
#include <builtinFonts/ubuntu_10.h>
|
|
|
|
|
#include <builtinFonts/ubuntu_bold_10.h>
|
2025-12-03 22:00:29 +11:00
|
|
|
|
|
|
|
|
#include "Battery.h"
|
2025-12-15 13:16:46 +01:00
|
|
|
#include "CrossPointSettings.h"
|
2025-12-04 00:07:25 +11:00
|
|
|
#include "CrossPointState.h"
|
2025-12-28 21:59:14 -06:00
|
|
|
#include "MappedInputManager.h"
|
2025-12-17 23:32:18 +11:00
|
|
|
#include "activities/boot_sleep/BootActivity.h"
|
|
|
|
|
#include "activities/boot_sleep/SleepActivity.h"
|
|
|
|
|
#include "activities/home/HomeActivity.h"
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
#include "activities/network/CrossPointWebServerActivity.h"
|
2025-12-17 23:32:18 +11:00
|
|
|
#include "activities/reader/ReaderActivity.h"
|
|
|
|
|
#include "activities/settings/SettingsActivity.h"
|
|
|
|
|
#include "activities/util/FullScreenMessageActivity.h"
|
2025-12-08 22:06:09 +11:00
|
|
|
#include "config.h"
|
2025-12-03 22:00:29 +11:00
|
|
|
|
|
|
|
|
#define SPI_FQ 40000000
|
|
|
|
|
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
|
|
|
|
#define EPD_SCLK 8 // SPI Clock
|
|
|
|
|
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
|
|
|
|
#define EPD_CS 21 // Chip Select
|
|
|
|
|
#define EPD_DC 4 // Data/Command
|
|
|
|
|
#define EPD_RST 5 // Reset
|
|
|
|
|
#define EPD_BUSY 6 // Busy
|
|
|
|
|
|
|
|
|
|
#define UART0_RXD 20 // Used for USB connection detection
|
|
|
|
|
|
|
|
|
|
#define SD_SPI_CS 12
|
|
|
|
|
#define SD_SPI_MISO 7
|
|
|
|
|
|
2025-12-08 19:48:49 +11:00
|
|
|
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
2025-12-06 12:35:41 +11:00
|
|
|
InputManager inputManager;
|
2025-12-28 21:59:14 -06:00
|
|
|
MappedInputManager mappedInputManager(inputManager);
|
2025-12-08 22:06:09 +11:00
|
|
|
GfxRenderer renderer(einkDisplay);
|
2025-12-17 23:32:18 +11:00
|
|
|
Activity* currentActivity;
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-08 22:06:09 +11:00
|
|
|
// Fonts
|
|
|
|
|
EpdFont bookerlyFont(&bookerly_2b);
|
|
|
|
|
EpdFont bookerlyBoldFont(&bookerly_bold_2b);
|
|
|
|
|
EpdFont bookerlyItalicFont(&bookerly_italic_2b);
|
|
|
|
|
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
|
|
|
|
|
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
|
|
|
|
|
2025-12-13 00:16:10 +11:00
|
|
|
EpdFont smallFont(&pixelarial14);
|
2025-12-08 22:06:09 +11:00
|
|
|
EpdFontFamily smallFontFamily(&smallFont);
|
|
|
|
|
|
|
|
|
|
EpdFont ubuntu10Font(&ubuntu_10);
|
|
|
|
|
EpdFont ubuntuBold10Font(&ubuntu_bold_10);
|
|
|
|
|
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
|
|
|
|
|
|
2025-12-16 12:49:31 +01:00
|
|
|
// Auto-sleep timeout (10 minutes of inactivity)
|
|
|
|
|
constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000;
|
2025-12-21 14:53:55 +01:00
|
|
|
// measurement of power button press duration calibration value
|
|
|
|
|
unsigned long t1 = 0;
|
|
|
|
|
unsigned long t2 = 0;
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-17 23:32:18 +11:00
|
|
|
void exitActivity() {
|
|
|
|
|
if (currentActivity) {
|
|
|
|
|
currentActivity->onExit();
|
|
|
|
|
delete currentActivity;
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
currentActivity = nullptr;
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|
2025-12-06 01:37:20 +11:00
|
|
|
}
|
|
|
|
|
|
2025-12-17 23:32:18 +11:00
|
|
|
void enterNewActivity(Activity* activity) {
|
|
|
|
|
currentActivity = activity;
|
|
|
|
|
currentActivity->onEnter();
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify long press on wake-up from deep sleep
|
|
|
|
|
void verifyWakeupLongPress() {
|
2025-12-19 13:37:34 +01:00
|
|
|
// Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration()
|
2025-12-04 00:57:32 +11:00
|
|
|
const auto start = millis();
|
2025-12-06 12:35:41 +11:00
|
|
|
bool abort = false;
|
2025-12-21 14:53:55 +01:00
|
|
|
// It takes us some time to wake up from deep sleep, so we need to subtract that from the duration
|
|
|
|
|
uint16_t calibration = 25;
|
|
|
|
|
uint16_t calibratedPressDuration =
|
|
|
|
|
(calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1;
|
2025-12-06 12:35:41 +11:00
|
|
|
|
|
|
|
|
inputManager.update();
|
2025-12-19 13:37:34 +01:00
|
|
|
// Verify the user has actually pressed
|
2025-12-06 12:35:41 +11:00
|
|
|
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
2025-12-19 13:37:34 +01:00
|
|
|
delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration.
|
2025-12-06 12:35:41 +11:00
|
|
|
inputManager.update();
|
2025-12-04 00:57:32 +11:00
|
|
|
}
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-21 14:53:55 +01:00
|
|
|
t2 = millis();
|
2025-12-06 12:35:41 +11:00
|
|
|
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
|
|
|
|
do {
|
2025-12-19 13:37:34 +01:00
|
|
|
delay(10);
|
2025-12-06 12:35:41 +11:00
|
|
|
inputManager.update();
|
2025-12-21 14:53:55 +01:00
|
|
|
} while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < calibratedPressDuration);
|
|
|
|
|
abort = inputManager.getHeldTime() < calibratedPressDuration;
|
2025-12-06 12:35:41 +11:00
|
|
|
} else {
|
|
|
|
|
abort = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (abort) {
|
2025-12-03 22:00:29 +11:00
|
|
|
// Button released too early. Returning to sleep.
|
|
|
|
|
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
2025-12-06 12:35:41 +11:00
|
|
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
2025-12-03 22:00:29 +11:00
|
|
|
esp_deep_sleep_start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:35:41 +11:00
|
|
|
void waitForPowerRelease() {
|
|
|
|
|
inputManager.update();
|
|
|
|
|
while (inputManager.isPressed(InputManager::BTN_POWER)) {
|
2025-12-05 17:47:23 +11:00
|
|
|
delay(50);
|
2025-12-06 12:35:41 +11:00
|
|
|
inputManager.update();
|
2025-12-05 17:47:23 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
// Enter deep sleep mode
|
|
|
|
|
void enterDeepSleep() {
|
2025-12-17 23:32:18 +11:00
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-08 19:48:49 +11:00
|
|
|
einkDisplay.deepSleep();
|
2025-12-21 14:53:55 +01:00
|
|
|
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
2025-12-21 21:16:41 +11:00
|
|
|
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
|
|
|
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
|
|
|
|
// Ensure that the power button has been released to avoid immediately turning back on if you're holding it
|
|
|
|
|
waitForPowerRelease();
|
2025-12-03 22:00:29 +11:00
|
|
|
// Enter Deep Sleep
|
|
|
|
|
esp_deep_sleep_start();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 00:07:25 +11:00
|
|
|
void onGoHome();
|
2025-12-17 23:32:18 +11:00
|
|
|
void onGoToReader(const std::string& initialEpubPath) {
|
|
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new ReaderActivity(renderer, mappedInputManager, initialEpubPath, onGoHome));
|
2025-12-17 20:47:43 +11:00
|
|
|
}
|
2025-12-17 23:32:18 +11:00
|
|
|
void onGoToReaderHome() { onGoToReader(std::string()); }
|
2025-12-26 09:55:23 +09:00
|
|
|
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); }
|
2025-12-17 20:47:43 +11:00
|
|
|
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
void onGoToFileTransfer() {
|
|
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-15 13:16:46 +01:00
|
|
|
void onGoToSettings() {
|
2025-12-17 23:32:18 +11:00
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome));
|
2025-12-15 13:16:46 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 01:37:20 +11:00
|
|
|
void onGoHome() {
|
2025-12-17 23:32:18 +11:00
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToReaderHome, onGoToSettings,
|
2025-12-26 09:55:23 +09:00
|
|
|
onGoToFileTransfer));
|
2025-12-06 01:37:20 +11:00
|
|
|
}
|
2025-12-04 00:07:25 +11:00
|
|
|
|
2025-12-24 22:33:21 +11:00
|
|
|
void setupDisplayAndFonts() {
|
|
|
|
|
einkDisplay.begin();
|
|
|
|
|
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
|
|
|
|
renderer.insertFont(READER_FONT_ID, bookerlyFontFamily);
|
|
|
|
|
renderer.insertFont(UI_FONT_ID, ubuntuFontFamily);
|
|
|
|
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
|
|
|
|
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
void setup() {
|
2025-12-21 14:53:55 +01:00
|
|
|
t1 = millis();
|
2025-12-23 14:14:10 +11:00
|
|
|
|
|
|
|
|
// Only start serial if USB connected
|
|
|
|
|
pinMode(UART0_RXD, INPUT);
|
|
|
|
|
if (digitalRead(UART0_RXD) == HIGH) {
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
}
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-08 23:13:33 +11:00
|
|
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
|
|
|
|
|
|
|
|
|
inputManager.begin();
|
2025-12-03 22:00:29 +11:00
|
|
|
// Initialize pins
|
|
|
|
|
pinMode(BAT_GPIO0, INPUT);
|
|
|
|
|
|
|
|
|
|
// Initialize SPI with custom pins
|
|
|
|
|
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
|
|
|
|
|
|
2025-12-19 13:37:34 +01:00
|
|
|
// SD Card Initialization
|
2025-12-24 22:36:13 +11:00
|
|
|
// We need 6 open files concurrently when parsing a new chapter
|
|
|
|
|
if (!SD.begin(SD_SPI_CS, SPI, SPI_FQ, "/sd", 6)) {
|
2025-12-19 13:37:34 +01:00
|
|
|
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
|
2025-12-24 22:33:21 +11:00
|
|
|
setupDisplayAndFonts();
|
2025-12-19 13:37:34 +01:00
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", BOLD));
|
2025-12-19 13:37:34 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SETTINGS.loadFromFile();
|
|
|
|
|
|
|
|
|
|
// verify power button press duration after we've read settings.
|
|
|
|
|
verifyWakeupLongPress();
|
|
|
|
|
|
2025-12-24 22:33:21 +11:00
|
|
|
setupDisplayAndFonts();
|
2025-12-08 22:06:09 +11:00
|
|
|
|
2025-12-17 23:32:18 +11:00
|
|
|
exitActivity();
|
2025-12-28 21:59:14 -06:00
|
|
|
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-17 23:32:18 +11:00
|
|
|
APP_STATE.loadFromFile();
|
|
|
|
|
if (APP_STATE.openEpubPath.empty()) {
|
|
|
|
|
onGoHome();
|
|
|
|
|
} else {
|
2025-12-21 18:41:52 +11:00
|
|
|
// Clear app state to avoid getting into a boot loop if the epub doesn't load
|
|
|
|
|
const auto path = APP_STATE.openEpubPath;
|
|
|
|
|
APP_STATE.openEpubPath = "";
|
|
|
|
|
APP_STATE.saveToFile();
|
|
|
|
|
onGoToReader(path);
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|
|
|
|
|
|
2025-12-05 17:47:23 +11:00
|
|
|
// Ensure we're not still holding the power button before leaving setup
|
2025-12-06 12:35:41 +11:00
|
|
|
waitForPowerRelease();
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop() {
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
static unsigned long maxLoopDuration = 0;
|
2025-12-21 15:43:53 +11:00
|
|
|
const unsigned long loopStartTime = millis();
|
|
|
|
|
static unsigned long lastMemPrint = 0;
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
|
2025-12-21 15:43:53 +11:00
|
|
|
inputManager.update();
|
2025-12-03 22:00:29 +11:00
|
|
|
|
2025-12-08 22:39:23 +11:00
|
|
|
if (Serial && millis() - lastMemPrint >= 10000) {
|
|
|
|
|
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
2025-12-06 12:56:39 +11:00
|
|
|
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
|
|
|
|
lastMemPrint = millis();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 12:49:31 +01:00
|
|
|
// Check for any user activity (button press or release)
|
|
|
|
|
static unsigned long lastActivityTime = millis();
|
|
|
|
|
if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased()) {
|
|
|
|
|
lastActivityTime = millis(); // Reset inactivity timer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (millis() - lastActivityTime >= AUTO_SLEEP_TIMEOUT_MS) {
|
|
|
|
|
Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), AUTO_SLEEP_TIMEOUT_MS);
|
|
|
|
|
enterDeepSleep();
|
|
|
|
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 21:16:41 +11:00
|
|
|
if (inputManager.isPressed(InputManager::BTN_POWER) &&
|
2025-12-19 13:37:34 +01:00
|
|
|
inputManager.getHeldTime() > SETTINGS.getPowerButtonDuration()) {
|
2025-12-03 22:00:29 +11:00
|
|
|
enterDeepSleep();
|
|
|
|
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 15:43:53 +11:00
|
|
|
const unsigned long activityStartTime = millis();
|
2025-12-17 23:32:18 +11:00
|
|
|
if (currentActivity) {
|
|
|
|
|
currentActivity->loop();
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|
2025-12-21 15:43:53 +11:00
|
|
|
const unsigned long activityDuration = millis() - activityStartTime;
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
|
2025-12-21 15:43:53 +11:00
|
|
|
const unsigned long loopDuration = millis() - loopStartTime;
|
Add connect to Wifi and File Manager Webserver (#41)
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-19 09:05:43 -05:00
|
|
|
if (loopDuration > maxLoopDuration) {
|
|
|
|
|
maxLoopDuration = loopDuration;
|
|
|
|
|
if (maxLoopDuration > 50) {
|
|
|
|
|
Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration,
|
|
|
|
|
activityDuration);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add delay at the end of the loop to prevent tight spinning
|
|
|
|
|
// When an activity requests skip loop delay (e.g., webserver running), use yield() for faster response
|
|
|
|
|
// Otherwise, use longer delay to save power
|
|
|
|
|
if (currentActivity && currentActivity->skipLoopDelay()) {
|
|
|
|
|
yield(); // Give FreeRTOS a chance to run tasks, but return immediately
|
|
|
|
|
} else {
|
|
|
|
|
delay(10); // Normal delay when no activity requires fast response
|
|
|
|
|
}
|
2025-12-03 22:00:29 +11:00
|
|
|
}
|