Files
meshtastic-firmware/src/modules/WaypointModule.cpp
T
Ben MeadorsGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Copilot Autofix powered by AI
94bb21ecc7 2.8: NodeDB shrink, decoupling, and restructuring (#10413)
* 2.8: NodeDB refactor to decouple satellite entries and decrease size

* Regen

* Refactor node mute handling to use dedicated functions for clarity and consistency

* Develop ref

* Fix NodeDB review follow-ups

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Address review validation nits

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Trunk

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Extract legacy NodeDatabase migration

* Fix remaining NodeDB review issues

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/c76b9a5a-7244-4fbc-9ef0-98091d8caaea

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Fixes

* Trunk

* Fix latest review compile follow-ups

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/5198da01-ec4c-4c16-8a09-68b8e6d5d410

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Fix cppcheck style warnings

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/e60287ba-4ece-46e0-83d8-a6d89664c0bb

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Change pointer type for mesh node in set_favorite function

* Change pointer types for mesh node references to const in multiple applets

* Add NodeDB layout v25 documentation and migration guidelines

* Remove tests for uninitialized PacketHistory state due to undefined behavior

* Fix code block formatting in copilot instructions

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-09 15:12:10 -05:00

217 lines
8.4 KiB
C++

#include "WaypointModule.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/CompassRenderer.h"
#if HAS_SCREEN
#include "gps/RTC.h"
#include "graphics/Screen.h"
#include "graphics/TimeFormatters.h"
#include "graphics/draw/NodeListRenderer.h"
#include "main.h"
#endif
WaypointModule *waypointModule;
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
auto &p = mp.decoded;
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif
// We only store/display messages destined for us.
// Keep a copy of the most recent text message.
devicestate.rx_waypoint = mp;
devicestate.has_rx_waypoint = true;
powerFSM.trigger(EVENT_RECEIVED_MSG);
#if HAS_SCREEN
UIFrameEvent e;
// New or updated waypoint: focus on this frame next time Screen::setFrames runs
if (shouldDraw()) {
requestFocus();
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
}
// Deleting an old waypoint: remove the frame quietly, don't change frame position if possible
else
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND;
notifyObservers(&e);
#endif
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
#if HAS_SCREEN
bool WaypointModule::shouldDraw()
{
#if !MESHTASTIC_EXCLUDE_WAYPOINT
if (!screen || !devicestate.has_rx_waypoint)
return false;
meshtastic_Waypoint wp{}; // <- replaces memset
if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size,
&meshtastic_Waypoint_msg, &wp)) {
return wp.expire > getTime();
}
return false; // no LOG_ERROR, no flag writes
#else
return false;
#endif
}
/// Draw the last waypoint we received
void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
if (!screen)
return;
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = "Waypoint";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
const int *textPos = graphics::getTextPositions(display);
// Decode the waypoint
const meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
meshtastic_Waypoint wp{};
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
devicestate.has_rx_waypoint = false;
return;
}
// Get timestamp info. Will pass as a field to drawColumns
char lastStr[20];
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
// Will contain distance information, passed as a field to drawColumns
char distStr[20] = "";
// Get our node, to use our own position
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
// Match compass sizing/placement to favorite node screen logic.
const int w = display->getWidth();
int16_t compassRadius = 8;
int16_t compassX = x + w - compassRadius - 8;
int16_t compassY = y + display->getHeight() / 2;
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = textPos[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
const int16_t usableHeight = bottomY - topY - 5;
compassRadius = usableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
compassX = x + SCREEN_WIDTH - compassRadius - 8;
compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
} else {
// Waypoint content uses rows 1..4, so place the compass below that block.
const int yBelowContent = textPos[4] + FONT_HEIGHT_SMALL + 2;
const int margin = 4;
#if defined(USE_EINK)
const int iconSize = (graphics::currentResolution == graphics::ScreenResolution::High) ? 16 : 8;
const int navBarHeight = iconSize + 6;
#else
const int navBarHeight = 0;
#endif
const int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
if (availableHeight > 0) {
compassRadius = availableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
if (compassRadius * 2 > SCREEN_WIDTH - 16)
compassRadius = (SCREEN_WIDTH - 16) / 2;
if (compassRadius < 8)
compassRadius = 8;
compassX = x + SCREEN_WIDTH / 2;
compassY = yBelowContent + availableHeight / 2;
}
}
const uint16_t compassDiam = compassRadius * 2;
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
const char *statusLine1 = nullptr;
const char *statusLine2 = nullptr;
// Distance only needs our own position fix; compass/bearing additionally needs heading.
meshtastic_PositionLite ownPos;
const bool haveOwnPos = ourNode && nodeDB->copyNodePosition(ourNode->num, ownPos);
if (hasOwnPositionFix && haveOwnPos) {
const meshtastic_PositionLite &op = ownPos;
const float d =
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
// Always show distance once we have an own-position fix, even without heading.
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
float feet = d * METERS_TO_FEET;
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft" : "%.1fmi",
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET);
} else {
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm" : "%.1fkm", d < 2000 ? d : d / 1000);
}
float myHeading = 0.0f;
const bool hasHeading =
graphics::CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
if (hasHeading) {
// Draw compass circle
display->drawCircle(compassX, compassY, compassRadius);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
bearingToOther = graphics::CompassRenderer::adjustBearingForCompassMode(bearingToOther, myHeading);
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
const float bearingToOtherDegrees = graphics::CompassRenderer::radiansToDegrees360(bearingToOther);
// Distance to waypoint with relative bearing when heading is available.
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
float feet = d * METERS_TO_FEET;
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
} else {
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
bearingToOtherDegrees);
}
} else {
statusLine1 = "No";
statusLine2 = "Heading";
}
} else {
// No own fix yet, so compass/bearing data would be misleading.
statusLine1 = "No";
statusLine2 = "Fix";
}
if (statusLine1) {
display->drawCircle(compassX, compassY, compassRadius);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(compassX, compassY - FONT_HEIGHT_SMALL, statusLine1);
display->drawString(compassX, compassY, statusLine2);
}
display->setTextAlignment(TEXT_ALIGN_LEFT); // Something above me changes to a different alignment, forcing a fix here!
display->drawString(0, textPos[line++], lastStr);
display->drawString(0, textPos[line++], wp.name);
display->drawString(0, textPos[line++], wp.description);
if (distStr[0])
display->drawString(0, textPos[line++], distStr);
}
#endif