diff --git a/boards/esp32-32e-4/DisplayDriverTFT.cpp b/boards/esp32-32e-4/DisplayDriverTFT.cpp index acbc15b..35b4cf5 100644 --- a/boards/esp32-32e-4/DisplayDriverTFT.cpp +++ b/boards/esp32-32e-4/DisplayDriverTFT.cpp @@ -197,9 +197,12 @@ void DisplayDriverTFT::drawAlert(const ScreenState& st) { uint32_t elapsed = millis() - st.alertStartMs; bool brightPhase = (elapsed / 2000) % 2 == 0; - // Only redraw when phase changes (timer-based, not every frame) - if(brightPhase != _lastAlertPhase) { + // Redraw when phase changes OR when touch was released (to clear fill) + bool needsRedraw = (brightPhase != _lastAlertPhase) || _alertNeedsRedraw; + + if(needsRedraw) { _lastAlertPhase = brightPhase; + _alertNeedsRedraw = false; // clear the flag uint16_t bg = brightPhase ? TFT_RED : _tft.color565(180, 0, 0); _tft.fillScreen(bg); @@ -218,7 +221,7 @@ void DisplayDriverTFT::drawAlert(const ScreenState& st) { _tft.print("Hold to silence..."); } - // Progressive fill hint - only redraw when touch state changes + // Progressive fill hint - while touch is held if(_alertTouchDown) { uint32_t touchElapsed = millis() - _alertTouchStartMs; float progress = (float)touchElapsed / (float)ALERT_FILL_DURATION_MS; @@ -374,6 +377,12 @@ TouchEvent DisplayDriverTFT::readTouch() { uint16_t tx, ty; uint8_t touched = _tft.getTouch(&tx, &ty, 100); + // Debug: log touch state changes + if(touched != _touchWasPressed) { + Serial.printf("[TOUCH] raw touched=%d wasPressed=%d (x=%d,y=%d)\n", touched, + _touchWasPressed, tx, ty); + } + // Detect transitions (press/release) if(touched && !_touchWasPressed) { // Press transition: finger just touched down @@ -406,6 +415,7 @@ TouchEvent DisplayDriverTFT::readTouch() { _alertTouchStartMs = millis(); } else if(evt.released) { _alertTouchDown = false; + _alertNeedsRedraw = true; // force redraw to clear fill } return evt; @@ -418,11 +428,10 @@ void DisplayDriverTFT::transformTouch(int* x, int* y) { *y = temp; } -HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { +HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) { HoldState h; - TouchEvent t = readTouch(); - if(t.pressed) { + if(evt.pressed) { if(!_holdActive) { _holdActive = true; _holdStartMs = millis(); diff --git a/boards/esp32-32e-4/DisplayDriverTFT.h b/boards/esp32-32e-4/DisplayDriverTFT.h index e54f511..4a35f9e 100644 --- a/boards/esp32-32e-4/DisplayDriverTFT.h +++ b/boards/esp32-32e-4/DisplayDriverTFT.h @@ -11,7 +11,7 @@ public: void setBacklight(bool on) override; void render(const ScreenState& state) override; TouchEvent readTouch() override; - HoldState updateHold(unsigned long holdMs) override; + HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override; int width() override { // Use TFT_eSPI's dimensions after rotation - it's more reliable return _tft.width(); @@ -43,6 +43,7 @@ private: // Touch hint for alert - progressive fill from bottom bool _alertTouchDown = false; + bool _alertNeedsRedraw = false; // force redraw after touch release uint32_t _alertTouchStartMs = 0; bool _lastAlertPhase = false; // tracks bright/dark phase for 2-color alert static constexpr uint32_t ALERT_FILL_DURATION_MS = 3000; diff --git a/boards/esp32-32e/DisplayDriverTFT.cpp b/boards/esp32-32e/DisplayDriverTFT.cpp index 2fc0f02..feee47d 100644 --- a/boards/esp32-32e/DisplayDriverTFT.cpp +++ b/boards/esp32-32e/DisplayDriverTFT.cpp @@ -298,11 +298,10 @@ int DisplayDriverTFT::dashboardTouch(int x, int y) { return row * 2 + col; // 0, 1, 2, or 3 } -HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { +HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) { HoldState h; - TouchEvent t = readTouch(); - if(t.pressed) { + if(evt.pressed) { if(!_holdActive) { _holdActive = true; _holdStartMs = millis(); diff --git a/boards/esp32-32e/DisplayDriverTFT.h b/boards/esp32-32e/DisplayDriverTFT.h index 3afa73b..691f938 100644 --- a/boards/esp32-32e/DisplayDriverTFT.h +++ b/boards/esp32-32e/DisplayDriverTFT.h @@ -12,7 +12,7 @@ public: void render(const ScreenState& state) override; TouchEvent readTouch() override; int dashboardTouch(int x, int y); - HoldState updateHold(unsigned long holdMs) override; + HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override; int width() override { return DISPLAY_WIDTH; } int height() override { return DISPLAY_HEIGHT; } diff --git a/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp b/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp index 4edd0e1..3a77220 100644 --- a/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp +++ b/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp @@ -261,10 +261,10 @@ int DisplayDriverGFX::dashboardTouch(int x, int y) { return row * cols + col; } -HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) { +HoldState DisplayDriverGFX::updateHold(const TouchEvent& evt, unsigned long holdMs) { HoldState state; - if(!_lastTouch.pressed) { + if(!evt.pressed) { _isHolding = false; return state; } diff --git a/boards/esp32-s3-lcd-43/DisplayDriverGFX.h b/boards/esp32-s3-lcd-43/DisplayDriverGFX.h index 33a81cc..45f6320 100644 --- a/boards/esp32-s3-lcd-43/DisplayDriverGFX.h +++ b/boards/esp32-s3-lcd-43/DisplayDriverGFX.h @@ -13,7 +13,7 @@ public: void render(const ScreenState& state) override; TouchEvent readTouch() override; - HoldState updateHold(unsigned long holdMs) override; + HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override; void drawDebugTouch(int x, int y) override; int width() override; diff --git a/libraries/KlubhausCore/src/Config.h b/libraries/KlubhausCore/src/Config.h index 27a4077..f0a470e 100644 --- a/libraries/KlubhausCore/src/Config.h +++ b/libraries/KlubhausCore/src/Config.h @@ -13,7 +13,7 @@ #define POLL_INTERVAL_MS 15000 #define HEARTBEAT_INTERVAL_MS 300000 #define BOOT_GRACE_MS 5000 -#define HOLD_TO_SILENCE_MS 3000 +#define HOLD_TO_SILENCE_MS 2000 #define ALERT_TIMEOUT_MS 120000 #define SILENCE_DISPLAY_MS 10000 #define INACTIVITY_TIMEOUT_MS 30000 diff --git a/libraries/KlubhausCore/src/DisplayManager.h b/libraries/KlubhausCore/src/DisplayManager.h index aa254a7..1df0546 100644 --- a/libraries/KlubhausCore/src/DisplayManager.h +++ b/libraries/KlubhausCore/src/DisplayManager.h @@ -60,6 +60,14 @@ private: /// Calculate optimal grid dimensions based on display and tile constraints static void calculateGrid( int tileCount, int displayW, int contentH, int* outCols, int* outRows) { + // Use 2x2 grid for 4 tiles regardless of aspect ratio + // This provides better visual balance than a single row + if(tileCount == 4) { + *outCols = 2; + *outRows = 2; + return; + } + // Calculate aspect ratio to determine preferred layout float aspectRatio = (float)displayW / contentH; @@ -138,7 +146,9 @@ public: TouchEvent readTouch() { return _drv ? _drv->readTouch() : TouchEvent {}; } - HoldState updateHold(unsigned long ms) { return _drv ? _drv->updateHold(ms) : HoldState {}; } + HoldState updateHold(const TouchEvent& evt, unsigned long ms) { + return _drv ? _drv->updateHold(evt, ms) : HoldState {}; + } void drawDebugTouch(int x, int y) { if(_drv) diff --git a/libraries/KlubhausCore/src/DoorbellLogic.cpp b/libraries/KlubhausCore/src/DoorbellLogic.cpp index 677d508..13850a4 100644 --- a/libraries/KlubhausCore/src/DoorbellLogic.cpp +++ b/libraries/KlubhausCore/src/DoorbellLogic.cpp @@ -338,6 +338,7 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) { // Check hold completion FIRST - before any release handling // This ensures hold-to-silence works on ALERT screen if(evt.released && _state.deviceState == DeviceState::ALERTING) { + Serial.printf("[TOUCH] checking hold: deviceState=ALERTING\n"); if(updateHold(evt)) { return (int)TileAction::SILENCE; } @@ -345,6 +346,8 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) { // Handle press - show visual feedback if(evt.pressed) { + Serial.printf("[TOUCH] pressed screen=%d deviceState=%d\n", (int)_state.screen, + (int)_state.deviceState); // Reset inactivity timer on any touch _lastActivityMs = millis(); @@ -377,6 +380,9 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) { // Handle release - fire action if same tile if(evt.released) { + Serial.printf("[TOUCH] released screen=%d deviceState=%d\n", (int)_state.screen, + (int)_state.deviceState); + if(_state.screen == ScreenID::DASHBOARD) { // Only fire action if finger stayed on same tile if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) { @@ -426,7 +432,7 @@ bool DoorbellLogic::updateHold(const TouchEvent& evt) { static int holdStartX = -1; static int holdStartY = -1; - HoldState h = _display->updateHold(HOLD_TO_SILENCE_MS); + HoldState h = _display->updateHold(evt, HOLD_TO_SILENCE_MS); if(h.completed) { silenceAlert(); diff --git a/libraries/KlubhausCore/src/IDisplayDriver.h b/libraries/KlubhausCore/src/IDisplayDriver.h index 5210e40..8903a19 100644 --- a/libraries/KlubhausCore/src/IDisplayDriver.h +++ b/libraries/KlubhausCore/src/IDisplayDriver.h @@ -31,7 +31,7 @@ public: // ── Touch ── virtual TouchEvent readTouch() = 0; - virtual HoldState updateHold(unsigned long holdMs) = 0; + virtual HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) = 0; virtual void drawDebugTouch(int x, int y) { /* default: no-op */ } virtual int width() = 0; virtual int height() = 0;