fix(heltec-v4): improve expansion kit usability
This commit is contained in:
1 parent
4b7882ca44
commit
e796984ad4
5 files changed
+298
-2
No files matched your search
+5
-1
@@ -40,7 +40,11 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases
|
||||
see [variants/lilygo_tdeck_pro/PORT.md](variants/lilygo_tdeck_pro/PORT.md).
|
||||
- **Heltec V4 + TFT**: touch UI with the on-screen keyboard; the optional
|
||||
Expansion Kit adds environment sensors (home-screen chart) and a piezo
|
||||
buzzer. V4.3 boards get the switchable high-gain receive LNA toggle.
|
||||
buzzer. On the original Expansion Kit, tap **IO** for Back or hold it for one
|
||||
second to return Home; this reuses GPIO35, so that target no longer drives the
|
||||
same line as a TX LED. **PWR** remains the expansion board's hardware power
|
||||
control and is not readable by firmware. V4.3 boards get the switchable
|
||||
high-gain receive LNA toggle.
|
||||
- **Tanmatsu**: keyboard-driven UI (no touchscreen) with the coloured function
|
||||
keys mapped to tabs, ALT accent picker, UI scaling (Normal/Large/Huge),
|
||||
microSD for all persistent data. Ships through the Tanmatsu launcher store,
|
||||
|
||||
+6
-1
@@ -72,7 +72,12 @@ build_flags =
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=35
|
||||
; Original Expansion Kit SW3 "IO": active-low KEY net on GPIO35. This pin was
|
||||
; previously treated as a TX LED, which drove the button line as an output.
|
||||
; Navigation is more valuable on the cramped 240 px touch UI: short=Back,
|
||||
; hold=Home. The PWR switch drives the EC190708 power controller directly and
|
||||
; cannot provide a firmware short-press. The V4-R8 uses GPIO46 for its TX LED.
|
||||
-D HELTEC_V4_EXPANSION_IO_PIN=35
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=12
|
||||
|
||||
@@ -221,6 +221,139 @@ static const lv_btnmatrix_ctrl_t kb_en_upper_ctrl[] = {
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
|
||||
};
|
||||
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
// Original V4 Expansion Kit: the 240 px portrait keyboard's stock first row
|
||||
// spends most of its width on mode/backspace controls, leaving ~18 px letters.
|
||||
// Keep QWERTY order but move controls to the bottom so letters get 24-27 px.
|
||||
static const char* const kb_v4_lower[] = {
|
||||
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
|
||||
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
|
||||
"z", "x", "c", "v", "b", "n", "m", ".", ",", "\n",
|
||||
"ABC", "1#", LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT,
|
||||
LV_SYMBOL_BACKSPACE, LV_SYMBOL_OK, ""
|
||||
};
|
||||
static const char* const kb_v4_upper[] = {
|
||||
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
|
||||
"A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
|
||||
"Z", "X", "C", "V", "B", "N", "M", ".", ",", "\n",
|
||||
"abc", "1#", LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT,
|
||||
LV_SYMBOL_BACKSPACE, LV_SYMBOL_OK, ""
|
||||
};
|
||||
static const lv_btnmatrix_ctrl_t kb_v4_text_ctrl[] = {
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | EN_KB_BTN(1),
|
||||
LV_BTNMATRIX_CTRL_CHECKED | EN_KB_BTN(1),
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 5,
|
||||
LV_BTNMATRIX_CTRL_CHECKED | 2, LV_BTNMATRIX_CTRL_CHECKED | 2,
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2
|
||||
};
|
||||
|
||||
// Five columns give each number/punctuation key roughly 48 px. "#+" switches
|
||||
// to the second page; "123" returns. UITask intercepts those two controls
|
||||
// before LVGL's default handler so their labels are never inserted as text.
|
||||
static const char* const kb_v4_special[] = {
|
||||
"1", "2", "3", "4", "5", "\n",
|
||||
"6", "7", "8", "9", "0", "\n",
|
||||
".", ",", "?", "!", "@", "\n",
|
||||
"-", "_", "+", "=", "/", "\n",
|
||||
"#+", "abc", LV_SYMBOL_BACKSPACE, " ", LV_SYMBOL_OK, ""
|
||||
};
|
||||
static const char* const kb_v4_special_more[] = {
|
||||
"#", "$", "%", "&", "*", "\n",
|
||||
"(", ")", "[", "]", "{", "\n",
|
||||
"}", ":", ";", "'", "\"", "\n",
|
||||
"\\", "|", "<", ">", "^", "\n",
|
||||
"123", "abc", LV_SYMBOL_BACKSPACE, " ", LV_SYMBOL_OK, ""
|
||||
};
|
||||
static const lv_btnmatrix_ctrl_t kb_v4_special_ctrl[] = {
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1), EN_KB_BTN(1),
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
|
||||
LV_BTNMATRIX_CTRL_CHECKED | 2, 5, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
|
||||
};
|
||||
static_assert(sizeof(kb_v4_lower) / sizeof(kb_v4_lower[0]) ==
|
||||
sizeof(kb_v4_text_ctrl) / sizeof(kb_v4_text_ctrl[0]) + 4,
|
||||
"V4 lower map/control count mismatch");
|
||||
static_assert(sizeof(kb_v4_upper) / sizeof(kb_v4_upper[0]) ==
|
||||
sizeof(kb_v4_text_ctrl) / sizeof(kb_v4_text_ctrl[0]) + 4,
|
||||
"V4 upper map/control count mismatch");
|
||||
static_assert(sizeof(kb_v4_special) / sizeof(kb_v4_special[0]) ==
|
||||
sizeof(kb_v4_special_ctrl) / sizeof(kb_v4_special_ctrl[0]) + 5,
|
||||
"V4 symbols map/control count mismatch");
|
||||
static_assert(sizeof(kb_v4_special_more) / sizeof(kb_v4_special_more[0]) ==
|
||||
sizeof(kb_v4_special_ctrl) / sizeof(kb_v4_special_ctrl[0]) + 5,
|
||||
"V4 more-symbols map/control count mismatch");
|
||||
|
||||
static const char* kb_v4_secondary_lower[64];
|
||||
static const char* kb_v4_secondary_upper[64];
|
||||
static lv_btnmatrix_ctrl_t kb_v4_secondary_lower_ctrl[56];
|
||||
static lv_btnmatrix_ctrl_t kb_v4_secondary_upper_ctrl[56];
|
||||
|
||||
static bool kbV4TextControl(const char* key) {
|
||||
return strcmp(key, "1#") == 0 || strcmp(key, "ABC") == 0 ||
|
||||
strcmp(key, "abc") == 0 || strcmp(key, LV_SYMBOL_UP) == 0 ||
|
||||
strcmp(key, LV_SYMBOL_NEW_LINE) == 0 ||
|
||||
strcmp(key, LV_SYMBOL_KEYBOARD) == 0 ||
|
||||
strcmp(key, LV_SYMBOL_LEFT) == 0 || strcmp(key, " ") == 0 ||
|
||||
strcmp(key, LV_SYMBOL_RIGHT) == 0 ||
|
||||
strcmp(key, LV_SYMBOL_BACKSPACE) == 0 ||
|
||||
strcmp(key, LV_SYMBOL_OK) == 0;
|
||||
}
|
||||
|
||||
static bool kbV4InlinePunctuation(const char* key) {
|
||||
return strcmp(key, "_") == 0 || strcmp(key, "-") == 0 ||
|
||||
strcmp(key, ".") == 0 || strcmp(key, ",") == 0 ||
|
||||
strcmp(key, ":") == 0;
|
||||
}
|
||||
|
||||
static void kbV4BuildSecondary(const char* const* source, bool upper,
|
||||
const char** map, lv_btnmatrix_ctrl_t* controls) {
|
||||
size_t map_count = 0;
|
||||
size_t control_count = 0;
|
||||
bool row_has_key = false;
|
||||
for (size_t i = 0; source[i] && source[i][0]; ++i) {
|
||||
const char* key = source[i];
|
||||
if (strcmp(key, "\n") == 0) {
|
||||
if (row_has_key) map[map_count++] = "\n";
|
||||
row_has_key = false;
|
||||
continue;
|
||||
}
|
||||
if (kbV4TextControl(key) || kbV4InlinePunctuation(key)) continue;
|
||||
map[map_count++] = key;
|
||||
controls[control_count++] = EN_KB_BTN(1);
|
||||
row_has_key = true;
|
||||
}
|
||||
if (row_has_key) map[map_count++] = "\n";
|
||||
|
||||
static const char* const footer_lower[] = {
|
||||
"ABC", "1#", LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT,
|
||||
LV_SYMBOL_BACKSPACE, LV_SYMBOL_OK
|
||||
};
|
||||
static const char* const footer_upper[] = {
|
||||
"abc", "1#", LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT,
|
||||
LV_SYMBOL_BACKSPACE, LV_SYMBOL_OK
|
||||
};
|
||||
const char* const* footer = upper ? footer_upper : footer_lower;
|
||||
for (size_t i = 0; i < 8; ++i) map[map_count++] = footer[i];
|
||||
map[map_count] = "";
|
||||
controls[control_count++] = LV_KEYBOARD_CTRL_BTN_FLAGS | 2;
|
||||
controls[control_count++] = LV_KEYBOARD_CTRL_BTN_FLAGS | 2;
|
||||
controls[control_count++] = LV_KEYBOARD_CTRL_BTN_FLAGS | 2;
|
||||
controls[control_count++] = LV_BTNMATRIX_CTRL_CHECKED | 2;
|
||||
controls[control_count++] = 5;
|
||||
controls[control_count++] = LV_BTNMATRIX_CTRL_CHECKED | 2;
|
||||
controls[control_count++] = LV_BTNMATRIX_CTRL_CHECKED | 2;
|
||||
controls[control_count] = LV_KEYBOARD_CTRL_BTN_FLAGS | 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ---------- French (AZERTY) on-screen keyboard ---------- */
|
||||
/* Keeps the proven LVGL control rows/punctuation, but swaps the alpha rows to
|
||||
* a familiar French AZERTY order. Accents still come from the existing popup. */
|
||||
@@ -806,8 +939,30 @@ void keyboardLayoutsApply(lv_obj_t* keyboard, KeyboardLayoutId id) {
|
||||
if (!keyboard) return;
|
||||
|
||||
const OsKeyboardLayout& lo = k_os_layouts[static_cast<int>(id)];
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
if (id == KeyboardLayoutId::EN) {
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER,
|
||||
const_cast<const char**>(kb_v4_lower), kb_v4_text_ctrl);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_UPPER,
|
||||
const_cast<const char**>(kb_v4_upper), kb_v4_text_ctrl);
|
||||
} else {
|
||||
kbV4BuildSecondary(lo.lower_map, false, kb_v4_secondary_lower,
|
||||
kb_v4_secondary_lower_ctrl);
|
||||
kbV4BuildSecondary(lo.upper_map, true, kb_v4_secondary_upper,
|
||||
kb_v4_secondary_upper_ctrl);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER,
|
||||
kb_v4_secondary_lower, kb_v4_secondary_lower_ctrl);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_UPPER,
|
||||
kb_v4_secondary_upper, kb_v4_secondary_upper_ctrl);
|
||||
}
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_SPECIAL,
|
||||
const_cast<const char**>(kb_v4_special), kb_v4_special_ctrl);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_USER_1,
|
||||
const_cast<const char**>(kb_v4_special_more), kb_v4_special_ctrl);
|
||||
#else
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER, const_cast<const char**>(lo.lower_map), lo.lower_ctrl); // maps now in flash; LVGL never writes them
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_UPPER, const_cast<const char**>(lo.upper_map), lo.upper_ctrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
KeyboardLayoutId keyboardLayoutsGetCurrent() {
|
||||
|
||||
@@ -1918,7 +1918,11 @@ static inline lv_coord_t chatComposerBaseH() {
|
||||
// composer automatically reclaims space from the message list. Reset to
|
||||
// CHAT_COMP_H on each chat-panel (re)build; updated by chatComposerAutoGrow().
|
||||
static lv_coord_t s_comp_h = CHAT_COMP_H;
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
constexpr int CHAT_KB_H = 160; // larger original-V4 touch targets
|
||||
#else
|
||||
constexpr int CHAT_KB_H = 130; // on-screen keyboard (portrait)
|
||||
#endif
|
||||
// Bottom tab-bar height (matches lv_tabview_create in buildUiTree). A tab
|
||||
// page's usable content area is the screen minus the status bar and tab bar —
|
||||
// queried live so it tracks the current rotation (240×260 portrait /
|
||||
@@ -10379,6 +10383,21 @@ static void kbBackspaceSelCb(lv_event_t* e) {
|
||||
lv_event_stop_processing(e);
|
||||
}
|
||||
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
static void kbV4SpecialPageCb(lv_event_t* e) {
|
||||
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
|
||||
lv_obj_t* kb = lv_event_get_target(e);
|
||||
const uint32_t button = lv_btnmatrix_get_selected_btn(kb);
|
||||
if (button == LV_BTNMATRIX_BTN_NONE) return;
|
||||
const char* text = lv_btnmatrix_get_btn_text(kb, button);
|
||||
if (!text) return;
|
||||
if (strcmp(text, "#+") == 0) lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_USER_1);
|
||||
else if (strcmp(text, "123") == 0) lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_SPECIAL);
|
||||
else return;
|
||||
lv_event_stop_processing(e);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void closeSettingsModal() {
|
||||
hideKb();
|
||||
#if CAP_KEYPAD_NAV
|
||||
@@ -40711,6 +40730,7 @@ static bool drawerPopupOpen() {
|
||||
// once behaves consistently everywhere. Adding a popup = one registry row.
|
||||
static bool popupRegistryAny();
|
||||
static bool popupRegistryDismissTop();
|
||||
static int popupRegistryDismissTopAboveAppDrawer();
|
||||
static bool popupRegistryBlocksSwipe();
|
||||
|
||||
// True if any popup/modal is currently up (rows flagged PF_COUNT).
|
||||
@@ -47310,6 +47330,11 @@ static void buildGlobalStatusBar() {
|
||||
// vertical centreline with the clock and connection/battery indicators.
|
||||
const lv_coord_t BH = 20, BW = 26, GAP = 3, BX0 = 6;
|
||||
const lv_coord_t BY = (STATUSBAR_H - BH) / 2;
|
||||
#elif defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
// Original V4 Expansion Kit: partition row 2 into larger, non-overlapping
|
||||
// cells; the former 30x20 actions were too easy to miss or hit beside.
|
||||
const lv_coord_t BH = 22, BW = 42, GAP = 8, BX0 = 4;
|
||||
const lv_coord_t BY = STATUSBAR_H;
|
||||
#else
|
||||
const bool portrait = lv_disp_get_hor_res(nullptr) < lv_disp_get_ver_res(nullptr);
|
||||
const lv_coord_t BH = portrait ? 20 : (lv_coord_t)((STATUSBAR_H * 2 - 4) * 7 / 10);
|
||||
@@ -47327,6 +47352,10 @@ static void buildGlobalStatusBar() {
|
||||
styleButton(b);
|
||||
lv_obj_set_style_radius(b, 9, LV_PART_MAIN); // softer corners
|
||||
lv_obj_set_style_border_opa(b, LV_OPA_20, LV_PART_MAIN); // dim the edge so it blends
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
// Three pixels each side still leave two pixels between adjacent hits.
|
||||
lv_obj_set_ext_click_area(b, 3);
|
||||
#endif
|
||||
lv_obj_add_flag(b, LV_OBJ_FLAG_HIDDEN);
|
||||
return b;
|
||||
};
|
||||
@@ -47400,7 +47429,11 @@ static void buildGlobalStatusBar() {
|
||||
lv_obj_align(g_statusbar.chan_gear, LV_ALIGN_LEFT_MID, 44, 0); // gap after the back chevron
|
||||
#endif
|
||||
lv_obj_add_flag(g_statusbar.chan_gear, LV_OBJ_FLAG_CLICKABLE);
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
lv_obj_set_ext_click_area(g_statusbar.chan_gear, 12);
|
||||
#else
|
||||
lv_obj_set_ext_click_area(g_statusbar.chan_gear, 10);
|
||||
#endif
|
||||
lv_obj_add_flag(g_statusbar.chan_gear, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_event_cb(g_statusbar.chan_gear, channelGearCb, LV_EVENT_CLICKED, nullptr);
|
||||
#if defined(HAS_TANMATSU)
|
||||
@@ -50776,11 +50809,20 @@ static void buildUiTree() {
|
||||
lv_obj_set_size(g_lv.keyboard, lv_disp_get_hor_res(nullptr), CHAT_KB_H);
|
||||
lv_obj_align(g_lv.keyboard, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_add_flag(g_lv.keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
// Magnify the selected key above the finger before release. The compact V4
|
||||
// maps mark character keys as POPOVER-capable; LVGL leaves the feature off
|
||||
// unless it is explicitly enabled on the keyboard widget.
|
||||
lv_keyboard_set_popovers(g_lv.keyboard, true);
|
||||
#endif
|
||||
// Insert our backspace-deletes-selection interceptor AHEAD of the keyboard's
|
||||
// own default handler: remove the auto-registered default, add the interceptor
|
||||
// first, then re-add the default after it. The interceptor runs first and can
|
||||
// stop_processing to replace the default single-char delete when text is selected.
|
||||
lv_obj_remove_event_cb(g_lv.keyboard, lv_keyboard_def_event_cb);
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
lv_obj_add_event_cb(g_lv.keyboard, kbV4SpecialPageCb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
#endif
|
||||
lv_obj_add_event_cb(g_lv.keyboard, kbBackspaceSelCb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
lv_obj_add_event_cb(g_lv.keyboard, lv_keyboard_def_event_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
lv_obj_add_event_cb(g_lv.keyboard, keyboardCb, LV_EVENT_READY, nullptr);
|
||||
@@ -59462,6 +59504,74 @@ void UITask::loop() {
|
||||
snprintf(msg, sizeof(msg), TR("No reply from %s"), s_ui_ping_target_name);
|
||||
showAlert(msg, 3500);
|
||||
}
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
// Original V4 Expansion Kit SW3 "IO" (GPIO35, active-low). Debounce before
|
||||
// starting the hold clock: short release walks Back; a one-second hold goes
|
||||
// directly Home. A press that only wakes an idle-dimmed screen is consumed.
|
||||
{
|
||||
static bool s_io_inited = false;
|
||||
static uint8_t s_io_raw = HIGH;
|
||||
static uint8_t s_io_stable = HIGH;
|
||||
static uint32_t s_io_changed_ms = 0;
|
||||
static uint32_t s_io_down_ms = 0;
|
||||
static bool s_io_long_fired = false;
|
||||
static bool s_io_wake_consumed = false;
|
||||
constexpr uint32_t kIoDebounceMs = 25;
|
||||
constexpr uint32_t kIoHomeHoldMs = 1000;
|
||||
|
||||
const uint8_t raw = digitalRead(HELTEC_V4_EXPANSION_IO_PIN);
|
||||
if (!s_io_inited) {
|
||||
s_io_raw = s_io_stable = raw;
|
||||
s_io_down_ms = now;
|
||||
s_io_wake_consumed = raw == LOW; // held through boot: wait for a clean release
|
||||
s_io_inited = true;
|
||||
}
|
||||
if (raw != s_io_raw) {
|
||||
s_io_raw = raw;
|
||||
s_io_changed_ms = now;
|
||||
}
|
||||
if (s_io_stable != s_io_raw && now - s_io_changed_ms >= kIoDebounceMs) {
|
||||
const uint8_t previous = s_io_stable;
|
||||
s_io_stable = s_io_raw;
|
||||
if (s_io_stable == LOW) {
|
||||
s_io_down_ms = now;
|
||||
s_io_long_fired = false;
|
||||
s_io_wake_consumed = _screen_off;
|
||||
if (_screen_off && !_manual_lock) wakeScreen();
|
||||
} else if (previous == LOW && !s_io_long_fired && !s_io_wake_consumed) {
|
||||
// Match the established keypad Back ladder: frontmost controls first,
|
||||
// then app page, popup, chat, and finally the Home tab.
|
||||
const int popup_result = popupRegistryDismissTopAboveAppDrawer();
|
||||
if (popup_result == 0) {
|
||||
if (s_apppage_close) s_apppage_close();
|
||||
else if (anyPopupOpen()) popupRegistryDismissTop();
|
||||
else if (g_lv.dm.detail_open) closeChatPanel(&g_lv.dm);
|
||||
else if (g_lv.ch.detail_open) closeChatPanel(&g_lv.ch);
|
||||
else if (getActiveTab() != HOME_TAB_INDEX) goToTab(HOME_TAB_INDEX);
|
||||
}
|
||||
noteUserInput();
|
||||
}
|
||||
}
|
||||
if (s_io_stable == LOW && !s_io_long_fired && !s_io_wake_consumed &&
|
||||
now - s_io_down_ms >= kIoHomeHoldMs) {
|
||||
// Home is a deliberate root action. Respect an undismissable progress
|
||||
// overlay, otherwise close every layer and return to Commander.
|
||||
bool clear = true;
|
||||
for (int i = 0; i < 8 && anyPopupOpen(); ++i) {
|
||||
if (!popupRegistryDismissTop()) { clear = false; break; }
|
||||
}
|
||||
if (clear && !anyPopupOpen()) {
|
||||
if (s_apppage_close) s_apppage_close();
|
||||
if (g_lv.dm.detail_open) closeChatPanel(&g_lv.dm);
|
||||
if (g_lv.ch.detail_open) closeChatPanel(&g_lv.ch);
|
||||
goToTab(HOME_TAB_INDEX);
|
||||
setHomeDrawer(false);
|
||||
noteUserInput();
|
||||
}
|
||||
s_io_long_fired = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if CAP_SD || defined(TLORA_PAGER)
|
||||
// Manual telemetry request timed out — flip the open window to "failed" (it
|
||||
// still shows the history). Auto-poll has no deadline, so it never lands here.
|
||||
@@ -60832,6 +60942,20 @@ static bool popupRegistryDismissTop() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static int popupRegistryDismissTopAboveAppDrawer() {
|
||||
// The app drawer is the registry's final row and intentionally stays open
|
||||
// beneath a launched app. Dismiss a visible popup above that app first, but
|
||||
// leave the hidden drawer for the app-page rung in the caller.
|
||||
const size_t count = sizeof(k_popup_registry) / sizeof(k_popup_registry[0]);
|
||||
for (size_t i = 0; i + 1 < count; ++i) {
|
||||
const PopupEnt& entry = k_popup_registry[i];
|
||||
if (!entry.is_open()) continue;
|
||||
if (!entry.close) return -1; // an active progress overlay blocks Back
|
||||
entry.close();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static bool popupRegistryBlocksSwipe() {
|
||||
for (const auto& e : k_popup_registry)
|
||||
if ((e.flags & PF_SWIPE) && e.is_open()) return true;
|
||||
|
||||
@@ -11,6 +11,10 @@ void HeltecV4Board::begin() {
|
||||
|
||||
loRaFEMControl.init();
|
||||
|
||||
#if defined(HELTEC_V4_EXPANSION_IO_PIN) && !defined(HELTEC_LORA_V4_R8)
|
||||
pinMode(HELTEC_V4_EXPANSION_IO_PIN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(HELTEC_LORA_V4_R8) && defined(PIN_SD_CS)
|
||||
// Park the shared-FSPI micro-SD's chip-select HIGH before LGFX starts
|
||||
// driving the bus (display.begin + logo blit run before the first
|
||||
@@ -48,12 +52,16 @@ void HeltecV4Board::begin() {
|
||||
}
|
||||
|
||||
void HeltecV4Board::onBeforeTransmit(void) {
|
||||
#ifdef P_LORA_TX_LED
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
#endif
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
void HeltecV4Board::onAfterTransmit(void) {
|
||||
#ifdef P_LORA_TX_LED
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
#endif
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user