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/>.
*/
2024-09-12 03:31:30 +03:00
# include "Screen.h"
2024-08-06 10:35:54 -07:00
# include "PowerMon.h"
2024-09-23 08:58:14 -05:00
# include "Throttle.h"
2021-06-27 10:56:28 -07:00
# include "configuration.h"
2022-07-31 07:11:47 -05:00
# if HAS_SCREEN
2020-03-18 17:16:19 -07:00
# include <OLEDDisplay.h>
2023-10-09 20:43:16 -05:00
# include "DisplayFormatters.h"
2024-03-25 05:33:57 -06:00
# if !MESHTASTIC_EXCLUDE_GPS
2020-02-07 13:51:17 -08:00
# include "GPS.h"
2024-03-25 05:33:57 -06:00
# endif
2020-05-21 17:21:44 -07:00
# include "MeshService.h"
2020-02-11 10:51:45 -08:00
# include "NodeDB.h"
2023-06-08 08:07:32 -05:00
# include "error.h"
2021-12-26 15:46:23 -08:00
# include "gps/GeoCoord.h"
2021-03-20 10:22:06 +08:00
# 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"
2024-08-07 10:16:56 +12:00
# include "input/ScanAndSelect.h"
2023-07-30 14:51:26 +02:00
# 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"
2023-08-17 20:22:34 -05:00
# include "meshUtils.h"
2024-07-12 11:51:26 +12:00
# include "modules/AdminModule.h"
2022-12-27 21:51:35 +01:00
# include "modules/ExternalNotificationModule.h"
2023-01-18 14:51:48 -06:00
# include "modules/TextMessageModule.h"
2024-09-25 23:27:04 +12:00
# include "modules/WaypointModule.h"
2021-12-26 15:46:23 -08:00
# include "sleep.h"
2020-09-25 12:52:26 -07:00
# include "target_specific.h"
2025-03-30 16:53:44 -04:00
# include "FSCommon.h"
2020-02-07 13:51:17 -08:00
2024-01-12 02:00:31 -06:00
# if HAS_WIFI && !defined(ARCH_PORTDUINO)
2023-12-04 22:45:07 +01:00
# include "mesh/wifi/WiFiAPClient.h"
# endif
2022-07-31 07:11:47 -05:00
# ifdef ARCH_ESP32
2021-12-17 14:02:29 -05:00
# 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
2024-01-12 02:00:31 -06:00
# if ARCH_PORTDUINO
2024-09-27 21:22:27 -05:00
# include "modules/StoreForwardModule.h"
2023-12-12 20:27:31 -06:00
# 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)
2020-12-31 20:17:18 -08:00
# 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
2020-03-15 16:47:38 -07:00
// A text message frame + debug frame + all the node infos
2024-03-21 09:06:37 -05:00
FrameCallback * normalFrames ;
2020-03-15 16:47:38 -07:00
static uint32_t targetFramerate = IDLE_FRAMERATE ;
2022-11-03 09:46:32 +01:00
uint32_t logo_timeout = 5000 ; // 4 seconds for EACH logo
2023-01-18 14:51:48 -06:00
2023-02-05 01:49:07 +01:00
uint32_t hours_in_month = 730 ;
2023-02-05 01:46:16 +01:00
2020-08-01 10:50:06 -07:00
// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function
2020-08-12 11:04:03 -07:00
uint8_t imgBattery [ 16 ] = { 0xFF , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0x81 , 0xE7 , 0x3C } ;
2020-07-03 02:53:56 -07:00
2020-08-01 10:50:06 -07:00
// Threshold values for the GPS lock accuracy bar display
2020-08-12 11:04:03 -07:00
uint32_t dopThresholds [ 5 ] = { 2000 , 1000 , 500 , 200 , 100 } ;
2020-06-21 17:28:37 -07:00
2022-02-27 00:29:05 -08:00
// 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.
2022-03-09 19:01:43 +11:00
std : : vector < MeshModule * > moduleFrames ;
2021-02-21 16:46:46 -05:00
2020-08-01 10:50:06 -07:00
// 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
2024-03-25 05:33:57 -06:00
# if HAS_GPS
2021-10-03 15:52:46 -04:00
// GeoCoord object for the screen
GeoCoord geoCoord ;
2024-03-25 05:33:57 -06:00
# endif
2021-10-03 15:52:46 -04:00
2020-07-01 10:09:06 -07:00
# ifdef SHOW_REDRAWS
2020-06-27 21:19:49 -07:00
static bool heartbeat = false ;
2020-07-01 10:09:06 -07:00
# endif
2020-06-21 17:28:37 -07:00
2024-06-29 21:16:07 -05:00
// 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()
2020-10-15 15:12:27 +08:00
2024-02-27 19:49:46 +01:00
# include "graphics/ScreenFonts.h"
2024-09-23 08:58:14 -05:00
# include <Throttle.h>
2020-10-16 11:22:07 +08:00
# define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
2025-01-28 15:38:22 +01:00
// Check if the display can render a string (detect special chars; emoji)
2024-06-28 08:55:54 +02:00
static bool haveGlyphs ( const char * str )
{
2024-12-26 10:08:23 +01:00
# if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
2024-06-28 08:55:54 +02:00
// 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 ;
}
}
2024-10-14 06:11:43 +02:00
LOG_DEBUG ( " haveGlyphs=%d " , have ) ;
2024-06-28 08:55:54 +02:00
return have ;
}
2020-11-13 07:49:01 +08:00
/**
* 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
2020-11-13 07:49:01 +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 ,
2024-02-08 16:29:15 -06:00
icon_width , icon_height , icon_bits ) ;
2020-02-07 14:52:45 -08:00
2020-10-16 11:22:07 +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
2020-11-13 07:49:01 +08:00
// Draw region in upper left
if ( upperMsg )
display - > drawString ( x + 0 , y + 0 , upperMsg ) ;
2020-10-21 17:27:13 +08:00
2024-06-28 08:55:54 +02: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 ( ) ;
2024-06-28 08:55:54 +02:00
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ; // Restore left align, just to be kind to any other unsuspecting code
2020-11-13 07:49:01 +08:00
}
2025-01-28 15:38:22 +01:00
# 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
2024-06-28 21:28:18 -05:00
void Screen : : drawFrameText ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y , const char * message )
2023-10-09 20:43:16 -05:00
{
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 " ) ;
2022-07-31 07:11:47 -05:00
# ifdef ARCH_ESP32
2021-12-17 14:02:29 -05:00
yield ( ) ;
esp_task_wdt_reset ( ) ;
# endif
2021-11-26 15:09:16 -05:00
display - > setFont ( FONT_SMALL ) ;
2021-12-14 23:50:49 -05:00
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 )
{
2022-08-22 16:41:23 -05:00
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 ) ;
2023-01-18 14:51:48 -06:00
2022-04-14 21:31:31 -07:00
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, " ) ;
2024-02-24 06:49:15 -07:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 4 - 3 , " Web or CLI clients. " ) ;
2022-04-14 21:31:31 -07:00
} 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
2022-07-31 07:11:47 -05: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 )
{
2024-11-04 19:15:59 -06:00
// 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 ) ;
}
}
2022-07-31 07:11:47 -05:00
# ifdef USE_EINK
2020-11-13 07:49:01 +08:00
/// Used on eink displays while in deep sleep
2024-03-29 12:31:11 +13:00
static void drawDeepSleepScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2020-11-13 07:49:01 +08:00
{
2024-06-28 08:55:54 +02:00
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 ) ;
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Draw deep sleep screen " ) ;
2024-06-28 08:55:54 +02:00
// Display displayStr on the screen
drawIconScreen ( " Sleeping " , display , state , x , y ) ;
2020-02-07 13:51:17 -08:00
}
2024-03-29 12:31:11 +13:00
/// Used on eink displays when screen updates are paused
static void drawScreensaverOverlay ( OLEDDisplay * display , OLEDDisplayUiState * state )
{
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Draw screensaver overlay " ) ;
2024-03-29 12:31:11 +13:00
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 ;
2024-06-16 07:58:46 +12:00
const bool useId = haveGlyphs ( idText ) ; // This bool is used to hide the idText box if we can't render the short name
2024-03-29 12:31:11 +13:00
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
2024-06-16 07:58:46 +12:00
const uint16_t idTextWidth = display - > getStringWidth ( idText , strlen ( idText ) , true ) ; // "true": handle utf8 chars
2024-03-29 12:31:11 +13:00
const uint16_t pauseTextWidth = display - > getStringWidth ( pauseText , strlen ( pauseText ) ) ;
2024-06-16 07:58:46 +12:00
const uint16_t boxWidth = padding + ( useId ? idTextWidth + padding + padding : 0 ) + pauseTextWidth + padding ;
2024-03-29 12:31:11 +13:00
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 ;
2024-06-16 07:58:46 +12:00
const int16_t pauseTextLeft = boxLeft + ( useId ? padding + idTextWidth + padding : 0 ) + padding ;
2024-03-29 12:31:11 +13:00
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
2024-06-16 07:58:46 +12:00
if ( useId )
display - > drawString ( idTextLeft , idTextTop , idText ) ;
2024-03-29 12:31:11 +13:00
display - > drawString ( pauseTextLeft , pauseTextTop , pauseText ) ;
display - > drawString ( pauseTextLeft + 1 , pauseTextTop , pauseText ) ; // Faux bold
// Draw: divider
2024-06-16 07:58:46 +12:00
if ( useId )
display - > drawLine ( dividerX , dividerTop , dividerX , dividerBottom ) ;
2024-03-29 12:31:11 +13:00
}
2021-03-08 17:09:35 +08:00
# endif
2020-02-07 13:51:17 -08:00
2022-02-27 02:26:22 -08:00
static void drawModuleFrame ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2021-02-21 16:46:46 -05:00
{
2022-02-27 01:49:24 -08:00
uint8_t module_frame ;
2021-02-22 20:18:36 -05:00
// there's a little but in the UI transition code
2021-03-20 10:22:06 +08:00
// where it invokes the function at the correct offset
2021-02-22 20:18:36 -05:00
// in the array of "drawScreen" functions; however,
2021-03-20 10:22:06 +08:00
// the passed-state doesn't quite reflect the "current"
2021-02-22 20:18:36 -05:00
// screen, so we have to detect it.
2024-06-25 11:26:02 -05:00
if ( state - > frameState = = IN_TRANSITION & & state - > transitionFrameRelationship = = TransitionRelationship_INCOMING ) {
2021-03-20 10:22:06 +08:00
// if we're transitioning from the end of the frame list back around to the first
2021-02-22 20:18:36 -05:00
// frame, then we want this to be `0`
2022-02-27 01:49:24 -08:00
module_frame = state - > transitionFrameTarget ;
2021-03-20 10:22:06 +08:00
} else {
2022-02-27 00:29:05 -08:00
// 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 ;
2024-10-14 06:11:43 +02:00
// LOG_DEBUG("Screen is not in transition. Frame: %d", module_frame);
2021-02-22 20:18:36 -05:00
}
2024-11-04 19:15:59 -06:00
// LOG_DEBUG("Draw Module Frame %d", module_frame);
2022-03-09 19:01:43 +11:00
MeshModule & pi = * moduleFrames . at ( module_frame ) ;
2021-03-20 10:22:06 +08:00
pi . drawFrame ( display , state , x , y ) ;
2021-02-21 16:46:46 -05:00
}
2021-02-12 13:48:12 +08:00
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 ) ;
2023-01-18 14:51:48 -06:00
display - > drawStringMaxWidth ( 0 + x , 2 + y + FONT_HEIGHT_SMALL * 2 , x + display - > getWidth ( ) ,
" Please be patient and do not power off. " ) ;
2021-02-12 13:48:12 +08:00
}
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 ] ;
2023-06-08 08:07:32 -05:00
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 \n meshtastic.org " ) ;
2020-12-26 13:36:21 +08:00
}
2023-07-14 17:25:20 -04:00
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
2023-01-21 18:22:19 +01:00
static bool shouldDrawMessage ( const meshtastic_MeshPacket * packet )
2021-12-26 15:46:23 -08:00
{
2023-08-19 09:44:40 -05:00
return packet - > from ! = 0 & & ! moduleConfig . store_forward . enabled ;
2021-11-21 18:44:08 -06:00
}
2024-05-23 08:21:27 -04:00
// 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 } ;
2025-03-28 21:03:37 -04:00
// Clear the bar area inside the battery image
2024-05-23 08:21:27 -04:00
for ( int i = 1 ; i < 14 ; i + + ) {
imgBuffer [ i ] = 0x81 ;
}
2025-03-28 21:03:37 -04:00
// Fill with lightning or power bars
2024-05-23 08:21:27 -04:00
if ( powerStatus - > getIsCharging ( ) ) {
memcpy ( imgBuffer + 3 , lightning , 8 ) ;
} else {
for ( int i = 0 ; i < 4 ; i + + ) {
if ( powerStatus - > getBatteryChargePercent ( ) > = 25 * i )
memcpy ( imgBuffer + 1 + ( i * 3 ) , powerBar , 3 ) ;
}
}
2025-03-28 21:03:37 -04:00
// Slightly more conservative scaling based on screen width
int screenWidth = display - > getWidth ( ) ;
int scale = 1 ;
if ( screenWidth > = 200 ) scale = 2 ;
2025-03-28 21:23:41 -04:00
if ( screenWidth > = 300 ) scale = 2 ; // Do NOT go higher than 2
2025-03-28 21:03:37 -04:00
// Draw scaled battery image (16 columns × 8 rows)
for ( int col = 0 ; col < 16 ; col + + ) {
uint8_t colBits = imgBuffer [ col ] ;
for ( int row = 0 ; row < 8 ; row + + ) {
if ( colBits & ( 1 < < row ) ) {
display - > fillRect ( x + col * scale , y + row * scale , scale , scale ) ;
}
}
}
2024-05-23 08:21:27 -04:00
}
2024-11-23 08:54:06 +08:00
# if defined(DISPLAY_CLOCK_FRAME)
2024-05-23 08:21:27 -04:00
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 ) ;
}
2024-10-10 22:37:25 +13:00
if ( nimbleBluetooth & & nimbleBluetooth - > isConnected ( ) ) {
2024-05-23 08:21:27 -04:00
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 ) ;
}
2024-10-10 22:37:25 +13:00
if ( nimbleBluetooth & & nimbleBluetooth - > isConnected ( ) ) {
2024-05-23 08:21:27 -04:00
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
2024-05-22 09:00:04 +12:00
// 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
2020-03-15 16:47:38 -07:00
static void drawTextMessageFrame ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2020-02-07 13:51:17 -08:00
{
2022-12-08 17:34:14 +01:00
// the max length of this buffer is much longer than we can possibly print
static char tempBuf [ 237 ] ;
2023-08-12 09:29:19 -05:00
const meshtastic_MeshPacket & mp = devicestate . rx_text_message ;
2024-03-21 09:06:37 -05:00
meshtastic_NodeInfoLite * node = nodeDB - > getMeshNode ( getFrom ( & mp ) ) ;
2024-11-04 19:15:59 -06:00
// LOG_DEBUG("Draw text message from 0x%x: %s", mp.from,
2020-05-09 17:51:20 -07:00
// mp.decoded.variant.data.decoded.bytes);
2020-02-12 19:58:44 -08:00
2020-02-07 13:51:17 -08:00
// Demo for drawStringMaxWidth:
2020-03-15 16:47:38 -07:00
// 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 ) ;
2020-10-16 11:22:07 +08:00
display - > setFont ( FONT_SMALL ) ;
2023-01-21 18:22:19 +01:00
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 ) ;
}
2023-01-24 18:52:09 +01:00
2024-05-22 09:00:04 +12:00
// For time delta
2023-02-03 00:40:51 +01:00
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
2024-05-22 09:00:04 +12: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
}
2023-01-24 18:52:09 +01:00
2022-12-08 17:34:14 +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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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
2024-11-18 22:25:11 -07:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 ) {
2024-05-23 01:28:30 +01:00
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 | |
2024-12-29 11:56:05 +11:00
strcmp ( msg , " \U0001F497 " ) = = 0 | | strcmp ( msg , " \U0001F498 " ) = = 0 ) {
2024-05-23 01:28:30 +01:00
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
}
2023-07-14 17:25:20 -04:00
/// Draw a series of fields in a column, wrapping to multiple columns if needed
2024-06-29 21:16:07 -05:00
void Screen : : drawColumns ( OLEDDisplay * display , int16_t x , int16_t y , const char * * fields )
2020-02-07 13:51:17 -08:00
{
2020-02-07 17:26:42 -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 ) {
2020-02-07 17:26:42 -08:00
display - > drawString ( xo , yo , * f ) ;
2022-12-29 15:44:22 +01:00
if ( ( display - > getColor ( ) = = BLACK ) & & config . display . heading_bold )
2022-12-13 12:33:51 +01:00
display - > drawString ( xo + 1 , yo , * f ) ;
display - > setColor ( WHITE ) ;
2020-10-16 11:22:07 +08:00
yo + = FONT_HEIGHT_SMALL ;
if ( yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL ) {
2020-02-07 17:26:42 -08:00
xo + = SCREEN_WIDTH / 2 ;
yo = 0 ;
}
f + + ;
}
}
2020-06-21 17:28:37 -07:00
// Draw nodes status
2023-08-12 09:29:19 -05:00
static void drawNodes ( OLEDDisplay * display , int16_t x , int16_t y , const NodeStatus * nodeStatus )
2020-06-26 15:04:22 -07:00
{
2020-06-21 17:28:37 -07:00
char usersString [ 20 ] ;
2023-01-16 10:55:40 +01:00
snprintf ( usersString , sizeof ( usersString ) , " %d/%d " , nodeStatus - > getNumOnline ( ) , nodeStatus - > getNumTotal ( ) ) ;
2024-10-04 14:47:14 +02:00
# 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 ) ) & & \
2023-08-03 13:58:19 +02:00
! defined ( DISPLAY_FORCE_SMALL_FONTS )
2022-12-21 13:00:15 +01:00
display - > drawFastImage ( x , y + 3 , 8 , 8 , imgUser ) ;
# else
2020-06-21 17:28:37 -07:00
display - > drawFastImage ( x , y , 8 , 8 , imgUser ) ;
2022-12-21 13:00:15 +01:00
# endif
2020-06-21 17:28:37 -07:00
display - > drawString ( x + 10 , y - 2 , usersString ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 11 , y - 2 , usersString ) ;
2020-06-21 17:28:37 -07:00
}
2024-03-25 05:33:57 -06:00
# if HAS_GPS
2020-06-21 17:28:37 -07:00
// 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 )
2020-06-26 15:04:22 -07:00
{
2022-05-21 22:38:33 +02:00
if ( config . position . fixed_position ) {
2021-09-15 18:58:09 -04:00
// GPS coordinates are currently fixed
display - > drawString ( x - 1 , y - 2 , " Fixed GPS " ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x , y - 2 , " Fixed GPS " ) ;
2021-09-15 18:58:09 -04:00
return ;
}
2020-08-12 11:04:03 -07:00
if ( ! gps - > getIsConnected ( ) ) {
2020-06-26 15:04:22 -07:00
display - > drawString ( x , y - 2 , " No GPS " ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 1 , y - 2 , " No GPS " ) ;
2020-06-26 15:04:22 -07:00
return ;
}
2025-03-28 14:39:15 -04:00
// Adjust position if we’ re going to draw too wide
int maxDrawWidth = 6 ; // Position icon
if ( ! gps - > getHasLock ( ) ) {
maxDrawWidth + = display - > getStringWidth ( " No sats " ) + 2 ; // icon + text + buffer
} else {
maxDrawWidth + = ( 5 * 2 ) + 8 + display - > getStringWidth ( " 99 " ) + 2 ; // bars + sat icon + text + buffer
}
if ( x + maxDrawWidth > SCREEN_WIDTH ) {
x = SCREEN_WIDTH - maxDrawWidth ;
if ( x < 0 ) x = 0 ; // Clamp to screen
}
2020-06-28 18:17:52 -07:00
display - > drawFastImage ( x , y , 6 , 8 , gps - > getHasLock ( ) ? imgPositionSolid : imgPositionEmpty ) ;
2020-08-12 11:04:03 -07:00
if ( ! gps - > getHasLock ( ) ) {
2025-03-28 14:39:15 -04:00
// Draw "No sats" to the right of the icon with slightly more gap
int textX = x + 9 ; // 6 (icon) + 3px spacing
display - > drawString ( textX , y - 2 , " No sats " ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2025-03-28 14:39:15 -04:00
display - > drawString ( textX + 1 , y - 2 , " No sats " ) ;
2020-06-26 15:04:22 -07:00
return ;
2020-08-12 11:04:03 -07:00
} else {
2020-07-03 02:53:56 -07:00
char satsString [ 3 ] ;
2020-08-12 11:04:03 -07:00
uint8_t bar [ 2 ] = { 0 } ;
2020-07-03 02:53:56 -07:00
2020-08-12 11:04:03 -07:00
// Draw DOP signal bars
for ( int i = 0 ; i < 5 ; i + + ) {
2020-07-03 02:53:56 -07:00
if ( gps - > getDOP ( ) < = dopThresholds [ i ] )
bar [ 0 ] = ~ ( ( 1 < < ( 5 - i ) ) - 1 ) ;
else
bar [ 0 ] = 0b10000000 ;
2025-03-28 14:39:15 -04:00
2020-07-03 02:53:56 -07:00
display - > drawFastImage ( x + 9 + ( i * 2 ) , y , 2 , 8 , bar ) ;
}
2020-08-12 11:04:03 -07:00
// Draw satellite image
2020-07-03 02:53:56 -07:00
display - > drawFastImage ( x + 24 , y , 8 , 8 , imgSatellite ) ;
2020-08-12 11:04:03 -07:00
// Draw the number of satellites
2023-01-16 10:55:40 +01:00
snprintf ( satsString , sizeof ( satsString ) , " %u " , gps - > getNumSatellites ( ) ) ;
2025-03-28 14:39:15 -04:00
int textX = x + 34 ;
display - > drawString ( textX , y - 2 , satsString ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2025-03-28 14:39:15 -04:00
display - > drawString ( textX + 1 , y - 2 , satsString ) ;
2020-06-26 15:04:22 -07:00
}
2020-06-21 17:28:37 -07:00
}
2025-03-28 14:39:15 -04:00
2024-02-01 15:24:39 -06:00
// Draw status when GPS is disabled or not present
2022-12-21 13:00:15 +01:00
static void drawGPSpowerstat ( OLEDDisplay * display , int16_t x , int16_t y , const GPSStatus * gps )
{
2024-02-01 15:24:39 -06:00
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 ;
2022-12-14 19:58:15 -05:00
}
2024-02-01 15:24:39 -06:00
display - > drawString ( x + pos , y , displayLine ) ;
2022-12-14 19:58:15 -05:00
}
2020-09-26 18:37:51 -07:00
static void drawGPSAltitude ( OLEDDisplay * display , int16_t x , int16_t y , const GPSStatus * gps )
{
String displayLine = " " ;
2022-05-21 22:38:33 +02:00
if ( ! gps - > getIsConnected ( ) & & ! config . position . fixed_position ) {
2020-09-29 00:59:26 -07:00
// displayLine = "No GPS Module";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
2022-05-21 22:38:33 +02:00
} else if ( ! gps - > getHasLock ( ) & & ! config . position . fixed_position ) {
2020-09-29 00:59:26 -07:00
// 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 " ;
2023-01-21 18:22:19 +01:00
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 ) ;
}
}
2020-09-05 09:30:18 -07:00
// Draw GPS status coordinates
static void drawGPScoordinates ( OLEDDisplay * display , int16_t x , int16_t y , const GPSStatus * gps )
{
2022-05-21 22:38:33 +02:00
auto gpsFormat = config . display . gps_format ;
2020-09-05 09:30:18 -07:00
String displayLine = " " ;
2021-08-30 01:17:18 -06:00
2022-05-21 22:38:33 +02:00
if ( ! gps - > getIsConnected ( ) & & ! config . position . fixed_position ) {
2024-02-01 15:24:39 -06:00
displayLine = " No GPS present " ;
2021-08-30 04:42:14 -06:00
display - > drawString ( x + ( SCREEN_WIDTH - ( display - > getStringWidth ( displayLine ) ) ) / 2 , y , displayLine ) ;
2022-05-21 22:38:33 +02:00
} else if ( ! gps - > getHasLock ( ) & & ! config . position . fixed_position ) {
2020-09-05 14:41:00 -07:00
displayLine = " No GPS Lock " ;
2021-08-30 04:42:14 -06:00
display - > drawString ( x + ( SCREEN_WIDTH - ( display - > getStringWidth ( displayLine ) ) ) / 2 , y , displayLine ) ;
} else {
2021-12-19 14:27:49 -05:00
2022-11-10 13:50:38 +01:00
geoCoord . updateCoords ( int32_t ( gps - > getLatitude ( ) ) , int32_t ( gps - > getLongitude ( ) ) , int32_t ( gps - > getAltitude ( ) ) ) ;
2023-01-21 18:22:19 +01:00
if ( gpsFormat ! = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS ) {
2021-09-03 23:19:47 -06:00
char coordinateLine [ 22 ] ;
2023-01-21 18:22:19 +01:00
if ( gpsFormat = = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC ) { // Decimal Degrees
2023-01-18 14:51:48 -06:00
snprintf ( coordinateLine , sizeof ( coordinateLine ) , " %f %f " , geoCoord . getLatitude ( ) * 1e-7 ,
geoCoord . getLongitude ( ) * 1e-7 ) ;
2023-01-21 18:22:19 +01:00
} else if ( gpsFormat = = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM ) { // Universal Transverse Mercator
2023-01-16 10:55:40 +01:00
snprintf ( coordinateLine , sizeof ( coordinateLine ) , " %2i%1c %06u %07u " , geoCoord . getUTMZone ( ) , geoCoord . getUTMBand ( ) ,
2023-01-18 14:51:48 -06:00
geoCoord . getUTMEasting ( ) , geoCoord . getUTMNorthing ( ) ) ;
2023-01-21 18:22:19 +01:00
} else if ( gpsFormat = = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS ) { // Military Grid Reference System
2023-01-18 14:51:48 -06:00
snprintf ( coordinateLine , sizeof ( coordinateLine ) , " %2i%1c %1c%1c %05u %05u " , geoCoord . getMGRSZone ( ) ,
geoCoord . getMGRSBand ( ) , geoCoord . getMGRSEast100k ( ) , geoCoord . getMGRSNorth100k ( ) ,
geoCoord . getMGRSEasting ( ) , geoCoord . getMGRSNorthing ( ) ) ;
2023-01-21 18:22:19 +01:00
} 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
2023-01-16 10:55:40 +01:00
snprintf ( coordinateLine , sizeof ( coordinateLine ) , " %s " , " Out of Boundary " ) ;
2021-09-03 23:19:47 -06:00
else
2023-01-18 14:51:48 -06:00
snprintf ( coordinateLine , sizeof ( coordinateLine ) , " %1c%1c %05u %05u " , geoCoord . getOSGRE100k ( ) ,
geoCoord . getOSGRN100k ( ) , geoCoord . getOSGREasting ( ) , geoCoord . getOSGRNorthing ( ) ) ;
2021-09-03 23:19:47 -06:00
}
2021-12-26 15:46:23 -08:00
2021-12-19 14:27:49 -05:00
// If fixed position, display text "Fixed GPS" alternating with the coordinates.
2022-05-21 22:38:33 +02:00
if ( config . position . fixed_position ) {
2021-12-19 14:27:49 -05:00
if ( ( millis ( ) / 10000 ) % 2 ) {
2021-12-19 14:55:57 -05:00
display - > drawString ( x + ( SCREEN_WIDTH - ( display - > getStringWidth ( coordinateLine ) ) ) / 2 , y , coordinateLine ) ;
2021-12-19 14:27:49 -05:00
} else {
2021-12-19 14:55:57 -05:00
display - > drawString ( x + ( SCREEN_WIDTH - ( display - > getStringWidth ( " Fixed GPS " ) ) ) / 2 , y , " Fixed GPS " ) ;
2021-12-19 14:27:49 -05:00
}
} else {
display - > drawString ( x + ( SCREEN_WIDTH - ( display - > getStringWidth ( coordinateLine ) ) ) / 2 , y , coordinateLine ) ;
}
2021-09-03 23:19:47 -06:00
} else {
char latLine [ 22 ] ;
char lonLine [ 22 ] ;
2023-01-18 14:51:48 -06:00
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 ( ) ) ;
2021-09-03 23:19:47 -06:00
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 ) ;
}
2021-08-30 04:42:14 -06:00
}
2020-09-05 09:30:18 -07:00
}
2024-03-25 05:33:57 -06:00
# endif
2020-02-19 15:29:18 -08:00
/**
* Given a recent lat / lon return a guess of the heading the user is walking on .
2020-03-18 17:16:19 -07:00
*
2020-03-15 16:47:38 -07:00
* We keep a series of " after you've gone 10 meters, what is your heading since
* the last reference point ? "
2020-02-19 15:29:18 -08:00
*/
2024-06-29 21:16:07 -05:00
float Screen : : estimatedHeading ( double lat , double lon )
2020-02-19 15:29:18 -08:00
{
static double oldLat , oldLon ;
static float b ;
2020-03-18 17:16:19 -07:00
if ( oldLat = = 0 ) {
2020-02-19 15:29:18 -08:00
// 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 ) ;
2020-02-19 15:29:18 -08:00
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 ) ;
2020-02-19 15:29:18 -08:00
oldLat = lat ;
oldLon = lon ;
return b ;
}
2020-03-15 16:47:38 -07:00
/// We will skip one node - the one for us, so we just blindly loop over all
/// nodes
2020-02-11 10:51:45 -08:00
static size_t nodeIndex ;
2020-02-19 15:29:18 -08:00
static int8_t prevFrame = - 1 ;
2020-02-11 10:51:45 -08:00
2020-07-03 02:53:56 -07:00
// Draw the arrow pointing to a node's location
2024-06-29 21:16:07 -05:00
void Screen : : drawNodeHeading ( OLEDDisplay * display , int16_t compassX , int16_t compassY , uint16_t compassDiam , float headingRadian )
2020-06-22 19:27:13 -04:00
{
2025-03-28 14:39:15 -04:00
Serial . print ( " 🔄 [Node Heading] Raw Bearing (rad): " ) ;
Serial . print ( headingRadian ) ;
Serial . print ( " | (deg): " ) ;
Serial . println ( headingRadian * RAD_TO_DEG ) ;
2024-09-12 18:15:31 +02:00
Point tip ( 0.0f , 0.5f ) , tail ( 0.0f , - 0.35f ) ; // pointing up initially
float arrowOffsetX = 0.14f , arrowOffsetY = 1.0f ;
2020-06-22 19:27:13 -04:00
Point leftArrow ( tip . x - arrowOffsetX , tip . y - arrowOffsetY ) , rightArrow ( tip . x + arrowOffsetX , tip . y - arrowOffsetY ) ;
2020-07-03 02:53:56 -07:00
Point * arrowPoints [ ] = { & tip , & tail , & leftArrow , & rightArrow } ;
2020-06-22 19:27:13 -04:00
for ( int i = 0 ; i < 4 ; i + + ) {
2020-07-03 02:53:56 -07:00
arrowPoints [ i ] - > rotate ( headingRadian ) ;
2024-06-29 21:16:07 -05:00
arrowPoints [ i ] - > scale ( compassDiam * 0.6 ) ;
2020-07-03 02:53:56 -07:00
arrowPoints [ i ] - > translate ( compassX , compassY ) ;
2020-06-22 19:27:13 -04:00
}
2024-09-12 18:15:31 +02:00
/* Old arrow
2024-06-28 21:28:18 -05:00
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 ) ;
2024-09-12 18:15:31 +02:00
display - > drawLine ( leftArrow . x , leftArrow . y , tail . x , tail . y ) ;
display - > drawLine ( rightArrow . x , rightArrow . y , tail . x , tail . y ) ;
*/
2025-03-28 14:39:15 -04:00
Serial . print ( " 🔥 Arrow Tail X: " ) ; Serial . print ( tail . x ) ;
Serial . print ( " | Y: " ) ; Serial . print ( tail . y ) ;
Serial . print ( " | Tip X: " ) ; Serial . print ( tip . x ) ;
Serial . print ( " | Tip Y: " ) ; Serial . println ( tip . y ) ;
2024-09-14 21:29:46 +12:00
# ifdef USE_EINK
display - > drawTriangle ( tip . x , tip . y , rightArrow . x , rightArrow . y , tail . x , tail . y ) ;
# else
2024-09-12 18:15:31 +02:00
display - > fillTriangle ( tip . x , tip . y , rightArrow . x , rightArrow . y , tail . x , tail . y ) ;
2024-09-14 21:29:46 +12:00
# endif
2024-09-12 18:15:31 +02:00
display - > drawTriangle ( tip . x , tip . y , leftArrow . x , leftArrow . y , tail . x , tail . y ) ;
2020-07-03 02:53:56 -07:00
}
2020-06-22 19:27:13 -04:00
2024-06-24 19:04:46 +12:00
// Get a string representation of the time passed since something happened
2024-06-29 21:16:07 -05:00
void Screen : : getTimeAgoStr ( uint32_t agoSecs , char * timeStr , uint8_t maxLength )
2024-06-24 19:04:46 +12:00
{
// 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 " ) ;
}
2020-05-04 08:09:08 -07:00
2024-06-29 21:16:07 -05:00
void Screen : : drawCompassNorth ( OLEDDisplay * display , int16_t compassX , int16_t compassY , float myHeading )
{
2025-03-28 14:39:15 -04:00
Serial . print ( " 🧭 [Main Compass] Raw Heading (deg): " ) ;
Serial . println ( myHeading * RAD_TO_DEG ) ;
2024-06-29 21:16:07 -05:00
// 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 ;
2024-09-12 18:15:31 +02:00
/* N sign points currently not deleted*/
2024-09-14 21:29:46 +12:00
Point N1 ( - 0.04f , 0.65f ) , N2 ( 0.04f , 0.65f ) ; // N sign points (N1-N4)
2024-06-29 21:16:07 -05:00
Point N3 ( - 0.04f , 0.55f ) , N4 ( 0.04f , 0.55f ) ;
2024-09-14 21:29:46 +12:00
Point NC1 ( 0.00f , 0.50f ) ; // north circle center point
2024-09-12 18:15:31 +02:00
Point * rosePoints [ ] = { & N1 , & N2 , & N3 , & N4 , & NC1 } ;
2024-06-29 21:16:07 -05:00
uint16_t compassDiam = Screen : : getCompassDiam ( SCREEN_WIDTH , SCREEN_HEIGHT ) ;
2024-09-12 18:15:31 +02:00
for ( int i = 0 ; i < 5 ; i + + ) {
2024-06-29 21:16:07 -05:00
// North on compass will be negative of heading
rosePoints [ i ] - > rotate ( - myHeading ) ;
rosePoints [ i ] - > scale ( compassDiam ) ;
rosePoints [ i ] - > translate ( compassX , compassY ) ;
}
2024-09-14 21:29:46 +12:00
display - > drawCircle ( NC1 . x , NC1 . y , 4 ) ; // North sign circle, 4px radius is sufficient for all displays.
2025-03-28 14:39:15 -04:00
Serial . print ( " 🔥 North Marker X: " ) ; Serial . print ( NC1 . x ) ;
Serial . print ( " | Y: " ) ; Serial . println ( NC1 . y ) ;
2024-06-29 21:16:07 -05:00
}
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 ;
} ;
2020-03-15 16:47:38 -07:00
static void drawNodeInfo ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
2020-02-07 17:26:42 -08:00
{
2020-03-15 16:47:38 -07:00
// 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 ) {
2020-02-11 10:51:45 -08:00
prevFrame = state - > currentFrame ;
2024-03-21 09:06:37 -05:00
nodeIndex = ( nodeIndex + 1 ) % nodeDB - > getNumMeshNodes ( ) ;
meshtastic_NodeInfoLite * n = nodeDB - > getMeshNodeByIndex ( nodeIndex ) ;
if ( n - > num = = nodeDB - > getNodeNum ( ) ) {
2020-02-11 10:51:45 -08:00
// Don't show our node, just skip to next
2024-03-21 09:06:37 -05:00
nodeIndex = ( nodeIndex + 1 ) % nodeDB - > getNumMeshNodes ( ) ;
n = nodeDB - > getMeshNodeByIndex ( nodeIndex ) ;
2020-02-11 10:51:45 -08:00
}
}
2024-03-21 09:06:37 -05:00
meshtastic_NodeInfoLite * node = nodeDB - > getMeshNodeByIndex ( nodeIndex ) ;
2020-02-11 10:51:45 -08:00
2020-10-16 11:22:07 +08:00
display - > setFont ( FONT_SMALL ) ;
2020-02-07 17:26:42 -08:00
// The coordinates define the left starting point of the text
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2023-01-21 18:22:19 +01:00
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 ) ;
}
2022-12-13 12:33:51 +01:00
2020-02-11 10:51:45 -08:00
const char * username = node - > has_user ? node - > user . long_name : " Unknown Name " ;
2020-02-11 11:03:03 -08:00
static char signalStr [ 20 ] ;
2024-05-26 08:04:31 -04:00
// section here to choose whether to display hops away rather than signal strength if more than 0 hops away.
if ( node - > hops_away > 0 ) {
2024-05-24 23:08:21 +10:00
snprintf ( signalStr , sizeof ( signalStr ) , " Hops Away: %d " , node - > hops_away ) ;
2024-05-26 08:04:31 -04:00
} else {
2024-05-24 23:08:21 +10:00
snprintf ( signalStr , sizeof ( signalStr ) , " Signal: %d%% " , clamp ( ( int ) ( ( node - > snr + 10 ) * 5 ) , 0 , 100 ) ) ;
}
2020-02-11 11:03:03 -08:00
2020-02-12 11:52:53 -08:00
static char lastStr [ 20 ] ;
2024-06-29 21:16:07 -05:00
screen - > getTimeAgoStr ( sinceLastSeen ( node ) , lastStr , sizeof ( lastStr ) ) ;
2020-02-12 11:52:53 -08:00
2020-02-14 16:25:11 -08:00
static char distStr [ 20 ] ;
2023-07-26 16:06:31 -07:00
if ( config . display . units = = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL ) {
2025-02-03 06:43:32 +01:00
strncpy ( distStr , " ? mi ?° " , sizeof ( distStr ) ) ; // might not have location data
2023-07-26 16:06:31 -07:00
} else {
2025-02-03 06:43:32 +01:00
strncpy ( distStr , " ? km ?° " , sizeof ( distStr ) ) ;
2023-07-26 16:06:31 -07:00
}
2024-03-21 09:06:37 -05:00
meshtastic_NodeInfoLite * ourNode = nodeDB - > getMeshNode ( nodeDB - > getNodeNum ( ) ) ;
2024-05-22 09:00:04 +12:00
const char * fields [ ] = { username , lastStr , signalStr , distStr , NULL } ;
2022-12-29 15:44:22 +01:00
int16_t compassX = 0 , compassY = 0 ;
2024-06-29 21:16:07 -05:00
uint16_t compassDiam = Screen : : getCompassDiam ( SCREEN_WIDTH , SCREEN_HEIGHT ) ;
2020-06-22 19:27:13 -04:00
// coordinates for the center of the compass/circle
2023-01-21 18:22:19 +01:00
if ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT ) {
2024-06-29 21:16:07 -05:00
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5 ;
2022-12-29 15:44:22 +01:00
compassY = y + SCREEN_HEIGHT / 2 ;
} else {
2024-06-29 21:16:07 -05:00
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 ;
}
2020-07-05 17:03:12 -07:00
bool hasNodeHeading = false ;
2020-06-22 19:27:13 -04:00
2024-11-26 14:00:10 -06:00
if ( ourNode & & ( nodeDB - > hasValidPosition ( ourNode ) | | screen - > hasHeading ( ) ) ) {
2023-08-12 09:29:19 -05:00
const meshtastic_PositionLite & op = ourNode - > position ;
2024-06-11 17:47:45 -05:00
float myHeading ;
if ( screen - > hasHeading ( ) )
myHeading = ( screen - > getHeading ( ) ) * PI / 180 ; // gotta convert compass degrees to Radians
else
2024-06-29 21:16:07 -05:00
myHeading = screen - > estimatedHeading ( DegD ( op . latitude_i ) , DegD ( op . longitude_i ) ) ;
2024-06-28 21:28:18 -05:00
screen - > drawCompassNorth ( display , compassX , compassY , myHeading ) ;
2020-07-03 02:53:56 -07:00
2024-11-26 14:00:10 -06:00
if ( nodeDB - > hasValidPosition ( node ) ) {
2020-07-05 17:03:12 -07:00
// display direction toward node
hasNodeHeading = true ;
2023-08-12 09:29:19 -05:00
const meshtastic_PositionLite & p = node - > position ;
2021-12-26 15:46:23 -08:00
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
2025-02-03 06:43:32 +01: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 ) ;
2025-02-10 14:30:43 -06:00
float bearingToOtherDegrees = ( bearingToOther < 0 ) ? bearingToOther + 2 * PI : bearingToOther ;
2025-02-03 06:43:32 +01:00
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI ;
2023-01-21 18:22:19 +01:00
if ( config . display . units = = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL ) {
2022-09-21 22:37:36 -04:00
if ( d < ( 2 * MILES_TO_FEET ) )
2025-02-03 06:43:32 +01:00
snprintf ( distStr , sizeof ( distStr ) , " %.0fft %.0f° " , d * METERS_TO_FEET , bearingToOtherDegrees ) ;
2022-09-21 22:37:36 -04:00
else
2025-02-10 14:30:43 -06:00
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 )
2025-02-03 06:43:32 +01:00
snprintf ( distStr , sizeof ( distStr ) , " %.0fm %.0f° " , d , bearingToOtherDegrees ) ;
2022-09-21 22:37:36 -04:00
else
2025-02-03 06:43:32 +01:00
snprintf ( distStr , sizeof ( distStr ) , " %.1fkm %.0f° " , d / 1000 , bearingToOtherDegrees ) ;
2022-09-21 22:37:36 -04:00
}
2020-08-12 11:04:03 -07:00
}
2020-02-11 09:39:47 -08:00
}
2022-12-29 15:44:22 +01:00
if ( ! hasNodeHeading ) {
2020-07-05 17:03:12 -07:00
// direction to node is unknown so display question mark
2020-03-25 13:09:12 -07:00
// Debug info for gps lock errors
2024-10-14 06:11:43 +02:00
// LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d", !!ourNode, ourNode && hasValidPosition(ourNode),
2023-05-30 05:26:34 -05:00
// hasValidPosition(node));
2020-10-16 11:22:07 +08:00
display - > drawString ( compassX - FONT_HEIGHT_SMALL / 4 , compassY - FONT_HEIGHT_SMALL / 2 , " ? " ) ;
2022-12-29 15:44:22 +01:00
}
2024-06-29 21:16:07 -05:00
display - > drawCircle ( compassX , compassY , compassDiam / 2 ) ;
2024-06-24 19:04:46 +12:00
if ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED ) {
display - > setColor ( BLACK ) ;
}
// Must be after distStr is populated
2024-06-29 21:16:07 -05:00
screen - > drawColumns ( display , x , y , fields ) ;
2024-06-24 19:04:46 +12:00
}
2025-03-29 02:47:08 -04:00
// h! Makes header invert rounder
2025-03-28 14:39:15 -04:00
void drawRoundedHighlight ( OLEDDisplay * display , int16_t x , int16_t y , int16_t w , int16_t h , int16_t r ) {
// Center rectangles
display - > fillRect ( x + r , y , w - 2 * r , h ) ;
display - > fillRect ( x , y + r , r , h - 2 * r ) ;
display - > fillRect ( x + w - r , y + r , r , h - 2 * r ) ;
// Rounded corners
display - > fillCircle ( x + r , y + r , r ) ; // Top-left
display - > fillCircle ( x + w - r - 1 , y + r , r ) ; // Top-right
display - > fillCircle ( x + r , y + h - r - 1 , r ) ; // Bottom-left
display - > fillCircle ( x + w - r - 1 , y + h - r - 1 , r ) ; // Bottom-right
}
2025-03-29 02:47:08 -04:00
// h! Each node entry holds a reference to its info and how long ago it was heard from
2025-03-28 14:39:15 -04:00
struct NodeEntry {
meshtastic_NodeInfoLite * node ;
uint32_t lastHeard ;
} ;
2025-03-29 02:47:08 -04:00
// h! Calculates bearing between two lat/lon points (used for compass)
2025-03-28 14:39:15 -04:00
float calculateBearing ( double lat1 , double lon1 , double lat2 , double lon2 ) {
double dLon = ( lon2 - lon1 ) * DEG_TO_RAD ;
lat1 = lat1 * DEG_TO_RAD ;
lat2 = lat2 * DEG_TO_RAD ;
double y = sin ( dLon ) * cos ( lat2 ) ;
double x = cos ( lat1 ) * sin ( lat2 ) - sin ( lat1 ) * cos ( lat2 ) * cos ( dLon ) ;
double initialBearing = atan2 ( y , x ) ;
return fmod ( ( initialBearing * RAD_TO_DEG + 360 ) , 360 ) ; // Normalize to 0-360°
}
2025-03-29 04:21:43 -04:00
// Shared scroll index state for node screens
static int scrollIndex = 0 ;
// Helper: Calculates max scroll index based on total entries
int calculateMaxScroll ( int totalEntries , int visibleRows ) {
int totalRows = ( totalEntries + 1 ) / 2 ;
return std : : max ( 0 , totalRows - visibleRows ) ;
}
// Helper: Draw vertical scrollbar matching CannedMessageModule style
void drawScrollbar ( OLEDDisplay * display , int visibleNodeRows , int totalEntries , int scrollIndex , int columns , int rowYOffset ) {
int totalPages = ( totalEntries + columns - 1 ) / columns ;
if ( totalPages < = visibleNodeRows ) return ; // no scrollbar needed
int scrollAreaHeight = visibleNodeRows * ( FONT_HEIGHT_SMALL - 3 ) ; // true pixel height used per row
int scrollbarX = display - > getWidth ( ) - 6 ;
int scrollbarWidth = 4 ;
int scrollBarHeight = ( scrollAreaHeight * visibleNodeRows ) / totalPages ;
int scrollBarY = rowYOffset + ( scrollAreaHeight * scrollIndex ) / totalPages ;
display - > drawRect ( scrollbarX , rowYOffset , scrollbarWidth , scrollAreaHeight ) ;
display - > fillRect ( scrollbarX , scrollBarY , scrollbarWidth , scrollBarHeight ) ;
}
2025-03-28 14:39:15 -04:00
// Grabs all nodes from the DB and sorts them (favorites and most recently heard first)
void retrieveAndSortNodes ( std : : vector < NodeEntry > & nodeList ) {
size_t numNodes = nodeDB - > getNumMeshNodes ( ) ;
for ( size_t i = 0 ; i < numNodes ; i + + ) {
meshtastic_NodeInfoLite * node = nodeDB - > getMeshNodeByIndex ( i ) ;
if ( ! node | | node - > num = = nodeDB - > getNodeNum ( ) ) continue ; // Skip self
nodeList . push_back ( { node , sinceLastSeen ( node ) } ) ;
}
std : : sort ( nodeList . begin ( ) , nodeList . end ( ) , [ ] ( const NodeEntry & a , const NodeEntry & b ) {
bool aFav = a . node - > is_favorite ;
bool bFav = b . node - > is_favorite ;
if ( aFav ! = bFav ) return aFav > bFav ;
if ( a . lastHeard = = 0 | | a . lastHeard = = UINT32_MAX ) return false ;
if ( b . lastHeard = = 0 | | b . lastHeard = = UINT32_MAX ) return true ;
return a . lastHeard < b . lastHeard ;
} ) ;
}
// Helper: Fallback-NodeID if emote is on ShortName for display purposes
String getSafeNodeName ( meshtastic_NodeInfoLite * node ) {
String nodeName = " ? " ;
if ( node - > has_user & & strlen ( node - > user . short_name ) > 0 ) {
bool valid = true ;
const char * name = node - > user . short_name ;
for ( size_t i = 0 ; i < strlen ( name ) ; i + + ) {
uint8_t c = ( uint8_t ) name [ i ] ;
if ( c < 32 | | c > 126 ) {
valid = false ;
break ;
}
}
if ( valid ) {
nodeName = name ;
} else {
// fallback: last 4 hex digits of node ID, no prefix
char idStr [ 6 ] ;
snprintf ( idStr , sizeof ( idStr ) , " %04X " , ( uint16_t ) ( node - > num & 0xFFFF ) ) ;
nodeName = String ( idStr ) ;
}
}
if ( node - > is_favorite ) nodeName = " * " + nodeName ;
return nodeName ;
}
// Draws the top header bar (optionally inverted or bold)
void drawScreenHeader ( OLEDDisplay * display , const char * title , int16_t x , int16_t y ) {
bool isInverted = ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED ) ;
bool isBold = config . display . heading_bold ;
display - > setFont ( FONT_SMALL ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
int screenWidth = display - > getWidth ( ) ;
int textWidth = display - > getStringWidth ( title ) ;
2025-03-29 02:47:08 -04:00
int titleX = ( screenWidth - textWidth ) / 2 ;
// Height of highlight row
const int highlightHeight = FONT_HEIGHT_SMALL - 1 ;
// Y offset to vertically center text in rounded bar
int textY = y + ( highlightHeight - FONT_HEIGHT_SMALL ) / 2 ;
2025-03-28 14:39:15 -04:00
if ( isInverted ) {
2025-03-29 02:47:08 -04:00
drawRoundedHighlight ( display , 0 , y , screenWidth , highlightHeight , 2 ) ;
2025-03-28 14:39:15 -04:00
display - > setColor ( BLACK ) ;
}
2025-03-29 02:47:08 -04:00
// Draw text centered vertically and horizontally
display - > drawString ( titleX , textY , title ) ;
if ( isBold ) display - > drawString ( titleX + 1 , textY , title ) ;
2025-03-28 14:39:15 -04:00
display - > setColor ( WHITE ) ;
}
// Draws separator line
void drawColumnSeparator ( OLEDDisplay * display , int16_t x , int16_t yStart , int16_t yEnd ) {
int columnWidth = display - > getWidth ( ) / 2 ;
int separatorX = x + columnWidth - 2 ;
display - > drawLine ( separatorX , yStart , separatorX , yEnd - 3 ) ;
}
// Draws node name with how long ago it was last heard from
void drawEntryLastHeard ( OLEDDisplay * display , meshtastic_NodeInfoLite * node , int16_t x , int16_t y , int columnWidth ) {
int screenWidth = display - > getWidth ( ) ;
bool isLeftCol = ( x < screenWidth / 2 ) ;
// Adjust offset based on column and screen width
int timeOffset = ( screenWidth > 128 ) ? ( isLeftCol ? 41 : 45 ) : ( isLeftCol ? 24 : 30 ) ; //offset large screen (?Left:Right column), offset small screen (?Left:Right column)
String nodeName = getSafeNodeName ( node ) ;
char timeStr [ 10 ] ;
uint32_t seconds = sinceLastSeen ( node ) ;
if ( seconds = = 0 | | seconds = = UINT32_MAX ) {
snprintf ( timeStr , sizeof ( timeStr ) , " ? " ) ;
} else {
uint32_t minutes = seconds / 60 , hours = minutes / 60 , days = hours / 24 ;
snprintf ( timeStr , sizeof ( timeStr ) , ( days > 365 ? " ? " : " %d%c " ) ,
( days ? days : hours ? hours : minutes ) , ( days ? ' d ' : hours ? ' h ' : ' m ' ) ) ;
}
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
display - > drawString ( x , y , nodeName ) ;
display - > drawString ( x + columnWidth - timeOffset , y , timeStr ) ;
}
// Draws each node's name, hop count, and signal bars
void drawEntryHopSignal ( OLEDDisplay * display , meshtastic_NodeInfoLite * node , int16_t x , int16_t y , int columnWidth ) {
int screenWidth = display - > getWidth ( ) ;
bool isLeftCol = ( x < screenWidth / 2 ) ;
int nameMaxWidth = columnWidth - 25 ;
int barsOffset = ( screenWidth > 128 ) ? ( isLeftCol ? 26 : 30 ) : ( isLeftCol ? 17 : 19 ) ; //offset large screen (?Left:Right column), offset small screen (?Left:Right column)
int hopOffset = ( screenWidth > 128 ) ? ( isLeftCol ? 32 : 38 ) : ( isLeftCol ? 18 : 20 ) ; //offset large screen (?Left:Right column), offset small screen (?Left:Right column)
int barsXOffset = columnWidth - barsOffset ;
String nodeName = getSafeNodeName ( node ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
display - > drawStringMaxWidth ( x , y , nameMaxWidth , nodeName ) ;
char hopStr [ 6 ] = " " ;
if ( node - > has_hops_away & & node - > hops_away > 0 )
snprintf ( hopStr , sizeof ( hopStr ) , " [%d] " , node - > hops_away ) ;
if ( hopStr [ 0 ] ! = ' \0 ' ) {
int hopX = x + columnWidth - hopOffset - display - > getStringWidth ( hopStr ) ;
display - > drawString ( hopX , y , hopStr ) ;
}
// Signal bars based on SNR
int bars = ( node - > snr > 5 ) ? 4 : ( node - > snr > 0 ) ? 3 : ( node - > snr > - 5 ) ? 2 : ( node - > snr > - 10 ) ? 1 : 0 ;
int barWidth = 2 ;
int barStartX = x + barsXOffset ;
int barStartY = y + ( FONT_HEIGHT_SMALL / 2 ) + 2 ;
for ( int b = 0 ; b < 4 ; b + + ) {
if ( b < bars ) {
int height = 2 + ( b * 2 ) ;
display - > fillRect ( barStartX + ( b * ( barWidth + 1 ) ) , barStartY - height , barWidth , height ) ;
}
}
}
// Typedef for passing different render functions into one reusable screen function
typedef void ( * EntryRenderer ) ( OLEDDisplay * , meshtastic_NodeInfoLite * , int16_t , int16_t , int ) ;
// Shared function that renders all node screens (LastHeard, Hop/Signal)
void drawNodeListScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y , const char * title , EntryRenderer renderer ) {
int columnWidth = display - > getWidth ( ) / 2 ;
2025-03-29 04:21:43 -04:00
int totalRowsAvailable = ( display - > getHeight ( ) - y - FONT_HEIGHT_SMALL ) / ( FONT_HEIGHT_SMALL - 3 ) ;
int visibleNodeRows = std : : min ( 6 , totalRowsAvailable ) ;
int rowYOffset = FONT_HEIGHT_SMALL - 3 ;
2025-03-28 14:39:15 -04:00
display - > clear ( ) ;
drawScreenHeader ( display , title , x , y ) ;
std : : vector < NodeEntry > nodeList ;
retrieveAndSortNodes ( nodeList ) ;
2025-03-29 04:21:43 -04:00
int totalEntries = nodeList . size ( ) ;
int maxScroll = calculateMaxScroll ( totalEntries , visibleNodeRows ) ;
scrollIndex = std : : min ( scrollIndex , maxScroll ) ;
int startIndex = scrollIndex * visibleNodeRows * 2 ;
int endIndex = std : : min ( startIndex + visibleNodeRows * 2 , totalEntries ) ;
int yOffset = rowYOffset ;
int col = 0 ;
int lastNodeY = y ;
int shownCount = 0 ;
for ( int i = startIndex ; i < endIndex ; + + i ) {
2025-03-28 14:39:15 -04:00
int xPos = x + ( col * columnWidth ) ;
2025-03-29 04:21:43 -04:00
int yPos = y + yOffset ;
renderer ( display , nodeList [ i ] . node , xPos , yPos , columnWidth ) ;
2025-03-28 14:39:15 -04:00
lastNodeY = std : : max ( lastNodeY , y + yOffset + FONT_HEIGHT_SMALL ) ;
yOffset + = FONT_HEIGHT_SMALL - 3 ;
2025-03-29 04:21:43 -04:00
shownCount + + ;
2025-03-28 14:39:15 -04:00
if ( y + yOffset > display - > getHeight ( ) - FONT_HEIGHT_SMALL ) {
2025-03-29 04:21:43 -04:00
yOffset = rowYOffset ;
2025-03-28 14:39:15 -04:00
col + + ;
if ( col > 1 ) break ;
}
}
2025-03-29 04:21:43 -04:00
2025-03-28 14:39:15 -04:00
drawColumnSeparator ( display , x , y + FONT_HEIGHT_SMALL - 2 , lastNodeY ) ;
2025-03-29 04:21:43 -04:00
drawScrollbar ( display , visibleNodeRows , totalEntries , scrollIndex , 2 , rowYOffset ) ;
2025-03-28 14:39:15 -04:00
}
// Public screen function: shows how recently nodes were heard
static void drawLastHeardScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
drawNodeListScreen ( display , state , x , y , " Node List " , drawEntryLastHeard ) ;
}
// Public screen function: shows hop count + signal strength
static void drawHopSignalScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
drawNodeListScreen ( display , state , x , y , " Hops/Signal " , drawEntryHopSignal ) ;
}
// Helper function: Draw a single node entry for Node List (Modified for Compass Screen)
void drawEntryCompass ( OLEDDisplay * display , meshtastic_NodeInfoLite * node , int16_t x , int16_t y , int columnWidth ) {
int screenWidth = display - > getWidth ( ) ;
bool isLeftCol = ( x < screenWidth / 2 ) ;
// Adjust max text width depending on column and screen width
int nameMaxWidth = columnWidth - ( screenWidth > 128 ? ( isLeftCol ? 25 : 28 ) : ( isLeftCol ? 20 : 22 ) ) ;
String nodeName = getSafeNodeName ( node ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
display - > drawStringMaxWidth ( x , y , nameMaxWidth , nodeName ) ;
}
// Extra compass element drawer (injects compass arrows)
typedef void ( * CompassExtraRenderer ) ( OLEDDisplay * , meshtastic_NodeInfoLite * , int16_t , int16_t , int columnWidth , float myHeading , double userLat , double userLon ) ;
void drawCompassArrow ( OLEDDisplay * display , meshtastic_NodeInfoLite * node , int16_t x , int16_t y , int columnWidth , float myHeading , double userLat , double userLon ) {
if ( ! nodeDB - > hasValidPosition ( node ) ) return ;
int screenWidth = display - > getWidth ( ) ;
bool isLeftCol = ( x < screenWidth / 2 ) ;
double nodeLat = node - > position . latitude_i * 1e-7 ;
double nodeLon = node - > position . longitude_i * 1e-7 ;
float bearingToNode = calculateBearing ( userLat , userLon , nodeLat , nodeLon ) ;
float relativeBearing = fmod ( ( bearingToNode - myHeading + 360 ) , 360 ) ;
float arrowAngle = relativeBearing * DEG_TO_RAD ;
// Adaptive offset for compass icon based on screen width + column
int arrowXOffset = ( screenWidth > 128 ) ? ( isLeftCol ? 22 : 24 ) : ( isLeftCol ? 12 : 18 ) ;
int compassX = x + columnWidth - arrowXOffset ;
int compassY = y + FONT_HEIGHT_SMALL / 2 ;
int size = FONT_HEIGHT_SMALL / 2 - 2 ;
int arrowLength = size - 2 ;
int xEnd = compassX + arrowLength * cos ( arrowAngle ) ;
int yEnd = compassY - arrowLength * sin ( arrowAngle ) ;
display - > fillCircle ( compassX , compassY , size ) ;
display - > drawCircle ( compassX , compassY , size ) ;
display - > drawLine ( compassX , compassY , xEnd , yEnd ) ;
}
// Generic node+compass renderer (like drawNodeListScreen but with compass support)
void drawNodeListWithExtrasScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y , const char * title ,
EntryRenderer renderer , CompassExtraRenderer extras ) {
int columnWidth = display - > getWidth ( ) / 2 ;
2025-03-29 04:21:43 -04:00
int totalRowsAvailable = ( display - > getHeight ( ) - y - FONT_HEIGHT_SMALL ) / ( FONT_HEIGHT_SMALL - 3 ) ;
int visibleNodeRows = std : : min ( 6 , totalRowsAvailable ) ;
int rowYOffset = FONT_HEIGHT_SMALL - 3 ;
2025-03-28 14:39:15 -04:00
display - > clear ( ) ;
drawScreenHeader ( display , title , x , y ) ;
std : : vector < NodeEntry > nodeList ;
retrieveAndSortNodes ( nodeList ) ;
2025-03-29 04:21:43 -04:00
int totalEntries = nodeList . size ( ) ;
int maxScroll = calculateMaxScroll ( totalEntries , visibleNodeRows ) ;
scrollIndex = std : : min ( scrollIndex , maxScroll ) ;
2025-03-28 14:39:15 -04:00
meshtastic_NodeInfoLite * ourNode = nodeDB - > getMeshNode ( nodeDB - > getNodeNum ( ) ) ;
double userLat = 0.0 , userLon = 0.0 ;
bool hasUserPosition = nodeDB - > hasValidPosition ( ourNode ) ;
if ( hasUserPosition ) {
userLat = ourNode - > position . latitude_i * 1e-7 ;
userLon = ourNode - > position . longitude_i * 1e-7 ;
}
float myHeading = screen - > hasHeading ( ) ? screen - > getHeading ( ) : 0.0f ;
2025-03-29 04:21:43 -04:00
int startIndex = scrollIndex * visibleNodeRows * 2 ;
int endIndex = std : : min ( startIndex + visibleNodeRows * 2 , totalEntries ) ;
int yOffset = rowYOffset ;
int col = 0 ;
int lastNodeY = y ;
int shownCount = 0 ;
for ( int i = startIndex ; i < endIndex ; + + i ) {
2025-03-28 14:39:15 -04:00
int xPos = x + ( col * columnWidth ) ;
2025-03-29 04:21:43 -04:00
int yPos = y + yOffset ;
renderer ( display , nodeList [ i ] . node , xPos , yPos , columnWidth ) ;
2025-03-28 14:39:15 -04:00
if ( hasUserPosition & & extras ) {
2025-03-29 04:21:43 -04:00
extras ( display , nodeList [ i ] . node , xPos , yPos , columnWidth , myHeading , userLat , userLon ) ;
2025-03-28 14:39:15 -04:00
}
lastNodeY = std : : max ( lastNodeY , y + yOffset + FONT_HEIGHT_SMALL ) ;
yOffset + = FONT_HEIGHT_SMALL - 3 ;
2025-03-29 04:21:43 -04:00
shownCount + + ;
2025-03-28 14:39:15 -04:00
if ( y + yOffset > display - > getHeight ( ) - FONT_HEIGHT_SMALL ) {
2025-03-29 04:21:43 -04:00
yOffset = rowYOffset ;
2025-03-28 14:39:15 -04:00
col + + ;
if ( col > 1 ) break ;
}
}
drawColumnSeparator ( display , x , y + FONT_HEIGHT_SMALL - 2 , lastNodeY ) ;
2025-03-29 04:21:43 -04:00
drawScrollbar ( display , visibleNodeRows , totalEntries , scrollIndex , 2 , rowYOffset ) ;
2025-03-28 14:39:15 -04:00
}
2025-03-29 04:21:43 -04:00
2025-03-28 14:39:15 -04:00
// Public screen entry for compass
static void drawNodeListWithCompasses ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
drawNodeListWithExtrasScreen ( display , state , x , y , " Bearings " , drawEntryCompass , drawCompassArrow ) ;
}
void drawNodeDistance ( OLEDDisplay * display , meshtastic_NodeInfoLite * node , int16_t x , int16_t y , int columnWidth ) {
int screenWidth = display - > getWidth ( ) ;
bool isLeftCol = ( x < screenWidth / 2 ) ;
int nameMaxWidth = columnWidth - ( screenWidth > 128 ? ( isLeftCol ? 25 : 28 ) : ( isLeftCol ? 20 : 22 ) ) ;
String nodeName = getSafeNodeName ( node ) ;
char distStr [ 10 ] = " " ;
meshtastic_NodeInfoLite * ourNode = nodeDB - > getMeshNode ( nodeDB - > getNodeNum ( ) ) ;
if ( nodeDB - > hasValidPosition ( ourNode ) & & nodeDB - > hasValidPosition ( node ) ) {
double lat1 = ourNode - > position . latitude_i * 1e-7 ;
double lon1 = ourNode - > position . longitude_i * 1e-7 ;
double lat2 = node - > position . latitude_i * 1e-7 ;
double lon2 = node - > position . longitude_i * 1e-7 ;
double earthRadiusKm = 6371.0 ;
double dLat = ( lat2 - lat1 ) * DEG_TO_RAD ;
double dLon = ( lon2 - lon1 ) * DEG_TO_RAD ;
double a = sin ( dLat / 2 ) * sin ( dLat / 2 ) +
cos ( lat1 * DEG_TO_RAD ) * cos ( lat2 * DEG_TO_RAD ) *
sin ( dLon / 2 ) * sin ( dLon / 2 ) ;
double c = 2 * atan2 ( sqrt ( a ) , sqrt ( 1 - a ) ) ;
double distanceKm = earthRadiusKm * c ;
if ( config . display . units = = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL ) {
double miles = distanceKm * 0.621371 ;
if ( miles < 0.1 ) {
snprintf ( distStr , sizeof ( distStr ) , " %dft " , ( int ) ( miles * 5280 ) ) ; // show feet
} else if ( miles < 10.0 ) {
snprintf ( distStr , sizeof ( distStr ) , " %.1fmi " , miles ) ; // 1 decimal
} else {
snprintf ( distStr , sizeof ( distStr ) , " %dmi " , ( int ) miles ) ; // no decimal
}
} else {
if ( distanceKm < 1.0 ) {
snprintf ( distStr , sizeof ( distStr ) , " %dm " , ( int ) ( distanceKm * 1000 ) ) ; // show meters
} else if ( distanceKm < 10.0 ) {
snprintf ( distStr , sizeof ( distStr ) , " %.1fkm " , distanceKm ) ; // 1 decimal
} else {
snprintf ( distStr , sizeof ( distStr ) , " %dkm " , ( int ) distanceKm ) ; // no decimal
}
}
}
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
display - > drawStringMaxWidth ( x , y , nameMaxWidth , nodeName ) ;
if ( strlen ( distStr ) > 0 ) {
int offset = ( screenWidth > 128 ) ? ( isLeftCol ? 55 : 63 ) : ( isLeftCol ? 32 : 37 ) ;
display - > drawString ( x + columnWidth - offset , y , distStr ) ;
}
}
static void drawDistanceScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
drawNodeListScreen ( display , state , x , y , " Distances " , drawNodeDistance ) ;
}
2025-03-30 16:53:44 -04:00
void drawCommonHeader ( OLEDDisplay * display , int16_t x , int16_t y ) {
const bool isInverted = ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED ) ;
const bool isBold = config . display . heading_bold ;
2025-03-29 02:47:08 -04:00
const int xOffset = 3 ;
const int highlightHeight = FONT_HEIGHT_SMALL - 1 ;
2025-03-30 16:53:44 -04:00
const int screenWidth = display - > getWidth ( ) ;
2025-03-29 20:27:59 -04:00
display - > setFont ( FONT_SMALL ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2025-03-28 14:39:15 -04:00
2025-03-30 16:53:44 -04:00
// Draw background highlight
2025-03-28 14:39:15 -04:00
if ( isInverted ) {
2025-03-30 16:53:44 -04:00
drawRoundedHighlight ( display , x , y , screenWidth , highlightHeight , 2 ) ;
2025-03-28 14:39:15 -04:00
display - > setColor ( BLACK ) ;
}
2025-03-29 20:27:59 -04:00
// Battery icon
2025-03-30 01:25:22 -04:00
drawBattery ( display , x + xOffset - 2 , y + 2 , imgBattery , powerStatus ) ;
2025-03-29 02:47:08 -04:00
2025-03-30 16:53:44 -04:00
// Text baseline
const int textY = y + ( highlightHeight - FONT_HEIGHT_SMALL ) / 2 ;
2025-03-28 14:39:15 -04:00
2025-03-29 20:27:59 -04:00
// Battery %
2025-03-28 14:39:15 -04:00
char percentStr [ 8 ] ;
snprintf ( percentStr , sizeof ( percentStr ) , " %d%% " , powerStatus - > getBatteryChargePercent ( ) ) ;
2025-03-30 16:53:44 -04:00
const int batteryOffset = screenWidth > 128 ? 34 : 16 ;
const int percentX = x + xOffset + batteryOffset ;
2025-03-29 02:47:08 -04:00
display - > drawString ( percentX , textY , percentStr ) ;
2025-03-29 20:10:05 -04:00
if ( isBold ) display - > drawString ( percentX + 1 , textY , percentStr ) ;
2025-03-28 14:39:15 -04:00
2025-03-30 16:53:44 -04:00
// Time (right side)
2025-03-29 20:27:59 -04:00
uint32_t rtc_sec = getValidTime ( RTCQuality : : RTCQualityDevice , true ) ;
2025-03-29 20:10:05 -04:00
if ( rtc_sec > 0 ) {
2025-03-30 16:53:44 -04:00
long hms = ( rtc_sec % SEC_PER_DAY + SEC_PER_DAY ) % SEC_PER_DAY ;
2025-03-29 20:10:05 -04:00
int hour = hms / SEC_PER_HOUR ;
int minute = ( hms % SEC_PER_HOUR ) / SEC_PER_MIN ;
bool isPM = hour > = 12 ;
hour = hour % 12 ;
if ( hour = = 0 ) hour = 12 ;
char timeStr [ 10 ] ;
snprintf ( timeStr , sizeof ( timeStr ) , " %d:%02d%s " , hour , minute , isPM ? " PM " : " AM " ) ;
2025-03-30 16:53:44 -04:00
int timeX = x + screenWidth - xOffset - display - > getStringWidth ( timeStr ) ;
2025-03-29 20:10:05 -04:00
display - > drawString ( timeX , textY , timeStr ) ;
if ( isBold ) display - > drawString ( timeX + 1 , textY , timeStr ) ;
2025-03-28 14:39:15 -04:00
}
display - > setColor ( WHITE ) ;
2025-03-29 20:27:59 -04:00
}
2025-03-30 01:25:22 -04:00
2025-03-30 16:53:44 -04:00
2025-03-29 20:27:59 -04:00
static void drawDefaultScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
display - > clear ( ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
// === Header ===
drawCommonHeader ( display , x , y ) ;
2025-03-28 14:39:15 -04:00
2025-03-29 02:47:08 -04:00
// === Second Row: Node and GPS ===
2025-03-28 14:39:15 -04:00
bool origBold = config . display . heading_bold ;
config . display . heading_bold = false ;
int secondRowY = y + FONT_HEIGHT_SMALL + 1 ;
drawNodes ( display , x , secondRowY , nodeStatus ) ;
# if HAS_GPS
if ( config . position . fixed_position ) {
drawGPS ( display , SCREEN_WIDTH - 44 , secondRowY , gpsStatus ) ;
} else if ( ! gpsStatus | | ! gpsStatus - > getIsConnected ( ) ) {
String displayLine = config . position . gps_mode = = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? " No GPS " : " GPS off " ;
int posX = SCREEN_WIDTH - display - > getStringWidth ( displayLine ) - 2 ;
display - > drawString ( posX , secondRowY , displayLine ) ;
} else {
drawGPS ( display , SCREEN_WIDTH - 44 , secondRowY , gpsStatus ) ;
}
# endif
config . display . heading_bold = origBold ;
2025-03-29 02:47:08 -04:00
// === Third Row: LongName Centered ===
2025-03-28 14:39:15 -04:00
meshtastic_NodeInfoLite * ourNode = nodeDB - > getMeshNode ( nodeDB - > getNodeNum ( ) ) ;
if ( ourNode & & ourNode - > has_user & & strlen ( ourNode - > user . long_name ) > 0 ) {
const char * longName = ourNode - > user . long_name ;
int textWidth = display - > getStringWidth ( longName ) ;
int nameX = ( SCREEN_WIDTH - textWidth ) / 2 ;
int nameY = y + ( FONT_HEIGHT_SMALL + 1 ) * 2 ;
display - > drawString ( nameX , nameY , longName ) ;
}
2025-03-29 02:47:08 -04:00
// === Fourth Row: Uptime ===
2025-03-28 14:39:15 -04:00
uint32_t uptime = millis ( ) / 1000 ;
char uptimeStr [ 6 ] ;
2025-03-29 02:47:08 -04:00
uint32_t minutes = uptime / 60 , hours = minutes / 60 , days = hours / 24 ;
2025-03-28 14:39:15 -04:00
if ( days > 365 ) {
snprintf ( uptimeStr , sizeof ( uptimeStr ) , " ? " ) ;
} else {
snprintf ( uptimeStr , sizeof ( uptimeStr ) , " %d%c " ,
days ? days : hours ? hours : minutes ? minutes : ( int ) uptime ,
days ? ' d ' : hours ? ' h ' : minutes ? ' m ' : ' s ' ) ;
}
2025-03-29 19:22:09 -04:00
char uptimeFullStr [ 16 ] ;
snprintf ( uptimeFullStr , sizeof ( uptimeFullStr ) , " Uptime: %s " , uptimeStr ) ;
int uptimeX = ( SCREEN_WIDTH - display - > getStringWidth ( uptimeFullStr ) ) / 2 ;
2025-03-28 14:39:15 -04:00
int uptimeY = y + ( FONT_HEIGHT_SMALL + 1 ) * 3 ;
2025-03-29 19:22:09 -04:00
display - > drawString ( uptimeX , uptimeY , uptimeFullStr ) ;
2025-03-28 14:39:15 -04:00
}
2025-03-29 23:46:54 -04:00
// ****************************
// * BatteryDeviceLoRa Screen *
// ****************************
static void drawBatteryDeviceLoRa ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
display - > clear ( ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
// === Header ===
drawCommonHeader ( display , x , y ) ;
// === Second Row: MAC ID and Region ===
bool origBold = config . display . heading_bold ;
config . display . heading_bold = false ;
int secondRowY = y + FONT_HEIGHT_SMALL + 1 ;
// Get our hardware ID
uint8_t dmac [ 6 ] ;
getMacAddr ( dmac ) ;
snprintf ( ourId , sizeof ( ourId ) , " %02x%02x " , dmac [ 4 ] , dmac [ 5 ] ) ;
# 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 ) | | ARCH_PORTDUINO ) & & \
! defined ( DISPLAY_FORCE_SMALL_FONTS )
display - > drawFastImage ( x , y + 3 + FONT_HEIGHT_SMALL , 12 , 8 , imgInfoL1 ) ;
display - > drawFastImage ( x , y + 11 + FONT_HEIGHT_SMALL , 12 , 8 , imgInfoL2 ) ;
# else
display - > drawFastImage ( x , y + 2 + FONT_HEIGHT_SMALL , 8 , 8 , imgInfo ) ;
# endif
display - > drawString ( x + 14 , secondRowY , ourId ) ;
const char * region = myRegion ? myRegion - > name : NULL ;
display - > drawString ( x + SCREEN_WIDTH - display - > getStringWidth ( region ) , secondRowY , region ) ;
config . display . heading_bold = origBold ;
// === Third Row: Channel and Channel Utilization ===
int thirdRowY = y + ( FONT_HEIGHT_SMALL * 2 ) + 1 ;
char channelStr [ 20 ] ;
{
snprintf ( channelStr , sizeof ( channelStr ) , " #%s " , channels . getName ( channels . getPrimaryIndex ( ) ) ) ;
}
display - > drawString ( x , thirdRowY , channelStr ) ;
// Display Channel Utilization
char chUtil [ 13 ] ;
snprintf ( chUtil , sizeof ( chUtil ) , " ChUtil %2.0f%% " , airTime - > channelUtilizationPercent ( ) ) ;
display - > drawString ( x + SCREEN_WIDTH - display - > getStringWidth ( chUtil ) , thirdRowY , chUtil ) ;
// === Fourth Row: Uptime ===
uint32_t uptime = millis ( ) / 1000 ;
char uptimeStr [ 6 ] ;
uint32_t minutes = uptime / 60 , hours = minutes / 60 , days = hours / 24 ;
if ( days > 365 ) {
snprintf ( uptimeStr , sizeof ( uptimeStr ) , " ? " ) ;
} else {
snprintf ( uptimeStr , sizeof ( uptimeStr ) , " %d%c " ,
days ? days
: hours ? hours
: minutes ? minutes
: ( int ) uptime ,
days ? ' d '
: hours ? ' h '
: minutes ? ' m '
: ' s ' ) ;
}
char uptimeFullStr [ 16 ] ;
snprintf ( uptimeFullStr , sizeof ( uptimeFullStr ) , " Uptime: %s " , uptimeStr ) ;
int uptimeX = ( SCREEN_WIDTH - display - > getStringWidth ( uptimeFullStr ) ) / 2 ;
int uptimeY = y + ( FONT_HEIGHT_SMALL + 1 ) * 3 ;
display - > drawString ( uptimeX , uptimeY , uptimeFullStr ) ;
}
// ****************************
// * Activity Screen *
// ****************************
static void drawActivity ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
display - > clear ( ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
// === Header ===
2025-03-30 16:53:44 -04:00
drawCommonHeader ( display , x , y ) ;
// === Draw title ===
const int highlightHeight = FONT_HEIGHT_SMALL - 1 ;
const int textY = y + ( highlightHeight - FONT_HEIGHT_SMALL ) / 2 ;
// Use black text if display is inverted
if ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED ) {
display - > setColor ( BLACK ) ;
}
display - > setTextAlignment ( TEXT_ALIGN_CENTER ) ;
const int centerX = x + SCREEN_WIDTH / 2 ;
display - > drawString ( centerX , textY , " Log " ) ;
if ( config . display . heading_bold ) {
display - > drawString ( centerX + 1 , textY , " Log " ) ;
}
// Restore default color after drawing
display - > setColor ( WHITE ) ;
2025-03-29 23:46:54 -04:00
// === Second Row: Draw any log messages ===
int secondRowY = y + FONT_HEIGHT_SMALL + 1 ;
display - > drawLogBuffer ( x , secondRowY ) ;
}
// ****************************
2025-03-30 01:25:22 -04:00
// * My Position Screen *
2025-03-29 23:46:54 -04:00
// ****************************
2025-03-29 23:22:54 -04:00
static void drawCompassAndLocationScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
display - > clear ( ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > setFont ( FONT_SMALL ) ;
// === Header ===
drawCommonHeader ( display , x , y ) ;
// Row Y offset just like drawNodeListScreen
int rowYOffset = FONT_HEIGHT_SMALL - 3 ;
int rowY = y + rowYOffset ;
2025-03-30 01:25:22 -04:00
// === Second Row: My Location ===
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > drawString ( x + 2 , rowY , " My Location: " ) ;
2025-03-29 23:22:54 -04:00
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
# if HAS_GPS
// === Update GeoCoord ===
geoCoord . updateCoords (
int32_t ( gpsStatus - > getLatitude ( ) ) ,
int32_t ( gpsStatus - > getLongitude ( ) ) ,
int32_t ( gpsStatus - > getAltitude ( ) )
) ;
2025-03-30 01:25:22 -04:00
// === Determine Compass Heading ===
float heading ;
bool validHeading = false ;
if ( screen - > hasHeading ( ) ) {
heading = radians ( screen - > getHeading ( ) ) ;
validHeading = true ;
} else {
heading = screen - > estimatedHeading ( geoCoord . getLatitude ( ) * 1e-7 , geoCoord . getLongitude ( ) * 1e-7 ) ;
validHeading = ! isnan ( heading ) ;
}
2025-03-29 23:22:54 -04:00
// === Third Row: Altitude ===
rowY + = rowYOffset ;
char altStr [ 32 ] ;
if ( config . display . units = = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL ) {
snprintf ( altStr , sizeof ( altStr ) , " Alt: %.1fft " , geoCoord . getAltitude ( ) * METERS_TO_FEET ) ;
} else {
snprintf ( altStr , sizeof ( altStr ) , " Alt: %.1fm " , geoCoord . getAltitude ( ) ) ;
}
display - > drawString ( x + 2 , rowY , altStr ) ;
// === Fourth Row: Latitude ===
rowY + = rowYOffset ;
char latStr [ 32 ] ;
snprintf ( latStr , sizeof ( latStr ) , " Lat: %.5f " , geoCoord . getLatitude ( ) * 1e-7 ) ;
display - > drawString ( x + 2 , rowY , latStr ) ;
// === Fifth Row: Longitude ===
rowY + = rowYOffset ;
char lonStr [ 32 ] ;
snprintf ( lonStr , sizeof ( lonStr ) , " Lon: %.5f " , geoCoord . getLongitude ( ) * 1e-7 ) ;
display - > drawString ( x + 2 , rowY , lonStr ) ;
// === Draw Compass if heading is valid ===
2025-03-30 01:25:22 -04:00
if ( validHeading ) {
2025-03-29 23:22:54 -04:00
uint16_t compassDiam = Screen : : getCompassDiam ( SCREEN_WIDTH , SCREEN_HEIGHT ) ;
int16_t compassX = x + SCREEN_WIDTH - compassDiam / 2 - 8 ;
int16_t compassY = y + SCREEN_HEIGHT / 2 + rowYOffset ;
screen - > drawCompassNorth ( display , compassX , compassY , heading ) ;
screen - > drawNodeHeading ( display , compassX , compassY , compassDiam , - heading ) ;
display - > drawCircle ( compassX , compassY , compassDiam / 2 ) ;
// === Draw moving "N" label slightly inside edge of circle ===
float northAngle = - heading ;
float radius = compassDiam / 2 ;
2025-03-30 01:25:22 -04:00
int16_t nX = compassX + ( radius - 1 ) * sin ( northAngle ) ;
int16_t nY = compassY - ( radius - 1 ) * cos ( northAngle ) ;
2025-03-29 23:22:54 -04:00
int16_t nLabelWidth = display - > getStringWidth ( " N " ) + 2 ;
int16_t nLabelHeight = FONT_HEIGHT_SMALL + 1 ;
2025-03-30 01:25:22 -04:00
display - > setColor ( BLACK ) ;
2025-03-29 23:22:54 -04:00
display - > fillRect ( nX - nLabelWidth / 2 , nY - nLabelHeight / 2 , nLabelWidth , nLabelHeight ) ;
display - > setColor ( WHITE ) ;
display - > setFont ( FONT_SMALL ) ;
display - > setTextAlignment ( TEXT_ALIGN_CENTER ) ;
display - > drawString ( nX , nY - FONT_HEIGHT_SMALL / 2 , " N " ) ;
}
# endif
}
2025-03-30 16:53:44 -04:00
// ****************************
// * Memory Screen *
// ****************************
static void drawMemoryScreen ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y ) {
display - > clear ( ) ;
display - > setFont ( FONT_SMALL ) ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
// === Header ===
drawCommonHeader ( display , x , y ) ;
// === Draw title ===
const int highlightHeight = FONT_HEIGHT_SMALL - 1 ;
const int textY = y + ( highlightHeight - FONT_HEIGHT_SMALL ) / 2 ;
if ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED ) {
display - > setColor ( BLACK ) ;
}
display - > setTextAlignment ( TEXT_ALIGN_CENTER ) ;
const int centerX = x + SCREEN_WIDTH / 2 ;
display - > drawString ( centerX , textY , " Memory " ) ;
if ( config . display . heading_bold ) {
display - > drawString ( centerX + 1 , textY , " Memory " ) ;
}
display - > setColor ( WHITE ) ;
// === Layout ===
const int screenWidth = display - > getWidth ( ) ;
const int rowYOffset = FONT_HEIGHT_SMALL - 3 ;
const int barHeight = 6 ;
const int labelX = x ;
const int barsOffset = ( screenWidth > 128 ) ? 24 : 0 ;
const int barX = x + 40 + barsOffset ;
const int barWidth = SCREEN_WIDTH - barX - 35 ;
const int textRightX = x + SCREEN_WIDTH - 2 ;
int rowY = y + rowYOffset ;
auto drawUsageRow = [ & ] ( const char * label , uint32_t used , uint32_t total ) {
if ( total = = 0 ) return ;
int percent = ( used * 100 ) / total ;
int fillWidth = ( used * barWidth ) / total ;
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
display - > drawString ( labelX , rowY , label ) ;
int barY = rowY + ( FONT_HEIGHT_SMALL - barHeight ) / 2 ;
display - > setColor ( WHITE ) ;
display - > drawRect ( barX , barY , barWidth , barHeight ) ;
if ( percent > = 80 ) display - > setColor ( BLACK ) ;
display - > fillRect ( barX , barY , fillWidth , barHeight ) ;
display - > setColor ( WHITE ) ;
char percentStr [ 6 ] ;
snprintf ( percentStr , sizeof ( percentStr ) , " %3d%% " , percent ) ;
display - > setTextAlignment ( TEXT_ALIGN_RIGHT ) ;
display - > drawString ( textRightX , rowY , percentStr ) ;
rowY + = rowYOffset ;
} ;
// === Memory values ===
uint32_t heapUsed = memGet . getHeapSize ( ) - memGet . getFreeHeap ( ) ;
uint32_t heapTotal = memGet . getHeapSize ( ) ;
uint32_t psramUsed = memGet . getPsramSize ( ) - memGet . getFreePsram ( ) ;
uint32_t psramTotal = memGet . getPsramSize ( ) ;
uint32_t flashUsed = 0 , flashTotal = 0 ;
# ifdef ESP32
flashUsed = FSCom . usedBytes ( ) ;
flashTotal = FSCom . totalBytes ( ) ;
# endif
# ifdef HAS_SDCARD
uint32_t sdUsed = 0 , sdTotal = 0 ;
bool hasSD = SD . cardType ( ) ! = CARD_NONE ;
if ( hasSD ) {
sdUsed = SD . usedBytes ( ) ;
sdTotal = SD . totalBytes ( ) ;
}
# else
bool hasSD = false ;
uint32_t sdUsed = 0 , sdTotal = 0 ;
# endif
// === Draw memory rows
drawUsageRow ( " Heap: " , heapUsed , heapTotal ) ;
drawUsageRow ( " PSRAM: " , psramUsed , psramTotal ) ;
# ifdef ESP32
if ( flashTotal > 0 ) drawUsageRow ( " Flash: " , flashUsed , flashTotal ) ;
# endif
if ( hasSD & & sdTotal > 0 ) drawUsageRow ( " SD: " , sdUsed , sdTotal ) ;
}
2025-03-29 23:22:54 -04:00
2025-03-29 20:27:59 -04:00
2024-07-10 00:56:57 +08:00
# if defined(ESP_PLATFORM) && defined(USE_ST7789)
SPIClass SPI1 ( HSPI ) ;
# endif
2023-03-08 19:13:46 -08:00
Screen : : Screen ( ScanI2C : : DeviceAddress address , meshtastic_Config_DisplayConfig_OledType screenType , OLEDDISPLAY_GEOMETRY geometry )
2023-12-12 20:27:31 -06:00
: concurrency : : OSThread ( " Screen " ) , address_found ( address ) , model ( screenType ) , geometry ( geometry ) , cmdQueue ( 32 )
2020-10-15 15:56:38 +08:00
{
2024-03-21 09:06:37 -05:00
graphics : : normalFrames = new FrameCallback [ MAX_NUM_NODES + NUM_EXTRA_FRAMES ] ;
2023-12-12 20:27:31 -06:00
# 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 ) ;
2024-07-10 00:56:57 +08:00
# 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 ) ;
2024-07-10 00:56:57 +08:00
# 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 ) ;
2025-03-29 17:47:51 -04:00
static_cast < ST7789Spi * > ( dispdev ) - > setRGB ( COLOR565 ( 255 , 255 , 128 ) ) ;
2024-07-10 00:56:57 +08:00
# endif
2023-12-12 20:27:31 -06:00
# 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 ) ;
2024-10-04 14:47:14 +02:00
# elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined ( RAK14014 ) | | defined ( HX8357_CS )
2023-12-12 20:27:31 -06:00
dispdev = new TFTDisplay ( address . address , - 1 , - 1 , geometry ,
( address . port = = ScanI2C : : I2CPort : : WIRE1 ) ? HW_I2C : : I2C_TWO : HW_I2C : : I2C_ONE ) ;
2024-03-03 15:07:29 +13:00
# elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
2023-12-12 20:27:31 -06:00
dispdev = new EInkDisplay ( address . address , - 1 , - 1 , geometry ,
( address . port = = ScanI2C : : I2CPort : : WIRE1 ) ? HW_I2C : : I2C_TWO : HW_I2C : : I2C_ONE ) ;
2024-03-03 15:07:29 +13:00
# 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 ) ;
2023-12-12 20:27:31 -06:00
# 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
2024-01-12 02:00:31 -06:00
if ( settingsMap [ displayPanel ] ! = no_screen ) {
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Make TFTDisplay! " ) ;
2023-12-12 20:27:31 -06:00
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 ) ;
}
2023-10-09 20:43:16 -05:00
2024-03-21 09:06:37 -05:00
Screen : : ~ Screen ( )
{
delete [ ] graphics : : normalFrames ;
}
2020-11-13 07:49:01 +08:00
/**
* 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 ( )
{
2022-07-31 07:11:47 -05:00
# ifdef USE_EINK
2024-03-29 12:31:11 +13:00
setOn ( false , drawDeepSleepScreen ) ;
2024-03-08 23:15:37 -03:00
# ifdef PIN_EINK_EN
digitalWrite ( PIN_EINK_EN , LOW ) ; // power off backlight
# endif
2024-03-29 12:31:11 +13:00
# else
// Without E-Ink display:
2020-11-13 07:49:01 +08:00
setOn ( false ) ;
2024-03-29 12:31:11 +13:00
# endif
2020-11-13 07:49:01 +08:00
}
2024-03-29 12:31:11 +13:00
void Screen : : handleSetOn ( bool on , FrameCallback einkScreensaver )
2020-03-15 16:47:38 -07:00
{
if ( ! useDisplay )
2020-02-07 13:51:17 -08:00
return ;
2020-03-18 17:16:19 -07:00
if ( on ! = screenOn ) {
if ( on ) {
2024-11-04 19:15:59 -06:00
LOG_INFO ( " Turn on screen " ) ;
2024-08-06 10:35:54 -07:00
powerMon - > setState ( meshtastic_PowerMon_State_Screen_On ) ;
2023-07-22 09:26:54 -05:00
# ifdef T_WATCH_S3
PMU - > enablePowerOutput ( XPOWERS_ALDO2 ) ;
# endif
2024-01-12 02:00:31 -06:00
# if !ARCH_PORTDUINO
2023-12-12 20:27:31 -06:00
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
2023-12-12 20:27:31 -06:00
dispdev - > displayOn ( ) ;
2024-07-10 00:56:57 +08:00
# ifdef USE_ST7789
2024-08-13 19:30:35 +08:00
pinMode ( VTFT_CTRL , OUTPUT ) ;
2024-08-13 06:52:03 -05:00
digitalWrite ( VTFT_CTRL , LOW ) ;
2024-08-13 19:30:35 +08:00
ui - > init ( ) ;
2024-07-10 00:56:57 +08:00
# ifdef ESP_PLATFORM
2024-07-09 12:16:56 -05:00
analogWrite ( VTFT_LEDA , BRIGHTNESS_DEFAULT ) ;
2024-07-10 00:56:57 +08:00
# else
2024-07-09 12:16:56 -05:00
pinMode ( VTFT_LEDA , OUTPUT ) ;
digitalWrite ( VTFT_LEDA , TFT_BACKLIGHT_ON ) ;
2024-07-10 00:56:57 +08:00
# endif
# endif
2020-10-09 14:16:51 +08:00
enabled = true ;
setInterval ( 0 ) ; // Draw ASAP
2021-03-28 12:16:37 +08:00
runASAP = true ;
2020-03-18 17:16:19 -07:00
} else {
2024-08-06 10:35:54 -07:00
powerMon - > clearState ( meshtastic_PowerMon_State_Screen_On ) ;
2024-03-29 12:31:11 +13:00
# ifdef USE_EINK
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames ( einkScreensaver ) ;
# endif
2024-11-04 19:15:59 -06:00
LOG_INFO ( " Turn off screen " ) ;
2023-12-12 20:27:31 -06:00
dispdev - > displayOff ( ) ;
2024-07-10 00:56:57 +08:00
# ifdef USE_ST7789
2024-08-13 19:30:35 +08:00
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 ) ;
2024-08-13 19:30:35 +08:00
# 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
2024-07-10 00:56:57 +08:00
# endif
2023-07-22 09:26:54 -05:00
# ifdef T_WATCH_S3
PMU - > disablePowerOutput ( XPOWERS_ALDO2 ) ;
# endif
2020-10-09 14:16:51 +08:00
enabled = false ;
2020-03-04 16:46:57 -08:00
}
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
}
2020-02-21 10:51:36 -08:00
void Screen : : setup ( )
2020-02-07 13:51:17 -08:00
{
2020-03-29 11:00:25 -07: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
2022-03-24 17:36:56 +01:00
# ifdef AutoOLEDWire_h
2023-12-12 20:27:31 -06:00
if ( isAUTOOled )
static_cast < AutoOLEDWire * > ( dispdev ) - > setDetected ( model ) ;
2022-03-24 17:36:56 +01:00
# endif
2023-03-08 15:42:45 +01:00
# ifdef USE_SH1107_128_64
2023-12-12 20:27:31 -06:00
static_cast < SH1106Wire * > ( dispdev ) - > setSubtype ( 7 ) ;
2023-03-08 15:42:45 +01:00
# endif
2024-09-21 06:27:41 +12:00
# 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
2020-03-15 16:47:38 -07:00
// Initialising the UI will init the display too.
2023-12-12 20:27:31 -06:00
ui - > init ( ) ;
2020-10-15 15:12:27 +08:00
2023-12-12 20:27:31 -06:00
displayWidth = dispdev - > width ( ) ;
displayHeight = dispdev - > height ( ) ;
2020-10-15 15:56:38 +08:00
2023-12-12 20:27:31 -06:00
ui - > setTimePerTransition ( 0 ) ;
2020-10-24 08:16:15 +08:00
2023-12-12 20:27:31 -06:00
ui - > setIndicatorPosition ( BOTTOM ) ;
2020-02-07 13:51:17 -08:00
// Defines where the first frame is located in the bar.
2023-12-12 20:27:31 -06:00
ui - > setIndicatorDirection ( LEFT_RIGHT ) ;
ui - > setFrameAnimation ( SLIDE_LEFT ) ;
2020-03-15 16:47:38 -07:00
// Don't show the page swipe dots while in boot screen.
2023-12-12 20:27:31 -06:00
ui - > disableAllIndicators ( ) ;
2020-03-26 09:24:53 -07:00
// Store a pointer to Screen so we can get to it from static functions.
2023-12-12 20:27:31 -06:00
ui - > getUiState ( ) - > userData = this ;
2020-02-07 13:51:17 -08:00
2020-06-22 12:03:26 -07:00
// Set the utf8 conversion function
2023-12-12 20:27:31 -06:00
dispdev - > setFontTableLookupFunction ( customFontTableLookup ) ;
2020-06-22 12:03:26 -07:00
2025-01-28 15:38:22 +01:00
# ifdef USERPREFS_OEM_TEXT
logo_timeout * = 2 ; // Double the time if we have a custom logo
# endif
2020-03-15 16:47:38 -07:00
// Add frames.
2024-03-29 12:31:11 +13:00
EINK_ADD_FRAMEFLAG ( dispdev , DEMAND_FAST ) ;
2024-06-28 21:28:18 -05:00
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 ) ;
2020-03-15 16:47:38 -07:00
// No overlays.
2023-12-12 20:27:31 -06:00
ui - > setOverlays ( nullptr , 0 ) ;
2020-02-07 13:51:17 -08:00
2020-03-15 16:47:38 -07:00
// Require presses to switch between frames.
2023-12-12 20:27:31 -06:00
ui - > disableAutoTransition ( ) ;
2020-02-07 13:51:17 -08:00
2020-03-15 16:47:38 -07:00
// Set up a log buffer with 3 lines, 32 chars each.
2023-12-12 20:27:31 -06:00
dispdev - > setLogBuffer ( 3 , 32 ) ;
2020-02-07 13:51:17 -08:00
2020-10-24 08:16:15 +08:00
# ifdef SCREEN_MIRROR
2023-12-12 20:27:31 -06:00
dispdev - > mirrorScreen ( ) ;
2022-10-04 09:47:54 +02:00
# else
2023-01-18 14:51:48 -06:00
// 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 ) {
2024-10-04 14:47:14 +02:00
# if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined ( ST7789_CS ) | | defined ( RAK14014 ) | | defined ( HX8357_CS )
2024-01-19 20:11:19 +01:00
static_cast < TFTDisplay * > ( dispdev ) - > flipScreenVertically ( ) ;
2024-09-21 06:27:41 +12:00
# elif defined(USE_ST7789)
static_cast < ST7789Spi * > ( dispdev ) - > flipScreenVertically ( ) ;
2024-01-19 20:11:19 +01:00
# else
2023-12-12 20:27:31 -06:00
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
2020-08-01 10:50:06 -07:00
// Get our hardware ID
uint8_t dmac [ 6 ] ;
getMacAddr ( dmac ) ;
2023-01-16 10:55:40 +01:00
snprintf ( ourId , sizeof ( ourId ) , " %02x%02x " , dmac [ 4 ] , dmac [ 5 ] ) ;
2024-01-12 02:00:31 -06:00
# if ARCH_PORTDUINO
2023-12-12 20:27:31 -06:00
handleSetOn ( false ) ; // force clean init
# endif
2020-08-01 10:50:06 -07:00
2020-03-19 20:15:51 -07:00
// Turn on the display.
2020-03-15 16:47:38 -07:00
handleSetOn ( true ) ;
2020-02-12 09:58:46 -08:00
2020-03-15 16:47:38 -07:00
// 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
2023-12-12 20:27:31 -06:00
ui - > update ( ) ;
2022-08-04 10:35:40 +02:00
# ifndef USE_EINK
2023-12-12 20:27:31 -06:00
ui - > update ( ) ;
2022-08-04 10:35:40 +02:00
# endif
2022-04-18 18:11:17 +02:00
serialSinceMsec = millis ( ) ;
2020-06-27 21:19:49 -07:00
2024-12-29 01:31:54 +11:00
# if ARCH_PORTDUINO && !HAS_TFT
2023-12-12 20:27:31 -06:00
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 ) ;
2023-07-30 14:51:26 +02:00
touchScreenImpl1 - > init ( ) ;
# endif
2020-06-27 21:19:49 -07:00
// Subscribe to status updates
2020-06-28 18:17:52 -07:00
powerStatusObserver . observe ( & powerStatus - > onNewStatus ) ;
gpsStatusObserver . observe ( & gpsStatus - > onNewStatus ) ;
nodeStatusObserver . observe ( & nodeStatus - > onNewStatus ) ;
2024-07-12 11:51:26 +12:00
adminMessageObserver . observe ( adminModule ) ;
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
2022-02-27 00:29:05 -08:00
// Modules can notify screen about refresh
2022-03-09 19:01:43 +11:00
MeshModule : : observeUIEvents ( & uiFrameEventObserver ) ;
2020-02-07 13:51:17 -08:00
}
2024-04-07 02:04:26 +13: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.
2022-07-31 07:11:47 -05:00
# ifdef USE_EINK
2024-04-07 02:04:26 +13:00
// If requested, make sure queued commands are run, and UI has rendered a new frame
if ( forceUiUpdate ) {
2024-09-06 11:25:41 +12:00
// 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 ) ;
2024-04-07 02:04:26 +13:00
// 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
2023-12-12 20:27:31 -06:00
static_cast < EInkDisplay * > ( dispdev ) - > forceDisplay ( ) ;
2020-10-15 15:56:38 +08:00
# endif
}
2021-11-27 09:08:23 -06:00
static uint32_t lastScreenTransition ;
2020-10-10 09:57:57 +08:00
int32_t Screen : : runOnce ( )
2020-02-07 13:51:17 -08:00
{
2020-03-15 16:47:38 -07:00
// If we don't have a screen, don't ever spend any CPU for us.
if ( ! useDisplay ) {
2020-10-09 14:16:51 +08:00
enabled = false ;
2020-10-10 09:57:57 +08:00
return RUN_SAME ;
}
2024-05-23 08:21:27 -04:00
if ( displayHeight = = 0 ) {
displayHeight = dispdev - > getHeight ( ) ;
}
2022-11-03 09:46:32 +01:00
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
2022-04-02 16:02:26 +02:00
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
2020-10-10 09:57:57 +08:00
static bool showingBootScreen = true ;
2022-11-03 09:46:32 +01:00
if ( showingBootScreen & & ( millis ( ) > ( logo_timeout + serialSinceMsec ) ) ) {
2024-11-06 07:03:25 -06:00
LOG_INFO ( " Done with boot screen " ) ;
2020-10-10 09:57:57 +08:00
stopBootScreen ( ) ;
showingBootScreen = false ;
2020-02-21 10:51:36 -08:00
}
2020-02-07 13:51:17 -08:00
2025-01-28 15:38:22 +01: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
2023-01-21 18:22:19 +01:00
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
2020-03-15 16:47:38 -07:00
// Process incoming commands.
for ( ; ; ) {
2020-07-07 10:46:49 +02:00
ScreenCmd cmd ;
2020-03-15 16:47:38 -07:00
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 :
2024-09-29 07:29:53 -05:00
handleOnPress ( ) ;
2020-03-15 16:47:38 -07:00
break ;
2023-07-30 14:51:26 +02:00
case Cmd : : SHOW_PREV_FRAME :
handleShowPrevFrame ( ) ;
break ;
case Cmd : : SHOW_NEXT_FRAME :
handleShowNextFrame ( ) ;
break ;
2024-06-25 11:26:02 -05:00
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 ) ;
2020-03-15 16:47:38 -07:00
break ;
2024-06-25 11:26:02 -05:00
}
2021-02-12 13:48:12 +08:00
case Cmd : : START_FIRMWARE_UPDATE_SCREEN :
handleStartFirmwareUpdateScreen ( ) ;
2021-03-20 10:22:06 +08:00
break ;
2024-06-25 11:26:02 -05:00
case Cmd : : STOP_ALERT_FRAME :
2020-03-15 16:47:38 -07:00
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
2020-03-15 16:47:38 -07:00
setFrames ( ) ;
break ;
case Cmd : : PRINT :
handlePrint ( cmd . print_text ) ;
free ( cmd . print_text ) ;
break ;
default :
2024-10-14 06:11:43 +02:00
LOG_ERROR ( " Invalid screen cmd " ) ;
2020-03-15 16:47:38 -07:00
}
}
if ( ! screenOn ) { // If we didn't just wake and the screen is still off, then
// stop updating until it is on again
2020-10-09 14:16:51 +08:00
enabled = false ;
return 0 ;
2020-02-21 10:51:36 -08:00
}
2020-02-12 09:58:46 -08:00
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
2023-12-12 20:27:31 -06:00
ui - > update ( ) ;
2020-10-16 10:53:55 +08:00
2020-02-21 10:13:51 -08:00
// Switch to a low framerate (to save CPU) when we are not in transition
2020-03-15 16:47:38 -07:00
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.
2023-12-12 20:27:31 -06:00
if ( targetFramerate ! = IDLE_FRAMERATE & & ui - > getUiState ( ) - > frameState = = FIXED ) {
// oldFrameState = ui->getUiState()->frameState;
2020-02-21 10:13:51 -08:00
targetFramerate = IDLE_FRAMERATE ;
2021-12-11 18:33:52 -08:00
2023-12-12 20:27:31 -06:00
ui - > setTargetFPS ( targetFramerate ) ;
2020-10-15 15:56:38 +08:00
forceDisplay ( ) ;
2020-02-21 10:13:51 -08:00
}
2020-02-07 15:37:25 -08:00
2020-03-15 16:47:38 -07:00
// While showing the bootscreen or Bluetooth pair screen all of our
// standard screen switching is stopped.
if ( showingNormalScreen ) {
2020-06-27 21:19:49 -07:00
// standard screen loop handling here
2022-05-21 22:38:33 +02:00
if ( config . display . auto_screen_carousel_secs > 0 & &
2024-09-23 08:58:14 -05:00
! Throttle : : isWithinTimespanMs ( lastScreenTransition , config . display . auto_screen_carousel_secs * 1000 ) ) {
2024-08-02 14:03:59 +12:00
// 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
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " LastScreenTransition exceeded %ums transition to next frame " , ( millis ( ) - lastScreenTransition ) ) ;
2021-11-27 09:08:23 -06:00
handleOnPress ( ) ;
}
2020-02-07 15:37:25 -08:00
}
2021-12-26 15:46:23 -08:00
2024-10-14 06:11:43 +02:00
// LOG_DEBUG("want fps %d, fixed=%d", targetFramerate,
2023-12-12 20:27:31 -06:00
// ui->getUiState()->frameState); If we are scrolling we need to be called
2020-03-15 16:47:38 -07:00
// 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
2020-10-09 14:16:51 +08:00
return ( 1000 / targetFramerate ) ;
2020-02-07 13:51:17 -08:00
}
2020-02-07 17:48:12 -08:00
2020-03-26 09:24:53 -07: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 ) ;
2020-03-26 09:24:53 -07:00
}
2020-09-05 09:30:18 -07:00
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 ) ;
2020-09-05 09:30:18 -07:00
}
2020-09-12 21:43:41 -07:00
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 ) ;
2020-09-12 21:43:41 -07:00
}
2020-09-05 09:30:18 -07:00
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 ( )
{
2023-03-08 19:13:46 -08:00
if ( address_found . address ) {
2024-11-04 19:15:59 -06:00
// LOG_DEBUG("Show SSL frames");
2021-12-18 11:02:54 -05:00
static FrameCallback sslFrames [ ] = { drawSSLScreen } ;
2023-12-12 20:27:31 -06:00
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 ( )
{
2023-03-08 19:13:46 -08:00
if ( address_found . address ) {
2024-11-04 19:15:59 -06:00
// LOG_DEBUG("Show Welcome frames");
2023-10-09 20:43:16 -05:00
static FrameCallback frames [ ] = { drawWelcomeScreen } ;
setFrameImmediateDraw ( frames ) ;
2022-04-10 19:15:10 -07:00
}
}
2024-03-29 12:31:11 +13: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 ;
2024-04-01 01:04:05 +13:00
# 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
2024-03-29 12:31:11 +13:00
// 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 ) ;
2024-04-01 01:04:05 +13:00
// Old EInkDisplay class
# if !defined(USE_EINK_DYNAMICDISPLAY)
static_cast < EInkDisplay * > ( dispdev ) - > forceDisplay ( 0 ) ; // Screen::forceDisplay(), but override rate-limit
2024-03-29 12:31:11 +13:00
# endif
// Prepare now for next frame, shown when display wakes
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
2024-03-29 12:31:11 +13:00
// 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
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
{
2024-07-12 11:51:26 +12:00
uint8_t originalPosition = ui - > getUiState ( ) - > currentFrame ;
FramesetInfo fsi ; // Location of specific frames, for applying focus parameter
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Show standard frames " ) ;
2020-03-15 16:47:38 -07:00
showingNormalScreen = true ;
2020-02-11 10:51:45 -08:00
2024-05-22 09:00:04 +12:00
# 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
2022-03-09 19:01:43 +11:00
moduleFrames = MeshModule : : GetMeshModulesWithUIFrames ( ) ;
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Show %d module frames " , moduleFrames . size ( ) ) ;
2023-05-13 13:33:14 +03:00
# ifdef DEBUG_PORT
2022-02-27 02:21:02 -08:00
int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames . size ( ) ;
2024-10-14 06:11:43 +02:00
LOG_DEBUG ( " Total frame count: %d " , totalFrameCount ) ;
2023-05-13 13:33:14 +03:00
# endif
2021-02-21 16:46:46 -05:00
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)
2024-03-21 09:06:37 -05:00
size_t numMeshNodes = nodeDB - > getNumMeshNodes ( ) ;
2023-06-17 09:10:09 -05:00
if ( numMeshNodes > 0 )
numMeshNodes - - ;
2020-02-11 10:51:45 -08:00
2020-02-12 19:58:44 -08:00
size_t numframes = 0 ;
2022-02-27 01:49:24 -08:00
// put all of the module frames first.
2021-02-21 16:46:46 -05:00
// 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
2021-02-21 16:46:46 -05:00
// 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 ) {
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 ;
2024-09-25 23:27:04 +12:00
if ( m - > isRequestingFocus ( ) ) {
2024-07-12 11:51:26 +12:00
fsi . positions . focusedModule = numframes ;
2024-09-25 23:27:04 +12:00
}
// Identify the position of specific modules, if we need to know this later
if ( m = = waypointModule )
fsi . positions . waypoint = numframes ;
2024-07-12 11:51:26 +12:00
numframes + + ;
2021-02-21 16:46:46 -05:00
}
2021-03-20 10:22:06 +08:00
2024-10-14 06:11:43 +02:00
LOG_DEBUG ( " Added modules. numframes: %d " , numframes ) ;
2021-02-21 16:46:46 -05:00
2020-12-26 13:36:21 +08:00
// If we have a critical fault, show it first
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 ;
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
2024-11-23 08:54:06 +08:00
# if defined(DISPLAY_CLOCK_FRAME)
2024-05-26 08:04:31 -04:00
normalFrames [ numframes + + ] = screen - > digitalWatchFace ? & Screen : : drawDigitalClockFrame : & Screen : : drawAnalogClockFrame ;
# endif
2022-02-27 00:29:05 -08:00
// If we have a text message - show it next, unless it's a phone message and we aren't using any special modules
2021-11-27 09:08:23 -06:00
if ( devicestate . has_rx_text_message & & shouldDrawMessage ( & devicestate . rx_text_message ) ) {
2024-09-25 23:27:04 +12:00
fsi . positions . textMessage = numframes ;
2020-03-15 16:47:38 -07:00
normalFrames [ numframes + + ] = drawTextMessageFrame ;
2021-11-27 09:08:23 -06:00
}
2024-06-24 19:04:46 +12:00
2025-03-28 14:39:15 -04:00
normalFrames [ numframes + + ] = drawDefaultScreen ;
normalFrames [ numframes + + ] = drawLastHeardScreen ;
normalFrames [ numframes + + ] = drawDistanceScreen ;
normalFrames [ numframes + + ] = drawNodeListWithCompasses ;
normalFrames [ numframes + + ] = drawHopSignalScreen ;
2025-03-29 23:46:54 -04:00
normalFrames [ numframes + + ] = drawBatteryDeviceLoRa ;
2025-03-29 23:22:54 -04:00
normalFrames [ numframes + + ] = drawCompassAndLocationScreen ;
2025-03-30 16:54:57 -04:00
normalFrames [ numframes + + ] = drawMemoryScreen ;
2025-03-29 23:46:54 -04:00
normalFrames [ numframes + + ] = drawActivity ;
2025-03-28 14:39:15 -04:00
2020-02-12 19:58:44 -08:00
// then all the nodes
2021-08-04 09:10:34 -07:00
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
2023-06-17 09:10:09 -05:00
size_t numToShow = min ( numMeshNodes , 4U ) ;
2021-08-04 09:10:34 -07:00
for ( size_t i = 0 ; i < numToShow ; i + + )
2020-03-15 16:47:38 -07:00
normalFrames [ numframes + + ] = drawNodeInfo ;
2020-02-12 19:58:44 -08:00
// then the debug info
2020-03-26 09:24:53 -07:00
//
// Since frames are basic function pointers, we have to use a helper to
// call a method on debugInfo object.
2024-07-12 11:51:26 +12:00
fsi . positions . log = numframes ;
2020-03-26 09:24:53 -07:00
normalFrames [ numframes + + ] = & Screen : : drawDebugInfoTrampoline ;
2020-02-11 10:51:45 -08:00
2020-09-05 09:30:18 -07:00
// call a method on debugInfoScreen object (for more details)
2024-07-12 11:51:26 +12:00
fsi . positions . settings = numframes ;
2020-09-05 09:30:18 -07:00
normalFrames [ numframes + + ] = & Screen : : drawDebugInfoSettingsTrampoline ;
2024-07-12 11:51:26 +12:00
fsi . positions . wifi = numframes ;
2024-01-12 02:00:31 -06:00
# if HAS_WIFI && !defined(ARCH_PORTDUINO)
2020-09-16 20:15:00 -07:00
if ( isWifiAvailable ( ) ) {
// call a method on debugInfoScreen object (for more details)
normalFrames [ numframes + + ] = & Screen : : drawDebugInfoWiFiTrampoline ;
}
2021-01-09 17:50:58 -08:00
# endif
2020-09-12 21:43:41 -07:00
2024-07-12 11:51:26 +12:00
fsi . frameCount = numframes ; // Total framecount is used to apply FOCUS_PRESERVE
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Finished build frames. numframes: %d " , numframes ) ;
2021-02-21 16:46:46 -05:00
2023-12-12 20:27:31 -06:00
ui - > setFrames ( normalFrames , numframes ) ;
ui - > enableAllIndicators ( ) ;
2020-02-19 15:29:18 -08: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
// 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 ) ;
2020-03-15 16:47:38 -07:00
prevFrame = - 1 ; // Force drawNodeInfo to pick a new node (because our list
// just changed)
2020-10-16 10:53:55 +08:00
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
2024-09-26 02:09:06 +02:00
const FramesetInfo & oldFsi = this - > framesetInfo ;
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 20:59:21 -08:00
}
2020-02-07 17:48:12 -08:00
2023-10-09 20:43:16 -05:00
void Screen : : setFrameImmediateDraw ( FrameCallback * drawFrames )
{
2023-12-12 20:27:31 -06:00
ui - > disableAllIndicators ( ) ;
ui - > setFrames ( drawFrames , 1 ) ;
2020-10-16 10:53:55 +08:00
setFastFramerate ( ) ;
2020-03-15 16:47:38 -07:00
}
2024-09-25 23:27:04 +12:00
// 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 ) {
2024-11-04 12:16:25 -06:00
LOG_INFO ( " Dismiss Text Message " ) ;
2024-09-25 23:27:04 +12:00
devicestate . has_rx_text_message = false ;
dismissed = true ;
}
else if ( currentFrame = = framesetInfo . positions . waypoint & & devicestate . has_rx_waypoint ) {
2024-11-04 12:16:25 -06:00
LOG_DEBUG ( " Dismiss Waypoint " ) ;
2024-09-25 23:27:04 +12:00
devicestate . has_rx_waypoint = false ;
dismissed = true ;
}
// If we did make changes to dismiss, we now need to regenerate the frameset
if ( dismissed )
setFrames ( ) ;
}
2021-02-12 13:48:12 +08:00
void Screen : : handleStartFirmwareUpdateScreen ( )
{
2024-11-04 19:15:59 -06:00
LOG_DEBUG ( " Show firmware screen " ) ;
2021-02-12 13:48:12 +08:00
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
2021-02-12 13:48:12 +08:00
2023-10-09 20:43:16 -05:00
static FrameCallback frames [ ] = { drawFrameFirmware } ;
setFrameImmediateDraw ( frames ) ;
2021-02-12 13:48:12 +08:00
}
2020-12-31 20:17:18 -08:00
void Screen : : blink ( )
{
2020-12-20 17:45:45 -05:00
setFastFramerate ( ) ;
uint8_t count = 10 ;
2023-12-12 20:27:31 -06:00
dispdev - > setBrightness ( 254 ) ;
2020-12-31 20:17:18 -08:00
while ( count > 0 ) {
2024-06-29 21:16:07 -05:00
dispdev - > fillRect ( 0 , 0 , dispdev - > getWidth ( ) , dispdev - > getHeight ( ) ) ;
2023-12-12 20:27:31 -06:00
dispdev - > display ( ) ;
2020-12-20 18:24:48 -05:00
delay ( 50 ) ;
2023-12-12 20:27:31 -06:00
dispdev - > clear ( ) ;
dispdev - > display ( ) ;
2020-12-20 18:24:48 -05:00
delay ( 50 ) ;
2020-12-31 20:17:18 -08:00
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.
2023-12-12 20:27:31 -06:00
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-03 00:40:51 +01:00
{
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 " ;
2023-02-03 00:40:51 +01:00
else if ( hours > = 2 )
2023-02-05 01:46:16 +01:00
uptime = std : : to_string ( hours ) + " h " ;
2023-02-03 00:40:51 +01:00
else if ( minutes > = 1 )
2023-02-05 01:46:16 +01:00
uptime = std : : to_string ( minutes ) + " m " ;
2023-02-03 00:40:51 +01:00
else
2023-02-05 01:46:16 +01:00
uptime = std : : to_string ( seconds ) + " s " ;
2023-02-03 00:40:51 +01:00
return uptime ;
}
2020-03-15 16:47:38 -07:00
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
2024-10-14 06:11:43 +02:00
LOG_DEBUG ( " Screen: %.*s " , strlen ( text ) - 1 , text ) ;
2020-10-16 10:53:55 +08:00
if ( ! useDisplay | | ! showingNormalScreen )
2020-03-15 16:47:38 -07:00
return ;
2023-12-12 20:27:31 -06:00
dispdev - > print ( text ) ;
2020-03-15 16:47:38 -07:00
}
void Screen : : handleOnPress ( )
2021-12-26 15:46:23 -08:00
{
2024-08-07 10:16:56 +12:00
// 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 ;
2020-02-12 09:58:46 -08:00
// If screen was off, just wake it, otherwise advance to next frame
2020-02-21 10:13:51 -08:00
// If we are in a transition, the press must have bounced, drop it.
2023-12-12 20:27:31 -06:00
if ( ui - > getUiState ( ) - > frameState = = FIXED ) {
ui - > nextFrame ( ) ;
2021-11-27 09:08:23 -06:00
lastScreenTransition = millis ( ) ;
2020-10-16 10:53:55 +08:00
setFastFramerate ( ) ;
2020-02-21 10:13:51 -08:00
}
2020-02-27 21:45:20 -08:00
}
2020-03-15 16:47:38 -07:00
2023-07-30 14:51:26 +02:00
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.
2023-12-12 20:27:31 -06:00
if ( ui - > getUiState ( ) - > frameState = = FIXED ) {
ui - > previousFrame ( ) ;
2023-07-30 14:51:26 +02:00
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.
2023-12-12 20:27:31 -06:00
if ( ui - > getUiState ( ) - > frameState = = FIXED ) {
ui - > nextFrame ( ) ;
2023-07-30 14:51:26 +02:00
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 ;
2021-12-11 18:33:52 -08:00
2023-12-12 20:27:31 -06:00
ui - > setTargetFPS ( targetFramerate ) ;
2020-10-16 10:53:55 +08:00
setInterval ( 0 ) ; // redraw ASAP
2021-03-28 12:16:37 +08:00
runASAP = true ;
2020-10-16 10:53:55 +08:00
}
2020-03-26 09:24:53 -07:00
void DebugInfo : : drawFrame ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
2020-10-16 11:22:07 +08:00
display - > setFont ( FONT_SMALL ) ;
2020-03-26 09:24:53 -07:00
// The coordinates define the left starting point of the text
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2023-01-21 18:22:19 +01:00
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-12-13 12:33:51 +01:00
2020-03-26 09:24:53 -07:00
char channelStr [ 20 ] ;
{
2020-07-06 00:54:30 +02:00
concurrency : : LockGuard guard ( & lock ) ;
2024-03-17 08:38:49 -05:00
snprintf ( channelStr , sizeof ( channelStr ) , " #%s " , channels . getName ( channels . getPrimaryIndex ( ) ) ) ;
2020-03-26 09:24:53 -07:00
}
2020-09-25 12:52:26 -07:00
// Display power status
2022-12-29 15:44:22 +01:00
if ( powerStatus - > getHasBattery ( ) ) {
2023-01-21 18:22:19 +01:00
if ( config . display . displaymode = = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT ) {
2023-01-18 14:51:48 -06:00
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 ( ) ) {
2023-01-21 18:22:19 +01:00
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
2023-01-21 18:22:19 +01:00
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 ) ;
}
2024-03-25 05:33:57 -06:00
# if HAS_GPS
2020-09-25 12:52:26 -07:00
// Display GPS status
2024-02-01 15:24:39 -06:00
if ( config . position . gps_mode ! = meshtastic_Config_PositionConfig_GpsMode_ENABLED ) {
2023-03-01 14:34:07 +00:00
drawGPSpowerstat ( display , x , y + 2 , gpsStatus ) ;
2022-12-14 19:58:15 -05:00
} else {
2023-01-21 18:22:19 +01:00
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 ) ;
}
2022-12-14 19:58:15 -05:00
}
2024-03-25 05:33:57 -06:00
# endif
2022-12-13 12:33:51 +01:00
display - > setColor ( WHITE ) ;
2020-08-01 10:50:06 -07:00
// Draw the channel name
2020-10-16 11:22:07 +08:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL , channelStr ) ;
2022-12-21 13:00:15 +01:00
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if ( moduleConfig . store_forward . enabled ) {
2023-01-18 14:51:48 -06:00
# ifdef ARCH_ESP32
2024-09-23 08:58:14 -05:00
if ( ! Throttle : : isWithinTimespanMs ( storeForwardModule - > lastHeartbeat ,
( storeForwardModule - > heartbeatInterval * 1200 ) ) ) { // no heartbeat, overlap a bit
2024-10-04 14:47:14 +02:00
# 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 ) | | ARCH_PORTDUINO ) & & \
2023-08-03 13:58:19 +02:00
! defined ( DISPLAY_FORCE_SMALL_FONTS )
2023-01-18 14:51:48 -06:00
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 ) ;
2022-12-21 13:00:15 +01:00
# else
2023-01-18 14:51:48 -06:00
display - > drawFastImage ( x + SCREEN_WIDTH - 10 - display - > getStringWidth ( ourId ) , y + 2 + FONT_HEIGHT_SMALL , 8 , 8 ,
imgQuestion ) ;
2022-12-21 13:00:15 +01:00
# endif
} else {
2024-10-04 14:47:14 +02:00
# if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
2024-10-06 20:37:20 +08:00
defined ( ST7789_CS ) | | defined ( USE_ST7789 ) | | defined ( HX8357_CS ) ) & & \
2023-08-03 13:58:19 +02:00
! defined ( DISPLAY_FORCE_SMALL_FONTS )
2023-01-18 14:51:48 -06:00
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 ) ;
2022-12-21 13:00:15 +01:00
# else
2023-01-18 14:51:48 -06:00
display - > drawFastImage ( x + SCREEN_WIDTH - 13 - display - > getStringWidth ( ourId ) , y + 2 + FONT_HEIGHT_SMALL , 11 , 8 ,
imgSF ) ;
2022-12-21 13:00:15 +01:00
# endif
}
2023-01-18 14:51:48 -06:00
# endif
2022-12-21 13:00:15 +01:00
} else {
2023-12-12 20:27:31 -06:00
// TODO: Raspberry Pi supports more than just the one screen size
2024-10-04 14:47:14 +02:00
# 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 ) | | ARCH_PORTDUINO ) & & \
2023-08-03 13:58:19 +02:00
! defined ( DISPLAY_FORCE_SMALL_FONTS )
2023-01-18 14:51:48 -06:00
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 ) ;
2022-12-21 13:00:15 +01:00
# else
display - > drawFastImage ( x + SCREEN_WIDTH - 10 - display - > getStringWidth ( ourId ) , y + 2 + FONT_HEIGHT_SMALL , 8 , 8 , imgInfo ) ;
# endif
}
2020-10-16 11:22:07 +08:00
display - > drawString ( x + SCREEN_WIDTH - display - > getStringWidth ( ourId ) , y + FONT_HEIGHT_SMALL , ourId ) ;
2020-03-26 09:24:53 -07:00
2020-08-01 10:50:06 -07:00
// Draw any log messages
2020-10-16 11:22:07 +08:00
display - > drawLogBuffer ( x , y + ( FONT_HEIGHT_SMALL * 2 ) ) ;
2020-03-26 09:24:53 -07:00
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 ) ;
2020-06-27 21:19:49 -07:00
heartbeat = ! heartbeat ;
2020-07-01 10:09:06 -07:00
# endif
2020-03-26 09:24:53 -07:00
}
2020-09-05 09:30:18 -07:00
// Jm
2020-09-12 21:43:41 -07:00
void DebugInfo : : drawFrameWiFi ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
2024-01-12 02:00:31 -06:00
# if HAS_WIFI && !defined(ARCH_PORTDUINO)
2022-09-09 12:51:41 +02:00
const char * wifiName = config . network . wifi_ssid ;
2020-09-13 16:58:36 -07:00
2020-10-16 11:22:07 +08:00
display - > setFont ( FONT_SMALL ) ;
2020-09-12 21:43:41 -07:00
// The coordinates define the left starting point of the text
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2023-01-21 18:22:19 +01:00
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-12-13 12:33:51 +01:00
2022-10-25 11:53:22 +02:00
if ( WiFi . status ( ) ! = WL_CONNECTED ) {
2020-09-19 21:58:21 -07:00
display - > drawString ( x , y , String ( " WiFi: Not Connected " ) ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 1 , y , String ( " WiFi: Not Connected " ) ) ;
2020-09-12 21:43:41 -07:00
} else {
2020-09-19 21:58:21 -07:00
display - > drawString ( x , y , String ( " WiFi: Connected " ) ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 1 , y , String ( " WiFi: Connected " ) ) ;
2020-09-19 16:38:59 -07:00
display - > drawString ( x + SCREEN_WIDTH - display - > getStringWidth ( " RSSI " + String ( WiFi . RSSI ( ) ) ) , y ,
" RSSI " + String ( WiFi . RSSI ( ) ) ) ;
2023-01-18 14:51:48 -06:00
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 ( ) ) ) ;
}
2020-09-12 21:43:41 -07:00
}
2022-12-29 15:44:22 +01:00
display - > setColor ( WHITE ) ;
2022-12-13 17:31:01 +01:00
2020-09-19 17:15:03 -07:00
/*
- 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 ;
2020-09-19 21:58:21 -07:00
- 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 ;
2020-09-19 17:15:03 -07:00
*/
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 ( ) ) ) ;
2020-09-19 17:15:03 -07:00
} else if ( WiFi . status ( ) = = WL_NO_SSID_AVAIL ) {
2020-10-16 11:22:07 +08:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , " SSID Not Found " ) ;
2020-09-19 17:15:03 -07:00
} else if ( WiFi . status ( ) = = WL_CONNECTION_LOST ) {
2020-10-16 11:22:07 +08:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , " Connection Lost " ) ;
2020-09-19 17:15:03 -07:00
} else if ( WiFi . status ( ) = = WL_CONNECT_FAILED ) {
2020-10-16 11:22:07 +08:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , " Connection Failed " ) ;
2020-09-19 21:58:21 -07:00
} else if ( WiFi . status ( ) = = WL_IDLE_STATUS ) {
2020-10-16 11:22:07 +08:00
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , " Idle ... Reconnecting " ) ;
2023-12-02 21:47:52 +01:00
}
# ifdef ARCH_ESP32
else {
2020-09-19 21:58:21 -07:00
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
2023-10-09 20:43:16 -05:00
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
}
2023-12-02 21:47:52 +01:00
# else
else {
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , " Unkown status: " + String ( WiFi . status ( ) ) ) ;
}
# endif
2020-09-12 21:43:41 -07:00
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 " ) ;
2020-09-19 16:38:59 -07:00
2020-09-12 21:43:41 -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 ;
# endif
2020-09-18 10:48:39 -07:00
# endif
2020-09-12 21:43:41 -07:00
}
2020-09-05 09:30:18 -07:00
void DebugInfo : : drawFrameSettings ( OLEDDisplay * display , OLEDDisplayUiState * state , int16_t x , int16_t y )
{
2020-10-16 11:22:07 +08:00
display - > setFont ( FONT_SMALL ) ;
2020-09-05 09:30:18 -07:00
// The coordinates define the left starting point of the text
display - > setTextAlignment ( TEXT_ALIGN_LEFT ) ;
2023-01-21 18:22:19 +01:00
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-12-13 12:33:51 +01:00
2020-09-05 09:30:18 -07:00
char batStr [ 20 ] ;
2020-09-19 16:38:59 -07:00
if ( powerStatus - > getHasBattery ( ) ) {
2020-09-05 09:30:18 -07:00
int batV = powerStatus - > getBatteryVoltageMv ( ) / 1000 ;
int batCv = ( powerStatus - > getBatteryVoltageMv ( ) % 1000 ) / 10 ;
2020-09-19 16:38:59 -07:00
snprintf ( batStr , sizeof ( batStr ) , " B %01d.%02dV %3d%% %c%c " , batV , batCv , powerStatus - > getBatteryChargePercent ( ) ,
powerStatus - > getIsCharging ( ) ? ' + ' : ' ' , powerStatus - > getHasUSB ( ) ? ' U ' : ' ' ) ;
2020-09-05 14:41:00 -07:00
// Line 1
display - > drawString ( x , y , batStr ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 1 , y , batStr ) ;
2020-09-19 16:38:59 -07:00
} else {
2020-09-05 14:41:00 -07:00
// Line 1
display - > drawString ( x , y , String ( " USB " ) ) ;
2023-01-18 14:51:48 -06:00
if ( config . display . heading_bold )
2022-12-29 15:44:22 +01:00
display - > drawString ( x + 1 , y , String ( " USB " ) ) ;
2020-09-19 16:38:59 -07:00
}
2020-09-05 09:30:18 -07:00
2025-02-10 14:30:43 -06:00
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
2021-12-11 19:36:58 -08:00
2025-02-10 14:30:43 -06:00
// 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);
2020-09-05 09:30:18 -07:00
uint32_t currentMillis = millis ( ) ;
uint32_t seconds = currentMillis / 1000 ;
uint32_t minutes = seconds / 60 ;
uint32_t hours = minutes / 60 ;
uint32_t days = hours / 24 ;
2021-03-20 10:22:06 +08:00
// currentMillis %= 1000;
// seconds %= 60;
// minutes %= 60;
// hours %= 24;
2025-03-28 14:39:15 -04:00
// 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 ( ) ) ;
2022-12-13 12:33:51 +01:00
display - > setColor ( WHITE ) ;
2025-02-10 14:30:43 -06:00
// Setup string to assemble analogClock string
std : : string analogClock = " " ;
2024-04-08 00:01:44 +02:00
uint32_t rtc_sec = getValidTime ( RTCQuality : : RTCQualityDevice , true ) ; // Display local timezone
2021-03-20 10:22:06 +08:00
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
2025-02-10 14:30:43 -06:00
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 ;
2021-03-20 10:22:06 +08:00
}
2025-02-10 14:30:43 -06:00
// Line 2
display - > drawString ( x , y + FONT_HEIGHT_SMALL * 1 , analogClock . c_str ( ) ) ;
2020-09-05 09:30:18 -07:00
2021-12-28 23:34:49 -08:00
// Display Channel Utilization
char chUtil [ 13 ] ;
2023-01-16 10:55:40 +01:00
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 ) ;
2025-02-10 14:30:43 -06:00
2024-03-25 05:33:57 -06:00
# if HAS_GPS
2024-02-01 15:24:39 -06:00
if ( config . position . gps_mode = = meshtastic_Config_PositionConfig_GpsMode_ENABLED ) {
2023-01-18 14:51:48 -06:00
// 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
2023-01-18 14:51:48 -06:00
drawGPSAltitude ( display , x , y + FONT_HEIGHT_SMALL * 2 , gpsStatus ) ;
// Line 4
drawGPScoordinates ( display , x , y + FONT_HEIGHT_SMALL * 3 , gpsStatus ) ;
} else {
2024-02-01 15:24:39 -06:00
drawGPSpowerstat ( display , x , y + FONT_HEIGHT_SMALL * 2 , gpsStatus ) ;
2023-01-18 14:51:48 -06:00
}
2024-03-25 05:33:57 -06:00
# endif
2020-09-05 09:30:18 -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 ;
# endif
}
2020-06-22 12:03:26 -07:00
2020-08-12 11:04:03 -07:00
int Screen : : handleStatusUpdate ( const meshtastic : : Status * arg )
2020-07-01 10:09:06 -07:00
{
2024-10-14 06:11:43 +02:00
// LOG_DEBUG("Screen got status update %d", arg->getStatusType());
2020-08-12 11:04:03 -07:00
switch ( arg - > getStatusType ( ) ) {
case STATUS_TYPE_NODE :
2020-12-12 19:09:58 -08:00
if ( showingNormalScreen & & nodeStatus - > getLastNumTotal ( ) ! = nodeStatus - > getNumTotal ( ) ) {
2024-07-12 11:51:26 +12:00
setFrames ( FOCUS_PRESERVE ) ; // Regen the list of screen frames (returning to same frame, if possible)
2020-08-25 12:08:18 -07:00
}
2024-03-21 09:06:37 -05:00
nodeDB - > updateGUI = false ;
2020-08-12 11:04:03 -07:00
break ;
2020-06-28 18:17:52 -07:00
}
2020-08-25 12:08:18 -07:00
2020-06-27 21:19:49 -07:00
return 0 ;
}
2020-11-28 12:10:19 +08:00
2023-01-21 18:22:19 +01:00
int Screen : : handleTextMessage ( const meshtastic_MeshPacket * packet )
2021-12-26 15:46:23 -08:00
{
2025-01-20 17:47:47 +01:00
if ( showingNormalScreen ) {
2025-01-25 12:01:25 -06:00
// 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
2020-11-28 12:10:19 +08:00
}
return 0 ;
}
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 ) {
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 ) ;
2024-11-04 12:16:25 -06:00
// Regenerate the frameset, while Attempt to maintain focus on the current frame
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 ;
}
2023-07-30 14:51:26 +02:00
int Screen : : handleInputEvent ( const InputEvent * event )
{
2024-05-23 08:21:27 -04:00
2024-11-23 08:54:06 +08:00
# if defined(DISPLAY_CLOCK_FRAME)
2024-05-23 08:21:27 -04:00
// For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
2024-05-26 08:04:31 -04:00
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 ) {
2024-05-23 08:21:27 -04:00
screen - > digitalWatchFace = ! screen - > digitalWatchFace ;
setFrames ( ) ;
return 0 ;
}
# endif
2024-09-25 23:27:04 +12:00
// 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 ( ) ;
2023-07-30 14:51:26 +02:00
}
}
return 0 ;
}
2024-07-12 11:51:26 +12:00
int Screen : : handleAdminMessage ( const meshtastic_AdminMessage * arg )
{
// Note: only selected admin messages notify this observer
// If you wish to handle a new type of message, you should modify AdminModule.cpp first
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
2023-03-08 19:13:46 -08:00
# else
graphics : : Screen : : Screen ( ScanI2C : : DeviceAddress , meshtastic_Config_DisplayConfig_OledType , OLEDDISPLAY_GEOMETRY ) { }
2025-02-10 14:30:43 -06:00
# endif // HAS_SCREEN