From 6a74f06d57b360350aa58ce70698246dd16824a0 Mon Sep 17 00:00:00 2001 From: tschundler Date: Fri, 11 Jul 2025 10:45:39 -0700 Subject: [PATCH] Initial Burning Man 2025 coordinates support. (#7252) * BRC 2024 Golden Spike Data * Configurable units, based on pull #1 Not exactly the same change; the interim number must be feet for use with golden spike data as-is. * update meshtastic 2.6 with BRC 2025 Golden Spike * add performance improvements * fixed roads + script to make roads * refactored string generation to allow getting hour & street separately for multi-line display * Display BRC location on self location page * formatting fixes via `trunk fmt` * Show BRC location of favorite node --------- Co-authored-by: Gaetan Gueraud --- src/graphics/BRC.h | 149 +++++++++++++++++++++++++++++++ src/graphics/draw/UIRenderer.cpp | 46 +++++----- 2 files changed, 169 insertions(+), 26 deletions(-) create mode 100644 src/graphics/BRC.h diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h new file mode 100644 index 000000000..16daff260 --- /dev/null +++ b/src/graphics/BRC.h @@ -0,0 +1,149 @@ +#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); + }; + + 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; + }; + + private: + float bearing; + float distance; +}; diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 71d92616f..f19c9f72b 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -8,6 +8,7 @@ #include "airtime.h" #include "configuration.h" #include "gps/GeoCoord.h" +#include "graphics/BRC.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" @@ -308,23 +309,12 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st display->drawString(x, getTextPositions(display)[line++], seenStr); } - // === 4. Uptime (only show if metric is present) === - char uptimeStr[32] = ""; - if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { - uint32_t uptime = node->device_metrics.uptime_seconds; - uint32_t days = uptime / 86400; - uint32_t hours = (uptime % 86400) / 3600; - uint32_t mins = (uptime % 3600) / 60; - // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" - if (days) - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours); - else if (hours) - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins); - else - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins); - } - if (uptimeStr[0] && line < 5) { - display->drawString(x, getTextPositions(display)[line++], uptimeStr); + // === 4. Burning Man location (only show if their position is known) === + char brcStr[32] = ""; + if (nodeDB->hasValidPosition(node) && line < 5) { + brcStr[0] = 32; // Space before the address to align with other rows. + BRCAddress(node->position.latitude_i, node->position.longitude_i).full(brcStr + 1, sizeof(brcStr) - 1); + display->drawString(x, getTextPositions(display)[line++], brcStr); } // === 5. Distance (only if both nodes have GPS position) === @@ -921,7 +911,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } // If GPS is off, no need to display these parts - if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { + if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0 && gpsStatus->getHasLock()) { // === Second Row: Date === uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); @@ -942,14 +932,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7); display->drawString(x, getTextPositions(display)[line++], lonStr); - // === Fifth Row: Altitude === - char DisplayLineTwo[32] = {0}; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET); - } else { - snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude()); + // === Fifth Row: Burning Man! === + char addrStr[32]; + BRCAddress(geoCoord.getLatitude(), geoCoord.getLongitude()).full(addrStr, sizeof(addrStr)); + display->drawString(x, getTextPositions(display)[line++], addrStr); + } else { + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + // from cell phone? + if (nodeDB->hasValidPosition(ourNode)) { + char addrStr[32]; + BRCAddress(ourNode->position.latitude_i, ourNode->position.longitude_i).full(addrStr, sizeof(addrStr)); + display->drawString(x, getTextPositions(display)[line++], addrStr); } - display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo); } // === Draw Compass if heading is valid === @@ -1225,4 +1219,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi } // namespace graphics -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN