Add touch-down and long-press input detection
Implement touch-down event detection to provide immediate visual feedback when touching list items, mirroring button navigation behavior. Add long-press detection (500ms threshold) to distinguish between tap and hold gestures. Apply touch-down selection updates to file browser, home menu, and recent books activities.
This commit is contained in:
@@ -245,6 +245,10 @@ unsigned long HalGPIO::getPowerButtonHeldTime() const { return inputMgr.getPower
|
||||
|
||||
bool HalGPIO::wasTouchTap(float& nx, float& ny) const { return inputMgr.wasTouchTap(nx, ny); }
|
||||
|
||||
bool HalGPIO::wasTouchDown(float& nx, float& ny) const { return inputMgr.wasTouchPressedAt(nx, ny); }
|
||||
|
||||
unsigned long HalGPIO::lastTouchHeldMs() const { return inputMgr.lastTouchHeldMs(); }
|
||||
|
||||
bool HalGPIO::hasTouch() const { return inputMgr.hasTouch(); }
|
||||
|
||||
void HalGPIO::startDeepSleep() {
|
||||
|
||||
@@ -77,6 +77,14 @@ class HalGPIO {
|
||||
// primitive; see MappedInputManager for the top-left = Back mapping.)
|
||||
bool wasTouchTap(float& nx, float& ny) const;
|
||||
|
||||
// Press-edge of a touch: true on touch-down with the down position normalized
|
||||
// 0..1 (panel native). For showing the pressed/selected element before release.
|
||||
bool wasTouchDown(float& nx, float& ny) const;
|
||||
|
||||
// Duration (ms) of the last touch contact, latched on release. Valid on the
|
||||
// release frame (alongside wasTouchTap). For tap-vs-long-press decisions.
|
||||
unsigned long lastTouchHeldMs() const;
|
||||
|
||||
// True if a touch controller is present/active (runtime gate; false on the C3).
|
||||
bool hasTouch() const;
|
||||
|
||||
|
||||
@@ -82,6 +82,24 @@ bool MappedInputManager::wasItemTapped(int& id) const {
|
||||
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasItemTouchedDown(int& id) const {
|
||||
float nx = 0.0f, ny = 0.0f;
|
||||
if (!gpio.wasTouchDown(nx, ny)) return false;
|
||||
int lx = 0, ly = 0;
|
||||
renderer.tapToLogical(nx, ny, lx, ly);
|
||||
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasItemLongPressed(int& id) const {
|
||||
static constexpr unsigned long TOUCH_LONG_PRESS_MS = 500;
|
||||
float nx = 0.0f, ny = 0.0f;
|
||||
if (!gpio.wasTouchTap(nx, ny)) return false; // release frame
|
||||
if (gpio.lastTouchHeldMs() < TOUCH_LONG_PRESS_MS) return false;
|
||||
int lx = 0, ly = 0;
|
||||
renderer.tapToLogical(nx, ny, lx, ly);
|
||||
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasTabTapped(int& id) const {
|
||||
float nx = 0.0f, ny = 0.0f;
|
||||
if (!gpio.wasTouchTap(nx, ny)) return false;
|
||||
|
||||
@@ -31,6 +31,15 @@ class MappedInputManager {
|
||||
// element's id. Activities treat the id as "select + activate". False on
|
||||
// non-touch devices or when the tap missed every target.
|
||||
bool wasItemTapped(int& id) const;
|
||||
// Press-edge analogue of wasItemTapped: fires on touch-DOWN over an item, so the
|
||||
// activity can move its selection to that item (showing the selected state) before
|
||||
// release. Release still activates via wasItemTapped. Mirrors button nav (move
|
||||
// selection, then confirm).
|
||||
bool wasItemTouchedDown(int& id) const;
|
||||
// Long-press variant of wasItemTapped: true on release of a touch over an item
|
||||
// held past the long-press threshold (a subset of wasItemTapped's releases, so
|
||||
// check this first). Lets a screen distinguish tap vs press-and-hold on touch.
|
||||
bool wasItemLongPressed(int& id) const;
|
||||
// Like wasItemTapped, but for tab-bar tabs (id = tab index) and cover/card
|
||||
// targets (id = item index). Distinct kinds so screens with both a list and a
|
||||
// tab bar / cover (Home, Settings) don't confuse them.
|
||||
|
||||
@@ -206,6 +206,14 @@ void FileBrowserActivity::loop() {
|
||||
const int pathReserved = renderer.getLineHeight(SMALL_FONT_ID) + UITheme::getInstance().getMetrics().verticalSpacing;
|
||||
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved);
|
||||
|
||||
// Touch-down moves the selector to the pressed entry (shows selected state); release
|
||||
// opens it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(files.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// A tap opens the tapped entry (held-time is 0 on a tap, so it takes the short-press
|
||||
// open path below, never the long-press delete).
|
||||
int tappedId = -1;
|
||||
|
||||
@@ -183,6 +183,14 @@ void HomeActivity::loop() {
|
||||
// menu-local ids (it is drawn with selectorIndex offset by recentBooks.size()),
|
||||
// so map the tapped id back into the global selector space. (The recent-book
|
||||
// cover is a separate, single-item draw path — tappable in a later phase.)
|
||||
// Touch-down moves the selector to the pressed menu button (shows selected state),
|
||||
// like Up/Down; release opens it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId)) {
|
||||
selectorIndex = static_cast<int>(recentBooks.size()) + downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped) {
|
||||
|
||||
@@ -63,6 +63,12 @@ void RecentBooksActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(recentBooks.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(recentBooks.size())) selectorIndex = tappedId;
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "SilentRestart.h"
|
||||
#include "WifiSelectionActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "network/TaskWatchdog.h"
|
||||
|
||||
namespace {
|
||||
constexpr const char* HOSTNAME = "crosspoint";
|
||||
@@ -110,12 +110,12 @@ void CalibreConnectActivity::loop() {
|
||||
LOG_DBG("CAL", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient);
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
constexpr int MAX_ITERATIONS = 80;
|
||||
for (int i = 0; i < MAX_ITERATIONS && webServer->isRunning(); i++) {
|
||||
webServer->handleClient();
|
||||
if ((i & 0x07) == 0x07) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
}
|
||||
if ((i & 0x0F) == 0x0F) {
|
||||
yield();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
@@ -16,6 +15,7 @@
|
||||
#include "activities/network/CalibreConnectActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "network/TaskWatchdog.h"
|
||||
#include "util/QrUtils.h"
|
||||
|
||||
namespace {
|
||||
@@ -328,7 +328,7 @@ void CrossPointWebServerActivity::loop() {
|
||||
}
|
||||
|
||||
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
// Process HTTP requests in tight loop for maximum throughput
|
||||
// More iterations = more data processed per main loop cycle
|
||||
@@ -337,7 +337,7 @@ void CrossPointWebServerActivity::loop() {
|
||||
webServer->handleClient();
|
||||
// Reset watchdog every 32 iterations
|
||||
if ((i & 0x1F) == 0x1F) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
}
|
||||
// Yield and check for exit button every 64 iterations
|
||||
if ((i & 0x3F) == 0x3F) {
|
||||
|
||||
@@ -30,6 +30,12 @@ void NetworkModeSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(MENU_ITEM_COUNT)) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// Handle confirm button (or a tap) - select current option
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
|
||||
@@ -409,6 +409,19 @@ void WifiSelectionActivity::loop() {
|
||||
|
||||
// Handle network list state
|
||||
if (state == WifiSelectionState::NETWORK_LIST) {
|
||||
// Touch: down-select highlights the pressed network, tap selects it (like Confirm).
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(networks.size())) {
|
||||
selectedNetworkIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < static_cast<int>(networks.size())) {
|
||||
selectedNetworkIndex = tappedId;
|
||||
selectNetwork(selectedNetworkIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for Back button to exit (cancel)
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onComplete(false);
|
||||
|
||||
@@ -103,6 +103,14 @@ void EpubReaderBookmarksActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmingDelete < DELETE_MODE_DISPLAY) {
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(bookmarks.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = (confirmingDelete < DELETE_MODE_DISPLAY) && mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(bookmarks.size())) selectorIndex = tappedId;
|
||||
|
||||
@@ -31,6 +31,12 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < totalItems) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < totalItems) selectorIndex = tappedId;
|
||||
|
||||
@@ -57,6 +57,14 @@ void EpubReaderMenuActivity::loop() {
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
// Touch-down moves the selection to the pressed item (shows selected state), like
|
||||
// moving with Up/Down; release activates it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(menuItems.size())) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// A tap selects the item and activates it in one gesture (falls into Confirm below).
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
|
||||
@@ -54,6 +54,12 @@ void FontSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex_ = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex_ = tappedId;
|
||||
|
||||
@@ -32,6 +32,12 @@ void LanguageSelectActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex = tappedId;
|
||||
|
||||
@@ -41,6 +41,12 @@ void OpdsServerListActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex = tappedId;
|
||||
|
||||
@@ -118,6 +118,14 @@ void SettingsActivity::loop() {
|
||||
// A tap on a settings row selects + activates it in one gesture. The list is drawn
|
||||
// with selectedIndex = selectedSettingIndex - 1 (row 0 is the category tab), so map
|
||||
// the tapped 0-based row back by +1. (Category tab bar is tappable in a later phase.)
|
||||
// Touch-down moves the selection to the pressed row (shows selected state); release
|
||||
// toggles/activates it below. (Row 0 is the tab bar, so settings list id 0 -> index 1.)
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < settingsCount) {
|
||||
selectedSettingIndex = downId + 1;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < settingsCount) {
|
||||
selectedSettingIndex = tappedId + 1;
|
||||
@@ -133,6 +141,7 @@ void SettingsActivity::loop() {
|
||||
selectedCategoryIndex = tabId;
|
||||
selectedSettingIndex = 0;
|
||||
hasChangedCategory = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// Handle actions with early return
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <I18n.h>
|
||||
|
||||
#include "HalDisplay.h"
|
||||
#include "components/TouchRegistry.h"
|
||||
#include "components/UITheme.h"
|
||||
|
||||
ConfirmationActivity::ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
@@ -46,8 +47,26 @@ void ConfirmationActivity::render(RenderLock&& lock) {
|
||||
// Draw Body
|
||||
if (!safeBody.empty()) {
|
||||
renderer.drawCenteredText(fontId, currentY, safeBody.c_str(), true, EpdFontFamily::REGULAR);
|
||||
currentY += lineHeight;
|
||||
}
|
||||
|
||||
// On-screen Cancel / Confirm buttons (also tappable). The footer hints below map
|
||||
// the same actions to Left/Right for button devices, but on touch-only devices
|
||||
// the hints are hidden, so these are the only affordance.
|
||||
const int btnH = lineHeight + 20;
|
||||
const int totalW = renderer.getScreenWidth() - margin * 2;
|
||||
const int btnW = (totalW - spacing) / 2;
|
||||
const int btnY = currentY + spacing * 3;
|
||||
const Rect cancelRect{margin, btnY, btnW, btnH};
|
||||
const Rect confirmRect{margin + btnW + spacing, btnY, btnW, btnH};
|
||||
renderer.drawRect(cancelRect.x, cancelRect.y, cancelRect.width, cancelRect.height);
|
||||
renderer.drawRect(confirmRect.x, confirmRect.y, confirmRect.width, confirmRect.height);
|
||||
const int btnTextY = btnY + (btnH - lineHeight) / 2;
|
||||
UITheme::drawCenteredText(renderer, cancelRect, fontId, btnTextY, I18N.get(StrId::STR_CANCEL));
|
||||
UITheme::drawCenteredText(renderer, confirmRect, fontId, btnTextY, I18N.get(StrId::STR_CONFIRM));
|
||||
TouchRegistry::getInstance().add(cancelRect, 0, TouchRegistry::Item);
|
||||
TouchRegistry::getInstance().add(confirmRect, 1, TouchRegistry::Item);
|
||||
|
||||
// Draw UI Elements
|
||||
const auto labels = mappedInput.mapLabels("", "", I18N.get(StrId::STR_CANCEL), I18N.get(StrId::STR_CONFIRM));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
@@ -56,6 +75,16 @@ void ConfirmationActivity::render(RenderLock&& lock) {
|
||||
}
|
||||
|
||||
void ConfirmationActivity::loop() {
|
||||
// Tap the on-screen buttons: id 1 = Confirm, id 0 = Cancel.
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId)) {
|
||||
ActivityResult res;
|
||||
res.isCancelled = (tappedId != 1);
|
||||
setResult(std::move(res));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
||||
ActivityResult res;
|
||||
res.isCancelled = false;
|
||||
|
||||
@@ -337,13 +337,24 @@ void KeyboardEntryActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// A tap selects the key and presses it in one gesture. Encoded id = row*100+col
|
||||
// (bottom function row = getContentRowCount()). Skipped in cursor mode, where a
|
||||
// tap on a key would be ambiguous with cursor editing.
|
||||
// A tap selects the key and presses it. Encoded id = row*100+col (bottom function
|
||||
// row = getContentRowCount()). A touch-and-hold inserts the alternate character
|
||||
// (numbers/symbols on a letter), mirroring the button long-press — wasItemLongPressed
|
||||
// is checked first since it's a subset of wasItemTapped's releases. Skipped in
|
||||
// cursor mode, where a tap on a key would be ambiguous with cursor editing.
|
||||
int tappedKey = -1;
|
||||
if (!cursorMode && mappedInput.wasItemTapped(tappedKey)) {
|
||||
selectedRow = tappedKey / 100;
|
||||
selectedCol = tappedKey % 100;
|
||||
int longKey = -1;
|
||||
if (mappedInput.wasItemLongPressed(longKey)) {
|
||||
const char alt = getAlternativeChar();
|
||||
if (alt != '\0') {
|
||||
insertChar(alt);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (handleKeyPress()) {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -15,6 +14,7 @@
|
||||
#include "OpdsServerStore.h"
|
||||
#include "SdCardFontSystem.h"
|
||||
#include "SettingsList.h"
|
||||
#include "TaskWatchdog.h"
|
||||
#include "WebDAVHandler.h"
|
||||
#include "WifiCredentialStore.h"
|
||||
#include "html/FilesPageHtml.generated.h"
|
||||
@@ -424,8 +424,8 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
|
||||
}
|
||||
|
||||
file.close();
|
||||
yield(); // Yield to allow WiFi and other tasks to process during long scans
|
||||
esp_task_wdt_reset(); // Reset watchdog to prevent timeout on large directories
|
||||
yield(); // Yield to allow WiFi and other tasks to process during long scans
|
||||
feedTaskWatchdog(); // Reset watchdog to prevent timeout on large directories
|
||||
file = root.openNextFile();
|
||||
}
|
||||
root.close();
|
||||
@@ -556,7 +556,7 @@ void CrossPointWebServer::handleDownload() const {
|
||||
size_t bytesRead = static_cast<size_t>(result);
|
||||
size_t totalWritten = 0;
|
||||
while (totalWritten < bytesRead) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
size_t wrote = client.write(buffer + totalWritten, bytesRead - totalWritten);
|
||||
if (wrote == 0) {
|
||||
downloadOk = false;
|
||||
@@ -576,12 +576,12 @@ static size_t writeCount = 0;
|
||||
|
||||
static bool flushUploadBuffer(CrossPointWebServer::UploadState& state) {
|
||||
if (state.bufferPos > 0 && state.file) {
|
||||
esp_task_wdt_reset(); // Reset watchdog before potentially slow SD write
|
||||
feedTaskWatchdog(); // Reset watchdog before potentially slow SD write
|
||||
const unsigned long writeStart = millis();
|
||||
const size_t written = state.file.write(state.buffer.data(), state.bufferPos);
|
||||
totalWriteTime += millis() - writeStart;
|
||||
writeCount++;
|
||||
esp_task_wdt_reset(); // Reset watchdog after SD write
|
||||
feedTaskWatchdog(); // Reset watchdog after SD write
|
||||
|
||||
if (written != state.bufferPos) {
|
||||
LOG_DBG("WEB", "[UPLOAD] Buffer flush failed: expected %d, wrote %d", state.bufferPos, written);
|
||||
@@ -597,7 +597,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
||||
static size_t lastLoggedSize = 0;
|
||||
|
||||
// Reset watchdog at start of every upload callback - HTTP parsing can be slow
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
// Safety check: ensure server is still valid
|
||||
if (!running || !server) {
|
||||
@@ -609,7 +609,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
||||
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
// Reset watchdog - this is the critical 1% crash point
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
state.fileName = upload.filename;
|
||||
state.size = 0;
|
||||
@@ -647,21 +647,21 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
||||
filePath += state.fileName;
|
||||
|
||||
// Check if file already exists - SD operations can be slow
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
if (Storage.exists(filePath.c_str())) {
|
||||
LOG_DBG("WEB", "[UPLOAD] Overwriting existing file: %s", filePath.c_str());
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
Storage.remove(filePath.c_str());
|
||||
}
|
||||
|
||||
// Open file for writing - this can be slow due to FAT cluster allocation
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
if (!Storage.openFileForWrite("WEB", filePath, state.file)) {
|
||||
state.error = "Failed to create file on SD card";
|
||||
LOG_DBG("WEB", "[UPLOAD] FAILED to create file: %s", filePath.c_str());
|
||||
return;
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
LOG_DBG("WEB", "[UPLOAD] File created successfully: %s", filePath.c_str());
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
@@ -1603,20 +1603,20 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
|
||||
filePath.c_str());
|
||||
|
||||
// Check if file exists and remove it
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
if (Storage.exists(filePath.c_str())) {
|
||||
Storage.remove(filePath.c_str());
|
||||
}
|
||||
|
||||
// Open file for writing
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
if (!Storage.openFileForWrite("WS", filePath, wsUploadFile)) {
|
||||
wsServer->sendTXT(num, "ERROR:Failed to create file");
|
||||
wsUploadInProgress = false;
|
||||
wsUploadClientNum = 255;
|
||||
return;
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
// Zero-byte upload: complete immediately without waiting for BIN frames
|
||||
if (wsUploadSize == 0) {
|
||||
@@ -1655,9 +1655,9 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
|
||||
wsServer->sendTXT(num, "ERROR:Upload overflow");
|
||||
return;
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
size_t written = wsUploadFile.write(payload, length);
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
if (written != length) {
|
||||
abortWsUpload("WS");
|
||||
@@ -1761,7 +1761,7 @@ void CrossPointWebServer::handleFontUploadData() {
|
||||
|
||||
switch (upload.status) {
|
||||
case UPLOAD_FILE_START: {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
String family = server->arg("family");
|
||||
fontUpload.file = HalFile();
|
||||
fontUpload.familyName.clear();
|
||||
@@ -1812,7 +1812,7 @@ void CrossPointWebServer::handleFontUploadData() {
|
||||
|
||||
case UPLOAD_FILE_WRITE: {
|
||||
if (!fontUpload.valid) break;
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
// Validate magic bytes on first chunk only
|
||||
if (!fontUpload.magicChecked && upload.currentSize >= 8) {
|
||||
@@ -1839,7 +1839,7 @@ void CrossPointWebServer::handleFontUploadData() {
|
||||
fontUpload.file.write(fontUpload.buffer.data(), fontUpload.bufferPos);
|
||||
fontUpload.bytesWritten += fontUpload.bufferPos;
|
||||
fontUpload.bufferPos = 0;
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
// Feed the Task Watchdog Timer only when the calling task is actually
|
||||
// subscribed to it. esp_task_wdt_reset() logs
|
||||
// "task_wdt: esp_task_wdt_reset(...): task not found"
|
||||
// on every call when the current task was never registered via
|
||||
// esp_task_wdt_add(). Whether the Arduino loopTask is auto-subscribed depends
|
||||
// on the chip target's framework sdkconfig: the ESP32-C3 (X4) build subscribes
|
||||
// it, the classic-ESP32 (m5paper) build does not, so the unguarded resets in
|
||||
// the web server / WiFi paths spammed the log there.
|
||||
//
|
||||
// esp_task_wdt_status(nullptr) returns ESP_OK only when the current task is
|
||||
// subscribed, so this guard makes the reset a no-op on builds where the loop
|
||||
// task is not watchdog-monitored, while preserving the reset where it is.
|
||||
static inline void feedTaskWatchdog() {
|
||||
if (esp_task_wdt_status(nullptr) == ESP_OK) {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
#include <FsHelpers.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include "TaskWatchdog.h"
|
||||
#include "util/BookCacheUtils.h"
|
||||
|
||||
namespace {
|
||||
@@ -84,7 +84,7 @@ void WebDAVHandler::raw(WebServer& server, const String& uri, HTTPRaw& raw) {
|
||||
|
||||
} else if (raw.status == RAW_WRITE) {
|
||||
if (_putFile && _putOk) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
size_t written = _putFile.write(raw.buf, raw.currentSize);
|
||||
if (written != raw.currentSize) {
|
||||
_putOk = false;
|
||||
@@ -252,7 +252,7 @@ void WebDAVHandler::handlePropfind(WebServer& s) {
|
||||
|
||||
file.close();
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
@@ -628,7 +628,7 @@ void WebDAVHandler::handleCopy(WebServer& s) {
|
||||
uint8_t buf[4096];
|
||||
bool copyOk = true;
|
||||
while (srcFile.available()) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
int bytesRead = srcFile.read(buf, sizeof(buf));
|
||||
if (bytesRead <= 0) break;
|
||||
size_t written = dstFile.write(buf, bytesRead);
|
||||
|
||||
Reference in New Issue
Block a user