diff --git a/examples/companion_radio/ui-new/QuickMsg.cpp b/examples/companion_radio/ui-new/QuickMsg.cpp new file mode 100644 index 000000000..f57269efa --- /dev/null +++ b/examples/companion_radio/ui-new/QuickMsg.cpp @@ -0,0 +1,176 @@ +#include "QuickMsg.h" + +#if UI_QUICK_MSG +#include "../MyMesh.h" + +#ifndef COUNTOF + #define COUNTOF(x) sizeof(x) / sizeof(x[0]) +#endif + +static constexpr const char* messages[] { + "test", "ping", "hello", "ack", "yes", "no", "share location", + "come to me", "going to you", "help", "SOS" +}; +static constexpr size_t messages_count = COUNTOF(messages); + +QuickMsgScreen::QuickMsgScreen(UITask* task) + : _task(task) { +} + +int QuickMsgScreen::render(DisplayDriver& display) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + display.drawTextCentered(display.width() / 2, 2, "quick messages"); + + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + display.setCursor(2, _row_defs[0]); + display.print("message:"); + display.setCursor(42, _row_defs[0]); + display.print(getMessageText()); + + display.setCursor(2, _row_defs[1]); + display.print("channel:"); + display.setCursor(42, _row_defs[1]); + display.print(getChannelName()); + + display.drawTextCentered(display.width() / 2, _row_defs[2], "[send]"); + + auto cursor_row = _row_defs[_row]; + display.drawRect(0, cursor_row - 1, display.width(), 12); + return 1000; +} + +bool QuickMsgScreen::handleInput(char c) { + switch (c) { + case KEY_ENTER: + _task->gotoHomeScreen(); + return true; + + // Move cursor + case KEY_PREV: + case KEY_LEFT: + _row = (_row + 1) % Row::Count; + return true; + + // Next value/Use button + case KEY_NEXT: + case KEY_RIGHT: + if (_row == Row::MSG) + nextMessage(); + else if (_row == Row::CHANNEL) + nextChannel(); + else if (_row == Row::SEND) + sendMessage(); + else + MESH_DEBUG_PRINTLN("Bad row index"); + return true; + + // Prev value + case KEY_SELECT: + if (_row == Row::MSG) + nextMessage(/*fwd=*/false); + else if (_row == Row::CHANNEL) + nextChannel(/*fwd=*/false); + else + MESH_DEBUG_PRINTLN("Bad row index"); + return true; + + default: + _task->showAlert(PRESS_LABEL " to exit", 1000); + break; + } + + return false; +} + +void QuickMsgScreen::nextMessage(bool fwd) { + auto msg_count = messages_count; + +#if ENV_INCLUDE_GPS + // Make fake index for GPS if enabled. + if (_task->getGPSState()) + msg_count += 1; +#endif + + _kind = MsgKind::TEXT; + _msg_ix = (_msg_ix + (fwd ? 1 : msg_count - 1)) % msg_count; + +#if ENV_INCLUDE_GPS + // Index at end of messages, add fake GPS entry. + if (_msg_ix == messages_count) + _kind = MsgKind::GPS; +#endif +} + +void QuickMsgScreen::nextChannel(bool fwd) { +#ifndef MAX_GROUP_CHANNELS + _channel_ix = 0; + return; +#else + ChannelDetails details; + _channel_ix = (_channel_ix + (fwd ? 1 : MAX_GROUP_CHANNELS - 1)) % MAX_GROUP_CHANNELS; + the_mesh.getChannel(_channel_ix, details); + + if (fwd) { + // Moving forward. Reset to 0 on first invalid channel. + if (details.name[0] == 0) + _channel_ix = 0; + } else { + // Moving backward. Find a valid channel or 0. + while (_channel_ix > 0 && details.name[0] == 0) + the_mesh.getChannel(--_channel_ix, details); + } + + // Copy valid names to the UI buffer. + if (details.name[0] != 0) + strcpy(_channel_name, details.name); +#endif +} + +void QuickMsgScreen::sendMessage() { + ChannelDetails details; + bool sent = false; + + if (the_mesh.getChannel(_channel_ix, details)) { + auto now = the_mesh.getRTCClock()->getCurrentTime(); + auto name = the_mesh.getNodeName(); + auto text = getMessageText(); + auto len = strlen(text); + + if (the_mesh.sendGroupMessage(now, details.channel, name, text, len)) + sent = true; + } + + if (sent) + _task->showAlert("Message sent!", 1000); + else + _task->showAlert("Message failed.", 1000); +} + +const char* QuickMsgScreen::getMessageText() { +#if ENV_INCLUDE_GPS + if (_kind == MsgKind::GPS) { + LocationProvider* nmea = sensors.getLocationProvider(); + if (!nmea) { + sprintf(_msg_text, "GPS Error"); + } else { + if (nmea->isValid()) { + sprintf(_msg_text, "%.4f %.4f", + nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); + } else { + sprintf(_msg_text, "No GPS fix"); + } + } + return _msg_text; + } +#endif + + return _msg_ix < messages_count ? messages[_msg_ix] : "???"; +} + +const char* QuickMsgScreen::getChannelName() { + return _channel_ix == 0 ? "public" : _channel_name; +} + +#endif \ No newline at end of file diff --git a/examples/companion_radio/ui-new/QuickMsg.h b/examples/companion_radio/ui-new/QuickMsg.h new file mode 100644 index 000000000..696cbe681 --- /dev/null +++ b/examples/companion_radio/ui-new/QuickMsg.h @@ -0,0 +1,48 @@ +#pragma once + +#include "UITask.h" + +#if UI_QUICK_MSG +#include +#include + +class QuickMsgScreen : public UIScreen { + enum Row { + MSG, + CHANNEL, + SEND, + Count + }; + + enum MsgKind { + TEXT, +#if ENV_INCLUDE_GPS + GPS, +#endif + }; + + UITask* _task; + uint8_t _msg_ix = 0; + uint8_t _channel_ix = 0; + uint8_t _kind = MsgKind::TEXT; + uint8_t _row = 0; + uint8_t _row_defs[Row::Count] { 20, 35, 50 }; + char _channel_name[32]; // see ChannelDetails.h +#if ENV_INCLUDE_GPS + char _msg_text[32]; // Buffer for dynamic messages. +#endif + + void nextMessage(bool fwd = true); + void nextChannel(bool fwd = true); + void sendMessage(); + + const char* getMessageText(); + const char* getChannelName(); + +public: + QuickMsgScreen(UITask* task); + int render(DisplayDriver& display) override; + bool handleInput(char c) override; +}; + +#endif \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a0892..f2f1c3889 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -3,6 +3,10 @@ #include "../MyMesh.h" #include "target.h" +#if UI_QUICK_MSG +#include "QuickMsg.h" +#endif + #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds #endif @@ -20,12 +24,6 @@ #define UI_RECENT_LIST_SIZE 4 #endif -#if UI_HAS_JOYSTICK - #define PRESS_LABEL "press Enter" -#else - #define PRESS_LABEL "long press" -#endif - #include "icons.h" class SplashScreen : public UIScreen { @@ -84,6 +82,9 @@ class HomeScreen : public UIScreen { #endif #if UI_SENSORS_PAGE == 1 SENSORS, +#endif +#if UI_QUICK_MSG + QUICK_MSG, #endif SHUTDOWN, Count // keep as last @@ -356,6 +357,14 @@ class HomeScreen : public UIScreen { } if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; else sensors_scroll_offset = 0; +#endif +#if UI_QUICK_MSG + } else if (_page == HomePage::QUICK_MSG) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + display.drawTextCentered(display.width() / 2, 24, "quick messages"); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 40, "enter/exit: " PRESS_LABEL); #endif } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::GREEN); @@ -364,7 +373,7 @@ class HomeScreen : public UIScreen { display.drawTextCentered(display.width() / 2, 34, "hibernating..."); } else { display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); + display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL); } } return 5000; // next render after 5000 ms @@ -411,6 +420,12 @@ class HomeScreen : public UIScreen { next_sensors_refresh=0; return true; } +#endif +#if UI_QUICK_MSG + if (c == KEY_ENTER && _page == HomePage::QUICK_MSG) { + _task->gotoQuickMsgScreen(); + return true; + } #endif if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released @@ -544,6 +559,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no splash = new SplashScreen(this); home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); msg_preview = new MsgPreviewScreen(this, &rtc_clock); +#if UI_QUICK_MSG + quick_msg = new QuickMsgScreen(this); +#endif setCurrScreen(splash); } @@ -663,52 +681,51 @@ bool UITask::isButtonPressed() const { } void UITask::loop() { - char c = 0; #if UI_HAS_JOYSTICK int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_ENTER); + handleSingleClick(KEY_ENTER); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code + handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code } ev = joystick_left.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_LEFT); + handleSingleClick(KEY_LEFT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_LEFT); + handleLongPress(KEY_LEFT); } ev = joystick_right.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_RIGHT); + handleSingleClick(KEY_RIGHT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_RIGHT); + handleLongPress(KEY_RIGHT); } ev = back_btn.check(); if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #elif defined(PIN_USER_BTN) int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + handleSingleClick(KEY_NEXT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); + handleLongPress(KEY_ENTER); } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); + handleDoubleClick(KEY_PREV); } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #endif #if defined(PIN_USER_BTN_ANA) ev = analog_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + handleSingleClick(KEY_NEXT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); + handleLongPress(KEY_ENTER); } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); + handleDoubleClick(KEY_PREV); } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + handleTripleClick(KEY_SELECT); } #endif #if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) @@ -719,12 +736,6 @@ void UITask::loop() { } #endif - if (c != 0 && curr) { - curr->handleInput(c); - _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer - _next_refresh = 100; // trigger refresh - } - userLedHandler(); #ifdef PIN_BUZZER @@ -789,38 +800,56 @@ void UITask::loop() { #endif } -char UITask::checkDisplayOn(char c) { +bool UITask::checkDisplayOn() { + // ensures that the display is on and the timer is reset + // returns false if the display was previously off + bool display_on = false; if (_display != NULL) { if (!_display->isOn()) { - _display->turnOn(); // turn display on and consume event - c = 0; + _display->turnOn(); // turn display on + } else { + display_on = true; } - _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _next_refresh = 0; // trigger refresh } - return c; + return display_on; } -char UITask::handleLongPress(char c) { - if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue +void UITask::handleLongPress(char c) { + if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue the_mesh.enterCLIRescue(); - c = 0; // consume event + } else { + MESH_DEBUG_PRINTLN("UITask: long press triggered"); + uiHandleKey(c); } - return c; } -char UITask::handleDoubleClick(char c) { +void UITask::handleSingleClick(char c) { + MESH_DEBUG_PRINTLN("UITask: single click triggered"); + uiHandleKey(c); +} + +void UITask::handleDoubleClick(char c) { MESH_DEBUG_PRINTLN("UITask: double click triggered"); - checkDisplayOn(c); - return c; + uiHandleKey(c); } -char UITask::handleTripleClick(char c) { +void UITask::handleTripleClick(char c) { MESH_DEBUG_PRINTLN("UITask: triple click triggered"); - checkDisplayOn(c); - toggleBuzzer(); - c = 0; - return c; + if (!uiHandleKey(c)) toggleBuzzer(); +} + +bool UITask::uiHandleKey(char c) { + bool handled = false; + bool display_on = checkDisplayOn(); + + if (c != 0 && display_on && curr) { + handled = curr->handleInput(c); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 100; // trigger refresh + } + return handled; } bool UITask::getGPSState() { diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index c24d33a48..0e0fd4a7d 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -15,6 +15,12 @@ #include #endif +#if UI_HAS_JOYSTICK + #define PRESS_LABEL "press Enter" +#else + #define PRESS_LABEL "long press" +#endif + #include "../AbstractUITask.h" #include "../NodePrefs.h" @@ -43,15 +49,20 @@ class UITask : public AbstractUITask { UIScreen* splash; UIScreen* home; UIScreen* msg_preview; +#if UI_QUICK_MSG + UIScreen* quick_msg; +#endif UIScreen* curr; void userLedHandler(); // Button action handlers - char checkDisplayOn(char c); - char handleLongPress(char c); - char handleDoubleClick(char c); - char handleTripleClick(char c); + bool checkDisplayOn(); + void handleLongPress(char c); + void handleSingleClick(char c); + void handleDoubleClick(char c); + void handleTripleClick(char c); + bool uiHandleKey(char c); void setCurrScreen(UIScreen* c); @@ -65,6 +76,9 @@ class UITask : public AbstractUITask { void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); void gotoHomeScreen() { setCurrScreen(home); } +#if UI_QUICK_MSG + void gotoQuickMsgScreen() { setCurrScreen(quick_msg); } +#endif void showAlert(const char* text, int duration_millis); int getMsgCount() const { return _msgcount; } bool hasDisplay() const { return _display != NULL; } @@ -74,7 +88,6 @@ class UITask : public AbstractUITask { bool getGPSState(); void toggleGPS(); - // from AbstractUITask void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index c482a30ad..850535c3e 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -171,6 +171,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI + -D UI_QUICK_MSG=1 ; enable quick messages in UI ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1