Files
firmware/src/graphics/Screen.cpp

2860 lines
111 KiB
C++
Raw Normal View History

2020-02-07 13:51:17 -08:00
/*
SSD1306 - Screen module
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Screen.h"
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
#if HAS_SCREEN
2020-03-18 17:16:19 -07:00
#include <OLEDDisplay.h>
#include "DisplayFormatters.h"
#if !MESHTASTIC_EXCLUDE_GPS
2020-02-07 13:51:17 -08:00
#include "GPS.h"
#endif
#include "ButtonThread.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "error.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
2024-02-28 08:01:59 -06:00
#include "graphics/ScreenFonts.h"
2020-07-07 10:46:49 +02:00
#include "graphics/images.h"
#include "input/ScanAndSelect.h"
#include "input/TouchScreenImpl1.h"
2020-03-18 17:16:19 -07:00
#include "main.h"
#include "mesh-pb-constants.h"
2021-02-16 15:41:52 +08:00
#include "mesh/Channels.h"
2023-01-18 08:56:47 -06:00
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "meshUtils.h"
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
#include "modules/AdminModule.h"
2022-12-27 21:51:35 +01:00
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h"
#include "sleep.h"
2020-09-25 12:52:26 -07:00
#include "target_specific.h"
2020-02-07 13:51:17 -08:00
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h"
#endif
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
2024-09-27 21:22:27 -05:00
#include "modules/StoreForwardModule.h"
2021-01-09 17:50:58 -08:00
#endif
#if ARCH_PORTDUINO
2024-09-27 21:22:27 -05:00
#include "modules/StoreForwardModule.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
2020-07-07 10:46:49 +02:00
using namespace meshtastic; /** @todo remove */
2020-02-07 13:51:17 -08:00
2020-07-07 10:46:49 +02:00
namespace graphics
2020-03-18 17:16:19 -07:00
{
2020-02-21 04:57:08 -08:00
2020-12-26 13:36:21 +08:00
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
#define IDLE_FRAMERATE 1 // in fps
2020-12-26 13:36:21 +08:00
// DEBUG
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
// if defined a pixel will blink to show redraws
// #define SHOW_REDRAWS
// A text message frame + debug frame + all the node infos
FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
uint32_t logo_timeout = 5000; // 4 seconds for EACH logo
2023-02-05 01:49:07 +01:00
uint32_t hours_in_month = 730;
2023-02-05 01:46:16 +01:00
// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function
uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
// Threshold values for the GPS lock accuracy bar display
uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
// At some point, we're going to ask all of the modules if they would like to display a screen frame
// we'll need to hold onto pointers for the modules that can draw a frame.
std::vector<MeshModule *> moduleFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
// vector where symbols (string) are displayed in bottom corner of display.
2024-11-19 05:52:20 -07:00
std::vector<std::string> functionSymbol;
// string displayed in bottom right corner of display. Created from elements in functionSymbol vector
std::string functionSymbolString = "";
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
#if HAS_GPS
2021-10-03 15:52:46 -04:00
// GeoCoord object for the screen
GeoCoord geoCoord;
#endif
2021-10-03 15:52:46 -04:00
2020-07-01 10:09:06 -07:00
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
2020-07-01 10:09:06 -07:00
#endif
// Quick access to screen dimensions from static drawing functions
// DEPRECATED. To-do: move static functions inside Screen class
#define SCREEN_WIDTH display->getWidth()
#define SCREEN_HEIGHT display->getHeight()
#include "graphics/ScreenFonts.h"
#include <Throttle.h>
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
// Don't want to make any assumptions about custom language support
return true;
#endif
// Check each character with the lookup function for the OLED library
// We're not really meant to use this directly..
bool have = true;
for (uint16_t i = 0; i < strlen(str); i++) {
uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
// If font doesn't support a character, it is substituted for ¿
if (result == 191 && (uint8_t)str[i] != 191) {
have = false;
break;
}
}
LOG_DEBUG("haveGlyphs=%d", have);
return have;
}
/**
* Draw the icon with extra info printed around the corners
*/
static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
2020-02-07 13:51:17 -08:00
{
// draw an xbm image.
// Please note that everything that should be transitioned
// needs to be drawn relative to x and y
2020-02-07 14:52:45 -08:00
// draw centered icon left to right and centered above the one line of app text
2020-10-21 19:18:03 +08:00
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, icon_bits);
2020-02-07 14:52:45 -08:00
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *title = "meshtastic.org";
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
2020-10-21 17:27:13 +08:00
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
2020-10-21 17:27:13 +08:00
// Draw version and short name in upper right
char buf[25];
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
2020-10-15 15:56:38 +08:00
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
#ifdef USERPREFS_OEM_TEXT
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
switch (USERPREFS_OEM_FONT_SIZE) {
case 0:
display->setFont(FONT_SMALL);
break;
case 2:
display->setFont(FONT_LARGE);
break;
default:
display->setFont(FONT_MEDIUM);
break;
}
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *title = USERPREFS_OEM_TEXT;
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version and shortname in upper right
char buf[25];
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawOEMIconScreen(region, display, state, x, y);
}
#endif
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
{
uint16_t x_offset = display->width() / 2;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, 26 + y, message);
}
2021-11-26 15:09:16 -05:00
// Used on boot when a certificate is being created
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawString(64 + x, y, "Creating SSL certificate");
#ifdef ARCH_ESP32
yield();
esp_task_wdt_reset();
#endif
2021-11-26 15:09:16 -05:00
display->setFont(FONT_SMALL);
if ((millis() / 1000) % 2) {
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . .");
} else {
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ");
}
2021-11-26 15:09:16 -05:00
}
2022-04-10 19:15:10 -07:00
// Used when booting without a region set
static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C");
display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName());
display->setTextAlignment(TEXT_ALIGN_LEFT);
if ((millis() / 10000) % 2) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,");
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients.");
} else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information.");
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "");
}
2022-04-10 19:15:10 -07:00
#ifdef ARCH_ESP32
2022-04-10 19:15:10 -07:00
yield();
esp_task_wdt_reset();
#endif
}
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active
static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
// LOG_DEBUG("Draw function overlay");
2024-11-19 05:52:20 -07:00
if (functionSymbol.begin() != functionSymbol.end()) {
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
char buf[64];
display->setFont(FONT_SMALL);
2024-11-19 05:52:20 -07:00
snprintf(buf, sizeof(buf), "%s", functionSymbolString.c_str());
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
display->drawString(SCREEN_WIDTH - display->getStringWidth(buf), SCREEN_HEIGHT - FONT_HEIGHT_SMALL, buf);
}
}
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
2024-03-10 05:00:51 +13:00
// Next frame should use full-refresh, and block while running, else device will sleep before async callback
EINK_ADD_FRAMEFLAG(display, COSMETIC);
EINK_ADD_FRAMEFLAG(display, BLOCKING);
LOG_DEBUG("Draw deep sleep screen");
// Display displayStr on the screen
drawIconScreen("Sleeping", display, state, x, y);
2020-02-07 13:51:17 -08:00
}
/// Used on eink displays when screen updates are paused
static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
LOG_DEBUG("Draw screensaver overlay");
EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
// Config
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *pauseText = "Screen Paused";
const char *idText = owner.short_name;
const bool useId = haveGlyphs(idText); // This bool is used to hide the idText box if we can't render the short name
constexpr uint16_t padding = 5;
constexpr uint8_t dividerGap = 1;
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
// Dimensions
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText), true); // "true": handle utf8 chars
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
const uint16_t boxWidth = padding + (useId ? idTextWidth + padding + padding : 0) + pauseTextWidth + padding;
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
// Position
const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
// const int16_t boxRight = boxLeft + boxWidth - 1;
const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
const int16_t boxBottom = boxTop + boxHeight - 1;
const int16_t idTextLeft = boxLeft + padding;
const int16_t idTextTop = boxTop + padding;
const int16_t pauseTextLeft = boxLeft + (useId ? padding + idTextWidth + padding : 0) + padding;
const int16_t pauseTextTop = boxTop + padding;
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
const int16_t dividerTop = boxTop + 1 + dividerGap;
const int16_t dividerBottom = boxBottom - 1 - dividerGap;
// Draw: box
display->setColor(EINK_WHITE);
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
display->setColor(EINK_BLACK);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Draw: Text
if (useId)
display->drawString(idTextLeft, idTextTop, idText);
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
// Draw: divider
if (useId)
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
}
2021-03-08 17:09:35 +08:00
#endif
2020-02-07 13:51:17 -08:00
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
2022-02-27 01:49:24 -08:00
uint8_t module_frame;
// there's a little but in the UI transition code
// where it invokes the function at the correct offset
// in the array of "drawScreen" functions; however,
// the passed-state doesn't quite reflect the "current"
// screen, so we have to detect it.
if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == TransitionRelationship_INCOMING) {
// if we're transitioning from the end of the frame list back around to the first
// frame, then we want this to be `0`
2022-02-27 01:49:24 -08:00
module_frame = state->transitionFrameTarget;
} else {
// otherwise, just display the module frame that's aligned with the current frame
2022-02-27 01:49:24 -08:00
module_frame = state->currentFrame;
// LOG_DEBUG("Screen is not in transition. Frame: %d", module_frame);
}
// LOG_DEBUG("Draw Module Frame %d", module_frame);
MeshModule &pi = *moduleFrames.at(module_frame);
pi.drawFrame(display, state, x, y);
}
static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(64 + x, y, "Updating");
display->setFont(FONT_SMALL);
2022-12-08 21:48:01 +01:00
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(),
"Please be patient and do not power off.");
}
2020-12-26 13:36:21 +08:00
/// Draw the last text message we received
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_MEDIUM);
char tempBuf[24];
snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code);
2020-12-26 13:36:21 +08:00
display->drawString(0 + x, 0 + y, tempBuf);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
2022-04-08 13:40:41 +10:00
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org");
2020-12-26 13:36:21 +08:00
}
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
{
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
{
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
// Clear the bar area on the battery image
for (int i = 1; i < 14; i++) {
imgBuffer[i] = 0x81;
}
// If charging, draw a charging indicator
if (powerStatus->getIsCharging()) {
memcpy(imgBuffer + 3, lightning, 8);
// If not charging, Draw power bars
} else {
for (int i = 0; i < 4; i++) {
if (powerStatus->getBatteryChargePercent() >= 25 * i)
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
}
}
display->drawFastImage(x, y, 16, 8, imgBuffer);
}
#if defined(DISPLAY_CLOCK_FRAME)
void Screen::drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
if (digitalMode) {
uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
display->drawCircle(centerX, centerY, radius);
display->drawCircle(centerX, centerY, radius + 1);
display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
} else {
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentOneX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
uint16_t segmentFourX = x;
uint16_t segmentFourY = y + segmentHeight + 2;
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
}
// Draw a digital clock
void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
display->setFont(FONT_SMALL);
display->drawString(x + 20, y + 2, batteryPercent);
}
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
display->setColor(OLEDDISPLAY_COLOR::WHITE);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
hour = hour > 12 ? hour - 12 : hour;
if (hour == 0) {
hour = 12;
}
// hours string
String hourString = String(hour);
// minutes string
String minuteString = minute < 10 ? "0" + String(minute) : String(minute);
String timeString = hourString + ":" + minuteString;
// seconds string
String secondString = second < 10 ? "0" + String(second) : String(second);
float scale = 1.5;
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// calculate hours:minutes string width
uint16_t timeStringWidth = timeString.length() * 5;
for (uint8_t i = 0; i < timeString.length(); i++) {
String character = String(timeString[i]);
if (character == ":") {
timeStringWidth += segmentHeight;
} else {
timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
}
}
// calculate seconds string width
uint16_t secondStringWidth = (secondString.length() * 12) + 4;
// sum these to get total string width
uint16_t totalWidth = timeStringWidth + secondStringWidth;
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 2);
uint16_t startingHourMinuteTextX = hourMinuteTextX;
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
// iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < timeString.length(); i++) {
String character = String(timeString[i]);
if (character == ":") {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale);
hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
}
hourMinuteTextX += 5;
}
// draw seconds string
display->setFont(FONT_MEDIUM);
display->drawString(startingHourMinuteTextX + timeStringWidth + 4,
(display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString);
}
}
void Screen::drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
uint16_t topAndBottomX = x + (4 * scale);
uint16_t quarterCellHeight = cellHeight / 4;
uint16_t topY = y + quarterCellHeight;
uint16_t bottomY = y + (quarterCellHeight * 3);
display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight);
display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight);
}
void Screen::drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
{
// the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
// segment {innerIndex + 1}
// e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
uint8_t numbers[10][7] = {
{1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
{0, 1, 1, 0, 0, 0, 0}, // 1 1
{1, 1, 0, 1, 1, 0, 1}, // 2 ___
{1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
{0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
{1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
{1, 0, 1, 1, 1, 1, 1}, // 6 |___|
{1, 1, 1, 0, 0, 1, 0}, // 7
{1, 1, 1, 1, 1, 1, 1}, // 8 4
{1, 1, 1, 1, 0, 1, 1}, // 9
};
// the width and height of each segment's central rectangle:
// _____________________
// ⋰| (only this part, |⋱
// ⋰ | not including | ⋱
// ⋱ | the triangles | ⋰
// ⋱| on the ends) |⋰
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// segment x and y coordinates
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentTwoX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2;
uint16_t segmentFourX = segmentOneX;
uint16_t segmentFourY = segmentThreeY + segmentWidth + 2;
uint16_t segmentFiveX = x;
uint16_t segmentFiveY = segmentThreeY;
uint16_t segmentSixX = x;
uint16_t segmentSixY = segmentTwoY;
uint16_t segmentSevenX = segmentOneX;
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
if (numbers[number][0]) {
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
}
if (numbers[number][1]) {
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
}
if (numbers[number][2]) {
drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
}
if (numbers[number][3]) {
drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
if (numbers[number][4]) {
drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
}
if (numbers[number][5]) {
drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
}
if (numbers[number][6]) {
drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
}
}
void Screen::drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, width, height);
// draw end triangles
display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight);
display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1);
}
void Screen::drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, height, width);
// draw end triangles
display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y);
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
}
void Screen::drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
}
// Draw an analog clock
void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
display->setFont(FONT_SMALL);
display->drawString(x + 20, y + 2, batteryPercent);
}
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2;
// clock face radius
int16_t radius = (display->getWidth() / 2) * 0.8;
// noon (0 deg) coordinates (outermost circle)
int16_t noonX = centerX;
int16_t noonY = centerY - radius;
// second hand radius and y coordinate (outermost circle)
int16_t secondHandNoonY = noonY + 1;
// tick mark outer y coordinate; (first nested circle)
int16_t tickMarkOuterNoonY = secondHandNoonY;
// seconds tick mark inner y coordinate; (second nested circle)
double secondsTickMarkInnerNoonY = (double)noonY + 8;
// hours tick mark inner y coordinate; (third nested circle)
double hoursTickMarkInnerNoonY = (double)noonY + 16;
// minute hand y coordinate
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
// hour string y coordinate
int16_t hourStringNoonY = minuteHandNoonY + 18;
// hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.55;
int16_t hourHandNoonY = centerY - hourHandRadius;
display->setColor(OLEDDISPLAY_COLOR::WHITE);
display->drawCircle(centerX, centerY, radius);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
hour = hour > 12 ? hour - 12 : hour;
int16_t degreesPerHour = 30;
int16_t degreesPerMinuteOrSecond = 6;
double hourBaseAngle = hour * degreesPerHour;
double hourAngleOffset = ((double)minute / 60) * degreesPerHour;
double hourAngle = radians(hourBaseAngle + hourAngleOffset);
double minuteBaseAngle = minute * degreesPerMinuteOrSecond;
double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond;
double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset);
double secondAngle = radians(second * degreesPerMinuteOrSecond);
double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX;
double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY;
double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX;
double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY;
double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX;
double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY;
display->setFont(FONT_MEDIUM);
// draw minute and hour tick marks and hour numbers
for (uint16_t angle = 0; angle < 360; angle += 6) {
double angleInRadians = radians(angle);
double sineAngleInRadians = sin(-angleInRadians);
double cosineAngleInRadians = cos(-angleInRadians);
double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX;
double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY;
if (angle % degreesPerHour == 0) {
double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY;
// draw hour tick mark
display->drawLine(startX, startY, endX, endY);
static char buffer[2];
uint8_t hourInt = (angle / 30);
if (hourInt == 0) {
hourInt = 12;
}
// hour number x offset needs to be adjusted for some cases
int8_t hourStringXOffset;
int8_t hourStringYOffset = 13;
switch (hourInt) {
case 3:
hourStringXOffset = 5;
break;
case 9:
hourStringXOffset = 7;
break;
case 10:
case 11:
hourStringXOffset = 8;
break;
case 12:
hourStringXOffset = 13;
break;
default:
hourStringXOffset = 6;
break;
}
double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset;
double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset;
// draw hour number
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
}
if (angle % degreesPerMinuteOrSecond == 0) {
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
// draw minute tick mark
display->drawLine(startX, startY, endX, endY);
}
}
// draw hour hand
display->drawLine(centerX, centerY, hourX, hourY);
// draw minute hand
display->drawLine(centerX, centerY, minuteX, minuteY);
// draw second hand
display->drawLine(centerX, centerY, secondX, secondY);
}
}
#endif
// Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
{
// Cache the result - avoid frequent recalculation
static uint8_t hoursCached = 0, minutesCached = 0;
static uint32_t daysAgoCached = 0;
static uint32_t secondsAgoCached = 0;
static bool validCached = false;
// Abort: if timezone not set
if (strlen(config.device.tzdef) == 0) {
validCached = false;
return validCached;
}
// Abort: if invalid pointers passed
if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) {
validCached = false;
return validCached;
}
// Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set)
if (secondsAgo > SEC_PER_DAY * 30UL * 6) {
validCached = false;
return validCached;
}
// If repeated request, don't bother recalculating
if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) {
if (validCached) {
*hours = hoursCached;
*minutes = minutesCached;
*daysAgo = daysAgoCached;
}
return validCached;
}
// Get local time
uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time
// Abort: if RTC not set
if (!secondsRTC) {
validCached = false;
return validCached;
}
// Get absolute time when last seen
uint32_t secondsSeenAt = secondsRTC - secondsAgo;
// Calculate daysAgo
*daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed
// Get seconds since midnight
uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into hours and minutes
*hours = hms / SEC_PER_HOUR;
*minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
// Cache the result
daysAgoCached = *daysAgo;
hoursCached = *hours;
minutesCached = *minutes;
secondsAgoCached = secondsAgo;
validCached = true;
return validCached;
}
2020-02-07 14:52:45 -08:00
/// Draw the last text message we received
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
2020-02-07 13:51:17 -08:00
{
// the max length of this buffer is much longer than we can possibly print
static char tempBuf[237];
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
// LOG_DEBUG("Draw text message from 0x%x: %s", mp.from,
// mp.decoded.variant.data.decoded.bytes);
2020-02-07 13:51:17 -08:00
// Demo for drawStringMaxWidth:
// with the third parameter you can define the width after which words will
// be wrapped. Currently only spaces and "-" are allowed for wrapping
2020-02-07 13:51:17 -08:00
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
2022-12-29 15:44:22 +01:00
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
// For time delta
uint32_t seconds = sinceReceived(&mp);
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
2023-01-25 17:41:54 +01:00
// For timestamp
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
bool useTimestamp = deltaToTimestamp(seconds, &timestampHours, &timestampMinutes, &daysAgo);
// If bold, draw twice, shifting right by one pixel
for (uint8_t xOff = 0; xOff <= (config.display.heading_bold ? 1 : 0); xOff++) {
// Show a timestamp if received today, but longer than 15 minutes ago
if (useTimestamp && minutes >= 15 && daysAgo == 0) {
display->drawStringf(xOff + x, 0 + y, tempBuf, "At %02hu:%02hu from %s", timestampHours, timestampMinutes,
(node && node->has_user) ? node->user.short_name : "???");
}
// Timestamp yesterday (if display is wide enough)
else if (useTimestamp && daysAgo == 1 && display->width() >= 200) {
display->drawStringf(xOff + x, 0 + y, tempBuf, "Yesterday %02hu:%02hu from %s", timestampHours, timestampMinutes,
(node && node->has_user) ? node->user.short_name : "???");
}
// Otherwise, show a time delta
else {
display->drawStringf(xOff + x, 0 + y, tempBuf, "%s ago from %s",
screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
(node && node->has_user) ? node->user.short_name : "???");
}
2022-12-29 15:44:22 +01:00
}
display->setColor(WHITE);
2024-05-22 20:40:26 -05:00
#ifndef EXCLUDE_EMOJI
2024-11-19 20:31:46 -07:00
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
if (strcmp(msg, "\U0001F44D") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
thumbup);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F44E") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
thumbdown);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F60A") == 0 || strcmp(msg, "\U0001F600") == 0 || strcmp(msg, "\U0001F642") == 0 ||
strcmp(msg, "\U0001F609") == 0 ||
strcmp(msg, "\U0001F601") == 0) { // matches 5 different common smileys, so that the phone user doesn't have to
// remember which one is compatible
display->drawXbm(x + (SCREEN_WIDTH - smiley_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - smiley_height) / 2 + 2 + 5, smiley_width, smiley_height,
smiley);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height,
question);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "‼️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5,
bang_width, bang_height, bang);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F4A9") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5,
poo_width, poo_height, poo);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F923") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5,
haha_width, haha_height, haha);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F44B") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width,
wave_icon_height, wave_icon);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F920") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height,
cowboy);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F42D") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height,
deadmau5);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\xE2\x98\x80\xEF\xB8\x8F") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5,
sun_width, sun_height, sun);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\u2614") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10,
rain_width, rain_height, rain);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "☁️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "🌫️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5,
fog_width, fog_height, fog);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "\U0001F608") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil);
2024-11-19 20:31:46 -07:00
} else if (strcmp(msg, "♥️") == 0 || strcmp(msg, "\U0001F9E1") == 0 || strcmp(msg, "\U00002763") == 0 ||
strcmp(msg, "\U00002764") == 0 || strcmp(msg, "\U0001F495") == 0 || strcmp(msg, "\U0001F496") == 0 ||
strcmp(msg, "\U0001F497") == 0 || strcmp(msg, "\U0001F498") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart);
} else {
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
}
2024-05-22 20:40:26 -05:00
#else
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
#endif
2020-02-07 13:51:17 -08:00
}
/// Draw a series of fields in a column, wrapping to multiple columns if needed
void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
2020-02-07 13:51:17 -08:00
{
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char **f = fields;
int xo = x, yo = y;
2020-03-18 17:16:19 -07:00
while (*f) {
display->drawString(xo, yo, *f);
2022-12-29 15:44:22 +01:00
if ((display->getColor() == BLACK) && config.display.heading_bold)
display->drawString(xo + 1, yo, *f);
display->setColor(WHITE);
yo += FONT_HEIGHT_SMALL;
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
xo += SCREEN_WIDTH / 2;
yo = 0;
}
f++;
}
}
// Draw nodes status
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus)
{
char usersString[20];
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x, y + 3, 8, 8, imgUser);
#else
display->drawFastImage(x, y, 8, 8, imgUser);
#endif
display->drawString(x + 10, y - 2, usersString);
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 11, y - 2, usersString);
}
#if HAS_GPS
// Draw GPS status summary
2020-06-28 18:17:52 -07:00
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
if (config.position.fixed_position) {
// GPS coordinates are currently fixed
display->drawString(x - 1, y - 2, "Fixed GPS");
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x, y - 2, "Fixed GPS");
return;
}
if (!gps->getIsConnected()) {
display->drawString(x, y - 2, "No GPS");
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 1, y - 2, "No GPS");
return;
}
2020-06-28 18:17:52 -07:00
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
if (!gps->getHasLock()) {
display->drawString(x + 8, y - 2, "No sats");
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 9, y - 2, "No sats");
return;
} else {
char satsString[3];
uint8_t bar[2] = {0};
// Draw DOP signal bars
for (int i = 0; i < 5; i++) {
if (gps->getDOP() <= dopThresholds[i])
bar[0] = ~((1 << (5 - i)) - 1);
else
bar[0] = 0b10000000;
// bar[1] = bar[0];
display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar);
}
// Draw satellite image
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
// Draw the number of satellites
snprintf(satsString, sizeof(satsString), "%u", gps->getNumSatellites());
display->drawString(x + 34, y - 2, satsString);
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 35, y - 2, satsString);
}
}
// Draw status when GPS is disabled or not present
static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
String displayLine;
int pos;
if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
pos = SCREEN_WIDTH - display->getStringWidth(displayLine);
} else {
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present"
: "GPS is disabled";
pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2;
}
display->drawString(x + pos, y, displayLine);
}
2020-09-26 18:37:51 -07:00
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
String displayLine = "";
if (!gps->getIsConnected() && !config.position.fixed_position) {
// displayLine = "No GPS Module";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) {
// displayLine = "No GPS Lock";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
2020-09-26 18:37:51 -07:00
} else {
2021-10-03 16:01:41 -04:00
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m";
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
2022-09-21 22:37:36 -04:00
displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft";
2020-09-26 18:37:51 -07:00
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
}
}
// Draw GPS status coordinates
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
auto gpsFormat = config.display.gps_format;
String displayLine = "";
if (!gps->getIsConnected() && !config.position.fixed_position) {
displayLine = "No GPS present";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) {
displayLine = "No GPS Lock";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
2022-11-10 13:50:38 +01:00
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) {
char coordinateLine[22];
if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7,
geoCoord.getLongitude() * 1e-7);
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(),
geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(),
geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code
2021-10-03 15:52:46 -04:00
geoCoord.getOLCCode(coordinateLine);
2023-01-21 18:39:58 +01:00
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
2021-10-03 15:52:46 -04:00
if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary");
else
snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(),
geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
}
// If fixed position, display text "Fixed GPS" alternating with the coordinates.
if (config.position.fixed_position) {
if ((millis() / 10000) % 2) {
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
} else {
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS");
}
} else {
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
}
} else {
char latLine[22];
char lonLine[22];
snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(),
geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(),
geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine);
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine);
}
}
}
#endif
/**
* Given a recent lat/lon return a guess of the heading the user is walking on.
2020-03-18 17:16:19 -07:00
*
* We keep a series of "after you've gone 10 meters, what is your heading since
* the last reference point?"
*/
float Screen::estimatedHeading(double lat, double lon)
{
static double oldLat, oldLon;
static float b;
2020-03-18 17:16:19 -07:00
if (oldLat == 0) {
// just prepare for next time
oldLat = lat;
oldLon = lon;
return b;
}
2021-10-09 13:28:51 -04:00
float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon);
if (d < 10) // haven't moved enough, just keep current bearing
return b;
2021-10-09 13:31:27 -04:00
b = GeoCoord::bearing(oldLat, oldLon, lat, lon);
oldLat = lat;
oldLon = lon;
return b;
}
/// We will skip one node - the one for us, so we just blindly loop over all
/// nodes
static size_t nodeIndex;
static int8_t prevFrame = -1;
// Draw the arrow pointing to a node's location
void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
{
Point tip(0.0f, 0.5f), tail(0.0f, -0.35f); // pointing up initially
float arrowOffsetX = 0.14f, arrowOffsetY = 1.0f;
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
for (int i = 0; i < 4; i++) {
arrowPoints[i]->rotate(headingRadian);
arrowPoints[i]->scale(compassDiam * 0.6);
arrowPoints[i]->translate(compassX, compassY);
}
/* Old arrow
display->drawLine(tip.x, tip.y, tail.x, tail.y);
display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y);
display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y);
display->drawLine(leftArrow.x, leftArrow.y, tail.x, tail.y);
display->drawLine(rightArrow.x, rightArrow.y, tail.x, tail.y);
*/
#ifdef USE_EINK
display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
#else
display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
#endif
display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y);
}
// Get a string representation of the time passed since something happened
void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
{
// Use an absolute timestamp in some cases.
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
bool useTimestamp = deltaToTimestamp(agoSecs, &timestampHours, &timestampMinutes, &daysAgo);
if (agoSecs < 120) // last 2 mins?
snprintf(timeStr, maxLength, "%u seconds ago", agoSecs);
// -- if suitable for timestamp --
else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE);
else if (useTimestamp && daysAgo == 0) // Today
snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes);
else if (useTimestamp && daysAgo == 1) // Yesterday
snprintf(timeStr, maxLength, "Seen yesterday");
else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method)
snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo);
// -- if using time delta instead --
else if (agoSecs < 120 * 60) // last 2 hrs
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60);
// Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data.
else if ((agoSecs / 60 / 60) < (hours_in_month * 6))
snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60);
else
snprintf(timeStr, maxLength, "unknown age");
}
void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
{
// If north is supposed to be at the top of the compass we want rotation to be +0
if (config.display.compass_north_top)
myHeading = -0;
/* N sign points currently not deleted*/
Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); // N sign points (N1-N4)
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
Point NC1(0.00f, 0.50f); // north circle center point
Point *rosePoints[] = {&N1, &N2, &N3, &N4, &NC1};
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
for (int i = 0; i < 5; i++) {
// North on compass will be negative of heading
rosePoints[i]->rotate(-myHeading);
rosePoints[i]->scale(compassDiam);
rosePoints[i]->translate(compassX, compassY);
}
/* changed the N sign to a small circle on the compass circle.
display->drawLine(N1.x, N1.y, N3.x, N3.y);
display->drawLine(N2.x, N2.y, N4.x, N4.y);
display->drawLine(N1.x, N1.y, N4.x, N4.y);
*/
display->drawCircle(NC1.x, NC1.y, 4); // North sign circle, 4px radius is sufficient for all displays.
}
uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
{
uint16_t diam = 0;
uint16_t offset = 0;
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
offset = FONT_HEIGHT_SMALL;
// get the smaller of the 2 dimensions and subtract 20
if (displayWidth > (displayHeight - offset)) {
diam = displayHeight - offset;
// if 2/3 of the other size would be smaller, use that
if (diam > (displayWidth * 2 / 3)) {
diam = displayWidth * 2 / 3;
}
} else {
diam = displayWidth;
if (diam > ((displayHeight - offset) * 2 / 3)) {
diam = (displayHeight - offset) * 2 / 3;
}
}
return diam - 20;
};
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// We only advance our nodeIndex if the frame # has changed - because
// drawNodeInfo will be called repeatedly while the frame is shown
2020-03-18 17:16:19 -07:00
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(nodeIndex);
if (n->num == nodeDB->getNodeNum()) {
// Don't show our node, just skip to next
nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
n = nodeDB->getMeshNodeByIndex(nodeIndex);
}
}
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(nodeIndex);
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
2022-12-29 15:44:22 +01:00
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
}
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
2020-02-11 11:03:03 -08:00
static char signalStr[20];
// section here to choose whether to display hops away rather than signal strength if more than 0 hops away.
if (node->hops_away > 0) {
snprintf(signalStr, sizeof(signalStr), "Hops Away: %d", node->hops_away);
} else {
snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
}
2020-02-11 11:03:03 -08:00
static char lastStr[20];
screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr));
2020-02-14 16:25:11 -08:00
static char distStr[20];
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
strncpy(distStr, "? mi ?°", sizeof(distStr)); // might not have location data
} else {
strncpy(distStr, "? km ?°", sizeof(distStr));
}
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
2022-12-29 15:44:22 +01:00
int16_t compassX = 0, compassY = 0;
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
// coordinates for the center of the compass/circle
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
2022-12-29 15:44:22 +01:00
compassY = y + SCREEN_HEIGHT / 2;
} else {
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
2022-12-29 15:44:22 +01:00
compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
}
bool hasNodeHeading = false;
2024-11-26 14:00:10 -06:00
if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
float myHeading;
if (screen->hasHeading())
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
screen->drawCompassNorth(display, compassX, compassY, myHeading);
2024-11-26 14:00:10 -06:00
if (nodeDB->hasValidPosition(node)) {
// display direction toward node
hasNodeHeading = true;
const meshtastic_PositionLite &p = node->position;
float d =
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
2022-09-21 22:37:36 -04:00
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2 * PI : bearingToOther;
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
2022-09-21 22:37:36 -04:00
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
2022-09-21 22:37:36 -04:00
else
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET,
bearingToOtherDegrees);
2022-09-21 22:37:36 -04:00
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
2022-09-21 22:37:36 -04:00
else
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
2022-09-21 22:37:36 -04:00
}
}
2020-02-11 09:39:47 -08:00
}
2022-12-29 15:44:22 +01:00
if (!hasNodeHeading) {
// direction to node is unknown so display question mark
// Debug info for gps lock errors
// LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d", !!ourNode, ourNode && hasValidPosition(ourNode),
// hasValidPosition(node));
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
2022-12-29 15:44:22 +01:00
}
display->drawCircle(compassX, compassY, compassDiam / 2);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->setColor(BLACK);
}
// Must be after distStr is populated
screen->drawColumns(display, x, y, fields);
}
#if defined(ESP_PLATFORM) && defined(USE_ST7789)
SPIClass SPI1(HSPI);
#endif
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
: concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32)
2020-10-15 15:56:38 +08:00
{
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
dispdev = new SH1106Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_ST7789)
#ifdef ESP_PLATFORM
2024-07-09 12:16:56 -05:00
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7789_SDA,
ST7789_MISO, ST7789_SCK);
#else
2024-07-09 12:16:56 -05:00
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
#endif
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
dispdev = new EInkDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_ST7567)
dispdev = new ST7567Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
2025-01-01 03:15:01 +11:00
#elif ARCH_PORTDUINO && !HAS_TFT
if (settingsMap[displayPanel] != no_screen) {
LOG_DEBUG("Make TFTDisplay!");
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
} else {
dispdev = new AutoOLEDWire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
isAUTOOled = true;
}
#else
dispdev = new AutoOLEDWire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
isAUTOOled = true;
#endif
ui = new OLEDDisplayUi(dispdev);
2020-10-10 09:57:57 +08:00
cmdQueue.setReader(this);
}
Screen::~Screen()
{
delete[] graphics::normalFrames;
}
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
*/
void Screen::doDeepSleep()
{
#ifdef USE_EINK
setOn(false, drawDeepSleepScreen);
#ifdef PIN_EINK_EN
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
#endif
#else
// Without E-Ink display:
setOn(false);
#endif
}
void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
{
if (!useDisplay)
2020-02-07 13:51:17 -08:00
return;
2020-03-18 17:16:19 -07:00
if (on != screenOn) {
if (on) {
LOG_INFO("Turn on screen");
buttonThread->setScreenFlag(true);
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
#ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
#if defined(ST7789_CS) && \
!defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
dispdev->displayOn();
#ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT);
2024-08-13 06:52:03 -05:00
digitalWrite(VTFT_CTRL, LOW);
ui->init();
#ifdef ESP_PLATFORM
2024-07-09 12:16:56 -05:00
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
#else
2024-07-09 12:16:56 -05:00
pinMode(VTFT_LEDA, OUTPUT);
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
#endif
#endif
enabled = true;
setInterval(0); // Draw ASAP
runASAP = true;
2020-03-18 17:16:19 -07:00
} else {
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
powerMon->clearState(meshtastic_PowerMon_State_Screen_On);
#ifdef USE_EINK
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver);
#endif
LOG_INFO("Turn off screen");
buttonThread->setScreenFlag(false);
#ifdef ELECROW_ThinkNode_M1
if (digitalRead(PIN_EINK_EN) == HIGH) {
digitalWrite(PIN_EINK_EN, LOW);
}
#endif
dispdev->displayOff();
#ifdef USE_ST7789
SPI1.end();
#if defined(ARCH_ESP32)
pinMode(VTFT_LEDA, ANALOG);
pinMode(VTFT_CTRL, ANALOG);
2024-08-13 06:52:03 -05:00
pinMode(ST7789_RESET, ANALOG);
pinMode(ST7789_RS, ANALOG);
pinMode(ST7789_NSS, ANALOG);
#else
nrf_gpio_cfg_default(VTFT_LEDA);
nrf_gpio_cfg_default(VTFT_CTRL);
nrf_gpio_cfg_default(ST7789_RESET);
nrf_gpio_cfg_default(ST7789_RS);
nrf_gpio_cfg_default(ST7789_NSS);
#endif
#endif
#ifdef T_WATCH_S3
PMU->disablePowerOutput(XPOWERS_ALDO2);
#endif
enabled = false;
}
2020-02-22 18:02:44 -08:00
screenOn = on;
2020-02-22 12:01:59 -08:00
}
2020-02-07 13:51:17 -08:00
}
void Screen::setup()
2020-02-07 13:51:17 -08:00
{
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
useDisplay = true;
2024-07-09 12:16:56 -05:00
#ifdef AutoOLEDWire_h
if (isAUTOOled)
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
#endif
2023-03-08 15:42:45 +01:00
#ifdef USE_SH1107_128_64
static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
2023-03-08 15:42:45 +01:00
#endif
#if defined(USE_ST7789) && defined(TFT_MESH)
// Heltec T114 and T190: honor a custom text color, if defined in variant.h
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
// Initialising the UI will init the display too.
ui->init();
displayWidth = dispdev->width();
displayHeight = dispdev->height();
2020-10-15 15:56:38 +08:00
ui->setTimePerTransition(0);
2020-10-24 08:16:15 +08:00
ui->setIndicatorPosition(BOTTOM);
2020-02-07 13:51:17 -08:00
// Defines where the first frame is located in the bar.
ui->setIndicatorDirection(LEFT_RIGHT);
ui->setFrameAnimation(SLIDE_LEFT);
// Don't show the page swipe dots while in boot screen.
ui->disableAllIndicators();
// Store a pointer to Screen so we can get to it from static functions.
ui->getUiState()->userData = this;
2020-02-07 13:51:17 -08:00
// Set the utf8 conversion function
dispdev->setFontTableLookupFunction(customFontTableLookup);
#ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Double the time if we have a custom logo
#endif
// Add frames.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
#ifdef ARCH_ESP32
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1) {
drawFrameText(display, state, x, y, "Resuming...");
} else
#endif
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawIconScreen(region, display, state, x, y);
}
};
ui->setFrames(alertFrames, 1);
// No overlays.
ui->setOverlays(nullptr, 0);
2020-02-07 13:51:17 -08:00
// Require presses to switch between frames.
ui->disableAutoTransition();
2020-02-07 13:51:17 -08:00
// Set up a log buffer with 3 lines, 32 chars each.
dispdev->setLogBuffer(3, 32);
2020-02-07 13:51:17 -08:00
2020-10-24 08:16:15 +08:00
#ifdef SCREEN_MIRROR
dispdev->mirrorScreen();
2022-10-04 09:47:54 +02:00
#else
// Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically
// flip it. If you have a headache now, you're welcome.
2022-10-04 09:47:54 +02:00
if (!config.display.flip_screen) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
2024-01-19 20:11:19 +01:00
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
2024-01-19 20:11:19 +01:00
#else
dispdev->flipScreenVertically();
2024-01-19 20:11:19 +01:00
#endif
2022-10-04 09:47:54 +02:00
}
2020-03-19 20:15:51 -07:00
#endif
// Get our hardware ID
uint8_t dmac[6];
getMacAddr(dmac);
snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]);
#if ARCH_PORTDUINO
handleSetOn(false); // force clean init
#endif
2020-03-19 20:15:51 -07:00
// Turn on the display.
handleSetOn(true);
// On some ssd1306 clones, the first draw command is discarded, so draw it
2022-08-04 10:35:40 +02:00
// twice initially. Skip this for EINK Displays to save a few seconds during boot
ui->update();
2022-08-04 10:35:40 +02:00
#ifndef USE_EINK
ui->update();
2022-08-04 10:35:40 +02:00
#endif
serialSinceMsec = millis();
#if ARCH_PORTDUINO && !HAS_TFT
if (settingsMap[touchscreenModule]) {
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init();
}
#elif HAS_TOUCHSCREEN
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init();
#endif
// Subscribe to status updates
2020-06-28 18:17:52 -07:00
powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
#if !MESHTASTIC_EXCLUDE_ADMIN
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
adminMessageObserver.observe(adminModule);
#endif
2022-02-27 02:21:02 -08:00
if (textMessageModule)
textMessageObserver.observe(textMessageModule);
2023-08-11 19:41:21 +02:00
if (inputBroker)
inputObserver.observe(inputBroker);
2022-01-13 09:19:36 +01:00
// Modules can notify screen about refresh
MeshModule::observeUIEvents(&uiFrameEventObserver);
2020-02-07 13:51:17 -08:00
}
void Screen::forceDisplay(bool forceUiUpdate)
2020-10-15 15:56:38 +08:00
{
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
#ifdef USE_EINK
// If requested, make sure queued commands are run, and UI has rendered a new frame
if (forceUiUpdate) {
// Force a display refresh, in addition to the UI update
// Changing the GPS status bar icon apparently doesn't register as a change in image
// (False negative of the image hashing algorithm used to skip identical frames)
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
// No delay between UI frame rendering
setFastFramerate();
// Make sure all CMDs have run first
while (!cmdQueue.isEmpty())
runOnce();
// Ensure at least one frame has drawn
uint64_t startUpdate;
do {
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
delay(10);
ui->update();
} while (ui->getUiState()->lastUpdate < startUpdate);
// Return to normal frame rate
targetFramerate = IDLE_FRAMERATE;
ui->setTargetFPS(targetFramerate);
}
// Tell EInk class to update the display
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
2020-10-15 15:56:38 +08:00
#endif
}
static uint32_t lastScreenTransition;
2020-10-10 09:57:57 +08:00
int32_t Screen::runOnce()
2020-02-07 13:51:17 -08:00
{
// If we don't have a screen, don't ever spend any CPU for us.
if (!useDisplay) {
enabled = false;
2020-10-10 09:57:57 +08:00
return RUN_SAME;
}
if (displayHeight == 0) {
displayHeight = dispdev->getHeight();
}
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
2020-10-10 09:57:57 +08:00
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) {
LOG_INFO("Done with boot screen");
2020-10-10 09:57:57 +08:00
stopBootScreen();
showingBootScreen = false;
}
2020-02-07 13:51:17 -08:00
#ifdef USERPREFS_OEM_TEXT
static bool showingOEMBootScreen = true;
if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
LOG_INFO("Switch to OEM screen...");
// Change frames.
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
ui->update();
#ifndef USE_EINK
ui->update();
#endif
showingOEMBootScreen = false;
}
#endif
2022-04-13 21:59:25 -07:00
#ifndef DISABLE_WELCOME_UNSET
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
2022-04-10 19:15:10 -07:00
setWelcomeFrames();
}
2022-04-13 21:59:25 -07:00
#endif
2022-04-10 19:15:10 -07:00
// Process incoming commands.
for (;;) {
2020-07-07 10:46:49 +02:00
ScreenCmd cmd;
if (!cmdQueue.dequeue(&cmd, 0)) {
break;
}
switch (cmd.cmd) {
case Cmd::SET_ON:
handleSetOn(true);
break;
case Cmd::SET_OFF:
handleSetOn(false);
break;
case Cmd::ON_PRESS:
handleOnPress();
break;
case Cmd::SHOW_PREV_FRAME:
handleShowPrevFrame();
break;
case Cmd::SHOW_NEXT_FRAME:
handleShowNextFrame();
break;
case Cmd::START_ALERT_FRAME: {
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
showingNormalScreen = false;
alertFrames[0] = alertFrame;
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
#endif
setFrameImmediateDraw(alertFrames);
break;
}
case Cmd::START_FIRMWARE_UPDATE_SCREEN:
handleStartFirmwareUpdateScreen();
break;
case Cmd::STOP_ALERT_FRAME:
case Cmd::STOP_BOOT_SCREEN:
2024-03-10 05:00:51 +13:00
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames();
break;
case Cmd::PRINT:
handlePrint(cmd.print_text);
free(cmd.print_text);
break;
default:
LOG_ERROR("Invalid screen cmd");
}
}
if (!screenOn) { // If we didn't just wake and the screen is still off, then
// stop updating until it is on again
enabled = false;
return 0;
}
2020-10-16 10:53:55 +08:00
// this must be before the frameState == FIXED check, because we always
// want to draw at least one FIXED frame before doing forceDisplay
ui->update();
2020-10-16 10:53:55 +08:00
// Switch to a low framerate (to save CPU) when we are not in transition
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.
if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) {
// oldFrameState = ui->getUiState()->frameState;
targetFramerate = IDLE_FRAMERATE;
ui->setTargetFPS(targetFramerate);
2020-10-15 15:56:38 +08:00
forceDisplay();
}
2020-02-07 15:37:25 -08:00
// While showing the bootscreen or Bluetooth pair screen all of our
// standard screen switching is stopped.
if (showingNormalScreen) {
// standard screen loop handling here
if (config.display.auto_screen_carousel_secs > 0 &&
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
// Carousel is potentially a major source of E-Ink display wear
#if !defined(EINK_BACKGROUND_USES_FAST)
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC);
#endif
LOG_DEBUG("LastScreenTransition exceeded %ums transition to next frame", (millis() - lastScreenTransition));
handleOnPress();
}
2020-02-07 15:37:25 -08:00
}
// LOG_DEBUG("want fps %d, fixed=%d", targetFramerate,
// ui->getUiState()->frameState); If we are scrolling we need to be called
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// as fast as we really need so that any rounding errors still result with
// the correct framerate
return (1000 / targetFramerate);
2020-02-07 13:51:17 -08:00
}
2020-02-07 17:48:12 -08:00
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
2022-01-24 18:39:17 +00:00
Screen *screen2 = reinterpret_cast<Screen *>(state->userData);
screen2->debugInfo.drawFrame(display, state, x, y);
}
void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
2022-01-24 18:39:17 +00:00
Screen *screen2 = reinterpret_cast<Screen *>(state->userData);
screen2->debugInfo.drawFrameSettings(display, state, x, y);
}
void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
2022-01-24 18:39:17 +00:00
Screen *screen2 = reinterpret_cast<Screen *>(state->userData);
screen2->debugInfo.drawFrameWiFi(display, state, x, y);
}
2021-11-26 15:09:16 -05:00
/* show a message that the SSL cert is being built
* it is expected that this will be used during the boot phase */
void Screen::setSSLFrames()
{
if (address_found.address) {
// LOG_DEBUG("Show SSL frames");
2021-12-18 11:02:54 -05:00
static FrameCallback sslFrames[] = {drawSSLScreen};
ui->setFrames(sslFrames, 1);
ui->update();
2021-12-18 11:02:54 -05:00
}
2021-11-26 15:09:16 -05:00
}
2022-04-10 19:15:10 -07:00
/* show a message that the SSL cert is being built
* it is expected that this will be used during the boot phase */
void Screen::setWelcomeFrames()
{
if (address_found.address) {
// LOG_DEBUG("Show Welcome frames");
static FrameCallback frames[] = {drawWelcomeScreen};
setFrameImmediateDraw(frames);
2022-04-10 19:15:10 -07:00
}
}
#ifdef USE_EINK
/// Determine which screensaver frame to use, then set the FrameCallback
void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
{
// Retain specified frame / overlay callback beyond scope of this method
static FrameCallback screensaverFrame;
static OverlayCallback screensaverOverlay;
#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY)
// Join (await) a currently running async refresh, then run the post-update code.
// Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread.
EINK_JOIN_ASYNCREFRESH(dispdev);
#endif
// If: one-off screensaver frame passed as argument. Handles doDeepSleep()
if (einkScreensaver != NULL) {
screensaverFrame = einkScreensaver;
ui->setFrames(&screensaverFrame, 1);
}
// Else, display the usual "overlay" screensaver
else {
screensaverOverlay = drawScreensaverOverlay;
ui->setOverlays(&screensaverOverlay, 1);
}
// Request new frame, ASAP
setFastFramerate();
uint64_t startUpdate;
do {
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
delay(1);
ui->update();
} while (ui->getUiState()->lastUpdate < startUpdate);
// Old EInkDisplay class
#if !defined(USE_EINK_DYNAMICDISPLAY)
static_cast<EInkDisplay *>(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
#endif
// Prepare now for next frame, shown when display wakes
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
ui->setOverlays(NULL, 0); // Clear overlay
setFrames(FOCUS_PRESERVE); // Return to normal display updates, showing same frame as before screensaver, ideally
// Pick a refresh method, for when display wakes
#ifdef EINK_HASQUIRK_GHOSTING
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
#else
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
#endif
}
#endif
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
// Regenerate the normal set of frames, focusing a specific frame if requested
// Called when a frame should be added / removed, or custom frames should be cleared
void Screen::setFrames(FrameFocus focus)
2020-02-08 07:55:12 -08:00
{
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
uint8_t originalPosition = ui->getUiState()->currentFrame;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
LOG_DEBUG("Show standard frames");
showingNormalScreen = true;
#ifdef USE_EINK
// If user has disabled the screensaver, warn them after boot
static bool warnedScreensaverDisabled = false;
if (config.display.screen_on_secs == 0 && !warnedScreensaverDisabled) {
screen->print("Screensaver disabled\n");
warnedScreensaverDisabled = true;
}
#endif
moduleFrames = MeshModule::GetMeshModulesWithUIFrames();
LOG_DEBUG("Show %d module frames", moduleFrames.size());
#ifdef DEBUG_PORT
2022-02-27 02:21:02 -08:00
int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size();
LOG_DEBUG("Total frame count: %d", totalFrameCount);
#endif
2024-06-04 14:02:43 -07:00
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
size_t numframes = 0;
2022-02-27 01:49:24 -08:00
// put all of the module frames first.
// this is a little bit of a dirty hack; since we're going to call
2022-02-27 01:49:24 -08:00
// the same drawModuleFrame handler here for all of these module frames
// and then we'll just assume that the state->currentFrame value
2022-02-27 01:49:24 -08:00
// is the same offset into the moduleFrames vector
// so that we can invoke the module's callback
2022-02-27 02:21:02 -08:00
for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) {
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
// Draw the module frame, using the hack described above
normalFrames[numframes] = drawModuleFrame;
// Check if the module being drawn has requested focus
// We will honor this request later, if setFrames was triggered by a UIFrameEvent
MeshModule *m = *i;
if (m->isRequestingFocus()) {
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.positions.focusedModule = numframes;
}
// Identify the position of specific modules, if we need to know this later
if (m == waypointModule)
fsi.positions.waypoint = numframes;
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
numframes++;
}
LOG_DEBUG("Added modules. numframes: %d", numframes);
2020-12-26 13:36:21 +08:00
// If we have a critical fault, show it first
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.positions.fault = numframes;
if (error_code) {
2020-12-26 13:36:21 +08:00
normalFrames[numframes++] = drawCriticalFaultFrame;
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame
}
2020-12-26 13:36:21 +08:00
#if defined(DISPLAY_CLOCK_FRAME)
normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame;
#endif
// If we have a text message - show it next, unless it's a phone message and we aren't using any special modules
if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) {
fsi.positions.textMessage = numframes;
normalFrames[numframes++] = drawTextMessageFrame;
}
// then all the nodes
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
size_t numToShow = min(numMeshNodes, 4U);
for (size_t i = 0; i < numToShow; i++)
normalFrames[numframes++] = drawNodeInfo;
// then the debug info
//
// Since frames are basic function pointers, we have to use a helper to
// call a method on debugInfo object.
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.positions.log = numframes;
normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline;
// call a method on debugInfoScreen object (for more details)
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.positions.settings = numframes;
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.positions.wifi = numframes;
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (isWifiAvailable()) {
// call a method on debugInfoScreen object (for more details)
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
}
2021-01-09 17:50:58 -08:00
#endif
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
LOG_DEBUG("Finished build frames. numframes: %d", numframes);
ui->setFrames(normalFrames, numframes);
ui->enableAllIndicators();
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
// Add function overlay here. This can show when notifications muted, modifier key is active etc
static OverlayCallback functionOverlay[] = {drawFunctionOverlay};
static const int functionOverlayCount = sizeof(functionOverlay) / sizeof(functionOverlay[0]);
ui->setOverlays(functionOverlay, functionOverlayCount);
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
2020-10-16 10:53:55 +08:00
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
// Focus on a specific frame, in the frame set we just created
switch (focus) {
case FOCUS_DEFAULT:
ui->switchToFrame(0); // First frame
break;
case FOCUS_FAULT:
ui->switchToFrame(fsi.positions.fault);
break;
case FOCUS_TEXTMESSAGE:
ui->switchToFrame(fsi.positions.textMessage);
break;
case FOCUS_MODULE:
// Whichever frame was marked by MeshModule::requestFocus(), if any
// If no module requested focus, will show the first frame instead
ui->switchToFrame(fsi.positions.focusedModule);
break;
case FOCUS_PRESERVE:
// If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset
const FramesetInfo &oldFsi = this->framesetInfo;
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
if (originalPosition == oldFsi.positions.log)
ui->switchToFrame(fsi.positions.log);
else if (originalPosition == oldFsi.positions.settings)
ui->switchToFrame(fsi.positions.settings);
else if (originalPosition == oldFsi.positions.wifi)
ui->switchToFrame(fsi.positions.wifi);
// If frame count has decreased
else if (fsi.frameCount < oldFsi.frameCount) {
uint8_t numDropped = oldFsi.frameCount - fsi.frameCount;
// Move n frames backwards
if (numDropped <= originalPosition)
ui->switchToFrame(originalPosition - numDropped);
// Unless that would put us "out of bounds" (< 0)
else
ui->switchToFrame(0);
}
// If we're not sure exactly which frame we were on, at least return to the same frame number
// (node frames; module frames)
else
ui->switchToFrame(originalPosition);
break;
}
// Store the info about this frameset, for future setFrames calls
this->framesetInfo = fsi;
2020-10-16 10:53:55 +08:00
setFastFramerate(); // Draw ASAP
}
2020-02-07 17:48:12 -08:00
void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
{
ui->disableAllIndicators();
ui->setFrames(drawFrames, 1);
2020-10-16 10:53:55 +08:00
setFastFramerate();
}
// Dismisses the currently displayed screen frame, if possible
// Relevant for text message, waypoint, others in future?
// Triggered with a CardKB keycombo
void Screen::dismissCurrentFrame()
{
uint8_t currentFrame = ui->getUiState()->currentFrame;
bool dismissed = false;
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
LOG_INFO("Dismiss Text Message");
devicestate.has_rx_text_message = false;
dismissed = true;
}
else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
LOG_DEBUG("Dismiss Waypoint");
devicestate.has_rx_waypoint = false;
dismissed = true;
}
// If we did make changes to dismiss, we now need to regenerate the frameset
if (dismissed)
setFrames();
}
void Screen::handleStartFirmwareUpdateScreen()
{
LOG_DEBUG("Show firmware screen");
showingNormalScreen = false;
2024-03-10 05:00:51 +13:00
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameFirmware};
setFrameImmediateDraw(frames);
}
void Screen::blink()
{
2020-12-20 17:45:45 -05:00
setFastFramerate();
uint8_t count = 10;
dispdev->setBrightness(254);
while (count > 0) {
dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
dispdev->display();
delay(50);
dispdev->clear();
dispdev->display();
delay(50);
count = count - 1;
2020-12-20 17:45:45 -05:00
}
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
dispdev->setBrightness(brightness);
2020-12-20 17:45:45 -05:00
}
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
void Screen::increaseBrightness()
{
brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
#if defined(ST7789_CS)
// run the setDisplayBrightness function. This works on t-decks
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
}
void Screen::decreaseBrightness()
{
brightness = (brightness < 70) ? brightness : (brightness - 62);
#if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
}
2024-11-19 05:52:20 -07:00
void Screen::setFunctionSymbol(std::string sym)
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
{
2024-11-19 05:52:20 -07:00
if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
functionSymbol.push_back(sym);
functionSymbolString = "";
for (auto symbol : functionSymbol) {
functionSymbolString = symbol + " " + functionSymbolString;
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
}
setFastFramerate();
}
}
2024-11-19 05:52:20 -07:00
void Screen::removeFunctionSymbol(std::string sym)
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
{
2024-11-19 05:52:20 -07:00
functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
functionSymbolString = "";
for (auto symbol : functionSymbol) {
functionSymbolString = symbol + " " + functionSymbolString;
Added modifier key combination to allow keyboard shortcuts on t-deck (#3668) * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Updated kbI2cBase.cpp Updated keyboard settings for t-deck to allow a modifier key to trigger 'tab', mute notifications, or quit. To trigger the modifier press the shift key and mic (0) button at the same time. Then press q (quit), m (mute), or t (tab). * Update kbI2cBase.cpp * fixed formatting issues in kbI2cBase.cpp * Removed keyboard shortcut code that doesnt work alt+t does not work on a t-deck so I removed it to avoid confusion. * Changed modifier key to alt+c * Added screen brightness functionality Use modifier key with o(+) to increase brightness or i(-) to decrease. Currently there are 4 levels of brightness, (L, ML, MH, H). I would like to add a popup message to tell you the brightness. * Added checks to disable screen brightness changes on unsupported hardware * Setting the brightness code to work on only applicable devices * Added "function symbol" display to bottom right corner of screen. Now shows when mute is active or modifier key is pressed. Also fixed some other minor issues. * commented out a log * Reworked how modifier functions worked, added I wasn’t happy with my previous implementation, and I think it would have caused issues with other devices. This should work on all devices. * Added back the function I moved causing issue with versions * Fixed the version conflicts, everything seems to work fine now --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2024-05-08 08:37:50 -04:00
}
setFastFramerate();
}
2023-02-05 01:46:16 +01:00
std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds)
{
2023-02-05 01:46:16 +01:00
std::string uptime;
if (days > (hours_in_month * 6))
uptime = "?";
else if (days >= 2)
uptime = std::to_string(days) + "d";
else if (hours >= 2)
2023-02-05 01:46:16 +01:00
uptime = std::to_string(hours) + "h";
else if (minutes >= 1)
2023-02-05 01:46:16 +01:00
uptime = std::to_string(minutes) + "m";
else
2023-02-05 01:46:16 +01:00
uptime = std::to_string(seconds) + "s";
return uptime;
}
void Screen::handlePrint(const char *text)
{
2020-12-25 15:39:42 +08:00
// the string passed into us probably has a newline, but that would confuse the logging system
// so strip it
LOG_DEBUG("Screen: %.*s", strlen(text) - 1, text);
2020-10-16 10:53:55 +08:00
if (!useDisplay || !showingNormalScreen)
return;
dispdev->print(text);
}
void Screen::handleOnPress()
{
// If Canned Messages is using the "Scan and Select" input, dismiss the canned message frame when user button is pressed
// Minimize impact as a courtesy, as "scan and select" may be used as default config for some boards
if (scanAndSelectInput != nullptr && scanAndSelectInput->dismissCannedMessageFrame())
return;
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui->getUiState()->frameState == FIXED) {
ui->nextFrame();
lastScreenTransition = millis();
2020-10-16 10:53:55 +08:00
setFastFramerate();
}
}
void Screen::handleShowPrevFrame()
{
// If screen was off, just wake it, otherwise go back to previous frame
// If we are in a transition, the press must have bounced, drop it.
if (ui->getUiState()->frameState == FIXED) {
ui->previousFrame();
lastScreenTransition = millis();
setFastFramerate();
}
}
void Screen::handleShowNextFrame()
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui->getUiState()->frameState == FIXED) {
ui->nextFrame();
lastScreenTransition = millis();
setFastFramerate();
}
}
2020-10-24 09:49:14 +08:00
#ifndef SCREEN_TRANSITION_FRAMERATE
#define SCREEN_TRANSITION_FRAMERATE 30 // fps
#endif
2020-10-16 10:53:55 +08:00
void Screen::setFastFramerate()
{
// We are about to start a transition so speed up fps
2020-10-24 09:49:14 +08:00
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
ui->setTargetFPS(targetFramerate);
2020-10-16 10:53:55 +08:00
setInterval(0); // redraw ASAP
runASAP = true;
2020-10-16 10:53:55 +08:00
}
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
2022-12-29 15:44:22 +01:00
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char channelStr[20];
{
2020-07-06 00:54:30 +02:00
concurrency::LockGuard guard(&lock);
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
}
2020-09-25 12:52:26 -07:00
// Display power status
2022-12-29 15:44:22 +01:00
if (powerStatus->getHasBattery()) {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
drawBattery(display, x, y + 2, imgBattery, powerStatus);
2022-12-29 15:44:22 +01:00
} else {
drawBattery(display, x + 1, y + 3, imgBattery, powerStatus);
}
} else if (powerStatus->knowsUSB()) {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
2022-12-29 15:44:22 +01:00
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
} else {
display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
}
}
2020-09-25 12:52:26 -07:00
// Display nodes status
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
2022-12-29 15:44:22 +01:00
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
} else {
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
#if HAS_GPS
2020-09-25 12:52:26 -07:00
// Display GPS status
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
drawGPSpowerstat(display, x, y + 2, gpsStatus);
} else {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
2022-12-29 15:44:22 +01:00
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
} else {
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
}
}
#endif
display->setColor(WHITE);
// Draw the channel name
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if (moduleConfig.store_forward.enabled) {
#ifdef ARCH_ESP32
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8,
imgQuestion);
#endif
} else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1);
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8,
imgSF);
#endif
}
#endif
} else {
// TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
#endif
}
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
// Draw any log messages
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
2020-07-01 10:09:06 -07:00
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
2020-07-01 10:09:06 -07:00
#endif
}
// Jm
void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
const char *wifiName = config.network.wifi_ssid;
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
2022-12-29 15:44:22 +01:00
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
2022-10-25 11:53:22 +02:00
if (WiFi.status() != WL_CONNECTED) {
display->drawString(x, y, String("WiFi: Not Connected"));
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 1, y, String("WiFi: Not Connected"));
} else {
display->drawString(x, y, String("WiFi: Connected"));
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 1, y, String("WiFi: Connected"));
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y,
"RSSI " + String(WiFi.RSSI()));
if (config.display.heading_bold) {
2022-12-29 15:44:22 +01:00
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y,
"RSSI " + String(WiFi.RSSI()));
}
}
2022-12-29 15:44:22 +01:00
display->setColor(WHITE);
/*
- WL_CONNECTED: assigned when connected to a WiFi network;
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
- WL_CONNECTION_LOST: assigned when the connection is lost;
- WL_DISCONNECTED: assigned when disconnected from a network;
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
- WL_NO_SHIELD: assigned when no WiFi shield is present;
*/
2022-10-25 11:53:22 +02:00
if (WiFi.status() == WL_CONNECTED) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost");
} else if (WiFi.status() == WL_CONNECT_FAILED) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
}
#ifdef ARCH_ESP32
else {
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
2020-09-18 15:33:03 -07:00
}
#else
else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status()));
}
#endif
2022-10-25 11:53:22 +02:00
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
2020-12-12 19:09:58 -08:00
2020-10-18 21:39:02 -07:00
display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local");
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
#endif
}
void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
2022-12-29 15:44:22 +01:00
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char batStr[20];
if (powerStatus->getHasBattery()) {
int batV = powerStatus->getBatteryVoltageMv() / 1000;
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
// Line 1
display->drawString(x, y, batStr);
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 1, y, batStr);
} else {
// Line 1
display->drawString(x, y, String("USB"));
if (config.display.heading_bold)
2022-12-29 15:44:22 +01:00
display->drawString(x + 1, y, String("USB"));
}
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
// if (config.display.heading_bold)
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
uint32_t currentMillis = millis();
uint32_t seconds = currentMillis / 1000;
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
// currentMillis %= 1000;
// seconds %= 60;
// minutes %= 60;
// hours %= 24;
// Show uptime as days, hours, minutes OR seconds
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
// Setup string to assemble analogClock string
std::string analogClock = "";
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
char timebuf[12];
if (config.display.use_12h_clock) {
std::string meridiem = "am";
if (hour >= 12) {
if (hour > 12)
hour -= 12;
meridiem = "pm";
}
if (hour == 00) {
hour = 12;
}
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
} else {
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
}
analogClock += timebuf;
}
// Line 2
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
// Display Channel Utilization
char chUtil[13];
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
2022-04-10 19:15:10 -07:00
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
#if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
2023-01-21 18:39:58 +01:00
if (config.display.gps_format !=
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
} else {
drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
}
#endif
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
}
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
2020-07-01 10:09:06 -07:00
{
// LOG_DEBUG("Screen got status update %d", arg->getStatusType());
switch (arg->getStatusType()) {
case STATUS_TYPE_NODE:
2020-12-12 19:09:58 -08:00
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible)
}
nodeDB->updateGUI = false;
break;
2020-06-28 18:17:52 -07:00
}
return 0;
}
int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
{
if (showingNormalScreen) {
// Outgoing message
if (packet->from == 0)
setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame)
// Incoming message
else
setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message
}
return 0;
}
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
// Triggered by MeshModules
2022-01-13 09:19:36 +01:00
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
2022-01-04 19:43:06 +01:00
{
if (showingNormalScreen) {
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
setFrames(FOCUS_MODULE);
// Regenerate the frameset, while Attempt to maintain focus on the current frame
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
setFrames(FOCUS_PRESERVE);
// Don't regenerate the frameset, just re-draw whatever is on screen ASAP
else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
2022-01-13 09:19:36 +01:00
setFastFramerate();
2022-01-04 19:43:06 +01:00
}
return 0;
}
int Screen::handleInputEvent(const InputEvent *event)
{
#if defined(DISPLAY_CLOCK_FRAME)
// For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
uint8_t watchFaceFrame = error_code ? 1 : 0;
if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 &&
event->touchY >= 204 && event->touchY <= 240) {
screen->digitalWatchFace = !screen->digitalWatchFace;
setFrames();
return 0;
}
#endif
// Use left or right input from a keyboard to move between frames,
// so long as a mesh module isn't using these events for some other purpose
if (showingNormalScreen) {
// Ask any MeshModules if they're handling keyboard input right now
bool inputIntercepted = false;
for (MeshModule *module : moduleFrames) {
if (module->interceptingKeyboardInput())
inputIntercepted = true;
}
// If no modules are using the input, move between frames
if (!inputIntercepted) {
if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT))
showPrevFrame();
else if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))
showNextFrame();
}
}
return 0;
}
Show specific frame when updating screen (#4264) * Updated setFrames in Screen.cpp Added code to attempt to revert back to the same frame that user was on prior to setFrame reload. * Space added Screen.cpp * Update Screen.cpp Make screen to revert to Frame 0 if the originally displayed frame is no longer there. * Update Screen.cpp Inserted boolean holdPosition into setFrames to indicate the requirement to stay on the same frame ( if =true) or else it will switch to new frame . Only Screen::handleStatusUpdate calls with setFrame(true). ( For Node Updates) All other types of updates call as before setFrame(), so it will change focus as needed. * Hold position, even if number of frames increases * Hold position, if handling an outgoing text message * Update Screen.cpp * Reverted chnages related to devicestate.has_rx_text_message * Reset to master * CannedMessages only handles routing packets when waiting for ACK Previously, this was calling Screen::setFrames at unexpected times * Gather position info about screen frames while regenerating * Make admin module observable Notify only when relevant. Currently: only to handle remove_nodenum. * Optionally specify which frame to focus when setFrames runs * UIFrameEvent uses enum instead of multiple booleans * Allow modules to request their own frame to be focussed This is done internally by calling MeshModule::requestFocus() Easier this way, insteady of passing the info in the UIFrameEvent: * Modules don't always know whether they should be focussed until after the UIFrameEvent has been raised, in dramFrame * Don't have to pass reference to module instance as parameter though several methods * E-Ink screensaver uses FOCUS_PRESERVE Previously, it had its own basic implementation of this. * Spelling: regional variant * trunk * Fix HAS_SCREEN guarding * More HAS_SCREEN guarding --------- Co-authored-by: BIST <77391720+slash-bit@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: slash-bit <v-b2@live.com>
2024-07-12 11:51:26 +12:00
int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg)
{
switch (arg->which_payload_variant) {
// Node removed manually (i.e. via app)
case meshtastic_AdminMessage_remove_by_nodenum_tag:
setFrames(FOCUS_PRESERVE);
break;
// Default no-op, in case the admin message observable gets used by other classes in future
default:
break;
}
return 0;
}
2020-07-07 10:46:49 +02:00
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN