mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-13 08:32:08 +00:00
Notification and time
This commit is contained in:
parent
6627f2e873
commit
29402a5e7a
@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "draw/DebugRenderer.h"
|
#include "draw/DebugRenderer.h"
|
||||||
#include "draw/MessageRenderer.h"
|
#include "draw/MessageRenderer.h"
|
||||||
#include "draw/NodeListRenderer.h"
|
#include "draw/NodeListRenderer.h"
|
||||||
|
#include "draw/NotificationRenderer.h"
|
||||||
#include "draw/UIRenderer.h"
|
#include "draw/UIRenderer.h"
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
@ -76,8 +77,6 @@ using graphics::NodeListRenderer::drawScaledXBitmap16x16;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
#include "esp_task_wdt.h"
|
|
||||||
#include "modules/StoreForwardModule.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
@ -141,61 +140,6 @@ static bool heartbeat = false;
|
|||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include <Throttle.h>
|
#include <Throttle.h>
|
||||||
|
|
||||||
// Start Functions to write date/time to the screen
|
|
||||||
#include <string> // Only needed if you're using std::string elsewhere
|
|
||||||
|
|
||||||
bool isLeapYear(int year)
|
|
||||||
{
|
|
||||||
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
const int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
||||||
|
|
||||||
// Fills the buffer with a formatted date/time string and returns pixel width
|
|
||||||
int formatDateTime(char *buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay *display, bool includeTime)
|
|
||||||
{
|
|
||||||
int sec = rtc_sec % 60;
|
|
||||||
rtc_sec /= 60;
|
|
||||||
int min = rtc_sec % 60;
|
|
||||||
rtc_sec /= 60;
|
|
||||||
int hour = rtc_sec % 24;
|
|
||||||
rtc_sec /= 24;
|
|
||||||
|
|
||||||
int year = 1970;
|
|
||||||
while (true) {
|
|
||||||
int daysInYear = isLeapYear(year) ? 366 : 365;
|
|
||||||
if (rtc_sec >= (uint32_t)daysInYear) {
|
|
||||||
rtc_sec -= daysInYear;
|
|
||||||
year++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int month = 0;
|
|
||||||
while (month < 12) {
|
|
||||||
int dim = daysInMonth[month];
|
|
||||||
if (month == 1 && isLeapYear(year))
|
|
||||||
dim++;
|
|
||||||
if (rtc_sec >= (uint32_t)dim) {
|
|
||||||
rtc_sec -= dim;
|
|
||||||
month++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int day = rtc_sec + 1;
|
|
||||||
|
|
||||||
if (includeTime) {
|
|
||||||
snprintf(buf, bufSize, "%04d-%02d-%02d %02d:%02d:%02d", year, month + 1, day, hour, min, sec);
|
|
||||||
} else {
|
|
||||||
snprintf(buf, bufSize, "%04d-%02d-%02d", year, month + 1, day);
|
|
||||||
}
|
|
||||||
|
|
||||||
return display->getStringWidth(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage: int stringWidth = formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display);
|
// Usage: int stringWidth = formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display);
|
||||||
// End Functions to write date/time to the screen
|
// End Functions to write date/time to the screen
|
||||||
|
|
||||||
@ -367,51 +311,6 @@ void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
display->setFont(FONT_MEDIUM);
|
display->setFont(FONT_MEDIUM);
|
||||||
display->drawString(x_offset + x, 26 + y, message);
|
display->drawString(x_offset + x, 26 + y, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used on boot when a certificate is being created
|
|
||||||
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->drawString(64 + x, y, "Creating SSL certificate");
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
yield();
|
|
||||||
esp_task_wdt_reset();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
if ((millis() / 1000) % 2) {
|
|
||||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . .");
|
|
||||||
} else {
|
|
||||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used when booting without a region set
|
|
||||||
static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C");
|
|
||||||
display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName());
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
|
|
||||||
if ((millis() / 10000) % 2) {
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the");
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,");
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients.");
|
|
||||||
} else {
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org");
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information.");
|
|
||||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
yield();
|
|
||||||
esp_task_wdt_reset();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
// ==============================
|
// ==============================
|
||||||
// Overlay Alert Banner Renderer
|
// Overlay Alert Banner Renderer
|
||||||
// ==============================
|
// ==============================
|
||||||
@ -426,79 +325,6 @@ void Screen::showOverlayBanner(const String &message, uint32_t durationMs)
|
|||||||
alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draws the overlay banner on screen, if still within display duration
|
|
||||||
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
|
||||||
{
|
|
||||||
// Exit if no message is active or duration has passed
|
|
||||||
if (alertBannerMessage.length() == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// === Layout Configuration ===
|
|
||||||
constexpr uint16_t padding = 5; // Padding around text inside the box
|
|
||||||
constexpr uint8_t lineSpacing = 1; // Extra space between lines
|
|
||||||
|
|
||||||
// Search the mesage to determine if we need the bell added
|
|
||||||
bool needs_bell = (alertBannerMessage.indexOf("Alert Received") != -1);
|
|
||||||
|
|
||||||
// Setup font and alignment
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line
|
|
||||||
|
|
||||||
// === Split the message into lines (supports multi-line banners) ===
|
|
||||||
std::vector<String> lines;
|
|
||||||
int start = 0, newlineIdx;
|
|
||||||
while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) {
|
|
||||||
lines.push_back(alertBannerMessage.substring(start, newlineIdx));
|
|
||||||
start = newlineIdx + 1;
|
|
||||||
}
|
|
||||||
lines.push_back(alertBannerMessage.substring(start));
|
|
||||||
|
|
||||||
// === Measure text dimensions ===
|
|
||||||
uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78;
|
|
||||||
uint16_t maxWidth = 0;
|
|
||||||
std::vector<uint16_t> lineWidths;
|
|
||||||
for (const auto &line : lines) {
|
|
||||||
uint16_t w = display->getStringWidth(line.c_str(), line.length(), true);
|
|
||||||
lineWidths.push_back(w);
|
|
||||||
if (w > maxWidth)
|
|
||||||
maxWidth = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t boxWidth = padding * 2 + maxWidth;
|
|
||||||
if (needs_bell && boxWidth < minWidth)
|
|
||||||
boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20;
|
|
||||||
|
|
||||||
uint16_t boxHeight = padding * 2 + lines.size() * FONT_HEIGHT_SMALL + (lines.size() - 1) * lineSpacing;
|
|
||||||
|
|
||||||
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
|
||||||
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
|
||||||
|
|
||||||
// === Draw background box ===
|
|
||||||
display->setColor(BLACK);
|
|
||||||
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Slightly oversized box
|
|
||||||
display->setColor(WHITE);
|
|
||||||
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); // Border
|
|
||||||
|
|
||||||
// === Draw each line centered in the box ===
|
|
||||||
int16_t lineY = boxTop + padding;
|
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
|
||||||
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
|
||||||
uint16_t line_width = display->getStringWidth(lines[i].c_str(), lines[i].length(), true);
|
|
||||||
|
|
||||||
if (needs_bell && i == 0) {
|
|
||||||
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;
|
|
||||||
display->drawXbm(textX - 10, bellY, 8, 8, bell_alert);
|
|
||||||
display->drawXbm(textX + line_width + 2, bellY, 8, 8, bell_alert);
|
|
||||||
}
|
|
||||||
|
|
||||||
display->drawString(textX, lineY, lines[i]);
|
|
||||||
if (SCREEN_WIDTH > 128)
|
|
||||||
display->drawString(textX + 1, lineY, lines[i]); // Faux bold
|
|
||||||
|
|
||||||
lineY += FONT_HEIGHT_SMALL + lineSpacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active
|
// 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)
|
static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
{
|
{
|
||||||
@ -602,32 +428,6 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
|||||||
pi.drawFrame(display, state, x, y);
|
pi.drawFrame(display, state, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->setFont(FONT_MEDIUM);
|
|
||||||
display->drawString(64 + x, y, "Updating");
|
|
||||||
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(),
|
|
||||||
"Please be patient and do not power off.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw the last text message we received
|
|
||||||
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->setFont(FONT_MEDIUM);
|
|
||||||
|
|
||||||
char tempBuf[24];
|
|
||||||
snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code);
|
|
||||||
display->drawString(0 + x, 0 + y, tempBuf);
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->setFont(FONT_SMALL);
|
|
||||||
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
|
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
|
||||||
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
|
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
|
||||||
{
|
{
|
||||||
@ -2107,7 +1907,7 @@ static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiStat
|
|||||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
|
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||||
char datetimeStr[25];
|
char datetimeStr[25];
|
||||||
bool showTime = false; // set to true for full datetime
|
bool showTime = false; // set to true for full datetime
|
||||||
formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
|
graphics::UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
|
||||||
char fullLine[40];
|
char fullLine[40];
|
||||||
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
|
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
|
||||||
display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine), fullLine);
|
display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine), fullLine);
|
||||||
@ -2926,7 +2726,7 @@ void Screen::setSSLFrames()
|
|||||||
{
|
{
|
||||||
if (address_found.address) {
|
if (address_found.address) {
|
||||||
// LOG_DEBUG("Show SSL frames");
|
// LOG_DEBUG("Show SSL frames");
|
||||||
static FrameCallback sslFrames[] = {drawSSLScreen};
|
static FrameCallback sslFrames[] = {NotificationRenderer::drawSSLScreen};
|
||||||
ui->setFrames(sslFrames, 1);
|
ui->setFrames(sslFrames, 1);
|
||||||
ui->update();
|
ui->update();
|
||||||
}
|
}
|
||||||
@ -2938,7 +2738,7 @@ void Screen::setWelcomeFrames()
|
|||||||
{
|
{
|
||||||
if (address_found.address) {
|
if (address_found.address) {
|
||||||
// LOG_DEBUG("Show Welcome frames");
|
// LOG_DEBUG("Show Welcome frames");
|
||||||
static FrameCallback frames[] = {drawWelcomeScreen};
|
static FrameCallback frames[] = {NotificationRenderer::drawWelcomeScreen};
|
||||||
setFrameImmediateDraw(frames);
|
setFrameImmediateDraw(frames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3057,7 +2857,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
// If we have a critical fault, show it first
|
// If we have a critical fault, show it first
|
||||||
fsi.positions.fault = numframes;
|
fsi.positions.fault = numframes;
|
||||||
if (error_code) {
|
if (error_code) {
|
||||||
normalFrames[numframes++] = drawCriticalFaultFrame;
|
normalFrames[numframes++] = NotificationRenderer::drawCriticalFaultFrame;
|
||||||
indicatorIcons.push_back(icon_error);
|
indicatorIcons.push_back(icon_error);
|
||||||
focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame
|
focus = FOCUS_FAULT; // Change our "focus" parameter, to ensure we show the fault frame
|
||||||
}
|
}
|
||||||
@ -3145,7 +2945,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
ui->disableAllIndicators();
|
ui->disableAllIndicators();
|
||||||
|
|
||||||
// Add overlays: frame icons and alert banner)
|
// Add overlays: frame icons and alert banner)
|
||||||
static OverlayCallback overlays[] = {NavigationBar, drawAlertBannerOverlay};
|
static OverlayCallback overlays[] = {NavigationBar, NotificationRenderer::drawAlertBannerOverlay};
|
||||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
|
||||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||||
@ -3231,7 +3031,7 @@ void Screen::handleStartFirmwareUpdateScreen()
|
|||||||
showingNormalScreen = false;
|
showingNormalScreen = false;
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||||
|
|
||||||
static FrameCallback frames[] = {drawFrameFirmware};
|
static FrameCallback frames[] = {NotificationRenderer::drawFrameFirmware};
|
||||||
setFrameImmediateDraw(frames);
|
setFrameImmediateDraw(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
167
src/graphics/draw/NotificationRenderer.cpp
Normal file
167
src/graphics/draw/NotificationRenderer.cpp
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#include "NotificationRenderer.h"
|
||||||
|
#include "DisplayFormatters.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "graphics/ScreenFonts.h"
|
||||||
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "graphics/images.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef ARCH_ESP32
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace meshtastic;
|
||||||
|
|
||||||
|
// External references to global variables from Screen.cpp
|
||||||
|
extern String alertBannerMessage;
|
||||||
|
extern uint32_t alertBannerUntil;
|
||||||
|
extern std::vector<std::string> functionSymbol;
|
||||||
|
extern std::string functionSymbolString;
|
||||||
|
extern bool hasUnreadMessage;
|
||||||
|
|
||||||
|
// Used on boot when a certificate is being created
|
||||||
|
void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->drawString(64 + x, y, "Creating SSL certificate");
|
||||||
|
|
||||||
|
#ifdef ARCH_ESP32
|
||||||
|
yield();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
if ((millis() / 1000) % 2) {
|
||||||
|
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . .");
|
||||||
|
} else {
|
||||||
|
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used when booting without a region set
|
||||||
|
void NotificationRenderer::drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
|
display->drawString(64 + x, y, "//\\ E S H T /\\ S T / C");
|
||||||
|
display->drawString(64 + x, y + FONT_HEIGHT_SMALL, getDeviceName());
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
|
if ((millis() / 10000) % 2) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the");
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,");
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients.");
|
||||||
|
} else {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org");
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information.");
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARCH_ESP32
|
||||||
|
yield();
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
|
{
|
||||||
|
// Exit if no message is active or duration has passed
|
||||||
|
if (alertBannerMessage.length() == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// === Layout Configuration ===
|
||||||
|
constexpr uint16_t padding = 5; // Padding around text inside the box
|
||||||
|
constexpr uint8_t lineSpacing = 1; // Extra space between lines
|
||||||
|
|
||||||
|
// Search the mesage to determine if we need the bell added
|
||||||
|
bool needs_bell = (alertBannerMessage.indexOf("Alert Received") != -1);
|
||||||
|
|
||||||
|
// Setup font and alignment
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line
|
||||||
|
|
||||||
|
// === Split the message into lines (supports multi-line banners) ===
|
||||||
|
std::vector<String> lines;
|
||||||
|
int start = 0, newlineIdx;
|
||||||
|
while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) {
|
||||||
|
lines.push_back(alertBannerMessage.substring(start, newlineIdx));
|
||||||
|
start = newlineIdx + 1;
|
||||||
|
}
|
||||||
|
lines.push_back(alertBannerMessage.substring(start));
|
||||||
|
|
||||||
|
// === Measure text dimensions ===
|
||||||
|
uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78;
|
||||||
|
uint16_t maxWidth = 0;
|
||||||
|
std::vector<uint16_t> lineWidths;
|
||||||
|
for (const auto &line : lines) {
|
||||||
|
uint16_t w = display->getStringWidth(line.c_str(), line.length(), true);
|
||||||
|
lineWidths.push_back(w);
|
||||||
|
if (w > maxWidth)
|
||||||
|
maxWidth = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t boxWidth = padding * 2 + maxWidth;
|
||||||
|
if (needs_bell && boxWidth < minWidth)
|
||||||
|
boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20;
|
||||||
|
|
||||||
|
uint16_t boxHeight = padding * 2 + lines.size() * FONT_HEIGHT_SMALL + (lines.size() - 1) * lineSpacing;
|
||||||
|
|
||||||
|
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||||
|
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||||
|
|
||||||
|
// === Draw background box ===
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Slightly oversized box
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); // Border
|
||||||
|
|
||||||
|
// === Draw each line centered in the box ===
|
||||||
|
int16_t lineY = boxTop + padding;
|
||||||
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||||
|
uint16_t line_width = display->getStringWidth(lines[i].c_str(), lines[i].length(), true);
|
||||||
|
|
||||||
|
if (needs_bell && i == 0) {
|
||||||
|
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;
|
||||||
|
display->drawXbm(textX - 10, bellY, 8, 8, bell_alert);
|
||||||
|
display->drawXbm(textX + line_width + 2, bellY, 8, 8, bell_alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
display->drawString(textX, lineY, lines[i]);
|
||||||
|
if (SCREEN_WIDTH > 128)
|
||||||
|
display->drawString(textX + 1, lineY, lines[i]); // Faux bold
|
||||||
|
|
||||||
|
lineY += FONT_HEIGHT_SMALL + lineSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the last text message we received
|
||||||
|
void NotificationRenderer::drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->setFont(FONT_MEDIUM);
|
||||||
|
|
||||||
|
char tempBuf[24];
|
||||||
|
snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code);
|
||||||
|
display->drawString(0 + x, 0 + y, tempBuf);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationRenderer::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);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(),
|
||||||
|
"Please be patient and do not power off.");
|
||||||
|
}
|
14
src/graphics/draw/NotificationRenderer.h
Normal file
14
src/graphics/draw/NotificationRenderer.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "OLEDDisplay.h"
|
||||||
|
#include "OLEDDisplayUi.h"
|
||||||
|
|
||||||
|
class NotificationRenderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
|
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
};
|
@ -183,8 +183,62 @@ void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UIRenderer
|
// Start Functions to write date/time to the screen
|
||||||
|
// Helper function to check if a year is a leap year
|
||||||
|
bool isLeapYear(int year)
|
||||||
|
{
|
||||||
|
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array of days in each month (non-leap year)
|
||||||
|
const int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
|
|
||||||
|
// Fills the buffer with a formatted date/time string and returns pixel width
|
||||||
|
int formatDateTime(char *buf, size_t bufSize, uint32_t rtc_sec, OLEDDisplay *display, bool includeTime)
|
||||||
|
{
|
||||||
|
int sec = rtc_sec % 60;
|
||||||
|
rtc_sec /= 60;
|
||||||
|
int min = rtc_sec % 60;
|
||||||
|
rtc_sec /= 60;
|
||||||
|
int hour = rtc_sec % 24;
|
||||||
|
rtc_sec /= 24;
|
||||||
|
|
||||||
|
int year = 1970;
|
||||||
|
while (true) {
|
||||||
|
int daysInYear = isLeapYear(year) ? 366 : 365;
|
||||||
|
if (rtc_sec >= (uint32_t)daysInYear) {
|
||||||
|
rtc_sec -= daysInYear;
|
||||||
|
year++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int month = 0;
|
||||||
|
while (month < 12) {
|
||||||
|
int dim = daysInMonth[month];
|
||||||
|
if (month == 1 && isLeapYear(year))
|
||||||
|
dim++;
|
||||||
|
if (rtc_sec >= (uint32_t)dim) {
|
||||||
|
rtc_sec -= dim;
|
||||||
|
month++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int day = rtc_sec + 1;
|
||||||
|
|
||||||
|
if (includeTime) {
|
||||||
|
snprintf(buf, bufSize, "%04d-%02d-%02d %02d:%02d:%02d", year, month + 1, day, hour, min, sec);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, bufSize, "%04d-%02d-%02d", year, month + 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
return display->getStringWidth(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UIRenderer
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
|
||||||
#endif // !MESHTASTIC_EXCLUDE_GPS
|
#endif // !MESHTASTIC_EXCLUDE_GPS
|
||||||
|
@ -55,7 +55,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
// Time and date utilities
|
// Time and date utilities
|
||||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||||
std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
||||||
void formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime);
|
int formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime);
|
||||||
|
|
||||||
// Message filtering
|
// Message filtering
|
||||||
bool shouldDrawMessage(const meshtastic_MeshPacket *packet);
|
bool shouldDrawMessage(const meshtastic_MeshPacket *packet);
|
||||||
|
Loading…
Reference in New Issue
Block a user