mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-02 20:05:52 +00:00

* Add missed include * Another Warning fix * Add another HAS_SCREEN * Namespace fixes * Removed depricated destination types and re-factored destination screen * Get rid of Arduino Strings * Clean up after Copilot * SixthLine Def, Screen Rename Added Sixth Line Definition Screen Rename, and Automatic Line Adjustment * Consistency is hard - fixed "Sixth" * System Frame Updates Adjusted line construction to ensure we fit maximum content per screen. * Fix up notifications * Add a couple more ifdef HAS_SCREEN lines * Add screen->isOverlayBannerShowing() * Don't forget the invert! * Adjust Nodelist Center Divider Adjust Nodelist Center Divider * Fix variable casting * Fix entryText variable as empty before update to fix validation * Altitude is int32_t * Update PowerTelemetry to have correct data type * Fix cppcheck warnings (#6945) * Fix cppcheck warnings * Adjust logic in Power.cpp for power sensor --------- Co-authored-by: Jason P <applewiz@mac.com> * More pixel wrangling so things line up NodeList edition * Adjust NodeList alignments and plumb some background padding for a possible title fix * Better alignment for banner notifications * Move title into drawCommonHeader; initial screen tested * Fonts make spacing items difficult * Improved beeping booping and other buzzer based feedback (#6947) * Improved beeping booping and other buzzer based feedback * audible button feedback (#6949) * Refactor --------- Co-authored-by: todd-herbert <herbert.todd@gmail.com> * Sandpapered the corners of the notification popup * Finalize drawCommonHeader migration * Update Title of Favorite Node Screens * Update node metric alignment on LoRa screen * Update the border for popups to separate it from background * Update PaxcounterModule.cpp with CommonHeader * Update WiFi screen with CommonHeader and related data reflow * It was not, in fact, pointing up * Fix build on wismeshtap * T-deck trackball debounce * Fix uptime on Device Focused page to actually detail * Update Sys screen for new uptime, add label to Freq/Chan on LoRa * Don't display DOP any longer, make Uptime consistent * Revert Uptime change on Favorites, Apply to Device Focused * Label the satelite number to avoid confusion * Boop boop boop boop * Correct GPS positioning and string consistency across strings for GPS * Fix GPS text alignment * Enable canned messages by default * Don't wake screen on new nodes * Cannedmessage list emote support added * Fn+e emote picker for freetext screen * Actually block CannedInput actions while display is shown * Add selection menu to bannerOverlay * Off by one * Move to unified text layouts and spacing * Still my Fav without an "e" * Fully remove EVENT_NODEDB_UPDATED * Simply LoRa screen * Make some char pointers const to fix compilation on native targets * Update drawCompassNorth to include radius * Fix warning * button thread cleanup * Pull OneButton handling from PowerFSM and add MUI switch (#6973) * Trunk * Onebutton Menu Support * Add temporary clock icon * Add gps location to fsi * Banner message state reset * Cast to char to satisfy compiler * Better fast handling of input during banner * Fix warning * Derp * oops * Update ref * Wire buzzer_mode * remove legacy string->print() * Only init screen if one found * Unsigned Char * More buttonThread cleaning * screen.cpp button handling cleanup * The Great Event Rename of 2025 * Fix the Radiomaster * Missed trackball type change * Remove unused function * Make ButtonThread an InputBroker * Coffee hadn't kicked in yet * Add clock icon for Navigation Bar * Restore clock screen definition code - whoops * ExternalNotifications now observe inputBroker * Clock rework (#6992) * Move Clock bits into ClockRenderer space * Rework clock into all device navigation * T-Watch Actually Builds Different * Compile fix --------- Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> * Add AM/PM to Digital Clock * Flip Seconds and AM/PM on Clock Display * Tik-tok pixels are hard * Fix builds on Thinknode M1 * Check for GPS and don't crash * Don't endif til the end * Rework the OneButton thread to be much less of a mess. (#6997) * Rework the OneButton thread to be much less of a mess. And break lots of targets temporarily * Update src/input/ButtonThread.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix GPS toggle * Send the shutdown event, not just the kbchar * Honor the back button in a notificaiton popup * Draw the right size box for popup with options * Try to un-break all the things --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * 24-hour Clock Should have leading zero, but not 12-hour * Fixup some compile errors * Add intRoutine to ButtonThread init, to get more responsive user button back * Add Timezone picker * Fix Warning * Optionally set the initial selection for the chooser popup * Make back buttons work in canned messages * Drop the wrapper classes * LonPressTime now configurable * Clock Frame can not longer be blank; just add valid time * Back buttons everywhere! * Key Verification confirm banner * Make Elecrow M* top button a back button * Add settings saves * EInk responsiveness fixes * Linux Input Fixes * Add Native Trackball/Joystick support, and move UserButton to Input * No Flight Stick Mode * Send input event * Add Channel Utilization to Device Focused frame * Don't shift screens when we draw new ones * Add showOverlayBanner arguments to no-op * trunk * Default Native trackball to NC * Fix crash in simulator mode * Add longLong button press * Get the args right * Adjust Bluetooth Pairing Screen to account for bottom navigation. * Trackball everywhere, and unPhone buttons * Remap visionmaster secondary button to TB_UP * Kill ScanAndSelect * trunk * No longer need the canned messages input filter * All Canned All the time * Fix stm32 compile error regarding inputBroker * Unify tft lineheights (#7033) * Create variable line heights based upon SCREEN_HEIGHT * Refactor textPositions into method -> getTextPositions * Update SharedUIDisplay.h --------- Co-authored-by: Jason P <applewiz@mac.com> * Adjust top distance for larger displays * Adjust icon sizes for larger displays * Fix Paxcounter compile errors after code updates * Pixel wrangling to make larger screens fit better * Alert frame has precedence over banner -- for now * Unify on ALT_BUTTON * Align AM/PM to the digit, not the segment on larger displays * Move some global pin defines into configuration.h * Scaffolding for BMM150 9-axis gyro * Alt button behavior * Don't add the blank GPS frames without HAS_GPS * EVENT_NODEDB_UPDATED has been retired * Clean out LOG_WARN messages from debugging * Add dismiss message function * Minor buttonThread cleanup * Add BMM150 support * Clean up last warning from dev * Simplify bmm150 init return logic * Add option to reply to messages * Add minimal menu upon selecting home screen * Move Messages to slot 2, rename GPS to Position, move variables nearer functional usage in Screen.cpp * Properly dismiss message * T-Deck Trackball press is not user button * Add select on favorite frame to launch cannedMessage DM * Minor wording change * Less capital letters * Fix empty message check, time isn't reliable * drop dead code * Make UIRenderer a static class instead of namespace * Fix the select on favorite * Check if message is empty early and then 'return' * Add kb_found, and show the option to launch freetype if appropriate * Ignore impossible touchscreen touches * Auto scroll fix * Move linebreak after "from" for banners to maximize screen usage. * Center "No messages to show" on Message frame * Start consolidating buzzer behavior * Fixed signed / unsigned warning * Cast second parameter of max() to make some targets happy * Cast kbchar to (char) to make arduino string happy * Shorten the notice of "No messages" * Add buzzer mode chooser * Add regionPicker to Lora icon * Reduce line spacing and reorder Position screen to resolve overlapping issues * Update message titles, fix GPS icons, add Back options * Leftover boops * Remove chirp * Make the region selection dismissable when a region is already set * Add read-aloud functionality on messages w/ esp8266sam * "Last Heard" is a better label * tweak the beep * 5 options * properly tear down freetext upon cancel * de-convelute canned messages just a bit * Correct height of Mail icon in navigation bar * Remove unused warning * Consolidate time methods into TimeFormatters * Oops * Change LoRa Picker Cancel to Back * Tweak selection characters on Banner * Message render not scrolling on 5th line * More fixes for message scrolling * Remove the safety next on text overflow - we found that root cause * Add pin definitions to fix compilation for obscure target * Don't let the touchscreen send unitialized kbchar values * Make virtual KB just a bit quicker * No more double tap, swipe! * Left is left, and Right is right * Update horizontal lightning bolt design * Move from solid to dashed separator for Message Frame * Single emote feature fix * Manually sort overlapping elements for now * Freetext and clearer choices * Fix ESP32 InkHUD builds on the unify-tft branch (#7087) * Remove BaseUI branding * Capitalization is fun * Revert Meshtastic Boot Frame Changes * Add ANZ_433 LoRa region to picker * Update settings.json --------- Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jason P <applewiz@mac.com> Co-authored-by: todd-herbert <herbert.todd@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
634 lines
24 KiB
C++
634 lines
24 KiB
C++
#include "configuration.h"
|
||
#if HAS_SCREEN
|
||
#include "../Screen.h"
|
||
#include "DebugRenderer.h"
|
||
#include "FSCommon.h"
|
||
#include "NodeDB.h"
|
||
#include "Throttle.h"
|
||
#include "UIRenderer.h"
|
||
#include "airtime.h"
|
||
#include "gps/RTC.h"
|
||
#include "graphics/ScreenFonts.h"
|
||
#include "graphics/SharedUIDisplay.h"
|
||
#include "graphics/images.h"
|
||
#include "main.h"
|
||
#include "mesh/Channels.h"
|
||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||
#include "sleep.h"
|
||
|
||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||
#include "mesh/wifi/WiFiAPClient.h"
|
||
#include <WiFi.h>
|
||
#ifdef ARCH_ESP32
|
||
#include "mesh/wifi/WiFiAPClient.h"
|
||
#endif
|
||
#endif
|
||
|
||
#ifdef ARCH_ESP32
|
||
#include "modules/StoreForwardModule.h"
|
||
#endif
|
||
#include <DisplayFormatters.h>
|
||
#include <RadioLibInterface.h>
|
||
#include <target_specific.h>
|
||
|
||
using namespace meshtastic;
|
||
|
||
// External variables
|
||
extern graphics::Screen *screen;
|
||
extern PowerStatus *powerStatus;
|
||
extern NodeStatus *nodeStatus;
|
||
extern GPSStatus *gpsStatus;
|
||
extern Channels channels;
|
||
extern AirTime *airTime;
|
||
|
||
// External functions from Screen.cpp
|
||
extern bool heartbeat;
|
||
|
||
#ifdef ARCH_ESP32
|
||
extern StoreForwardModule *storeForwardModule;
|
||
#endif
|
||
|
||
namespace graphics
|
||
{
|
||
namespace DebugRenderer
|
||
{
|
||
|
||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
display->setFont(FONT_SMALL);
|
||
|
||
// The coordinates define the left starting point of the text
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
|
||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||
display->setColor(BLACK);
|
||
}
|
||
|
||
char channelStr[20];
|
||
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
|
||
|
||
// Display power status
|
||
if (powerStatus->getHasBattery()) {
|
||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||
UIRenderer::drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||
} else {
|
||
UIRenderer::drawBattery(display, x + 1, y + 3, imgBattery, powerStatus);
|
||
}
|
||
} else if (powerStatus->knowsUSB()) {
|
||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||
} else {
|
||
display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||
}
|
||
}
|
||
// Display nodes status
|
||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||
} else {
|
||
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
|
||
}
|
||
#if HAS_GPS
|
||
// Display GPS status
|
||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||
UIRenderer::drawGpsPowerStatus(display, x, y + 2, gpsStatus);
|
||
} else {
|
||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||
} else {
|
||
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
|
||
}
|
||
}
|
||
#endif
|
||
display->setColor(WHITE);
|
||
// Draw the channel name
|
||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
|
||
if (moduleConfig.store_forward.enabled) {
|
||
#ifdef ARCH_ESP32
|
||
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
||
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
||
8, imgQuestionL1);
|
||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12,
|
||
8, imgQuestionL2);
|
||
#else
|
||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8,
|
||
8, imgQuestion);
|
||
#endif
|
||
} else {
|
||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
||
8, imgSFL1);
|
||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 16,
|
||
8, imgSFL2);
|
||
#else
|
||
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 11,
|
||
8, imgSF);
|
||
#endif
|
||
}
|
||
#endif
|
||
} else {
|
||
// TODO: Raspberry Pi supports more than just the one screen size
|
||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||
imgInfoL1);
|
||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
|
||
imgInfoL2);
|
||
#else
|
||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8,
|
||
imgInfo);
|
||
#endif
|
||
}
|
||
|
||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(screen->ourId), y + FONT_HEIGHT_SMALL, screen->ourId);
|
||
|
||
// Draw any log messages
|
||
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
|
||
|
||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||
#ifdef SHOW_REDRAWS
|
||
if (heartbeat)
|
||
display->setPixel(0, 0);
|
||
heartbeat = !heartbeat;
|
||
#endif
|
||
}
|
||
|
||
// ****************************
|
||
// * WiFi Screen *
|
||
// ****************************
|
||
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||
display->clear();
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
display->setFont(FONT_SMALL);
|
||
int line = 1;
|
||
|
||
// === Set Title
|
||
const char *titleStr = "WiFi";
|
||
|
||
// === Header ===
|
||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||
|
||
const char *wifiName = config.network.wifi_ssid;
|
||
|
||
if (WiFi.status() != WL_CONNECTED) {
|
||
display->drawString(x, getTextPositions(display)[line++], "WiFi: Not Connected");
|
||
} else {
|
||
display->drawString(x, getTextPositions(display)[line++], "WiFi: Connected");
|
||
|
||
char rssiStr[32];
|
||
snprintf(rssiStr, sizeof(rssiStr), "RSSI: %d", WiFi.RSSI());
|
||
display->drawString(x, getTextPositions(display)[line++], rssiStr);
|
||
}
|
||
|
||
/*
|
||
- WL_CONNECTED: assigned when connected to a WiFi network;
|
||
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
|
||
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
|
||
- WL_CONNECTION_LOST: assigned when the connection is lost;
|
||
- WL_DISCONNECTED: assigned when disconnected from a network;
|
||
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
|
||
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
|
||
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
|
||
- WL_NO_SHIELD: assigned when no WiFi shield is present;
|
||
|
||
*/
|
||
if (WiFi.status() == WL_CONNECTED) {
|
||
char ipStr[64];
|
||
snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str());
|
||
display->drawString(x, getTextPositions(display)[line++], ipStr);
|
||
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
||
display->drawString(x, getTextPositions(display)[line++], "SSID Not Found");
|
||
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
||
display->drawString(x, getTextPositions(display)[line++], "Connection Lost");
|
||
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
||
display->drawString(x, getTextPositions(display)[line++], "Idle ... Reconnecting");
|
||
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
||
display->drawString(x, getTextPositions(display)[line++], "Connection Failed");
|
||
}
|
||
#ifdef ARCH_ESP32
|
||
else {
|
||
// Codes:
|
||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
||
display->drawString(x, getTextPositions(display)[line++],
|
||
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
|
||
}
|
||
#else
|
||
else {
|
||
char statusStr[32];
|
||
snprintf(statusStr, sizeof(statusStr), "Unknown status: %d", WiFi.status());
|
||
display->drawString(x, getTextPositions(display)[line++], statusStr);
|
||
}
|
||
#endif
|
||
|
||
char ssidStr[64];
|
||
snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName);
|
||
display->drawString(x, getTextPositions(display)[line++], ssidStr);
|
||
|
||
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
|
||
|
||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||
#ifdef SHOW_REDRAWS
|
||
if (heartbeat)
|
||
display->setPixel(0, 0);
|
||
heartbeat = !heartbeat;
|
||
#endif
|
||
#endif
|
||
}
|
||
|
||
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
display->setFont(FONT_SMALL);
|
||
|
||
// The coordinates define the left starting point of the text
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
|
||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||
display->setColor(BLACK);
|
||
}
|
||
|
||
char batStr[20];
|
||
if (powerStatus->getHasBattery()) {
|
||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
||
|
||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
|
||
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
|
||
|
||
// Line 1
|
||
display->drawString(x, y, batStr);
|
||
if (config.display.heading_bold)
|
||
display->drawString(x + 1, y, batStr);
|
||
} else {
|
||
// Line 1
|
||
display->drawString(x, y, "USB");
|
||
if (config.display.heading_bold)
|
||
display->drawString(x + 1, y, "USB");
|
||
}
|
||
|
||
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||
|
||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||
// if (config.display.heading_bold)
|
||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||
|
||
uint32_t currentMillis = millis();
|
||
uint32_t seconds = currentMillis / 1000;
|
||
uint32_t minutes = seconds / 60;
|
||
uint32_t hours = minutes / 60;
|
||
uint32_t days = hours / 24;
|
||
// currentMillis %= 1000;
|
||
// seconds %= 60;
|
||
// minutes %= 60;
|
||
// hours %= 24;
|
||
|
||
// Show uptime as days, hours, minutes OR seconds
|
||
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
||
|
||
// Line 1 (Still)
|
||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||
if (config.display.heading_bold)
|
||
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||
|
||
display->setColor(WHITE);
|
||
|
||
// Setup string to assemble analogClock string
|
||
std::string analogClock = "";
|
||
|
||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||
if (rtc_sec > 0) {
|
||
long hms = rtc_sec % SEC_PER_DAY;
|
||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
|
||
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
|
||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||
|
||
// Tear apart hms into h:m:s
|
||
int hour = hms / SEC_PER_HOUR;
|
||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||
|
||
char timebuf[12];
|
||
|
||
if (config.display.use_12h_clock) {
|
||
std::string meridiem = "am";
|
||
if (hour >= 12) {
|
||
if (hour > 12)
|
||
hour -= 12;
|
||
meridiem = "pm";
|
||
}
|
||
if (hour == 00) {
|
||
hour = 12;
|
||
}
|
||
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
|
||
} else {
|
||
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
|
||
}
|
||
analogClock += timebuf;
|
||
}
|
||
|
||
// Line 2
|
||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
|
||
|
||
// Display Channel Utilization
|
||
char chUtil[13];
|
||
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
|
||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
|
||
|
||
#if HAS_GPS
|
||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||
// Line 3
|
||
if (config.display.gps_format !=
|
||
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
|
||
UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||
|
||
// Line 4
|
||
UIRenderer::drawGpsCoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||
} else {
|
||
UIRenderer::drawGpsPowerStatus(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||
}
|
||
#endif
|
||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||
#ifdef SHOW_REDRAWS
|
||
if (heartbeat)
|
||
display->setPixel(0, 0);
|
||
heartbeat = !heartbeat;
|
||
#endif
|
||
}
|
||
|
||
// Trampoline functions for DebugInfo class access
|
||
void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
drawFrame(display, state, x, y);
|
||
}
|
||
|
||
void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
drawFrameSettings(display, state, x, y);
|
||
}
|
||
|
||
void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
drawFrameWiFi(display, state, x, y);
|
||
}
|
||
|
||
// ****************************
|
||
// * LoRa Focused Screen *
|
||
// ****************************
|
||
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
display->clear();
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
display->setFont(FONT_SMALL);
|
||
int line = 1;
|
||
|
||
// === Set Title
|
||
const char *titleStr = (SCREEN_WIDTH > 128) ? "LoRa Info" : "LoRa";
|
||
|
||
// === Header ===
|
||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||
|
||
// === First Row: Region / BLE Name ===
|
||
graphics::UIRenderer::drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, 0, true, "");
|
||
|
||
uint8_t dmac[6];
|
||
char shortnameble[35];
|
||
getMacAddr(dmac);
|
||
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
||
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
||
int textWidth = display->getStringWidth(shortnameble);
|
||
int nameX = (SCREEN_WIDTH - textWidth);
|
||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||
|
||
// === Second Row: Radio Preset ===
|
||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
||
char regionradiopreset[25];
|
||
const char *region = myRegion ? myRegion->name : NULL;
|
||
if (region != nullptr) {
|
||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
||
}
|
||
textWidth = display->getStringWidth(regionradiopreset);
|
||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
|
||
|
||
// === Third Row: Frequency / ChanNum ===
|
||
char frequencyslot[35];
|
||
char freqStr[16];
|
||
float freq = RadioLibInterface::instance->getFreq();
|
||
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
||
if (config.lora.channel_num == 0) {
|
||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr);
|
||
} else {
|
||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %smhz (%d)", freqStr, config.lora.channel_num);
|
||
}
|
||
size_t len = strlen(frequencyslot);
|
||
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
||
frequencyslot[len - 4] = '\0'; // Remove the last three characters
|
||
}
|
||
textWidth = display->getStringWidth(frequencyslot);
|
||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
||
|
||
// === Fourth Row: Channel Utilization ===
|
||
const char *chUtil = "ChUtil:";
|
||
char chUtilPercentage[10];
|
||
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
||
|
||
int chUtil_x = (SCREEN_WIDTH > 128) ? display->getStringWidth(chUtil) + 10 : display->getStringWidth(chUtil) + 5;
|
||
int chUtil_y = getTextPositions(display)[line] + 3;
|
||
|
||
int chutil_bar_width = (SCREEN_WIDTH > 128) ? 100 : 50;
|
||
int chutil_bar_height = (SCREEN_WIDTH > 128) ? 12 : 7;
|
||
int extraoffset = (SCREEN_WIDTH > 128) ? 6 : 3;
|
||
int chutil_percent = airTime->channelUtilizationPercent();
|
||
|
||
int centerofscreen = SCREEN_WIDTH / 2;
|
||
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
|
||
int starting_position = centerofscreen - total_line_content_width;
|
||
|
||
display->drawString(starting_position, getTextPositions(display)[line++], chUtil);
|
||
|
||
// Force 56% or higher to show a full 100% bar, text would still show related percent.
|
||
if (chutil_percent >= 61) {
|
||
chutil_percent = 100;
|
||
}
|
||
|
||
// Weighting for nonlinear segments
|
||
float milestone1 = 25;
|
||
float milestone2 = 40;
|
||
float weight1 = 0.45; // Weight for 0–25%
|
||
float weight2 = 0.35; // Weight for 25–40%
|
||
float weight3 = 0.20; // Weight for 40–100%
|
||
float totalWeight = weight1 + weight2 + weight3;
|
||
|
||
int seg1 = chutil_bar_width * (weight1 / totalWeight);
|
||
int seg2 = chutil_bar_width * (weight2 / totalWeight);
|
||
int seg3 = chutil_bar_width * (weight3 / totalWeight);
|
||
|
||
int fillRight = 0;
|
||
|
||
if (chutil_percent <= milestone1) {
|
||
fillRight = (seg1 * (chutil_percent / milestone1));
|
||
} else if (chutil_percent <= milestone2) {
|
||
fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
|
||
} else {
|
||
fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
|
||
}
|
||
|
||
// Draw outline
|
||
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
|
||
|
||
// Fill progress
|
||
if (fillRight > 0) {
|
||
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
|
||
}
|
||
|
||
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
|
||
chUtilPercentage);
|
||
}
|
||
|
||
// ****************************
|
||
// * Memory Screen *
|
||
// ****************************
|
||
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||
{
|
||
display->clear();
|
||
display->setFont(FONT_SMALL);
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
|
||
// === Set Title
|
||
const char *titleStr = "System";
|
||
|
||
// === Header ===
|
||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||
|
||
// === Layout ===
|
||
int line = 1;
|
||
const int barHeight = 6;
|
||
const int labelX = x;
|
||
const int barsOffset = (SCREEN_WIDTH > 128) ? 24 : 0;
|
||
const int barX = x + 40 + barsOffset;
|
||
|
||
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
||
if (total == 0)
|
||
return;
|
||
|
||
int percent = (used * 100) / total;
|
||
|
||
char combinedStr[24];
|
||
if (SCREEN_WIDTH > 128) {
|
||
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%% %u/%uKB", (percent > 80) ? "! " : "", percent, used / 1024,
|
||
total / 1024);
|
||
} else {
|
||
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%%", (percent > 80) ? "! " : "", percent);
|
||
}
|
||
|
||
int textWidth = display->getStringWidth(combinedStr);
|
||
int adjustedBarWidth = SCREEN_WIDTH - barX - textWidth - 6;
|
||
if (adjustedBarWidth < 10)
|
||
adjustedBarWidth = 10;
|
||
|
||
int fillWidth = (used * adjustedBarWidth) / total;
|
||
|
||
// Label
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
display->drawString(labelX, getTextPositions(display)[line], label);
|
||
|
||
// Bar
|
||
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
|
||
display->setColor(WHITE);
|
||
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
|
||
|
||
display->fillRect(barX, barY, fillWidth, barHeight);
|
||
display->setColor(WHITE);
|
||
|
||
// Value string
|
||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
||
};
|
||
|
||
// === 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
|
||
|
||
uint32_t sdUsed = 0, sdTotal = 0;
|
||
bool hasSD = false;
|
||
/*
|
||
#ifdef HAS_SDCARD
|
||
hasSD = SD.cardType() != CARD_NONE;
|
||
if (hasSD) {
|
||
sdUsed = SD.usedBytes();
|
||
sdTotal = SD.totalBytes();
|
||
}
|
||
#endif
|
||
*/
|
||
// === Draw memory rows
|
||
drawUsageRow("Heap:", heapUsed, heapTotal, true);
|
||
#ifdef ESP32
|
||
if (psramUsed > 0) {
|
||
line += 1;
|
||
drawUsageRow("PSRAM:", psramUsed, psramTotal);
|
||
}
|
||
if (flashTotal > 0) {
|
||
line += 1;
|
||
drawUsageRow("Flash:", flashUsed, flashTotal);
|
||
}
|
||
#endif
|
||
if (hasSD && sdTotal > 0) {
|
||
line += 1;
|
||
drawUsageRow("SD:", sdUsed, sdTotal);
|
||
}
|
||
|
||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||
// System Uptime
|
||
if (line < 2) {
|
||
line += 1;
|
||
}
|
||
line += 1;
|
||
char appversionstr[35];
|
||
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
|
||
int textWidth = display->getStringWidth(appversionstr);
|
||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
||
|
||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
||
line += 1;
|
||
char uptimeStr[32] = "";
|
||
uint32_t uptime = millis() / 1000;
|
||
uint32_t days = uptime / 86400;
|
||
uint32_t hours = (uptime % 86400) / 3600;
|
||
uint32_t mins = (uptime % 3600) / 60;
|
||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||
if (days)
|
||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
|
||
else if (hours)
|
||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
|
||
else
|
||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
|
||
textWidth = display->getStringWidth(uptimeStr);
|
||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||
}
|
||
}
|
||
} // namespace DebugRenderer
|
||
} // namespace graphics
|
||
#endif |