Last active
July 3, 2025 00:59
-
-
Save exadeci/9ce0dd6e35407e6edda33cf699fcfb0e to your computer and use it in GitHub Desktop.
LilyGo T-Echo Meshtastic patches for burning man street addresses (2.7 re-enables touch button as backlight button)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h | |
new file mode 100644 | |
index 000000000..9b13ea653 | |
--- /dev/null | |
+++ b/src/graphics/BRC.h | |
@@ -0,0 +1,123 @@ | |
+#pragma once | |
+ | |
+#include "GPSStatus.h" | |
+#include "gps/GeoCoord.h" | |
+#include "graphics/Screen.h" | |
+#include "configuration.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; | |
+ | |
+// Pre-calculated street data for performance | |
+struct StreetInfo { | |
+ float center; | |
+ float width; | |
+ const char* name; | |
+}; | |
+ | |
+static const StreetInfo streets[] = { | |
+ {2500, 50, "Esp"}, | |
+ {2940, 220, "A"}, | |
+ {2940+290, 145, "B"}, | |
+ {2940+290*2, 145, "C"}, | |
+ {2940+290*3, 145, "D"}, | |
+ {2940+290*4, 145, "E"}, | |
+ {2940+290*4+490, 245, "F"}, | |
+ {2940+290*5+490, 145, "G"}, | |
+ {2940+290*6+490, 145, "H"}, | |
+ {2940+290*7+490, 145, "I"}, | |
+ {2940+290*7+490+190, 95, "J"}, | |
+ {2940+290*7+490+190*2, 95, "K"}, | |
+ {2940+290*7+490+190*2+75, 0, nullptr} | |
+}; | |
+ | |
+static char* BRCAddress(int32_t lat, int32_t lon) | |
+{ | |
+ thread_local static char addrStr[20]; | |
+ | |
+ // Cache previous calculations to avoid expensive trigonometric operations | |
+ // when position hasn't changed significantly | |
+ thread_local static int32_t cachedLat = 0; | |
+ thread_local static int32_t cachedLon = 0; | |
+ thread_local static float cachedBearing = 0.0f; | |
+ thread_local static float cachedDistance = 0.0f; | |
+ thread_local static bool cacheValid = false; | |
+ | |
+ // Check if we can use cached values | |
+ // GPS int32_t format: 1e-7 degrees, so 1 unit ≈ 1.11 cm at equator | |
+ // 25 units ≈ 28 cm, 90 units ≈ 1 meter, 900 units ≈ 10 meters | |
+ const int32_t CACHE_THRESHOLD = 90; // ~1 meter - good balance of precision vs performance | |
+ bool positionChanged = !cacheValid || | |
+ abs(lat - cachedLat) > CACHE_THRESHOLD || | |
+ abs(lon - cachedLon) > CACHE_THRESHOLD; | |
+ | |
+ if (positionChanged) { | |
+ // Update cache with new calculations | |
+ double latD = DegD(lat); | |
+ double lonD = DegD(lon); | |
+ | |
+ cachedBearing = GeoCoord::bearing(BRC_LATF, BRC_LONF, latD, lonD) * RAD_TO_HOUR; | |
+ cachedDistance = GeoCoord::latLongToMeter(BRC_LATF, BRC_LONF, latD, lonD); | |
+ | |
+ cachedLat = lat; | |
+ cachedLon = lon; | |
+ cacheValid = true; | |
+ } | |
+ | |
+ // Use cached values for calculations | |
+ float bearingToMan = cachedBearing; | |
+ bearingToMan += 12.0 - BRC_NOON; | |
+ while (bearingToMan > 12.0) {bearingToMan -= 12.0;} | |
+ uint8_t hour = (uint8_t)(bearingToMan); | |
+ uint8_t minute = (uint8_t)((bearingToMan - hour) * 60.0); | |
+ hour %= 12; | |
+ if (hour == 0) {hour = 12;} | |
+ | |
+ float d = cachedDistance; | |
+ | |
+ // Check unit preference once and set conversion factor and label | |
+ const bool useImperial = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL); | |
+ const float conversionFactor = useImperial ? METER_TO_FEET : 1.0f; | |
+ const char* unitLabel = useImperial ? "ft" : "m"; | |
+ | |
+ if (bearingToMan > 1.75 && bearingToMan < 10.25) { | |
+ const char* street = nullptr; | |
+ float dist = 0; | |
+ // Find the appropriate street based on distance (using original meter values for street matching) | |
+ for (const auto& s : streets) { | |
+ if (d > s.center - s.width) { | |
+ street = s.name; | |
+ dist = d - s.center; | |
+ } else { | |
+ break; | |
+ } | |
+ } | |
+ if (street) { | |
+ snprintf(addrStr, sizeof(addrStr), "%d:%02d & %s %d%s", hour, minute, street, int(dist * conversionFactor), unitLabel); | |
+ return addrStr; | |
+ } | |
+ | |
+ } | |
+ | |
+ snprintf(addrStr, sizeof(addrStr), "%d:%02d & %d%s", hour, minute, (uint32_t)(d * conversionFactor), unitLabel); | |
+ return addrStr; | |
+} | |
+ | |
+ | |
+static void drawBRCAddress(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) | |
+{ | |
+ // Only draw if we have a valid GPS position | |
+ if ((!gps->getIsConnected() || !gps->getHasLock()) && !config.position.fixed_position) { | |
+ return; // Skip drawing - no valid position available | |
+ } | |
+ | |
+ auto displayLine = BRCAddress(int32_t(gps->getLatitude()), int32_t(gps->getLongitude())); | |
+ display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); | |
+} | |
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp | |
index 4ce073c9b..18498f404 100644 | |
--- a/src/graphics/draw/UIRenderer.cpp | |
+++ b/src/graphics/draw/UIRenderer.cpp | |
@@ -12,6 +12,7 @@ | |
#include "graphics/ScreenFonts.h" | |
#include "graphics/SharedUIDisplay.h" | |
#include "graphics/images.h" | |
+#include "graphics/BRC.h" | |
#include "main.h" | |
#include "target_specific.h" | |
#include <OLEDDisplay.h> | |
@@ -641,6 +642,16 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta | |
line += 1; | |
+ // === BRC Address (Burning Man) === | |
+#if HAS_GPS | |
+ if ((gpsStatus->getIsConnected() && gpsStatus->getHasLock()) || config.position.fixed_position) { | |
+ auto brcDisplayLine = BRCAddress(int32_t(gpsStatus->getLatitude()), int32_t(gpsStatus->getLongitude())); | |
+ int brcTextWidth = display->getStringWidth(brcDisplayLine); | |
+ int brcX = (SCREEN_WIDTH - brcTextWidth) / 2; | |
+ display->drawString(brcX, getTextPositions(display)[line++], brcDisplayLine); | |
+ } | |
+#endif | |
+ | |
// === Fourth & Fifth Rows: Node Identity === | |
int textWidth = 0; | |
int nameX = 0; | |
@@ -841,7 +852,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED | |
display->setFont(FONT_MEDIUM); | |
display->setTextAlignment(TEXT_ALIGN_LEFT); | |
- const char *title = "meshtastic.org"; | |
+ const char *title = "Peachy 7:30 & B"; | |
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); | |
display->setFont(FONT_SMALL); | |
diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp | |
index ad667f003..a873cd41f 100644 | |
--- a/src/input/ButtonThread.cpp | |
+++ b/src/input/ButtonThread.cpp | |
@@ -14,6 +14,7 @@ | |
#include "modules/ExternalNotificationModule.h" | |
#include "power.h" | |
#include "sleep.h" | |
+#include "PowerFSM.h" | |
#ifdef ARCH_PORTDUINO | |
#include "platform/portduino/PortduinoGlue.h" | |
#endif | |
@@ -161,6 +162,19 @@ int32_t ButtonThread::runOnce() | |
leadUpPlayed = false; | |
leadUpSequenceActive = false; | |
resetLeadUpSequence(); | |
+ | |
+ #ifdef TTGO_T_ECHO | |
+ // Special handling for T-Echo touch button: turn off backlight when released after short press | |
+ if (_touchQuirk && _pinNum == PIN_BUTTON_TOUCH) { | |
+ // Only turn off if it was a short press (not a long press that's being handled elsewhere) | |
+ uint32_t pressDuration = millis() - buttonPressStartTime; | |
+ if (pressDuration < _longPressTime) { | |
+ #ifdef PIN_EINK_EN | |
+ digitalWrite(PIN_EINK_EN, LOW); // Turn off backlight | |
+ #endif | |
+ } | |
+ } | |
+ #endif | |
} | |
buttonWasPressed = buttonCurrentlyPressed; | |
@@ -174,6 +188,19 @@ int32_t ButtonThread::runOnce() | |
evt.touchY = 0; | |
switch (btnEvent) { | |
case BUTTON_EVENT_PRESSED: { | |
+ #ifdef TTGO_T_ECHO | |
+ // Special handling for T-Echo touch button: turn on backlight immediately when touched | |
+ if (_touchQuirk && _pinNum == PIN_BUTTON_TOUCH) { | |
+ #ifdef PIN_EINK_EN | |
+ digitalWrite(PIN_EINK_EN, HIGH); // Turn on backlight immediately | |
+ // Wake screen when backlight turns on | |
+ if (powerFSM.getState() == &stateDARK) { | |
+ powerFSM.trigger(EVENT_INPUT); | |
+ } | |
+ #endif | |
+ } | |
+ #endif | |
+ | |
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event) | |
evt.inputEvent = _singlePress; | |
// evt.kbchar = _singlePress; // todo: fix this. Some events are kb characters rather than event types | |
@@ -191,6 +218,22 @@ int32_t ButtonThread::runOnce() | |
if (_touchQuirk && RadioLibInterface::instance && RadioLibInterface::instance->isSending()) | |
break; | |
+ #ifdef TTGO_T_ECHO | |
+ // Special handling for T-Echo touch button: turn on backlight while held | |
+ if (_touchQuirk && _pinNum == PIN_BUTTON_TOUCH) { | |
+ #ifdef PIN_EINK_EN | |
+ digitalWrite(PIN_EINK_EN, HIGH); // Turn on backlight | |
+ // Wake screen when backlight turns on | |
+ if (powerFSM.getState() == &stateDARK) { | |
+ powerFSM.trigger(EVENT_INPUT); | |
+ } | |
+ #endif | |
+ // Don't send the normal long press event for touch button backlight control | |
+ waitingForLongPress = false; | |
+ break; | |
+ } | |
+ #endif | |
+ | |
// Check if this is part of a short-press + long-press combination | |
if (_shortLong != INPUT_BROKER_NONE && waitingForLongPress && | |
(millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) { | |
@@ -254,6 +297,19 @@ int32_t ButtonThread::runOnce() | |
case BUTTON_EVENT_LONG_RELEASED: { | |
LOG_INFO("LONG PRESS RELEASE"); | |
+ | |
+ #ifdef TTGO_T_ECHO | |
+ // Special handling for T-Echo touch button: turn off backlight when released | |
+ if (_touchQuirk && _pinNum == PIN_BUTTON_TOUCH) { | |
+ #ifdef PIN_EINK_EN | |
+ digitalWrite(PIN_EINK_EN, LOW); // Turn off backlight | |
+ #endif | |
+ // Reset combination tracking | |
+ waitingForLongPress = false; | |
+ break; | |
+ } | |
+ #endif | |
+ | |
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && | |
(millis() - buttonPressStartTime) >= _longLongPressTime) { | |
evt.inputEvent = _longLongPress; | |
diff --git a/src/main.cpp b/src/main.cpp | |
index ddc1df28c..b78bcebf0 100644 | |
--- a/src/main.cpp | |
+++ b/src/main.cpp | |
@@ -961,6 +961,7 @@ void setup() | |
}; | |
touchConfig.singlePress = INPUT_BROKER_NONE; | |
touchConfig.longPress = INPUT_BROKER_BACK; | |
+ touchConfig.touchQuirk = true; // Enable T-Echo touch quirk protection | |
TouchButtonThread->initButton(touchConfig); | |
#endif | |
diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h | |
index 4f3a53ebf..d39390dc0 100644 | |
--- a/variants/t-echo/variant.h | |
+++ b/variants/t-echo/variant.h | |
@@ -68,6 +68,10 @@ extern "C" { | |
#define BUTTON_TOUCH_ACTIVE_LOW true | |
#define BUTTON_TOUCH_ACTIVE_PULLUP true | |
+// Main user button for BaseUI (use the physical side button) | |
+#define BUTTON_PIN PIN_BUTTON1 | |
+#define BUTTON_NEED_PULLUP | |
+ | |
#define BUTTON_CLICK_MS 400 | |
#define BUTTON_TOUCH_MS 200 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/graphics/BRC.h b/src/graphics/BRC.h | |
new file mode 100644 | |
index 000000000..9b13ea653 | |
--- /dev/null | |
+++ b/src/graphics/BRC.h | |
@@ -0,0 +1,123 @@ | |
+#pragma once | |
+ | |
+#include "GPSStatus.h" | |
+#include "gps/GeoCoord.h" | |
+#include "graphics/Screen.h" | |
+#include "configuration.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; | |
+ | |
+// Pre-calculated street data for performance | |
+struct StreetInfo { | |
+ float center; | |
+ float width; | |
+ const char* name; | |
+}; | |
+ | |
+static const StreetInfo streets[] = { | |
+ {2500, 50, "Esp"}, | |
+ {2940, 220, "A"}, | |
+ {2940+290, 145, "B"}, | |
+ {2940+290*2, 145, "C"}, | |
+ {2940+290*3, 145, "D"}, | |
+ {2940+290*4, 145, "E"}, | |
+ {2940+290*4+490, 245, "F"}, | |
+ {2940+290*5+490, 145, "G"}, | |
+ {2940+290*6+490, 145, "H"}, | |
+ {2940+290*7+490, 145, "I"}, | |
+ {2940+290*7+490+190, 95, "J"}, | |
+ {2940+290*7+490+190*2, 95, "K"}, | |
+ {2940+290*7+490+190*2+75, 0, nullptr} | |
+}; | |
+ | |
+static char* BRCAddress(int32_t lat, int32_t lon) | |
+{ | |
+ thread_local static char addrStr[20]; | |
+ | |
+ // Cache previous calculations to avoid expensive trigonometric operations | |
+ // when position hasn't changed significantly | |
+ thread_local static int32_t cachedLat = 0; | |
+ thread_local static int32_t cachedLon = 0; | |
+ thread_local static float cachedBearing = 0.0f; | |
+ thread_local static float cachedDistance = 0.0f; | |
+ thread_local static bool cacheValid = false; | |
+ | |
+ // Check if we can use cached values | |
+ // GPS int32_t format: 1e-7 degrees, so 1 unit ≈ 1.11 cm at equator | |
+ // 25 units ≈ 28 cm, 90 units ≈ 1 meter, 900 units ≈ 10 meters | |
+ const int32_t CACHE_THRESHOLD = 90; // ~1 meter - good balance of precision vs performance | |
+ bool positionChanged = !cacheValid || | |
+ abs(lat - cachedLat) > CACHE_THRESHOLD || | |
+ abs(lon - cachedLon) > CACHE_THRESHOLD; | |
+ | |
+ if (positionChanged) { | |
+ // Update cache with new calculations | |
+ double latD = DegD(lat); | |
+ double lonD = DegD(lon); | |
+ | |
+ cachedBearing = GeoCoord::bearing(BRC_LATF, BRC_LONF, latD, lonD) * RAD_TO_HOUR; | |
+ cachedDistance = GeoCoord::latLongToMeter(BRC_LATF, BRC_LONF, latD, lonD); | |
+ | |
+ cachedLat = lat; | |
+ cachedLon = lon; | |
+ cacheValid = true; | |
+ } | |
+ | |
+ // Use cached values for calculations | |
+ float bearingToMan = cachedBearing; | |
+ bearingToMan += 12.0 - BRC_NOON; | |
+ while (bearingToMan > 12.0) {bearingToMan -= 12.0;} | |
+ uint8_t hour = (uint8_t)(bearingToMan); | |
+ uint8_t minute = (uint8_t)((bearingToMan - hour) * 60.0); | |
+ hour %= 12; | |
+ if (hour == 0) {hour = 12;} | |
+ | |
+ float d = cachedDistance; | |
+ | |
+ // Check unit preference once and set conversion factor and label | |
+ const bool useImperial = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL); | |
+ const float conversionFactor = useImperial ? METER_TO_FEET : 1.0f; | |
+ const char* unitLabel = useImperial ? "ft" : "m"; | |
+ | |
+ if (bearingToMan > 1.75 && bearingToMan < 10.25) { | |
+ const char* street = nullptr; | |
+ float dist = 0; | |
+ // Find the appropriate street based on distance (using original meter values for street matching) | |
+ for (const auto& s : streets) { | |
+ if (d > s.center - s.width) { | |
+ street = s.name; | |
+ dist = d - s.center; | |
+ } else { | |
+ break; | |
+ } | |
+ } | |
+ if (street) { | |
+ snprintf(addrStr, sizeof(addrStr), "%d:%02d & %s %d%s", hour, minute, street, int(dist * conversionFactor), unitLabel); | |
+ return addrStr; | |
+ } | |
+ | |
+ } | |
+ | |
+ snprintf(addrStr, sizeof(addrStr), "%d:%02d & %d%s", hour, minute, (uint32_t)(d * conversionFactor), unitLabel); | |
+ return addrStr; | |
+} | |
+ | |
+ | |
+static void drawBRCAddress(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) | |
+{ | |
+ // Only draw if we have a valid GPS position | |
+ if ((!gps->getIsConnected() || !gps->getHasLock()) && !config.position.fixed_position) { | |
+ return; // Skip drawing - no valid position available | |
+ } | |
+ | |
+ auto displayLine = BRCAddress(int32_t(gps->getLatitude()), int32_t(gps->getLongitude())); | |
+ display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); | |
+} | |
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp | |
index 0c18f3287..9e330b12f 100644 | |
--- a/src/graphics/Screen.cpp | |
+++ b/src/graphics/Screen.cpp | |
@@ -50,6 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. | |
#include "modules/WaypointModule.h" | |
#include "sleep.h" | |
#include "target_specific.h" | |
+#include "graphics/BRC.h" | |
#if HAS_WIFI && !defined(ARCH_PORTDUINO) | |
#include "mesh/wifi/WiFiAPClient.h" | |
@@ -1343,8 +1344,8 @@ void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) | |
// -- if using time delta instead -- | |
else if (agoSecs < 120 * 60) // last 2 hrs | |
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60); | |
- // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data. | |
- else if ((agoSecs / 60 / 60) < (hours_in_month * 6)) | |
+ // Only show hours ago if it's been less than half a month. Otherwise, we may have bad data. | |
+ else if ((agoSecs / 60 / 60) < (hours_in_month / 2)) | |
snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60); | |
else | |
snprintf(timeStr, maxLength, "unknown age"); | |
@@ -1451,7 +1452,20 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ | |
strncpy(distStr, "? km ?°", sizeof(distStr)); | |
} | |
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); | |
- const char *fields[] = {username, lastStr, signalStr, distStr, NULL}; | |
+ const char *fields[] = {username, lastStr, signalStr, distStr, NULL, NULL}; | |
+ | |
+ | |
+ if (node && nodeDB->hasValidPosition(node)) { | |
+ meshtastic_PositionLite &p = node->position; | |
+ // Overwrite the signal strength with the BRC position if screen is small | |
+ if ((SCREEN_HEIGHT / FONT_HEIGHT_SMALL) < 6) { | |
+ fields[2] = BRCAddress(p.latitude_i, p.longitude_i); | |
+ } else { | |
+ fields[4] = fields[3]; | |
+ fields[3] = BRCAddress(p.latitude_i, p.longitude_i); | |
+ } | |
+ } | |
+ | |
int16_t compassX = 0, compassY = 0; | |
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT); | |
@@ -2678,6 +2692,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat | |
// hms += tz.tz_dsttime * SEC_PER_HOUR; | |
// hms -= tz.tz_minuteswest * SEC_PER_MIN; | |
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY) | |
+ hms += SEC_PER_DAY - 7 * SEC_PER_HOUR; // pacific time | |
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; | |
// Tear apart hms into h:m:s | |
@@ -2718,10 +2733,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat | |
#if HAS_GPS | |
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { | |
// Line 3 | |
- if (config.display.gps_format != | |
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude | |
- drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); | |
- | |
+ drawBRCAddress(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); | |
// Line 4 | |
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); | |
} else { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment