From b8b3353cf3fbb48634b9e68244dd9e83e5c17d78 Mon Sep 17 00:00:00 2001 From: Pelgraine Date: Tue, 7 Jul 2026 06:03:12 +1000 Subject: [PATCH] Fix twatch tilt raise lock screen functionality. Implement unread count on tw_picker. Fix channel missage history and full message view --- examples/companion_radio/MyMesh.h | 2 +- .../companion_radio/ui-new/ChannelScreen.h | 68 ++++++++ examples/companion_radio/ui-new/UITask.cpp | 43 +++-- src/helpers/ui/LGFXDisplay.h | 4 + .../TWatchComposeScreens.h | 162 +++++++++++------- .../TWatchS3PlusBoard.cpp | 4 + 6 files changed, 201 insertions(+), 82 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 71e2518b..f1ea0222 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,7 +8,7 @@ #define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "4 July 2026" +#define FIRMWARE_BUILD_DATE "7 July 2026" #endif #ifndef FIRMWARE_VERSION diff --git a/examples/companion_radio/ui-new/ChannelScreen.h b/examples/companion_radio/ui-new/ChannelScreen.h index 6f890600..5b51a455 100644 --- a/examples/companion_radio/ui-new/ChannelScreen.h +++ b/examples/companion_radio/ui-new/ChannelScreen.h @@ -182,7 +182,58 @@ public: // Sanitize emoji: replace UTF-8 emoji sequences with single-byte escape codes // The text already contains "Sender: message" format +#if defined(LILYGO_TWATCH_S3_PLUS) + // Watch build: drop emoji entirely instead of storing sprite escape bytes. + // The watch UI has no sprite rendering and no room for emoji at 120x120 + // virtual. Non-emoji UTF-8 (accented letters etc.) passes through + // unchanged, the same as emojiSanitize does. + { + const uint8_t* s = (const uint8_t*)text; + int srcLen = (int)strlen(text); + int si = 0, di = 0; + while (si < srcLen && di < CHANNEL_MSG_TEXT_LEN - 1) { + uint8_t b = s[si]; + if (b < 0x80) { msg->text[di++] = (char)b; si++; continue; } + int consumed; + uint32_t cp = emojiDecodeUtf8(s + si, srcLen - si, &consumed); + if (cp == 0xFE0F) { si += consumed; continue; } // variation selector + if (cp == 0xFFFD) { si += consumed; continue; } // invalid UTF-8 -- skip + bool isEmoji = false; + for (int e = 0; e < EMOJI_COUNT; e++) { + if (EMOJI_CODEPOINTS[e].cp == cp) { + if (EMOJI_CODEPOINTS[e].cp2 != 0) { + // two-codepoint emoji: only a match when the pair matches + int consumed2; + if (si + consumed < srcLen) { + uint32_t cp2 = emojiDecodeUtf8(s + si + consumed, srcLen - si - consumed, &consumed2); + if (cp2 == EMOJI_CODEPOINTS[e].cp2) { + si += consumed + consumed2; + isEmoji = true; + break; + } + } + continue; + } + si += consumed; + isEmoji = true; + break; + } + } + if (!isEmoji) { + for (int a = 0; a < EMOJI_ALIAS_COUNT; a++) { + if (EMOJI_ALIASES[a].cp == cp) { si += consumed; isEmoji = true; break; } + } + } + if (isEmoji) continue; // dropped + if (di + consumed >= CHANNEL_MSG_TEXT_LEN) break; + for (int k = 0; k < consumed; k++) msg->text[di++] = (char)s[si + k]; + si += consumed; + } + msg->text[di] = 0; + } +#else emojiSanitize(text, msg->text, CHANNEL_MSG_TEXT_LEN); +#endif if (_msgCount < CHANNEL_MSG_HISTORY_SIZE) { _msgCount++; @@ -221,6 +272,23 @@ public: } int getMessageCount() const { return _msgCount; } + + // Read-only access to stored messages for a given channel, by recency. + // n = 0 returns the newest message on that channel, n = 1 the next newest, + // and so on. Returns nullptr when fewer than n + 1 messages exist. + const ChannelMessage* getChannelMsgByRecency(uint8_t channel_idx, int n) const { + if (n < 0 || _msgCount == 0) return nullptr; + int seen = 0; + for (int i = 0; i < _msgCount; i++) { + int idx = _newestIdx - i; + while (idx < 0) idx += CHANNEL_MSG_HISTORY_SIZE; + const ChannelMessage& m = _messages[idx]; + if (!m.valid || m.channel_idx != channel_idx) continue; + if (seen == n) return &m; + seen++; + } + return nullptr; + } uint8_t getViewChannelIdx() const { return _viewChannelIdx; } void setViewChannelIdx(uint8_t idx) { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 3b900061..ecc2e71a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -478,8 +478,15 @@ public: char timeBuf[6]; sprintf(timeBuf, "%02d:%02d", hrs, mins); int clockX = (display.width() - lcd->smallTextWidth(timeBuf)) / 2; - display.setColor(DisplayDriver::LIGHT); - lcd->printSmallFont(clockX, HOME_HDR_Y, timeBuf); + // Long node names: keep the clock centred when there is room, + // otherwise push it right so it starts just past the name. When even + // that would run into the unread count, hide the clock instead. + int nameEndX = lcd->smallTextWidth(filtered_name) + 3; + if (clockX < nameEndX) clockX = nameEndX; + if (clockX + lcd->smallTextWidth(timeBuf) + 3 <= numX) { + display.setColor(DisplayDriver::LIGHT); + lcd->printSmallFont(clockX, HOME_HDR_Y, timeBuf); + } } } #else @@ -1459,7 +1466,9 @@ public: "Jul","Aug","Sep","Oct","Nov","Dec"}; sprintf(buf, "%s %d %s", days[tmv.tm_wday], tmv.tm_mday, mons[tmv.tm_mon]); display.setTextSize(2); + display.setColor(DisplayDriver::GREEN); // light grey on the watch palette display.drawTextCentered(display.width() / 2, display.height() / 2 + 12, buf); + display.setColor(DisplayDriver::LIGHT); // restore for the battery line } // battery % and unread count @@ -1657,6 +1666,8 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no tw_picker = new TWatchChannelPicker(_display); tw_channel = new TWatchChannelScreen(_display); tw_keyboard = new TWatchKeyboardScreen(_display); + ((TWatchChannelPicker*)tw_picker)->setStore((ChannelScreen*)channel_screen); + ((TWatchChannelScreen*)tw_channel)->setStore((ChannelScreen*)channel_screen); #endif audiobook_screen = nullptr; // Created and assigned from main.cpp if audio hardware present #ifdef MECK_AUDIO_VARIANT @@ -1774,9 +1785,6 @@ void UITask::msgRead(int msgcount) { void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, const uint8_t* path, int8_t snr, uint8_t scope_idx) { _msgcount = msgcount; -#ifdef TWATCH_COMPOSE_ENABLED - if (tw_channel) ((TWatchChannelScreen*)tw_channel)->notifyMsg(from_name, text); -#endif // --- Dedup: suppress retry spam (same sender + text within 60s) --- uint32_t nameH = simpleHash(from_name); @@ -1930,6 +1938,10 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i // app is connected (they'll see it there), mark as read immediately if ((isOnChannelScreen() && ((ChannelScreen *) channel_screen)->getViewChannelIdx() == channel_idx) || +#ifdef TWATCH_COMPOSE_ENABLED + (curr == tw_channel && + ((TWatchChannelScreen*)tw_channel)->getChannelIdx() == channel_idx) || +#endif hasConnection()) { ((ChannelScreen *) channel_screen)->markChannelRead(channel_idx); } @@ -2309,6 +2321,7 @@ if (curr) curr->poll(); if (curr == tw_picker) { TWatchChannelPicker* picker = (TWatchChannelPicker*)tw_picker; if (picker->isConfirmed()) { + ((TWatchChannelScreen*)tw_channel)->setSelfName(the_mesh.getNodePrefs()->node_name); ((TWatchChannelScreen*)tw_channel)->activate(picker->getSelectedChannelIdx(), picker->getSelectedChannelName()); setCurrScreen(tw_channel); @@ -2347,7 +2360,7 @@ if (curr) curr->poll(); the_mesh.sendGroupMessage(ts, ch.channel, the_mesh.getNodeName(), sendText, strlen(sendText)); showAlert("Sent!", 800); - ((TWatchChannelScreen*)tw_channel)->addSentMsg(sendText); + addSentChannelMessage(kb->getChannelIdx(), the_mesh.getNodePrefs()->node_name, sendText); } kb->clearOutBuf(); setCurrScreen(tw_channel); @@ -2541,17 +2554,17 @@ if (curr) curr->poll(); if (millis() > _auto_off) { _display->turnOff(); } -#endif -#if defined(LILYGO_TWATCH_S3_PLUS) - // Raise-to-wake: a wrist-raise (BMA423 tilt) while the display is off turns - // it back on showing the clock screen and restarts the idle timer. - if (!_display->isOn() && board.tiltFired()) { - _display->turnOn(); - setCurrScreen(lock_screen); - _auto_off = millis() + AUTO_OFF_MILLIS; - } #endif } +#if defined(LILYGO_TWATCH_S3_PLUS) + // Raise-to-wake: a wrist-raise (BMA423 tilt) while the display is off turns + // it back on showing the clock screen and restarts the idle timer. + if (_display != NULL && !_display->isOn() && board.tiltFired()) { + _display->turnOn(); + setCurrScreen(lock_screen); + _auto_off = millis() + AUTO_OFF_MILLIS; + } +#endif // Auto-lock idle timer — runs regardless of display on/off state #if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro) diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h index a2e247d3..04ca1835 100644 --- a/src/helpers/ui/LGFXDisplay.h +++ b/src/helpers/ui/LGFXDisplay.h @@ -55,6 +55,10 @@ public: buffer.setFont(&fonts::Font0); return w; } + // Enable/disable horizontal text wrap on the sprite buffer. The compose + // ticker turns wrap off so a long scrolling line is clipped at the screen + // edge instead of wrapping onto the rows below. + void setTextWrap(bool en) { buffer.setTextWrap(en, false); } #endif void setCursor(int x, int y) override; void print(const char* str) override; diff --git a/variants/lilygo_twatch_s3_plus/TWatchComposeScreens.h b/variants/lilygo_twatch_s3_plus/TWatchComposeScreens.h index 39d94ab5..592fd8db 100644 --- a/variants/lilygo_twatch_s3_plus/TWatchComposeScreens.h +++ b/variants/lilygo_twatch_s3_plus/TWatchComposeScreens.h @@ -13,10 +13,11 @@ // top half to move the highlight up, the bottom half // to move it down; long-press selects the highlighted // channel; the back arrow (top-left) returns home. -// TWatchChannelScreen — shows the last TW_INBOX_SIZE incoming messages for -// the selected channel (live, in-RAM, no history). -// Tap the compose bar to open the keyboard; back -// arrow returns home. +// TWatchChannelScreen -- shows the most recent messages (sent + received) +// for the selected channel, read live from the shared +// ChannelScreen history store. Tap a line to ticker- +// scroll it; tap the compose bar to open the keyboard; +// back arrow returns home. // TWatchKeyboardScreen — on-screen QWERTY. Bottom-left mode key cycles // lower -> UPPER -> SYM. Bottom row is mode | space | // enter | backspace. Enter sends on the channel; back @@ -25,8 +26,8 @@ // Each screen reads getTouch() itself in poll() and does its own release-edge // detection, so actions fire on touch release (finger up) — this keeps the // held finger from carrying into the next screen after a transition. Send/exit -// are delegated to UITask via the consume/flag pattern, so this header has no -// dependency on MyMesh / BaseChatMesh. +// are delegated to UITask via the consume/flag pattern; message history and +// unread counts are read from the shared ChannelScreen store. // ============================================================================= #ifdef TWATCH_COMPOSE_ENABLED @@ -36,12 +37,11 @@ #include #include #include +#include "ChannelScreen.h" // shared message history store (-I examples/companion_radio/ui-new) // ---- Tunables --------------------------------------------------------------- #define TW_LONG_PRESS_MS 600 // hold to select a channel in the picker #define TW_OUT_BUF_LEN 134 // MeshCore per-channel msg cap (~133) + NUL -#define TW_INBOX_SIZE 5 // messages kept/shown on the channel screen -#define TW_INBOX_TEXT_LEN 96 #define TW_TICKER_MS_PER_PX 20 // channel-screen ticker scroll speed (ms per pixel) #define TW_CH_NAME_LEN 32 #define TW_PICKER_MAX 20 // matches MAX_GROUP_CHANNELS @@ -57,6 +57,7 @@ class TWatchChannelPicker : public UIScreen { uint8_t _numChannels; uint8_t _highlighted; uint8_t _scrollTop; + ChannelScreen* _store; // unread badge source // touch edge state bool _touchDown; @@ -94,11 +95,15 @@ class TWatchChannelPicker : public UIScreen { public: TWatchChannelPicker(DisplayDriver* display) : _display(display), _numChannels(0), _highlighted(0), _scrollTop(0), + _store(nullptr), _touchDown(false), _downX(0), _downY(0), _downAt(0), _confirmed(false), _wantsExit(false) { memset(_channels, 0, sizeof(_channels)); } + // Shared history store, used for the per-channel unread badges. + void setStore(ChannelScreen* store) { _store = store; } + // Called by UITask before showing the screen. void beginChannelSelect() { _numChannels = 0; _highlighted = 0; _scrollTop = 0; @@ -170,7 +175,18 @@ public: } else { display.setColor(DisplayDriver::LIGHT); } - display.drawTextEllipsized(3, y + 2, W - 6, _channels[ci].name); + int nameMaxW = W - 6; + int unread = _store ? _store->getUnreadForChannel(_channels[ci].idx) : 0; + if (unread > 0) { + char cnt[8]; + snprintf(cnt, sizeof(cnt), "%d", unread > 99 ? 99 : unread); + int cw = display.getTextWidth(cnt); + display.setColor((ci == _highlighted) ? DisplayDriver::DARK : DisplayDriver::BLUE); + display.drawTextRightAlign(W - 3, y + 2, cnt); + display.setColor((ci == _highlighted) ? DisplayDriver::DARK : DisplayDriver::LIGHT); + nameMaxW = W - 6 - cw - 4; + } + display.drawTextEllipsized(3, y + 2, nameMaxW, _channels[ci].name); y += ROW_H; } return 500; @@ -182,13 +198,10 @@ public: // ============================================================================= class TWatchChannelScreen : public UIScreen { DisplayDriver* _display; + ChannelScreen* _store; // shared message history store uint8_t _channelIdx; char _channelName[TW_CH_NAME_LEN]; - - struct InboxEntry { char text[TW_INBOX_TEXT_LEN]; bool valid; bool isSent; }; - InboxEntry _inbox[TW_INBOX_SIZE]; - uint8_t _inboxNewest; - uint8_t _inboxCount; + char _selfPrefix[TW_CH_NAME_LEN + 2]; // "NodeName: " -- marks sent lines bool _touchDown; int _downX, _downY; @@ -196,23 +209,35 @@ class TWatchChannelScreen : public UIScreen { bool _wantsCompose; bool _wantsExit; - int _selectedSlot; // ring slot shown as ticker, -1 = none + int _selectedN; // recency index shown as ticker, -1 = none unsigned long _tickerStartMs; + const void* _lastNewest; // newest store entry seen last render + uint32_t _lastNewestTs; // (a change dismisses the ticker) static const int HEADER_H = 14; static const int COMPOSE_BAR_H = 18; static const int MSG_LINE_H = 11; static const int MSG_TOP = HEADER_H + 2; - void pushEntry(const char* text, bool sent) { - _inboxNewest = (_inboxCount == 0) ? 0 : (uint8_t)((_inboxNewest + 1) % TW_INBOX_SIZE); - InboxEntry& e = _inbox[_inboxNewest]; - if (text) { strncpy(e.text, text, TW_INBOX_TEXT_LEN - 1); e.text[TW_INBOX_TEXT_LEN - 1] = 0; } - else { e.text[0] = 0; } - e.valid = true; - e.isSent = sent; - if (_inboxCount < TW_INBOX_SIZE) _inboxCount++; - _selectedSlot = -1; // a new message dismisses any open ticker + int rowsThatFit() const { + int h = _display ? _display->height() : 120; + int rows = (h - COMPOSE_BAR_H - 1 - MSG_TOP) / MSG_LINE_H; + return rows < 1 ? 1 : rows; + } + + // Store messages available for this channel, capped at what fits on screen. + int visibleCount() const { + if (!_store) return 0; + int maxRows = rowsThatFit(); + int n = 0; + while (n < maxRows && _store->getChannelMsgByRecency(_channelIdx, n)) n++; + return n; + } + + // Sent messages are stored with path_len 0 and a "NodeName: " prefix. + bool isSent(const ChannelScreen::ChannelMessage* m) const { + return m->path_len == 0 && _selfPrefix[0] != 0 && + strncmp(m->text, _selfPrefix, strlen(_selfPrefix)) == 0; } void drawMsgLine(DisplayDriver& display, int y, const char* text, bool sent) { @@ -227,7 +252,7 @@ class TWatchChannelScreen : public UIScreen { display.drawTextRightAlign(W - 2, y, text); return; } - char buf[TW_INBOX_TEXT_LEN + 4]; + char buf[CHANNEL_MSG_TEXT_LEN + 4]; strncpy(buf, text, sizeof(buf) - 4); buf[sizeof(buf) - 4] = 0; int ellW = display.getTextWidth("..."); @@ -249,49 +274,51 @@ class TWatchChannelScreen : public UIScreen { int period = textW + 24; // full text width + trailing gap unsigned long elapsed = millis() - _tickerStartMs; int off = (int)((elapsed / TW_TICKER_MS_PER_PX) % (unsigned long)period); + // Wrap off while the marquee draws: a long line must clip at the screen + // edge, not wrap onto the rows below. Restored straight after. + ((LGFXDisplay*)_display)->setTextWrap(false); display.setCursor(2 - off, y); display.print(text); display.setCursor(2 - off + period, y); display.print(text); + ((LGFXDisplay*)_display)->setTextWrap(true); } public: TWatchChannelScreen(DisplayDriver* display) - : _display(display), _channelIdx(0), - _inboxNewest(0), _inboxCount(0), + : _display(display), _store(nullptr), _channelIdx(0), _touchDown(false), _downX(0), _downY(0), _wantsCompose(false), _wantsExit(false), - _selectedSlot(-1), _tickerStartMs(0) { + _selectedN(-1), _tickerStartMs(0), + _lastNewest(nullptr), _lastNewestTs(0) { _channelName[0] = 0; - memset(_inbox, 0, sizeof(_inbox)); + _selfPrefix[0] = 0; + } + + // Shared history store this screen renders from. + void setStore(ChannelScreen* store) { _store = store; } + + // Own node name, used to right-align sent lines ("NodeName: text"). + void setSelfName(const char* name) { + if (!name || !name[0]) { _selfPrefix[0] = 0; return; } + snprintf(_selfPrefix, sizeof(_selfPrefix), "%s: ", name); } // Switch to a channel (called when a channel is selected in the picker). + // Opening a channel marks its messages as read. void activate(uint8_t idx, const char* name) { _channelIdx = idx; strncpy(_channelName, name ? name : "", TW_CH_NAME_LEN - 1); _channelName[TW_CH_NAME_LEN - 1] = 0; - _inboxNewest = 0; _inboxCount = 0; - memset(_inbox, 0, sizeof(_inbox)); _touchDown = false; _wantsCompose = false; _wantsExit = false; - _selectedSlot = -1; + _selectedN = -1; + _lastNewest = nullptr; _lastNewestTs = 0; + if (_store) _store->markChannelRead(_channelIdx); } uint8_t getChannelIdx() const { return _channelIdx; } const char* getChannelName() const { return _channelName; } - // Fed by UITask::newMsg for every incoming message; kept only if it matches - // this channel by name. - void notifyMsg(const char* from, const char* text) { - if (!from || strcmp(from, _channelName) != 0) return; - pushEntry(text, false); - } - - // Called by UITask after a message is sent on this channel. - void addSentMsg(const char* text) { - pushEntry(text, true); - } - bool wantsCompose() const { return _wantsCompose; } void acknowledgeCompose() { _wantsCompose = false; } bool wantsExit() const { return _wantsExit; } @@ -308,20 +335,16 @@ public: } else if (!now && _touchDown) { _touchDown = false; int H = _display->height(); - if (_downX < 20 && _downY < HEADER_H) { _selectedSlot = -1; _wantsExit = true; return; } // back arrow - if (_downY >= H - COMPOSE_BAR_H) { _selectedSlot = -1; _wantsCompose = true; return; } // compose bar + if (_downX < 20 && _downY < HEADER_H) { _selectedN = -1; _wantsExit = true; return; } // back arrow + if (_downY >= H - COMPOSE_BAR_H) { _selectedN = -1; _wantsCompose = true; return; } // compose bar // message area -> tap to open/close ticker if (_downY >= MSG_TOP && _downY < H - COMPOSE_BAR_H) { int visualRow = (_downY - MSG_TOP) / MSG_LINE_H; - if (visualRow >= 0 && visualRow < _inboxCount) { - int i = (_inboxCount - 1) - visualRow; - int slot = (int)_inboxNewest - i; - while (slot < 0) slot += TW_INBOX_SIZE; - slot %= TW_INBOX_SIZE; - if (_inbox[slot].valid) { - if (_selectedSlot == slot) _selectedSlot = -1; - else { _selectedSlot = slot; _tickerStartMs = millis(); } - } + int count = visibleCount(); + if (visualRow >= 0 && visualRow < count) { + int n = (count - 1) - visualRow; // top row = oldest visible + if (_selectedN == n) _selectedN = -1; + else { _selectedN = n; _tickerStartMs = millis(); } } } } @@ -340,22 +363,29 @@ public: display.setColor(DisplayDriver::LIGHT); display.drawRect(0, HEADER_H - 2, W, 1); - const int areaBottom = H - COMPOSE_BAR_H - 1; + // A new message (sent or received) shifts the rows, so dismiss any open + // ticker when the newest store entry for this channel changes. + const ChannelScreen::ChannelMessage* newest = + _store ? _store->getChannelMsgByRecency(_channelIdx, 0) : nullptr; + if (newest != _lastNewest || (newest && newest->timestamp != _lastNewestTs)) { + _lastNewest = newest; + _lastNewestTs = newest ? newest->timestamp : 0; + _selectedN = -1; + } - if (_inboxCount == 0) { + int count = visibleCount(); + if (count == 0) { display.setColor(DisplayDriver::LIGHT); display.setCursor(2, MSG_TOP); display.print("(no messages)"); } else { int y = MSG_TOP; - for (int i = _inboxCount - 1; i >= 0; i--) { // oldest first (top) -> newest (bottom) - int idx = (int)_inboxNewest - i; - while (idx < 0) idx += TW_INBOX_SIZE; - const InboxEntry& e = _inbox[idx]; - if (!e.valid) continue; - if (y + MSG_LINE_H > areaBottom) break; - if (idx == _selectedSlot) drawTicker(display, y, e.text, e.isSent); - else drawMsgLine(display, y, e.text, e.isSent); + for (int n = count - 1; n >= 0; n--) { // oldest visible (top) -> newest (bottom) + const ChannelScreen::ChannelMessage* m = _store->getChannelMsgByRecency(_channelIdx, n); + if (!m) continue; + bool sent = isSent(m); + if (n == _selectedN) drawTicker(display, y, m->text, sent); + else drawMsgLine(display, y, m->text, sent); y += MSG_LINE_H; } } @@ -364,7 +394,7 @@ public: display.setColor(DisplayDriver::LIGHT); display.drawRect(0, H - COMPOSE_BAR_H, W, COMPOSE_BAR_H - 1); display.drawTextEllipsized(3, H - COMPOSE_BAR_H + 4, W - 6, "Tap to compose"); - return (_selectedSlot >= 0) ? 60 : 500; + return (_selectedN >= 0) ? 60 : 500; } }; diff --git a/variants/lilygo_twatch_s3_plus/TWatchS3PlusBoard.cpp b/variants/lilygo_twatch_s3_plus/TWatchS3PlusBoard.cpp index df379d47..1a4d71eb 100644 --- a/variants/lilygo_twatch_s3_plus/TWatchS3PlusBoard.cpp +++ b/variants/lilygo_twatch_s3_plus/TWatchS3PlusBoard.cpp @@ -14,6 +14,10 @@ void TWatchS3PlusBoard::begin() { _accel->configAccelerometer(OperationMode::NORMAL, AccelFullScaleRange::FS_2G, 50.0f, AccelBandwidth::OSR2_AVG2, AccelPerfMode::CIC_AVG_MODE); _accel->enableTiltDetector(true, true); + // INT1 pin electrical config: level trigger, active high, push-pull, + // output enabled. INT1_IO_CTRL resets to output-disabled, so without + // this the pin never drives and INPUT_PULLDOWN reads low forever. + _accel->setInterruptPinConfig(InterruptPinMap::PIN1, false, false, true, false); pinMode(PIN_ACCEL_IRQ, INPUT_PULLDOWN); }