From 982d5d728a1a50fabd52ef186677f365da4515c9 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Thu, 10 Jul 2025 22:10:45 -0700 Subject: [PATCH 01/10] Move BRC address code to a .cpp file since it will be used from multiple places. --- src/graphics/BRC.cpp | 140 ++++++++++++++++++++++++++++++++++++++++++ src/graphics/BRC.h | 142 ++----------------------------------------- 2 files changed, 144 insertions(+), 138 deletions(-) create mode 100644 src/graphics/BRC.cpp diff --git a/src/graphics/BRC.cpp b/src/graphics/BRC.cpp new file mode 100644 index 00000000000..3928e3d334d --- /dev/null +++ b/src/graphics/BRC.cpp @@ -0,0 +1,140 @@ +#include "GPSStatus.h" +#include "gps/GeoCoord.h" +#include "graphics/Screen.h" +#include "BRC.h" + +using namespace meshtastic; + +const int32_t BRC_LATI = (40.786958 * 1e7); +const int32_t BRC_LONI = (-119.202994 * 1e7); +const double BRC_LATF = 40.786958; +const double BRC_LONF = -119.202994; +const double BRC_NOON = 1.5; +const double RAD_TO_HOUR = (6.0 / 3.14159); +const double METER_TO_FEET = 3.28084; +const double FEET_TO_METER = 1.0 / METER_TO_FEET; + +// Pre-calculated street data for performance +struct StreetInfo { + float center; + float width; + const char *name; +}; + +/* +# python code to generate the StreetInfo + +esp_center = 2500 +street_info = [ + # name, width, preceeding block depth + ('Esp', 40, 60), # block size is fake + ('A', 30, 400), + ('B', 30, 250), + ('C', 30, 250), + ('D', 30, 250), + ('E', 40, 250), + ('F', 30, 450), # E-F block is exra deep + ('G', 30, 250), + ('H', 30, 250), + ('I', 30, 250), + ('J', 30, 150), + ('K', 50, 150), +] + +street_center = esp_center - street_info[0][1] //2 - street_info[0][2] +last_center = esp_center +for (name, street_width, block_width) in street_info: + offset = (street_width + block_width) // 2 + street_center += street_width //2 + block_width + + dia = street_center * 2 + dist = street_center - last_center + + print(f"{{{street_center}, {offset}, \"{name}\"}},\t// +{dist}ft\tdia: {dia:,}ft") + + last_center = street_center + street_center += street_width //2 + +street_center += 50 # extra buffer after the edge of k to include walk-in camping parking +print(f"{{{street_center}, 0, nullptr}},\t// +{street_center-last_center}ft") +*/ + +static const StreetInfo streets[] = { + {2500, 50, "Esp"}, // +0ft dia: 5,000ft + {2935, 215, "A"}, // +435ft dia: 5,870ft + {3215, 140, "B"}, // +280ft dia: 6,430ft + {3495, 140, "C"}, // +280ft dia: 6,990ft + {3775, 140, "D"}, // +280ft dia: 7,550ft + {4060, 145, "E"}, // +285ft dia: 8,120ft + {4545, 240, "F"}, // +485ft dia: 9,090ft + {4825, 140, "G"}, // +280ft dia: 9,650ft + {5105, 140, "H"}, // +280ft dia: 10,210ft + {5385, 140, "I"}, // +280ft dia: 10,770ft + {5565, 90, "J"}, // +180ft dia: 11,130ft + {5755, 100, "K"}, // +190ft dia: 11,510ft + {5830, 0, nullptr}, // +75ft +}; + +BRCAddress::BRCAddress(int32_t lat, int32_t lon) +{ + bearing = GeoCoord::bearing(BRC_LATF, BRC_LONF, DegD(lat), DegD(lon)) * RAD_TO_HOUR; + bearing += 12.0 - BRC_NOON; + while (bearing > 12.0) { + bearing -= 12.0; + } + + // In imperial units because that is how golden spike data is provided. + distance = GeoCoord::latLongToMeter(BRC_LATF, BRC_LONF, DegD(lat), DegD(lon)) * METER_TO_FEET; +}; + +int BRCAddress::radial(char *buf, size_t len) +{ + uint8_t hour = (uint8_t)(bearing); + uint8_t minute = (uint8_t)((bearing - hour) * 60.0); + hour %= 12; + if (hour == 0) { + hour = 12; + } + return snprintf(buf, len, "%d:%02d", hour, minute); +}; + +int BRCAddress::annular(char *buf, size_t len) +{ + const char *unit = "m"; + float unitMultiplier = FEET_TO_METER; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + unitMultiplier = 1.0; + unit = "ft"; + } + + if (bearing > 1.75 && bearing < 10.25) { + const char *street = nullptr; + float dist = 0; + // Find the appropriate street based on distance + for (const auto &s : streets) { + if (distance > s.center - s.width) { + street = s.name; + dist = distance - s.center; + } else { + break; + } + } + if (street) { + return snprintf(buf, len, "%s %d%s", street, int(dist * unitMultiplier), unit); + } + } + + return snprintf(buf, len, "%d%s", int(distance * unitMultiplier), unit); +}; + +int BRCAddress::full(char *buf, size_t len) +{ + auto l = radial(buf, len - 4); + buf += l; + *(buf++) = ' '; + *(buf++) = '&'; + *(buf++) = ' '; + buf += annular(buf, len - l - 4); + buf[l] = 0; // always null terminated + return l; +}; diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h index 16daff2602f..84cb903a824 100644 --- a/src/graphics/BRC.h +++ b/src/graphics/BRC.h @@ -1,147 +1,13 @@ #pragma once -#include "GPSStatus.h" -#include "gps/GeoCoord.h" -#include "graphics/Screen.h" - -using namespace meshtastic; - -const int32_t BRC_LATI = (40.786958 * 1e7); -const int32_t BRC_LONI = (-119.202994 * 1e7); -const double BRC_LATF = 40.786958; -const double BRC_LONF = -119.202994; -const double BRC_NOON = 1.5; -const double RAD_TO_HOUR = (6.0 / 3.14159); -const double METER_TO_FEET = 3.28084; -const double FEET_TO_METER = 1.0 / METER_TO_FEET; - -// Pre-calculated street data for performance -struct StreetInfo { - float center; - float width; - const char *name; -}; - -/* -# python code to generate the StreetInfo - -esp_center = 2500 -street_info = [ - # name, width, preceeding block depth - ('Esp', 40, 60), # block size is fake - ('A', 30, 400), - ('B', 30, 250), - ('C', 30, 250), - ('D', 30, 250), - ('E', 40, 250), - ('F', 30, 450), # E-F block is exra deep - ('G', 30, 250), - ('H', 30, 250), - ('I', 30, 250), - ('J', 30, 150), - ('K', 50, 150), -] - -street_center = esp_center - street_info[0][1] //2 - street_info[0][2] -last_center = esp_center -for (name, street_width, block_width) in street_info: - offset = (street_width + block_width) // 2 - street_center += street_width //2 + block_width - - dia = street_center * 2 - dist = street_center - last_center - - print(f"{{{street_center}, {offset}, \"{name}\"}},\t// +{dist}ft\tdia: {dia:,}ft") - - last_center = street_center - street_center += street_width //2 - -street_center += 50 # extra buffer after the edge of k to include walk-in camping parking -print(f"{{{street_center}, 0, nullptr}},\t// +{street_center-last_center}ft") -*/ - -static const StreetInfo streets[] = { - {2500, 50, "Esp"}, // +0ft dia: 5,000ft - {2935, 215, "A"}, // +435ft dia: 5,870ft - {3215, 140, "B"}, // +280ft dia: 6,430ft - {3495, 140, "C"}, // +280ft dia: 6,990ft - {3775, 140, "D"}, // +280ft dia: 7,550ft - {4060, 145, "E"}, // +285ft dia: 8,120ft - {4545, 240, "F"}, // +485ft dia: 9,090ft - {4825, 140, "G"}, // +280ft dia: 9,650ft - {5105, 140, "H"}, // +280ft dia: 10,210ft - {5385, 140, "I"}, // +280ft dia: 10,770ft - {5565, 90, "J"}, // +180ft dia: 11,130ft - {5755, 100, "K"}, // +190ft dia: 11,510ft - {5830, 0, nullptr}, // +75ft -}; - class BRCAddress { public: - BRCAddress(int32_t lat, int32_t lon) - { - bearing = GeoCoord::bearing(BRC_LATF, BRC_LONF, DegD(lat), DegD(lon)) * RAD_TO_HOUR; - bearing += 12.0 - BRC_NOON; - while (bearing > 12.0) { - bearing -= 12.0; - } - - // In imperial units because that is how golden spike data is provided. - distance = GeoCoord::latLongToMeter(BRC_LATF, BRC_LONF, DegD(lat), DegD(lon)) * METER_TO_FEET; - }; - - int radial(char *buf, size_t len) - { - uint8_t hour = (uint8_t)(bearing); - uint8_t minute = (uint8_t)((bearing - hour) * 60.0); - hour %= 12; - if (hour == 0) { - hour = 12; - } - return snprintf(buf, len, "%d:%02d", hour, minute); - }; - - int annular(char *buf, size_t len) - { - const char *unit = "m"; - float unitMultiplier = FEET_TO_METER; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - unitMultiplier = 1.0; - unit = "ft"; - } - - if (bearing > 1.75 && bearing < 10.25) { - const char *street = nullptr; - float dist = 0; - // Find the appropriate street based on distance - for (const auto &s : streets) { - if (distance > s.center - s.width) { - street = s.name; - dist = distance - s.center; - } else { - break; - } - } - if (street) { - return snprintf(buf, len, "%s %d%s", street, int(dist * unitMultiplier), unit); - } - } - - return snprintf(buf, len, "%d%s", int(distance * unitMultiplier), unit); - }; + BRCAddress(int32_t lat, int32_t lon); - int full(char *buf, size_t len) - { - auto l = radial(buf, len - 4); - buf += l; - *(buf++) = ' '; - *(buf++) = '&'; - *(buf++) = ' '; - buf += annular(buf, len - l - 4); - buf[l] = 0; // always null terminated - return l; - }; + int radial(char *buf, size_t len); + int annular(char *buf, size_t len); + int full(char *buf, size_t len); private: float bearing; From 9e6f4bcf97558700504db29c8340de0da0d7f5f6 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Thu, 10 Jul 2025 22:10:45 -0700 Subject: [PATCH 02/10] Nodelist with addresses --- src/graphics/Screen.cpp | 2 +- src/graphics/draw/NodeListRenderer.cpp | 63 ++++++++++++++++++++++++-- src/graphics/draw/NodeListRenderer.h | 3 +- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 57ea64fa90d..934040d8850 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -919,7 +919,7 @@ void Screen::setFrames(FrameFocus focus) #endif #if HAS_GPS fsi.positions.nodelist_bearings = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; + normalFrames[numframes++] = graphics::NodeListRenderer::drawBRCListWithCompasses; indicatorIcons.push_back(icon_list); fsi.positions.gps = numframes; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index d8746fb69f7..519c4363e29 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -9,6 +9,7 @@ #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" +#include "graphics/BRC.h" #include "meshUtils.h" #include @@ -335,6 +336,34 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 } } + +void drawEntryBRC(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth) +{ + bool isLeftCol = (x < SCREEN_WIDTH / 2); + + // Adjust max text width depending on column and screen width + int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); + + const char *nodeName = getSafeNodeName(node); + auto nameWidth = display->getStringWidth("XXXXX"); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + auto xText = x + ((isHighResolution) ? 6 : 3); + display->drawStringMaxWidth(xText, y, nameMaxWidth, nodeName); + + char buf[32] = ""; + BRCAddress(node->position.latitude_i, node->position.longitude_i).full(buf, 32); + display->drawStringMaxWidth(xText + nameWidth, y, nameMaxWidth - nameWidth + 20, buf); + + if (node->is_favorite) { + if (isHighResolution) { + drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); + } else { + display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint); + } + } +} + void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading, double userLat, double userLon) { @@ -389,12 +418,12 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 // ============================= void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title, - EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon) + EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon, int totalColumns) { const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1; const int rowYOffset = FONT_HEIGHT_SMALL - 3; - int columnWidth = display->getWidth() / 2; + int columnWidth = display->getWidth() / totalColumns; display->clear(); @@ -408,7 +437,6 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int visibleNodeRows = totalRowsAvailable; - int totalColumns = 2; int startIndex = scrollIndex * visibleNodeRows * totalColumns; if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) { @@ -446,7 +474,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t } // Draw column separator - if (shownCount > 0) { + if (shownCount > 0 && totalColumns > 1) { const int firstNodeY = y + 3; drawColumnSeparator(display, x, firstNodeY, lastNodeY); } @@ -539,6 +567,31 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow, heading, lat, lon); } +void drawBRCListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + float heading = 0; + bool validHeading = false; + auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + double lat = DegD(ourNode->position.latitude_i); + double lon = DegD(ourNode->position.longitude_i); + + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { +#if HAS_GPS + if (screen->hasHeading()) { + heading = screen->getHeading(); // degrees + validHeading = true; + } else { + heading = screen->estimatedHeading(lat, lon); + validHeading = !isnan(heading); + } +#endif + + if (!validHeading) + return; + } + drawNodeListScreen(display, state, x, y, "Black Rock City", drawEntryBRC, drawCompassArrow, heading, lat, lon, 1); +} + /// Draw a series of fields in a column, wrapping to multiple columns if needed void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) { @@ -564,4 +617,4 @@ void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields } // namespace NodeListRenderer } // namespace graphics -#endif \ No newline at end of file +#endif diff --git a/src/graphics/draw/NodeListRenderer.h b/src/graphics/draw/NodeListRenderer.h index ea8df8bd9ce..32fb8144d2b 100644 --- a/src/graphics/draw/NodeListRenderer.h +++ b/src/graphics/draw/NodeListRenderer.h @@ -29,7 +29,7 @@ enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, // Main node list screen function void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title, EntryRenderer renderer, NodeExtrasRenderer extras = nullptr, float heading = 0, double lat = 0, - double lon = 0); + double lon = 0, int totalColumns = 2); // Entry renderers void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth); @@ -48,6 +48,7 @@ void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +void drawBRCListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); // Utility functions const char *getCurrentModeTitle(int screenWidth); From 9104070e9490f8488a12af4da50963607fd69a11 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 12:02:03 -0700 Subject: [PATCH 03/10] Burning Man icon for BRC page --- src/graphics/Screen.cpp | 13 +++++++++---- src/graphics/Screen.h | 1 + src/graphics/draw/NodeListRenderer.cpp | 2 +- src/graphics/draw/NodeListRenderer.h | 2 +- src/graphics/images.h | 12 ++++++++++++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 934040d8850..a86a6f0eb91 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -905,7 +905,7 @@ void Screen::setFrames(FrameFocus focus) // Show detailed node views only on E-Ink builds #ifdef USE_EINK - fsi.positions.nodelist_lastheard = numframes; + fsi.positions.nodelist_brc = numframes; normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; indicatorIcons.push_back(icon_nodes); @@ -916,11 +916,15 @@ void Screen::setFrames(FrameFocus focus) fsi.positions.nodelist_distance = numframes; normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; indicatorIcons.push_back(icon_distance); + + fsi.positions.nodelist_bearings = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; + indicatorIcons.push_back(icon_list); #endif #if HAS_GPS fsi.positions.nodelist_bearings = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawBRCListWithCompasses; - indicatorIcons.push_back(icon_list); + normalFrames[numframes++] = graphics::NodeListRenderer::drawBRCList; + indicatorIcons.push_back(icon_bm); fsi.positions.gps = numframes; normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; @@ -1386,7 +1390,8 @@ int Screen::handleInputEvent(const InputEvent *event) this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance || this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) { + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_brc) { menuHandler::nodeListMenu(); } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) { menuHandler::wifiBaseMenu(); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 265900131ab..9e31cb99285 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -662,6 +662,7 @@ class Screen : public concurrency::OSThread uint8_t nodelist_hopsignal = 255; uint8_t nodelist_distance = 255; uint8_t nodelist_bearings = 255; + uint8_t nodelist_brc = 255; uint8_t clock = 255; uint8_t firstFavorite = 255; uint8_t lastFavorite = 255; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 519c4363e29..98e5332e266 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -567,7 +567,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow, heading, lat, lon); } -void drawBRCListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +void drawBRCList(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { float heading = 0; bool validHeading = false; diff --git a/src/graphics/draw/NodeListRenderer.h b/src/graphics/draw/NodeListRenderer.h index 32fb8144d2b..ad4653cc257 100644 --- a/src/graphics/draw/NodeListRenderer.h +++ b/src/graphics/draw/NodeListRenderer.h @@ -48,7 +48,7 @@ void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); -void drawBRCListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +void drawBRCList(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); // Utility functions const char *getCurrentModeTitle(int screenWidth); diff --git a/src/graphics/images.h b/src/graphics/images.h index beef3a1b24c..1f053e56b5f 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -156,6 +156,18 @@ const uint8_t icon_list[] PROGMEM = { 0x82 // Row 7: #.....#. }; +// ➤ The Man Icon (8x8) +const uint8_t icon_bm[] PROGMEM = { + 0x42, // Row 0: .#....#. + 0x3C, // Row 1: ..####.. + 0x3C, // Row 2: ..####.. + 0x18, // Row 3: ...##... + 0x18, // Row 4: ...##... + 0x24, // Row 5: ..#..#.. + 0x24, // Row 6: ..#..#.. + 0x42 // Row 7: .#....#. +}; + // 📶 Signal Bars Icon (left to right, small to large with spacing) const uint8_t icon_signal[] PROGMEM = { 0b00000000, // ░░░░░░░ From 32be1d9f6c4cbb5096b3a21655f979deec6f9a3f Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 16:14:52 -0700 Subject: [PATCH 04/10] Draw last-seen time instead of compass This is likely more important - don't want to wander to a device that is off. --- src/graphics/BRC.cpp | 15 ++++++- src/graphics/BRC.h | 3 +- src/graphics/draw/NodeListRenderer.cpp | 57 ++++++++++++++++---------- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/graphics/BRC.cpp b/src/graphics/BRC.cpp index 3928e3d334d..d54ef4380e8 100644 --- a/src/graphics/BRC.cpp +++ b/src/graphics/BRC.cpp @@ -98,7 +98,7 @@ int BRCAddress::radial(char *buf, size_t len) return snprintf(buf, len, "%d:%02d", hour, minute); }; -int BRCAddress::annular(char *buf, size_t len) +int BRCAddress::annular(char *buf, size_t len, bool noUnit) { const char *unit = "m"; float unitMultiplier = FEET_TO_METER; @@ -106,6 +106,7 @@ int BRCAddress::annular(char *buf, size_t len) unitMultiplier = 1.0; unit = "ft"; } + if (noUnit) unit = ""; if (bearing > 1.75 && bearing < 10.25) { const char *street = nullptr; @@ -134,7 +135,17 @@ int BRCAddress::full(char *buf, size_t len) *(buf++) = ' '; *(buf++) = '&'; *(buf++) = ' '; - buf += annular(buf, len - l - 4); + buf += annular(buf, len - l - 4, false); + buf[l] = 0; // always null terminated + return l; +}; + +int BRCAddress::compact(char *buf, size_t len) +{ + auto l = radial(buf, len - 2); + buf += l; + *(buf++) = '&'; + buf += annular(buf, len - l - 2, true); buf[l] = 0; // always null terminated return l; }; diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h index 84cb903a824..764131fd057 100644 --- a/src/graphics/BRC.h +++ b/src/graphics/BRC.h @@ -6,8 +6,9 @@ class BRCAddress BRCAddress(int32_t lat, int32_t lon); int radial(char *buf, size_t len); - int annular(char *buf, size_t len); + int annular(char *buf, size_t len, bool noUnit); int full(char *buf, size_t len); + int compact(char *buf, size_t len); private: float bearing; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 98e5332e266..ae4bcd7d6dd 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -345,15 +345,16 @@ void drawEntryBRC(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); const char *nodeName = getSafeNodeName(node); - auto nameWidth = display->getStringWidth("XXXXX"); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); auto xText = x + ((isHighResolution) ? 6 : 3); - display->drawStringMaxWidth(xText, y, nameMaxWidth, nodeName); + display->drawString(xText, y, nodeName); + + char buf[14] = ""; + BRCAddress(node->position.latitude_i, node->position.longitude_i).compact(buf, 14); + auto nameWidth = display->getStringWidth("WWWW"); // Fixed width so they are aligned. + display->drawString(xText + nameWidth, y, buf); - char buf[32] = ""; - BRCAddress(node->position.latitude_i, node->position.longitude_i).full(buf, 32); - display->drawStringMaxWidth(xText + nameWidth, y, nameMaxWidth - nameWidth + 20, buf); if (node->is_favorite) { if (isHighResolution) { @@ -413,6 +414,34 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 */ } +void drawLastSeenExtra(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, + float /*myHeading*/, double /*userLat*/, double /*userLon*/) +{ + char timeStr[10]; + uint32_t seconds = sinceLastSeen(node); + if (seconds == 0 || seconds == UINT32_MAX) { + snprintf(timeStr, sizeof(timeStr), "?"); + } else { + uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24; + snprintf(timeStr, sizeof(timeStr), (days > 99 ? "?" : "%d%c"), + (days ? days + : hours ? hours + : minutes), + (days ? 'd' + : hours ? 'h' + : 'm')); + } + + bool isLeftCol = (x < SCREEN_WIDTH / 2); + int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7); + int rightEdge = x + columnWidth - timeOffset; + if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time + rightEdge -= 1; + //display->setTextAlignment(TEXT_ALIGN_RIGHT); + int textWidth = display->getStringWidth(timeStr); + display->drawString(rightEdge - textWidth, y, timeStr); +} + // ============================= // Main Screen Functions // ============================= @@ -569,27 +598,11 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, void drawBRCList(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { - float heading = 0; - bool validHeading = false; auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); double lat = DegD(ourNode->position.latitude_i); double lon = DegD(ourNode->position.longitude_i); - if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { -#if HAS_GPS - if (screen->hasHeading()) { - heading = screen->getHeading(); // degrees - validHeading = true; - } else { - heading = screen->estimatedHeading(lat, lon); - validHeading = !isnan(heading); - } -#endif - - if (!validHeading) - return; - } - drawNodeListScreen(display, state, x, y, "Black Rock City", drawEntryBRC, drawCompassArrow, heading, lat, lon, 1); + drawNodeListScreen(display, state, x, y, "BRC", drawEntryBRC, drawLastSeenExtra, 0, lat, lon, 1); } /// Draw a series of fields in a column, wrapping to multiple columns if needed From 74e786e5d711a962de71cf2c0bf8ff9327e7a4c5 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 16:16:53 -0700 Subject: [PATCH 05/10] Add the bearing list back to the cycled pages on oled devices --- src/graphics/draw/NodeListRenderer.cpp | 5 +++++ src/graphics/draw/NodeListRenderer.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index ae4bcd7d6dd..e1c67cc79de 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -88,6 +88,8 @@ const char *getCurrentModeTitle(int screenWidth) #endif case MODE_DISTANCE: return "Distance"; + case MODE_BEARING: + return "Bearing"; default: return "Nodes"; } @@ -310,6 +312,9 @@ void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 case MODE_DISTANCE: drawNodeDistance(display, node, x, y, columnWidth); break; + case MODE_BEARING: + drawEntryCompass(display, node, x, y, columnWidth); + break; default: break; } diff --git a/src/graphics/draw/NodeListRenderer.h b/src/graphics/draw/NodeListRenderer.h index ad4653cc257..bede8e73f38 100644 --- a/src/graphics/draw/NodeListRenderer.h +++ b/src/graphics/draw/NodeListRenderer.h @@ -24,7 +24,7 @@ typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double); // Node list mode enumeration -enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_COUNT = 3 }; +enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_BEARING = 3, MODE_COUNT = 4 }; // Main node list screen function void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title, From bec732046a8ef409ec7fe11f6c39167bf3ddd344 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 17:01:41 -0700 Subject: [PATCH 06/10] Add bearings screen into the carousel of rotated screens --- src/graphics/draw/NodeListRenderer.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index e1c67cc79de..62954a764af 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -544,7 +544,31 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, // Render screen based on currentMode const char *title = getCurrentModeTitle(display->getWidth()); - drawNodeListScreen(display, state, x, y, title, drawEntryDynamic); + + if (currentMode == MODE_BEARING) { + float heading = 0; + bool validHeading = false; + auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + double lat = DegD(ourNode->position.latitude_i); + double lon = DegD(ourNode->position.longitude_i); + + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { + if (screen->hasHeading()) { + heading = screen->getHeading(); // degrees + validHeading = true; + } else { + heading = screen->estimatedHeading(lat, lon); + validHeading = !isnan(heading); + } + if (!validHeading) { + lastRenderedMode = MODE_COUNT; + return; + } + } + drawNodeListScreen(display, state, x, y, title, drawEntryCompass, drawCompassArrow, heading, lat, lon); + } else { + drawNodeListScreen(display, state, x, y, title, drawEntryDynamic); + } // Track the last mode to avoid reinitializing modeStartTime lastRenderedMode = currentMode; From 803818935eae145e4ba1471f4415499b60a5e295 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 18:36:28 -0700 Subject: [PATCH 07/10] trunk fmt --- src/graphics/BRC.cpp | 5 +++-- src/graphics/draw/NodeListRenderer.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/graphics/BRC.cpp b/src/graphics/BRC.cpp index d54ef4380e8..a65a8ee7cf2 100644 --- a/src/graphics/BRC.cpp +++ b/src/graphics/BRC.cpp @@ -1,7 +1,7 @@ +#include "BRC.h" #include "GPSStatus.h" #include "gps/GeoCoord.h" #include "graphics/Screen.h" -#include "BRC.h" using namespace meshtastic; @@ -106,7 +106,8 @@ int BRCAddress::annular(char *buf, size_t len, bool noUnit) unitMultiplier = 1.0; unit = "ft"; } - if (noUnit) unit = ""; + if (noUnit) + unit = ""; if (bearing > 1.75 && bearing < 10.25) { const char *street = nullptr; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 62954a764af..991ebc29d1f 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -6,10 +6,10 @@ #include "UIRenderer.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" // for getTime() function +#include "graphics/BRC.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" -#include "graphics/BRC.h" #include "meshUtils.h" #include @@ -341,7 +341,6 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 } } - void drawEntryBRC(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth) { bool isLeftCol = (x < SCREEN_WIDTH / 2); @@ -360,7 +359,6 @@ void drawEntryBRC(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x auto nameWidth = display->getStringWidth("WWWW"); // Fixed width so they are aligned. display->drawString(xText + nameWidth, y, buf); - if (node->is_favorite) { if (isHighResolution) { drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display); @@ -442,7 +440,7 @@ void drawLastSeenExtra(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int1 int rightEdge = x + columnWidth - timeOffset; if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time rightEdge -= 1; - //display->setTextAlignment(TEXT_ALIGN_RIGHT); + // display->setTextAlignment(TEXT_ALIGN_RIGHT); int textWidth = display->getStringWidth(timeStr); display->drawString(rightEdge - textWidth, y, timeStr); } @@ -452,7 +450,8 @@ void drawLastSeenExtra(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int1 // ============================= void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title, - EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon, int totalColumns) + EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon, + int totalColumns) { const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1; const int rowYOffset = FONT_HEIGHT_SMALL - 3; From cc5ca1682ff0e1339d774eeb449d3b419fd3c46c Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 12 Jul 2025 20:11:25 -0700 Subject: [PATCH 08/10] fix build errors caused by trunk fmt --- src/graphics/BRC.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h index 764131fd057..9dc85e15c19 100644 --- a/src/graphics/BRC.h +++ b/src/graphics/BRC.h @@ -1,5 +1,10 @@ #pragma once +// For size_t/int32_t types on some platforms. +#include +// For size_t +#include + class BRCAddress { public: From 8aaedb81ddd08ee030f4a59853c84bf2bb46af50 Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Thu, 17 Jul 2025 23:07:37 -0700 Subject: [PATCH 09/10] correct swapped screen positions for BRC vs bearings display --- src/graphics/Screen.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index a86a6f0eb91..b129b3d457d 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -905,7 +905,7 @@ void Screen::setFrames(FrameFocus focus) // Show detailed node views only on E-Ink builds #ifdef USE_EINK - fsi.positions.nodelist_brc = numframes; + fsi.positions.nodelist_bearings = numframes; normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; indicatorIcons.push_back(icon_nodes); @@ -922,7 +922,7 @@ void Screen::setFrames(FrameFocus focus) indicatorIcons.push_back(icon_list); #endif #if HAS_GPS - fsi.positions.nodelist_bearings = numframes; + fsi.positions.nodelist_brc = numframes; normalFrames[numframes++] = graphics::NodeListRenderer::drawBRCList; indicatorIcons.push_back(icon_bm); @@ -1452,4 +1452,4 @@ bool shouldWakeOnReceivedMessage() return false; } return true; -} \ No newline at end of file +} From 5ef85475eea3ea6363f8345b161eeace2674b6dd Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Thu, 17 Jul 2025 23:18:56 -0700 Subject: [PATCH 10/10] don't show playa position if unknown --- src/graphics/draw/NodeListRenderer.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 991ebc29d1f..722fa143dc1 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -354,10 +354,12 @@ void drawEntryBRC(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x auto xText = x + ((isHighResolution) ? 6 : 3); display->drawString(xText, y, nodeName); - char buf[14] = ""; - BRCAddress(node->position.latitude_i, node->position.longitude_i).compact(buf, 14); - auto nameWidth = display->getStringWidth("WWWW"); // Fixed width so they are aligned. - display->drawString(xText + nameWidth, y, buf); + if (nodeDB->hasValidPosition(node)) { + char buf[14] = ""; + BRCAddress(node->position.latitude_i, node->position.longitude_i).compact(buf, 14); + auto nameWidth = display->getStringWidth("WWWW") - 2; // Fixed width so they are aligned. + display->drawString(xText + nameWidth, y, buf); + } if (node->is_favorite) { if (isHighResolution) { @@ -626,11 +628,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, void drawBRCList(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { - auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - double lat = DegD(ourNode->position.latitude_i); - double lon = DegD(ourNode->position.longitude_i); - - drawNodeListScreen(display, state, x, y, "BRC", drawEntryBRC, drawLastSeenExtra, 0, lat, lon, 1); + drawNodeListScreen(display, state, x, y, "BRC", drawEntryBRC, drawLastSeenExtra, 0, 0, 0, 1); } /// Draw a series of fields in a column, wrapping to multiple columns if needed