Compare commits

...

9 Commits

Author SHA1 Message Date
Jason P
673f5d3ede Update drawCompassNorth to include radius 2025-06-06 15:35:20 -05:00
Jonathan Bennett
51c90be8ed
Merge branch 'master' into unify-tft 2025-06-06 13:48:25 -05:00
Jonathan Bennett
58ec9893df Make some char pointers const to fix compilation on native targets 2025-06-06 13:47:23 -05:00
Jason P
3f9b116a13 Simply LoRa screen 2025-06-06 12:28:20 -05:00
Ben Meadors
6b4f6a0cef Fully remove EVENT_NODEDB_UPDATED 2025-06-06 11:58:44 -05:00
Jason P
2b5a7ab06d Still my Fav without an "e" 2025-06-06 11:33:10 -05:00
Jason P
37145abbfb Move to unified text layouts and spacing 2025-06-06 11:17:20 -05:00
todd-herbert
ba296db701
Add InkHUD driver for WeAct Studio 2.9" display module (#6963)
Some checks failed
CI / build-esp32-c6 (push) Blocked by required conditions
CI / build-nrf52 (push) Blocked by required conditions
CI / build-rpi2040 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / build-debian-src (push) Waiting to run
CI / package-pio-deps-native-tft (push) Waiting to run
CI / test-native (push) Waiting to run
CI / docker-deb-amd64 (push) Waiting to run
CI / docker-deb-amd64-tft (push) Waiting to run
CI / docker-alp-amd64 (push) Waiting to run
CI / docker-alp-amd64-tft (push) Waiting to run
CI / docker-deb-arm64 (push) Waiting to run
CI / docker-deb-armv7 (push) Waiting to run
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
CI / publish-firmware (push) Blocked by required conditions
Semgrep Full Scan / semgrep-full (push) Has been cancelled
* Driver for WeAct Studio 2.9" ePaper module

* Clarify that flex connector marking is not a unique id

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-06 17:35:47 +12:00
github-actions[bot]
c0e1616382
Upgrade trunk (#6948)
Some checks failed
CI / build-nrf52 (push) Blocked by required conditions
CI / build-rpi2040 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / build-debian-src (push) Waiting to run
CI / package-pio-deps-native-tft (push) Waiting to run
CI / test-native (push) Waiting to run
CI / docker-deb-amd64 (push) Waiting to run
CI / docker-deb-amd64-tft (push) Waiting to run
CI / docker-alp-amd64 (push) Waiting to run
CI / docker-alp-amd64-tft (push) Waiting to run
CI / docker-deb-arm64 (push) Waiting to run
CI / docker-deb-armv7 (push) Waiting to run
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
CI / publish-firmware (push) Blocked by required conditions
Nightly / Trunk Check and Upload (push) Has been cancelled
Nightly / Trunk Upgrade (PR) (push) Has been cancelled
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-05 10:11:43 -05:00
25 changed files with 251 additions and 155 deletions

View File

@ -8,8 +8,8 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.435
- renovate@40.36.2
- checkov@3.2.436
- renovate@40.41.0
- prettier@3.5.3
- trufflehog@3.88.35
- yamllint@1.37.1
@ -28,7 +28,7 @@ lint:
- shellcheck@0.10.0
- black@25.1.0
- git-diff-check
- gitleaks@8.26.0
- gitleaks@8.27.0
- clang-format@16.0.3
ignore:
- linters: [ALL]

View File

@ -11,7 +11,7 @@
#define EVENT_RECEIVED_MSG 5
// #define EVENT_BOOT 6 // now done with a timed transition
#define EVENT_BLUETOOTH_PAIR 7
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
// #define EVENT_NODEDB_UPDATED 8 // Now defunct: NodeDB has a big enough change that we think you should turn on the screen
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
#define EVENT_SERIAL_CONNECTED 11

View File

@ -9,27 +9,14 @@ namespace graphics
// Shared UI Helpers
// =======================
// Compact line layout
#define compactFirstLine ((FONT_HEIGHT_SMALL - 1) * 1)
#define compactSecondLine ((FONT_HEIGHT_SMALL - 1) * 2) - 2
#define compactThirdLine ((FONT_HEIGHT_SMALL - 1) * 3) - 4
#define compactFourthLine ((FONT_HEIGHT_SMALL - 1) * 4) - 6
#define compactFifthLine ((FONT_HEIGHT_SMALL - 1) * 5) - 8
#define compactSixthLine ((FONT_HEIGHT_SMALL - 1) * 6) - 10
// Standard line layout
#define standardFirstLine (FONT_HEIGHT_SMALL + 1) * 1
#define standardSecondLine (FONT_HEIGHT_SMALL + 1) * 2
#define standardThirdLine (FONT_HEIGHT_SMALL + 1) * 3
#define standardFourthLine (FONT_HEIGHT_SMALL + 1) * 4
// More Compact line layout
#define moreCompactFirstLine compactFirstLine
#define moreCompactSecondLine (moreCompactFirstLine + (FONT_HEIGHT_SMALL - 5))
#define moreCompactThirdLine (moreCompactSecondLine + (FONT_HEIGHT_SMALL - 5))
#define moreCompactFourthLine (moreCompactThirdLine + (FONT_HEIGHT_SMALL - 5))
#define moreCompactFifthLine (moreCompactFourthLine + (FONT_HEIGHT_SMALL - 5))
#define moreCompactSixthLine (moreCompactFifthLine + (FONT_HEIGHT_SMALL - 5))
// Consistent Line Spacing
#define textZeroLine 0
#define textFirstLine ((FONT_HEIGHT_SMALL - 1) * 1)
#define textSecondLine (textFirstLine + (FONT_HEIGHT_SMALL - 5))
#define textThirdLine (textSecondLine + (FONT_HEIGHT_SMALL - 5))
#define textFourthLine (textThirdLine + (FONT_HEIGHT_SMALL - 5))
#define textFifthLine (textFourthLine + (FONT_HEIGHT_SMALL - 5))
#define textSixthLine (textFifthLine + (FONT_HEIGHT_SMALL - 5))
// Quick screen access
#define SCREEN_WIDTH display->getWidth()

View File

@ -39,20 +39,28 @@ struct Point {
}
};
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius)
{
// Show the compass heading (not implemented in original)
// This could draw a "N" indicator or north arrow
// For now, we'll draw a simple north indicator
const float radius = 8.0f;
// const float radius = 17.0f;
if (display->width() > 128) {
radius += 4;
}
Point north(0, -radius);
north.rotate(-myHeading);
north.translate(compassX, compassY);
// Draw a small "N" or north indicator
display->drawCircle(north.x, north.y, 2);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setColor(BLACK);
if (display->width() > 128) {
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
} else {
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
}
display->setColor(WHITE);
display->drawString(north.x, north.y - 3, "N");
}

View File

@ -20,7 +20,7 @@ class Screen;
namespace CompassRenderer
{
// Compass drawing functions
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading);
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius);
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing);

View File

@ -16,6 +16,9 @@
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "sleep.h"
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h"
#include <WiFi.h>
@ -173,6 +176,7 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = "WiFi";
@ -183,13 +187,13 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
const char *wifiName = config.network.wifi_ssid;
if (WiFi.status() != WL_CONNECTED) {
display->drawString(x, moreCompactFirstLine, "WiFi: Not Connected");
display->drawString(x, textPositions[line++], "WiFi: Not Connected");
} else {
display->drawString(x, moreCompactFirstLine, "WiFi: Connected");
display->drawString(x, textPositions[line++], "WiFi: Connected");
char rssiStr[32];
snprintf(rssiStr, sizeof(rssiStr), "RSSI: %d", WiFi.RSSI());
display->drawString(x, moreCompactSecondLine, rssiStr);
display->drawString(x, textPositions[line++], rssiStr);
}
/*
@ -207,36 +211,36 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
if (WiFi.status() == WL_CONNECTED) {
char ipStr[64];
snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str());
display->drawString(x, moreCompactThirdLine, ipStr);
display->drawString(x, textPositions[line++], ipStr);
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, moreCompactThirdLine, "SSID Not Found");
display->drawString(x, textPositions[line++], "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) {
display->drawString(x, moreCompactThirdLine, "Connection Lost");
display->drawString(x, textPositions[line++], "Connection Lost");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, moreCompactThirdLine, "Idle ... Reconnecting");
display->drawString(x, textPositions[line++], "Idle ... Reconnecting");
} else if (WiFi.status() == WL_CONNECT_FAILED) {
display->drawString(x, moreCompactThirdLine, "Connection Failed");
display->drawString(x, textPositions[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, moreCompactThirdLine,
display->drawString(x, textPositions[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, moreCompactThirdLine, statusStr);
display->drawString(x, textPositions[line++], statusStr);
}
#endif
char ssidStr[64];
snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName);
display->drawString(x, moreCompactFourthLine, ssidStr);
display->drawString(x, textPositions[line++], ssidStr);
display->drawString(x, moreCompactFifthLine, "URL: http://meshtastic.local");
display->drawString(x, textPositions[line++], "URL: http://meshtastic.local");
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
@ -392,6 +396,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
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";
@ -400,7 +405,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
graphics::drawCommonHeader(display, x, y, titleStr);
// === First Row: Region / BLE Name ===
graphics::UIRenderer::drawNodes(display, x, compactFirstLine + 2, nodeStatus, 0, true, "");
graphics::UIRenderer::drawNodes(display, x, textPositions[line] + 2, nodeStatus, 0, true, "");
uint8_t dmac[6];
char shortnameble[35];
@ -409,7 +414,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, compactFirstLine, shortnameble);
display->drawString(nameX, textPositions[line++], shortnameble);
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
@ -420,7 +425,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, compactSecondLine, regionradiopreset);
display->drawString(nameX, textPositions[line++], regionradiopreset);
// === Third Row: Frequency / ChanNum ===
char frequencyslot[35];
@ -430,11 +435,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
if (config.lora.channel_num == 0) {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr);
} else {
if (SCREEN_WIDTH > 128) {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Chan: %smhz (%d)", freqStr, config.lora.channel_num);
} else {
snprintf(frequencyslot, sizeof(frequencyslot), "Frq/Ch: %smhz (%d)", freqStr, config.lora.channel_num);
}
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) {
@ -442,7 +443,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
textWidth = display->getStringWidth(frequencyslot);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, compactThirdLine, frequencyslot);
display->drawString(nameX, textPositions[line++], frequencyslot);
// === Fourth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
@ -450,7 +451,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
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 = compactFourthLine + 3;
int chUtil_y = textPositions[line] + 3;
int chutil_bar_width = (SCREEN_WIDTH > 128) ? 100 : 50;
int chutil_bar_height = (SCREEN_WIDTH > 128) ? 12 : 7;
@ -461,7 +462,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
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, compactFourthLine, chUtil);
display->drawString(starting_position, textPositions[line++], chUtil);
// Force 56% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
@ -498,7 +499,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, compactFourthLine, chUtilPercentage);
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, textPositions[4], chUtilPercentage);
}
// ****************************
@ -517,9 +518,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
graphics::drawCommonHeader(display, x, y, titleStr);
// === Layout ===
const int yPositions[6] = {moreCompactFirstLine, moreCompactSecondLine, moreCompactThirdLine,
moreCompactFourthLine, moreCompactFifthLine, moreCompactSixthLine};
int line = 0;
int line = 1;
const int barHeight = 6;
const int labelX = x;
const int barsOffset = (SCREEN_WIDTH > 128) ? 24 : 0;
@ -548,10 +547,10 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, yPositions[line], label);
display->drawString(labelX, textPositions[line], label);
// Bar
int barY = yPositions[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
int barY = textPositions[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
@ -560,7 +559,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, yPositions[line], combinedStr);
display->drawString(SCREEN_WIDTH - 2, textPositions[line], combinedStr);
};
// === Memory values ===
@ -614,7 +613,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, yPositions[line], appversionstr);
display->drawString(nameX, textPositions[line], appversionstr);
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1;
@ -632,7 +631,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, yPositions[line], uptimeStr);
display->drawString(nameX, textPositions[line], uptimeStr);
}
}
} // namespace DebugRenderer

View File

@ -12,6 +12,9 @@
#include "meshUtils.h"
#include <algorithm>
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
// Forward declarations for functions defined in Screen.cpp
namespace graphics
{
@ -612,14 +615,12 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
graphics::drawCommonHeader(display, x, y, shortName);
// Dynamic row stacking with predefined Y positions
const int yPositions[5] = {moreCompactFirstLine, moreCompactSecondLine, moreCompactThirdLine, moreCompactFourthLine,
moreCompactFifthLine};
int line = 0;
int line = 1;
// 1. Long Name (always try to show first)
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
if (username && line < 5) {
display->drawString(x, yPositions[line++], username);
display->drawString(x, textPositions[line++], username);
}
// 2. Signal and Hops (combined on one line, if available)
@ -644,7 +645,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
}
}
if (signalHopsStr[0] && line < 5) {
display->drawString(x, yPositions[line++], signalHopsStr);
display->drawString(x, textPositions[line++], signalHopsStr);
}
// 3. Heard (last seen, skip if node never seen)
@ -661,7 +662,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
: 'm'));
}
if (seenStr[0] && line < 5) {
display->drawString(x, yPositions[line++], seenStr);
display->drawString(x, textPositions[line++], seenStr);
}
// 4. Uptime (only show if metric is present)
@ -681,7 +682,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
}
}
if (uptimeStr[0] && line < 5) {
display->drawString(x, yPositions[line++], uptimeStr);
display->drawString(x, textPositions[line++], uptimeStr);
}
// 5. Distance (only if both nodes have GPS position)
@ -733,7 +734,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
}
// Only display if we actually have a value!
if (haveDistance && distStr[0] && line < 5) {
display->drawString(x, yPositions[line++], distStr);
display->drawString(x, textPositions[line++], distStr);
}
// Compass rendering for different screen orientations
@ -744,7 +745,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
showCompass = true;
}
if (showCompass) {
const int16_t topY = compactFirstLine;
const int16_t topY = textPositions[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
const int16_t usableHeight = bottomY - topY - 5;
int16_t compassRadius = usableHeight / 2;
@ -757,7 +758,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
const auto &op = ourNode->position;
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading);
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
const auto &p = node->position;
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
@ -774,7 +775,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
showCompass = true;
}
if (showCompass) {
int yBelowContent = (line > 0 && line <= 5) ? (yPositions[line - 1] + FONT_HEIGHT_SMALL + 2) : moreCompactFirstLine;
int yBelowContent = (line > 1 && line <= 6) ? (textPositions[line - 1] + FONT_HEIGHT_SMALL + 2) : textPositions[1];
const int margin = 4;
#if defined(USE_EINK)
@ -800,7 +801,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
const auto &op = ourNode->position;
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading);
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
const auto &p = node->position;
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));

View File

@ -17,6 +17,9 @@
#include <RTC.h>
#include <cstring>
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
#if !MESHTASTIC_EXCLUDE_GPS
// External variables
@ -38,8 +41,8 @@ namespace UIRenderer
void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
{
// Draw satellite image
int yOffset = (SCREEN_WIDTH > 128) ? 4 : 2;
display->drawFastImage(x + 1, y + yOffset, 8, 8, imgSatellite);
int yOffset = (SCREEN_WIDTH > 128) ? 3 : 1;
display->drawXbm(x + 1, y + yOffset, imgSatellite_width, imgSatellite_height, imgSatellite);
char textString[10];
if (config.position.fixed_position) {
@ -268,7 +271,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
// === Create the shortName and title string ===
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
char titlestr[32] = {0};
snprintf(titlestr, sizeof(titlestr), (SCREEN_WIDTH > 128) ? "Fave: %s" : "Fav: %s", shortName);
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
// === Draw battery/time/mail header (common across screens) ===
graphics::drawCommonHeader(display, x, y, titlestr);
@ -280,15 +283,13 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
// 4. The first line is ALWAYS at your macro position; subsequent lines use the next available macro slot.
// List of available macro Y positions in order, from top to bottom.
const int yPositions[5] = {moreCompactFirstLine, moreCompactSecondLine, moreCompactThirdLine, moreCompactFourthLine,
moreCompactFifthLine};
int line = 0; // which slot to use next
int line = 1; // which slot to use next
// === 1. Long Name (always try to show first) ===
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
if (username && line < 5) {
// Print node's long name (e.g. "Backpack Node")
display->drawString(x, yPositions[line++], username);
display->drawString(x, textPositions[line++], username);
}
// === 2. Signal and Hops (combined on one line, if available) ===
@ -319,7 +320,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
}
}
if (signalHopsStr[0] && line < 5) {
display->drawString(x, yPositions[line++], signalHopsStr);
display->drawString(x, textPositions[line++], signalHopsStr);
}
// === 3. Heard (last seen, skip if node never seen) ===
@ -337,7 +338,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
: 'm'));
}
if (seenStr[0] && line < 5) {
display->drawString(x, yPositions[line++], seenStr);
display->drawString(x, textPositions[line++], seenStr);
}
// === 4. Uptime (only show if metric is present) ===
@ -356,7 +357,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
}
if (uptimeStr[0] && line < 5) {
display->drawString(x, yPositions[line++], uptimeStr);
display->drawString(x, textPositions[line++], uptimeStr);
}
// === 5. Distance (only if both nodes have GPS position) ===
@ -416,7 +417,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
}
// Only display if we actually have a value!
if (haveDistance && distStr[0] && line < 5) {
display->drawString(x, yPositions[line++], distStr);
display->drawString(x, textPositions[line++], distStr);
}
// --- Compass Rendering: landscape (wide) screens use the original side-aligned logic ---
@ -426,7 +427,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
showCompass = true;
}
if (showCompass) {
const int16_t topY = compactFirstLine;
const int16_t topY = textPositions[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
const int16_t usableHeight = bottomY - topY - 5;
int16_t compassRadius = usableHeight / 2;
@ -439,7 +440,6 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
const auto &op = ourNode->position;
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading);
const auto &p = node->position;
/* unused
@ -449,9 +449,10 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
if (!config.display.compass_north_top)
bearing -= myHeading;
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearing);
display->drawCircle(compassX, compassY, compassRadius);
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearing);
}
// else show nothing
} else {
@ -461,7 +462,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
showCompass = true;
}
if (showCompass) {
int yBelowContent = (line > 0 && line <= 5) ? (yPositions[line - 1] + FONT_HEIGHT_SMALL + 2) : moreCompactFirstLine;
int yBelowContent = (line > 0 && line <= 5) ? (textPositions[line - 1] + FONT_HEIGHT_SMALL + 2) : textPositions[1];
const int margin = 4;
// --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) -----------
#if defined(USE_EINK)
@ -488,7 +489,7 @@ void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t
const auto &op = ourNode->position;
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
const auto &p = node->position;
/* unused
@ -514,6 +515,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Header ===
graphics::drawCommonHeader(display, x, y, "");
@ -531,9 +533,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
config.display.heading_bold = false;
// Display Region and Channel Utilization
drawNodes(display, x + 1,
((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)) + 2, nodeStatus,
-1, false, "online");
drawNodes(display, x + 1, textPositions[line] + 2, nodeStatus, -1, false, "online");
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
@ -547,9 +547,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr),
((rows == 4) ? compactFirstLine : ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine)),
uptimeStr);
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), textPositions[line++], uptimeStr);
// === Second Row: Satellites and Voltage ===
config.display.heading_bold = false;
@ -562,13 +560,9 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
} else {
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
}
display->drawString(
0, ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)),
displayLine);
display->drawString(0, textPositions[line], displayLine);
} else {
UIRenderer::drawGps(
display, 0, ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)),
gpsStatus);
UIRenderer::drawGps(display, 0, textPositions[line], gpsStatus);
}
#endif
@ -577,21 +571,16 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int batV = powerStatus->getBatteryVoltageMv() / 1000;
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
snprintf(batStr, sizeof(batStr), "%01d.%02dV", batV, batCv);
display->drawString(
x + SCREEN_WIDTH - display->getStringWidth(batStr),
((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), batStr);
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(batStr), textPositions[line++], batStr);
} else {
display->drawString(
x + SCREEN_WIDTH - display->getStringWidth("USB"),
((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), "USB");
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("USB"), textPositions[line++], "USB");
}
config.display.heading_bold = origBold;
// === Third Row: Bluetooth Off (Only If Actually Off) ===
if (!config.bluetooth.enabled) {
display->drawString(
0, ((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactThirdLine : moreCompactThirdLine)), "BT off");
display->drawString(0, textPositions[line++], "BT off");
}
// === Third & Fourth Rows: Node Identity ===
@ -619,28 +608,18 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
textWidth = display->getStringWidth(combinedName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(
nameX,
((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset,
combinedName);
display->drawString(nameX, ((rows == 4) ? textPositions[line++] : textPositions[line++]) + yOffset, combinedName);
} else {
// === LongName Centered ===
textWidth = display->getStringWidth(longName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
yOffset = (strcmp(shortnameble, "") == 0) ? 1 : 0;
if (yOffset == 1) {
yOffset = (SCREEN_WIDTH > 128) ? 0 : 7;
}
display->drawString(
nameX,
((rows == 4) ? compactThirdLine : ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine)) + yOffset,
longName);
yOffset = (rows == 4 && SCREEN_WIDTH <= 128) ? 7 : 0;
display->drawString(nameX, textPositions[line++] + yOffset, longName);
// === Fourth Row: ShortName Centered ===
// === ShortName Centered ===
textWidth = display->getStringWidth(shortnameble);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX,
((rows == 4) ? compactFourthLine : ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine)),
shortnameble);
display->drawString(nameX, textPositions[line++] + yOffset, shortnameble);
}
}
@ -886,6 +865,7 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = "GPS";
@ -905,10 +885,11 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
} else {
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
}
display->drawFastImage(x + 1, y, 8, 8, imgSatellite);
display->drawString(x + 11, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine);
int yOffset = (SCREEN_WIDTH > 128) ? 3 : 1;
display->drawXbm(x + 1, textPositions[line] + yOffset, imgSatellite_width, imgSatellite_height, imgSatellite);
display->drawString(x + 11, textPositions[line++], displayLine);
} else {
UIRenderer::drawGps(display, 0, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), gpsStatus);
UIRenderer::drawGps(display, 0, textPositions[line++], gpsStatus);
}
config.display.heading_bold = origBold;
@ -939,17 +920,17 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
} else {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
}
display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine), DisplayLineTwo);
display->drawString(x, textPositions[line++], DisplayLineTwo);
// === Third Row: Latitude ===
char latStr[32];
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactThirdLine : moreCompactThirdLine), latStr);
display->drawString(x, textPositions[line++], latStr);
// === Fourth Row: Longitude ===
char lonStr[32];
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactFourthLine : moreCompactFourthLine), lonStr);
display->drawString(x, textPositions[line++], lonStr);
// === Fifth Row: Date ===
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
@ -958,14 +939,14 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
char fullLine[40];
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine), fullLine);
display->drawString(0, textPositions[line++], fullLine);
}
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = compactFirstLine;
const int16_t topY = textPositions[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1); // nav row height
const int16_t usableHeight = bottomY - topY - 5;
@ -998,7 +979,7 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
} else {
// Portrait or square: put compass at the bottom and centered, scaled to fit available space
// For E-Ink screens, account for navigation bar at the bottom!
int yBelowContent = ((SCREEN_HEIGHT > 64) ? compactFifthLine : moreCompactFifthLine) + FONT_HEIGHT_SMALL + 2;
int yBelowContent = textPositions[5] + FONT_HEIGHT_SMALL + 2;
const int margin = 4;
int availableHeight =
#if defined(USE_EINK)

View File

@ -6,7 +6,12 @@ const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
const uint8_t imgSatellite[] PROGMEM = {0x70, 0x71, 0x22, 0xFA, 0xFA, 0x22, 0x71, 0x70};
#define imgSatellite_width 8
#define imgSatellite_height 8
const uint8_t imgSatellite[] PROGMEM = {
0b00000000, 0b00000000, 0b00000000, 0b00011000, 0b11011011, 0b11111111, 0b11011011, 0b00011000,
};
const uint8_t imgUSB[] PROGMEM = {0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C};
const uint8_t imgPower[] PROGMEM = {0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08,
0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22};

View File

@ -5,7 +5,7 @@ E-Ink display driver
- Manufacturer: DKE
- Size: 2.13 inch
- Resolution: 122px x 250px
- Flex connector marking: FPC-7528B
- Flex connector marking (not a unique identifier): FPC-7528B
Note: this is from an older generation of DKE panels, which still used Solomon Systech controller ICs.
DKE's website suggests that the latest DEPG0213BN displays may use Fitipower controllers instead.

View File

@ -5,7 +5,7 @@ E-Ink display driver
- Manufacturer: DKE
- Size: 2.9 inch
- Resolution: 128px x 296px
- Flex connector marking: FPC-7519 rev.b
- Flex connector marking (not a unique identifier): FPC-7519 rev.b
*/

View File

@ -5,7 +5,7 @@ E-Ink display driver
- Manufacturer: Goodisplay
- Size: 1.54 inch
- Resolution: 200px x 200px
- Flex connector marking: FPC-B001
- Flex connector marking (not a unique identifier): FPC-B001
*/

View File

@ -5,7 +5,9 @@ E-Ink display driver
- Manufacturer: Goodisplay
- Size: 2.13 inch
- Resolution: 250px x 122px
- Flex connector marking: FPC-A002
- Flex connector marking (not a unique identifier):
- FPC-A002
- FPC-A005 20.06.15 TRX
*/

View File

@ -5,7 +5,7 @@ E-Ink display driver
- Manufacturer: Holitech
- Size: 4.2 inch
- Resolution: 400px x 300px
- Flex connector marking: HINK-E042A07-FPC-A1
- Flex connector marking (not a unique identifier): HINK-E042A07-FPC-A1
- Silver sticker with QR code, marked: HE042A87
Note: as of Feb. 2025, these panels are used for "WeActStudio 4.2in B&W" display modules

View File

@ -5,7 +5,6 @@ E-Ink display driver
- Manufacturer: WISEVAST
- Size: 2.13 inch
- Resolution: 122px x 255px
- Flex connector marking: Soldering connector, no connector is needed
*/

View File

@ -5,7 +5,7 @@ E-Ink display driver
- Manufacturer: Wisevast
- Size: 2.13 inch
- Resolution: 122px x 250px
- Flex connector marking: HINK-E0213A162-FPC-A0 (Hidden, printed on back-side)
- Flex connector marking (not a unique identifier): HINK-E0213A162-FPC-A0 (Hidden, printed on back-side)
Note: this display uses an uncommon controller IC, Fitipower JD79656.
It is implemented as a "one-off", directly inheriting the EInk base class, unlike SSD16XX displays.

View File

@ -0,0 +1,59 @@
#include "./ZJY128296_029EAAMFGN.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void ZJY128296_029EAAMFGN::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 295 (vertical resolution 296px)
sendCommand(0x01);
sendData(0x27); // Number of gates (295, bits 0-7)
sendData(0x01); // Number of gates (295, bit 8)
sendData(0x00); // (Do not invert scanning order)
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void ZJY128296_029EAAMFGN::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x05); // Screen border should follow LUT1 waveform (actively drive pixels white)
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void ZJY128296_029EAAMFGN::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void ZJY128296_029EAAMFGN::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 300); // At least 300ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@ -0,0 +1,44 @@
/*
E-Ink display driver
- ZJY128296-029EAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 2.9 inch
- Resolution: 128px x 296px
- Flex connector label (not a unique identifier): FPC-A005 20.06.15 TRX
Note: as of Feb. 2025, these panels are used for "WeActStudio 2.9in B&W" display modules
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class ZJY128296_029EAAMFGN : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 128;
static constexpr uint32_t height = 296;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
ZJY128296_029EAAMFGN() : SSD16XX(width, height, supported) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@ -1524,7 +1524,6 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
// Mark the node's key as manually verified to indicate trustworthiness.
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
notifyObservers(true); // Force an update whether or not our node counts have changed
saveNodeDatabaseToDisk();
}
@ -1568,7 +1567,6 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
notifyObservers(true); // Force an update whether or not our node counts have changed
// We just changed something about a User,

View File

@ -1737,7 +1737,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
const char *label = graphics::emotes[j].label;
if (!label || !*label)
continue;
char *found = strstr(msg + pos, label);
const char *found = strstr(msg + pos, label);
if (found && (found - msg) < nextEmote) {
nextEmote = found - msg;
}
@ -1929,7 +1929,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
const char *label = graphics::emotes[j].label;
if (label[0] == 0)
continue;
char *found = strstr(msg + pos, label);
const char *found = strstr(msg + pos, label);
if (found && (found - msg) < nextEmote) {
nextEmote = found - msg;
}

View File

@ -22,6 +22,9 @@
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
// Sensors
@ -343,6 +346,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
display->clear();
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
int line = 1;
// === Set Title
const char *titleStr = (SCREEN_WIDTH > 128) ? "Environment" : "Env.";
@ -352,7 +356,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
// === Row spacing setup ===
const int rowHeight = FONT_HEIGHT_SMALL - 4;
int currentY = compactFirstLine;
int currentY = textPositions[line++];
// === Show "No Telemetry" if no data available ===
if (!lastMeasurementPacket) {

View File

@ -16,6 +16,9 @@
#include "sleep.h"
#include "target_specific.h"
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
@ -112,6 +115,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = (SCREEN_WIDTH > 128) ? "Power Telem." : "Power";
@ -121,7 +125,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
if (lastMeasurementPacket == nullptr) {
// In case of no valid packet, display "Power Telemetry", "No measurement"
display->drawString(x, compactFirstLine, "No measurement");
display->drawString(x, textPositions[line++], "No measurement");
return;
}
@ -132,7 +136,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
const meshtastic_Data &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
display->drawString(x, compactFirstLine, "Measurement Error");
display->drawString(x, textPositions[line++], "Measurement Error");
LOG_ERROR("Unable to decode last packet");
return;
}
@ -140,11 +144,11 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
// Display "Pow. From: ..."
char fromStr[64];
snprintf(fromStr, sizeof(fromStr), "Pow. From: %s (%us)", lastSender, agoSecs);
display->drawString(x, compactFirstLine, fromStr);
display->drawString(x, textPositions[line++], fromStr);
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
const auto &m = lastMeasurement.variant.power_metrics;
int lineY = compactSecondLine;
int lineY = textSecondLine;
auto drawLine = [&](const char *label, float voltage, float current) {
char lineStr[64];

View File

@ -140,7 +140,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, (compassDiam / 2));
// Compass bearing to waypoint
float bearingToOther =

View File

@ -115,12 +115,17 @@ int32_t PaxcounterModule::runOnce()
#if HAS_SCREEN
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, textThirdLine,
textFourthLine, textFifthLine, textSixthLine};
void PaxcounterModule::drawFrame(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 = "Pax";
@ -136,7 +141,7 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawStringf(display->getWidth() / 2 + x, compactFirstLine, buffer, "WiFi: %d\nBLE: %d\nUptime: %ds",
display->drawStringf(display->getWidth() / 2 + x, textPositions[line++], buffer, "WiFi: %d\nBLE: %d\nUptime: %ds",
count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000);
}
#endif // HAS_SCREEN

View File

@ -60,7 +60,7 @@ void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState
compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
}
display->drawCircle(compassX, compassY, compassDiam / 2);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180, (compassDiam / 2));
}
#endif