Merge branch 'master' into chatter_2_fixes

This commit is contained in:
Jason P 2025-07-10 21:21:29 -05:00 committed by GitHub
commit 6f47fa3c34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 485 additions and 209 deletions

View File

@ -143,7 +143,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Test Report - name: Test Report
uses: dorny/test-reporter@v2.1.0 uses: dorny/test-reporter@v2.1.1
with: with:
name: PlatformIO Tests name: PlatformIO Tests
path: testreport.xml path: testreport.xml

View File

@ -87,6 +87,9 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.3" date="2025-07-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3</url>
</release>
<release version="2.7.2" date="2025-07-04"> <release version="2.7.2" date="2025-07-04">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
</release> </release>

7
debian/changelog vendored
View File

@ -1,4 +1,4 @@
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
[ Austin Lane ] [ Austin Lane ]
* Initial packaging * Initial packaging
@ -28,4 +28,7 @@ meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ ] [ ]
* GitHub Actions Automatic version bump * GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000 [ Ubuntu ]
* GitHub Actions Automatic version bump
-- Ubuntu <github-actions[bot]@users.noreply.github.com> Thu, 10 Jul 2025 16:29:27 +0000

View File

@ -104,7 +104,7 @@ lib_deps =
[radiolib_base] [radiolib_base]
lib_deps = lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.2.0 jgromes/RadioLib@7.2.1
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
@ -115,7 +115,7 @@ lib_deps =
[environmental_base] [environmental_base]
lib_deps = lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO # renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.1 adafruit/Adafruit BusIO@1.17.2
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor # renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15 adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library # renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

View File

@ -83,6 +83,29 @@ extern uint16_t TFT_MESH;
#include "platform/portduino/PortduinoGlue.h" #include "platform/portduino/PortduinoGlue.h"
#endif #endif
bool shouldWakeOnReceivedMessage()
{
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT &&
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
return false;
}
return true;
}
bool wake_on_received_message = shouldWakeOnReceivedMessage(); // Master Switch to enable here
using namespace meshtastic; /** @todo remove */ using namespace meshtastic; /** @todo remove */
namespace graphics namespace graphics
@ -171,7 +194,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
} }
// Called to trigger a banner with custom message and duration // Called to trigger a banner with custom message and duration
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback) void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
{ {
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@ -196,7 +219,6 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback) std::function<void(uint32_t)> bannerCallback)
{ {
LOG_WARN("Show Number Picker");
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif #endif
@ -1258,40 +1280,46 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
devicestate.has_rx_text_message = true; // Needed to include the message frame devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
forceDisplay(); // Forces screen redraw
// === Prepare banner content === // Only wake/force display if the configuration allows it
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); wake_on_received_message = shouldWakeOnReceivedMessage();
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; if (wake_on_received_message) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes); // === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
char banner[256]; const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
// Check for bell character in message to determine alert type char banner[256];
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { // Check for bell character in message to determine alert type
if (msgRaw[i] == '\x07') { bool isAlert = false;
isAlert = true; for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
break; if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
} }
}
if (isAlert) { if (isAlert) {
if (longName && longName[0]) { if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else { } else {
strcpy(banner, "Alert Received"); if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
} }
} else {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
screen->showSimpleBanner(banner, 3000); screen->showSimpleBanner(banner, 3000);
}
} }
} }
@ -1330,7 +1358,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP
#endif #endif
if (NotificationRenderer::isOverlayBannerShowing()) { if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = event->inputEvent; NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP

View File

@ -26,6 +26,8 @@ struct BannerOverlayOptions {
}; };
} // namespace graphics } // namespace graphics
bool shouldWakeOnReceivedMessage();
#if !HAS_SCREEN #if !HAS_SCREEN
#include "power.h" #include "power.h"
namespace graphics namespace graphics
@ -92,6 +94,7 @@ class Screen
#include "commands.h" #include "commands.h"
#include "concurrency/LockGuard.h" #include "concurrency/LockGuard.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "graphics/draw/MenuHandler.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
#include "mesh/MeshModule.h" #include "mesh/MeshModule.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
@ -122,6 +125,8 @@ class Screen
#define SEGMENT_WIDTH 16 #define SEGMENT_WIDTH 16
#define SEGMENT_HEIGHT 4 #define SEGMENT_HEIGHT 4
extern bool wake_on_received_message;
/// Convert an integer GPS coords to a floating point /// Convert an integer GPS coords to a floating point
#define DegD(i) (i * 1e-7) #define DegD(i) (i * 1e-7)
extern bool hasUnreadMessage; extern bool hasUnreadMessage;
@ -308,9 +313,15 @@ class Screen : public concurrency::OSThread
void showSimpleBanner(const char *message, uint32_t durationMs = 0); void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions); void showOverlayBanner(BannerOverlayOptions);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback); void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback); void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
graphics::menuHandler::menuQueue = menuToShow;
runNow();
}
void startFirmwareUpdateScreen() void startFirmwareUpdateScreen()
{ {
ScreenCmd cmd; ScreenCmd cmd;

View File

@ -13,6 +13,7 @@
#include "main.h" #include "main.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h" #include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
extern uint16_t TFT_MESH; extern uint16_t TFT_MESH;
@ -128,11 +129,11 @@ void menuHandler::ClockFacePicker()
screen->runNow(); screen->runNow();
} else if (selected == Digital) { } else if (selected == Digital) {
uiconfig.is_clockface_analog = false; uiconfig.is_clockface_analog = false;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK); screen->setFrames(Screen::FOCUS_CLOCK);
} else { } else {
uiconfig.is_clockface_analog = true; uiconfig.is_clockface_analog = true;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK); screen->setFrames(Screen::FOCUS_CLOCK);
} }
}; };
@ -237,27 +238,25 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu() void menuHandler::messageResponseMenu()
{ {
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
static const char **optionsArrayPtr;
int options;
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3 };
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"}; optionsArray[options] = "Reply via Freetext";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
optionsArrayPtr = optionsArray;
options = 3;
} }
#ifdef HAS_I2S #ifdef HAS_I2S
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"}; optionsArray[options] = "Read Aloud";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Aloud;
options = 5;
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action"; bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) { if (selected == Dismiss) {
@ -276,7 +275,7 @@ void menuHandler::messageResponseMenu()
} }
} }
#ifdef HAS_I2S #ifdef HAS_I2S
else if (selected == 4) { else if (selected == Aloud) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message; const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes); const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@ -289,10 +288,10 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu() void menuHandler::homeBaseMenu()
{ {
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep }; enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd };
static const char *optionsArray[6] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[6] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
#ifdef PIN_EINK_EN #ifdef PIN_EINK_EN
@ -347,37 +346,28 @@ void menuHandler::homeBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
// Check if brightness is supported // Check if brightness is supported
bool hasSupportBrightness = false; bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT #if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true; hasSupportBrightness = true;
#endif #endif
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test }; enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd };
static const char *optionsArray[7] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[7] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
optionsArray[options] = "Reboot"; optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Reboot; optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
optionsArray[options] = "Beeps Action"; defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsEnumArray[options++] = Beeps; optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = Color;
#endif
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif #endif
optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) { if (test_enabled) {
optionsArray[options] = "Test Menu"; optionsArray[options] = "Test Menu";
optionsEnumArray[options++] = Test; optionsEnumArray[options++] = Test;
@ -389,20 +379,14 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Beeps) { if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker; menuHandler::menuQueue = menuHandler::notifications_menu;
screen->runNow(); screen->runNow();
} else if (selected == Brightness) { } else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::brightness_picker; menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow(); screen->runNow();
} else if (selected == Reboot) { } else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::reboot_menu; menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else if (selected == Color) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow(); screen->runNow();
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu; menuHandler::menuQueue = menuHandler::test_menu;
@ -419,21 +403,22 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu() void menuHandler::favoriteBaseMenu()
{ {
int options; enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
static const char **optionsArrayPtr; static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"}; optionsArray[options] = "New Freetext Msg";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 3;
} }
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action"; bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == 1) {
@ -450,34 +435,29 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu() void menuHandler::positionBaseMenu()
{ {
int options; enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"}; static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"}; static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
if (accelerometerThread) { if (accelerometerThread) {
optionsArrayPtr = optionsArrayCalibrate; optionsArray[options] = "Compass Calibrate";
options = 4; optionsEnumArray[options++] = CompassCalibrate;
} else {
optionsArrayPtr = optionsArray;
options = 3;
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action"; bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == GPSToggle) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
menuQueue = gps_toggle_menu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
#endif } else if (selected == CompassMenu) {
} else if (selected == 2) {
menuQueue = compass_point_north_menu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
} else if (selected == 3) { } else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30); accelerometerThread->calibrate(30);
} }
}; };
@ -486,16 +466,20 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu() void menuHandler::nodeListMenu()
{ {
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"}; enum optionsNumbers { Back, Favorite, Verify, Reset };
static const char *optionsArray[] = {"Back", "Add Favorite", "Key Verification", "Reset NodeDB"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action"; bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Favorite) {
menuQueue = add_favorite; menuQueue = add_favorite;
screen->runNow(); screen->runNow();
} else if (selected == 2) { } else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu; menuQueue = reset_node_db_menu;
screen->runNow(); screen->runNow();
} }
@ -523,6 +507,7 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu() void menuHandler::compassNorthMenu()
{ {
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"}; static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?"; bannerOptions.message = "North Directions?";
@ -530,28 +515,25 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1; bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) { if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC; uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 2) { } else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING; uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 3) { } else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING; uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 0) { } else if (selected == Back) {
menuQueue = position_base_menu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} }
@ -562,6 +544,7 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu() void menuHandler::GPSToggleMenu()
{ {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS"; bannerOptions.message = "Toggle GPS";
@ -609,7 +592,7 @@ void menuHandler::BuzzerModeMenu()
{ {
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"}; static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Beep Action"; bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
@ -659,7 +642,7 @@ void menuHandler::BrightnessPickerMenu()
#endif #endif
// Save to device // Save to device
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness); LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness);
} }
@ -670,13 +653,13 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu() void menuHandler::switchToMUIMenu()
{ {
static const char *optionsArray[] = {"Yes", "No"}; static const char *optionsArray[] = {"No", "Yes"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?"; bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 1) {
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
config.bluetooth.enabled = false; config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
@ -741,6 +724,9 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
TFT_MESH_r = 255; TFT_MESH_r = 255;
TFT_MESH_g = 255; TFT_MESH_g = 255;
TFT_MESH_b = 255; TFT_MESH_b = 255;
} else {
menuQueue = system_base_menu;
screen->runNow();
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
@ -770,7 +756,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b; uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
} }
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color); LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
} }
#endif #endif
}; };
@ -789,6 +775,29 @@ void menuHandler::rebootMenu()
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0)); IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
nodeDB->saveToDisk(); nodeDB->saveToDisk();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
IF_SCREEN(screen->showSimpleBanner("Shutting Down...", 0));
nodeDB->saveToDisk();
power->shutdown();
} else {
menuQueue = power_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@ -796,7 +805,7 @@ void menuHandler::rebootMenu()
void menuHandler::addFavoriteMenu() void menuHandler::addFavoriteMenu()
{ {
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void { screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum); LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum); nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@ -887,6 +896,148 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness && !HAS_TFT) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::keyVerificationInitMenu()
{
screen->showNodePicker("Node to Verify", 30000,
[](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); });
}
void menuHandler::keyVerificationFinalPrompt()
{
char message[40] = {0};
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != menu_none) if (menuQueue != menu_none)
@ -909,6 +1060,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case clock_menu: case clock_menu:
clockMenu(); clockMenu();
break; break;
case system_base_menu:
systemBaseMenu();
break;
case position_base_menu: case position_base_menu:
positionBaseMenu(); positionBaseMenu();
break; break;
@ -938,6 +1092,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case reboot_menu: case reboot_menu:
rebootMenu(); rebootMenu();
break; break;
case shutdown_menu:
shutdownMenu();
break;
case add_favorite: case add_favorite:
addFavoriteMenu(); addFavoriteMenu();
break; break;
@ -953,13 +1110,36 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case wifi_toggle_menu: case wifi_toggle_menu:
wifiToggleMenu(); wifiToggleMenu();
break; break;
case key_verification_init:
keyVerificationInitMenu();
break;
case key_verification_final_prompt:
keyVerificationFinalPrompt();
break;
case bluetooth_toggle_menu: case bluetooth_toggle_menu:
BluetoothToggleMenu(); BluetoothToggleMenu();
break; break;
case notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;
case power_menu:
powerMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
} }
menuQueue = menu_none; menuQueue = menu_none;
} }
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
} // namespace graphics } // namespace graphics
#endif #endif

View File

@ -1,3 +1,5 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h" #include "configuration.h"
namespace graphics namespace graphics
{ {
@ -21,12 +23,20 @@ class menuHandler
tftcolormenupicker, tftcolormenupicker,
brightness_picker, brightness_picker,
reboot_menu, reboot_menu,
shutdown_menu,
add_favorite, add_favorite,
remove_favorite, remove_favorite,
test_menu, test_menu,
number_test, number_test,
wifi_toggle_menu, wifi_toggle_menu,
bluetooth_toggle_menu bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
key_verification_init,
key_verification_final_prompt,
throttle_message
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
@ -50,13 +60,23 @@ class menuHandler
static void resetNodeDBMenu(); static void resetNodeDBMenu();
static void BrightnessPickerMenu(); static void BrightnessPickerMenu();
static void rebootMenu(); static void rebootMenu();
static void shutdownMenu();
static void addFavoriteMenu(); static void addFavoriteMenu();
static void removeFavoriteMenu(); static void removeFavoriteMenu();
static void testMenu(); static void testMenu();
static void numberTest(); static void numberTest();
static void wifiBaseMenu(); static void wifiBaseMenu();
static void wifiToggleMenu(); static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
private:
static void saveUIConfig();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu(); static void BluetoothToggleMenu();
}; };
} // namespace graphics } // namespace graphics
#endif

View File

@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics namespace graphics
{ {
char NotificationRenderer::inEvent = INPUT_BROKER_NONE; InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0; int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0}; char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
@ -72,11 +72,25 @@ void NotificationRenderer::resetBanner()
{ {
alertBannerMessage[0] = '\0'; alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none; current_notification_type = notificationTypeEnum::none;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
pauseBanner = false;
numDigits = 0;
currentNumber = 0;
nodeDB->pause_sort(false); nodeDB->pause_sort(false);
} }
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{ {
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
if (!isOverlayBannerShowing() || pauseBanner) if (!isOverlayBannerShowing() || pauseBanner)
return; return;
switch (current_notification_type) { switch (current_notification_type) {
@ -115,31 +129,40 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract // modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1)); uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (this_digit == 9) { if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber += (pow_of_10(numDigits - curSelected - 1)); currentNumber += (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
if (this_digit == 0) { if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber -= (pow_of_10(numDigits - curSelected - 1)); currentNumber -= (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) { } else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_LEFT) { } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
curSelected--; curSelected--;
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == numDigits) { if (curSelected == numDigits) {
resetBanner();
alertBannerCallback(currentNumber); alertBannerCallback(currentNumber);
resetBanner();
return;
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@ -193,16 +216,18 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
resetBanner();
alertBannerCallback(selectedNodenum); alertBannerCallback(selectedNodenum);
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@ -210,7 +235,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@ -308,11 +333,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input // Handle input
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) { if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]); alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr; optionsEnumPtr = nullptr;
@ -320,8 +345,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected); alertBannerCallback(curSelected);
} }
resetBanner(); resetBanner();
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@ -329,12 +357,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
} else { } else {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) { if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
resetBanner(); resetBanner();
return;
} }
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;

View File

@ -11,7 +11,8 @@ namespace graphics
class NotificationRenderer class NotificationRenderer
{ {
public: public:
static char inEvent; static InputEvent inEvent;
static char inKeypress;
static int8_t curSelected; static int8_t curSelected;
static char alertBannerMessage[256]; static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever static uint32_t alertBannerUntil; // 0 is a special case meaning forever

View File

@ -13,8 +13,9 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
#if HAS_SCREEN #if HAS_SCREEN
#define IF_SCREEN(X) \ #define IF_SCREEN(X) \
if (screen) \ if (screen) { \
X; X; \
}
#else #else
#define IF_SCREEN(...) #define IF_SCREEN(...)
#endif #endif

View File

@ -1327,12 +1327,6 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code, LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code,
inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y); inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y);
// Validate input parameters
if (inputEvent.event_code > INPUT_BROKER_ANYKEY) {
LOG_WARN("Invalid input event code: %u", inputEvent.event_code);
return;
}
// Create InputEvent for injection // Create InputEvent for injection
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code, InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
.kbchar = (unsigned char)inputEvent.kb_char, .kbchar = (unsigned char)inputEvent.kb_char,

View File

@ -716,7 +716,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
} }
// Backspace // Backspace
if (event->inputEvent == INPUT_BROKER_BACK) { if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
payload = 0x08; payload = 0x08;
lastTouchMillis = millis(); lastTouchMillis = millis();
runOnce(); runOnce();
@ -739,7 +739,8 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
} }
// Cancel (dismiss freetext screen) // Cancel (dismiss freetext screen)
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) { if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG ||
(event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) {
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
freetext = ""; freetext = "";
cursor = 0; cursor = 0;
@ -989,6 +990,7 @@ int32_t CannedMessageModule::runOnce()
} }
this->cursor--; this->cursor--;
} }
} else {
} }
break; break;
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler

View File

@ -2,7 +2,9 @@
#include "KeyVerificationModule.h" #include "KeyVerificationModule.h"
#include "MeshService.h" #include "MeshService.h"
#include "RTC.h" #include "RTC.h"
#include "graphics/draw/MenuHandler.h"
#include "main.h" #include "main.h"
#include "meshUtils.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include <SHA256.h> #include <SHA256.h>
@ -48,18 +50,22 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r) bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r)
{ {
updateState(); updateState();
if (mp.pki_encrypted == false) if (mp.pki_encrypted == false) {
return false; return false;
if (mp.from != currentRemoteNode) // because the inital connection request is handled in allocReply() }
if (mp.from != currentRemoteNode) { // because the inital connection request is handled in allocReply()
return false; return false;
}
if (currentState == KEY_VERIFICATION_IDLE) { if (currentState == KEY_VERIFICATION_IDLE) {
return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply() return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply()
}
} else if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 && if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) { r->hash1.size == 0) {
memcpy(hash2, r->hash2.bytes, 32); memcpy(hash2, r->hash2.bytes, 32);
if (screen) IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void {
screen->showSimpleBanner("Enter Security Number", 30000); keyVerificationModule->processSecurityNumber(number_picked);
});)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
@ -79,23 +85,20 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
memset(message, 0, sizeof(message)); memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n"); sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); generateVerificationCode(message + 15);
static const char *optionsArray[] = {"ACCEPT", "REJECT"};
LOG_INFO("Hash1 matches!"); LOG_INFO("Hash1 matches!");
if (screen) { static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options; // Don't try to put the array definition in the macro. Does not work with curly braces.
options.message = message; IF_SCREEN(graphics::BannerOverlayOptions options; options.message = message; options.durationMs = 30000;
options.durationMs = 30000; options.optionsArrayPtr = optionsArray; options.optionsCount = 2;
options.optionsArrayPtr = optionsArray; options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.optionsCount = 2; options.bannerCallback =
options.notificationType = graphics::notificationTypeEnum::selection_picker; [=](int selected) {
options.bannerCallback = [=](int selected) { if (selected == 1) {
if (selected == 0) { auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode); remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; }
} };
}; screen->showOverlayBanner(options);)
screen->showOverlayBanner(options);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message); sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
@ -120,6 +123,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode)
// generate nonce // generate nonce
updateState(); updateState();
if (currentState != KEY_VERIFICATION_IDLE) { if (currentState != KEY_VERIFICATION_IDLE) {
IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;)
return false; return false;
} }
currentNonce = random(); currentNonce = random();
@ -190,11 +194,8 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
responsePacket = allocDataProtobuf(response); responsePacket = allocDataProtobuf(response);
responsePacket->pki_encrypted = true; responsePacket->pki_encrypted = true;
if (screen) { IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000); screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);)
screen->showSimpleBanner(message, 30000);
LOG_WARN("%s", message);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000, sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000,
@ -258,12 +259,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
p->priority = meshtastic_MeshPacket_Priority_HIGH; p->priority = meshtastic_MeshPacket_Priority_HIGH;
service->sendToMesh(p, RX_SRC_LOCAL, true); service->sendToMesh(p, RX_SRC_LOCAL, true);
currentState = KEY_VERIFICATION_SENDER_AWAITING_USER; currentState = KEY_VERIFICATION_SENDER_AWAITING_USER;
memset(message, 0, sizeof(message)); IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);)
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
screen->showSimpleBanner(message, 30000);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message); sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
@ -282,7 +278,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
void KeyVerificationModule::updateState() void KeyVerificationModule::updateState()
{ {
if (currentState != KEY_VERIFICATION_IDLE) { if (currentState != KEY_VERIFICATION_IDLE) {
// check for the 30 second timeout // check for the 60 second timeout
if (currentNonceTimestamp < getTime() - 60) { if (currentNonceTimestamp < getTime() - 60) {
resetToIdle(); resetToIdle();
} else { } else {

View File

@ -27,6 +27,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
}*/ }*/
virtual bool wantUIFrame() { return false; }; virtual bool wantUIFrame() { return false; };
bool sendInitialRequest(NodeNum remoteNode); bool sendInitialRequest(NodeNum remoteNode);
void generateVerificationCode(char *); // fills char with the user readable verification code
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
protected: protected:
/* Called to handle a particular incoming message /* Called to handle a particular incoming message
@ -56,9 +58,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
char message[40] = {0}; char message[40] = {0};
void processSecurityNumber(uint32_t); void processSecurityNumber(uint32_t);
void updateState(); // check the timeouts and maybe reset the state to idle void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state void resetToIdle(); // Zero out module state
void generateVerificationCode(char *); // fills char with the user readable verification code
}; };
extern KeyVerificationModule *keyVerificationModule; extern KeyVerificationModule *keyVerificationModule;

View File

@ -4,6 +4,7 @@
#include "PowerFSM.h" #include "PowerFSM.h"
#include "buzz.h" #include "buzz.h"
#include "configuration.h" #include "configuration.h"
#include "graphics/Screen.h"
TextMessageModule *textMessageModule; TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
@ -17,7 +18,11 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
devicestate.rx_text_message = mp; devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true; devicestate.has_rx_text_message = true;
powerFSM.trigger(EVENT_RECEIVED_MSG); wake_on_received_message = shouldWakeOnReceivedMessage();
// Only trigger screen wake if configuration allows it
if (wake_on_received_message) {
powerFSM.trigger(EVENT_RECEIVED_MSG);
}
notifyObservers(&mp); notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want return ProcessMessage::CONTINUE; // Let others look at this message also if they want

View File

@ -1,4 +1,4 @@
; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 ; Seeed Xiao BLE: https://wiki.seeedstudio.com/XIAO_BLE/
[env:seeed_xiao_nrf52840_kit] [env:seeed_xiao_nrf52840_kit]
extends = nrf52840_base extends = nrf52840_base
board = xiao_ble_sense board = xiao_ble_sense

View File

@ -68,6 +68,7 @@
#define TB_LEFT 1 #define TB_LEFT 1
#define TB_RIGHT 2 #define TB_RIGHT 2
#define TB_PRESS 0 // BUTTON_PIN #define TB_PRESS 0 // BUTTON_PIN
#define TB_DIRECTION FALLING
// microphone // microphone
#define ES7210_SCK 47 #define ES7210_SCK 47

View File

@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 2 major = 2
minor = 7 minor = 7
build = 2 build = 3