mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-24 01:16:55 +00:00
Move waypoint (#4202)
* Move waypoint screen draw into the waypoint module * Get the observer set up for the waypoint screen draw * Static squashing: screen dimensions Macros moved back to Screen.cpp, as a band-aid until we eventually move all those static functions into the Screen class. * Move getCompassDiam into Screen class (supress compiler warnings) At this stage, the method is still static, because it's used by drawNodeInfo, which has no tidy reference to our screen instance. This is probably just another band-aid until these static functions all move. * Use new getCompassDiam function in AccelerometerThread * Properly gate display code in WaypointModule --------- Co-authored-by: Todd Herbert <herbert.todd@gmail.com>
This commit is contained in:
parent
47a94d7a07
commit
b5d7718319
@ -278,16 +278,17 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(x, y, "Calibrating\nCompass");
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
|
||||
|
||||
// coordinates for the center of the compass/circle
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5;
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + display->getHeight() / 2;
|
||||
} else {
|
||||
compassX = x + display->getWidth() - getCompassDiam(display) / 2 - 5;
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
|
||||
}
|
||||
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
|
||||
display->drawCircle(compassX, compassY, compassDiam / 2);
|
||||
screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180);
|
||||
}
|
||||
#endif
|
||||
|
@ -43,7 +43,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "meshUtils.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#include "modules/WaypointModule.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
@ -60,9 +59,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
/// Convert an integer GPS coords to a floating point
|
||||
#define DegD(i) (i * 1e-7)
|
||||
|
||||
using namespace meshtastic; /** @todo remove */
|
||||
|
||||
namespace graphics
|
||||
@ -111,10 +107,10 @@ GeoCoord geoCoord;
|
||||
static bool heartbeat = false;
|
||||
#endif
|
||||
|
||||
static uint16_t displayWidth, displayHeight;
|
||||
|
||||
#define SCREEN_WIDTH displayWidth
|
||||
#define SCREEN_HEIGHT displayHeight
|
||||
// Quick access to screen dimensions from static drawing functions
|
||||
// DEPRECATED. To-do: move static functions inside Screen class
|
||||
#define SCREEN_WIDTH display->getWidth()
|
||||
#define SCREEN_HEIGHT display->getHeight()
|
||||
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
@ -416,37 +412,6 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
|
||||
return packet->from != 0 && !moduleConfig.store_forward.enabled;
|
||||
}
|
||||
|
||||
// Determine whether the waypoint frame should be drawn (waypoint deleted? expired?)
|
||||
static bool shouldDrawWaypoint(const meshtastic_MeshPacket *packet)
|
||||
{
|
||||
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
||||
// If no waypoint to show
|
||||
if (!devicestate.has_rx_waypoint)
|
||||
return false;
|
||||
|
||||
// Decode the message, to find the expiration time (is waypoint still valid)
|
||||
// This handles "deletion" as well as expiration
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (pb_decode_from_bytes(packet->decoded.payload.bytes, packet->decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
|
||||
// Valid waypoint
|
||||
if (wp.expire > getTime())
|
||||
return devicestate.has_rx_waypoint = true;
|
||||
|
||||
// Expired, or deleted
|
||||
else
|
||||
return devicestate.has_rx_waypoint = false;
|
||||
}
|
||||
|
||||
// If decoding failed
|
||||
LOG_ERROR("Failed to decode waypoint\n");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
|
||||
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
|
||||
{
|
||||
@ -1093,7 +1058,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
}
|
||||
|
||||
/// Draw a series of fields in a column, wrapping to multiple columns if needed
|
||||
static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||
void Screen::drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||
{
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@ -1279,7 +1244,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
|
||||
* We keep a series of "after you've gone 10 meters, what is your heading since
|
||||
* the last reference point?"
|
||||
*/
|
||||
static float estimatedHeading(double lat, double lon)
|
||||
float Screen::estimatedHeading(double lat, double lon)
|
||||
{
|
||||
static double oldLat, oldLon;
|
||||
static float b;
|
||||
@ -1309,7 +1274,7 @@ static size_t nodeIndex;
|
||||
static int8_t prevFrame = -1;
|
||||
|
||||
// Draw the arrow pointing to a node's location
|
||||
static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian)
|
||||
void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
|
||||
{
|
||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||
@ -1319,7 +1284,7 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
arrowPoints[i]->rotate(headingRadian);
|
||||
arrowPoints[i]->scale(getCompassDiam(display) * 0.6);
|
||||
arrowPoints[i]->scale(compassDiam * 0.6);
|
||||
arrowPoints[i]->translate(compassX, compassY);
|
||||
}
|
||||
display->drawLine(tip.x, tip.y, tail.x, tail.y);
|
||||
@ -1328,7 +1293,7 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp
|
||||
}
|
||||
|
||||
// Get a string representation of the time passed since something happened
|
||||
static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
||||
void Screen::getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
||||
{
|
||||
// Use an absolute timestamp in some cases.
|
||||
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
|
||||
@ -1357,6 +1322,54 @@ static void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
||||
snprintf(timeStr, maxLength, "unknown age");
|
||||
}
|
||||
|
||||
void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
|
||||
{
|
||||
// If north is supposed to be at the top of the compass we want rotation to be +0
|
||||
if (config.display.compass_north_top)
|
||||
myHeading = -0;
|
||||
|
||||
Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f);
|
||||
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
|
||||
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
|
||||
|
||||
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// North on compass will be negative of heading
|
||||
rosePoints[i]->rotate(-myHeading);
|
||||
rosePoints[i]->scale(compassDiam);
|
||||
rosePoints[i]->translate(compassX, compassY);
|
||||
}
|
||||
display->drawLine(N1.x, N1.y, N3.x, N3.y);
|
||||
display->drawLine(N2.x, N2.y, N4.x, N4.y);
|
||||
display->drawLine(N1.x, N1.y, N4.x, N4.y);
|
||||
}
|
||||
|
||||
uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
|
||||
{
|
||||
uint16_t diam = 0;
|
||||
uint16_t offset = 0;
|
||||
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
|
||||
offset = FONT_HEIGHT_SMALL;
|
||||
|
||||
// get the smaller of the 2 dimensions and subtract 20
|
||||
if (displayWidth > (displayHeight - offset)) {
|
||||
diam = displayHeight - offset;
|
||||
// if 2/3 of the other size would be smaller, use that
|
||||
if (diam > (displayWidth * 2 / 3)) {
|
||||
diam = displayWidth * 2 / 3;
|
||||
}
|
||||
} else {
|
||||
diam = displayWidth;
|
||||
if (diam > ((displayHeight - offset) * 2 / 3)) {
|
||||
diam = (displayHeight - offset) * 2 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
return diam - 20;
|
||||
};
|
||||
|
||||
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// We only advance our nodeIndex if the frame # has changed - because
|
||||
@ -1396,7 +1409,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
}
|
||||
|
||||
static char lastStr[20];
|
||||
getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr));
|
||||
screen->getTimeAgoStr(sinceLastSeen(node), lastStr, sizeof(lastStr));
|
||||
|
||||
static char distStr[20];
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
@ -1407,13 +1420,14 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
// coordinates for the center of the compass/circle
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
|
||||
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
|
||||
compassY = y + SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
|
||||
compassX = x + SCREEN_WIDTH - compassDiam / 2 - 5;
|
||||
compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
|
||||
}
|
||||
bool hasNodeHeading = false;
|
||||
@ -1424,7 +1438,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
if (screen->hasHeading())
|
||||
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
|
||||
else
|
||||
myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
screen->drawCompassNorth(display, compassX, compassY, myHeading);
|
||||
|
||||
if (hasValidPosition(node)) {
|
||||
@ -1452,7 +1466,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
drawNodeHeading(display, compassX, compassY, bearingToOther);
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
}
|
||||
}
|
||||
if (!hasNodeHeading) {
|
||||
@ -1462,119 +1476,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
// hasValidPosition(node));
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
}
|
||||
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
|
||||
display->drawCircle(compassX, compassY, compassDiam / 2);
|
||||
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
// Must be after distStr is populated
|
||||
drawColumns(display, x, y, fields);
|
||||
}
|
||||
|
||||
/// Draw the last waypoint we received
|
||||
static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Prepare to draw
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// Handle inverted display
|
||||
// Unsure of expected behavior: for now, copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
|
||||
// Decode the waypoint
|
||||
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
|
||||
// This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case
|
||||
display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get timestamp info. Will pass as a field to drawColumns
|
||||
static char lastStr[20];
|
||||
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
|
||||
|
||||
// Will contain distance information, passed as a field to drawColumns
|
||||
static char distStr[20];
|
||||
|
||||
// Get our node, to use our own position
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// Text fields to draw (left of compass)
|
||||
// Last element must be NULL. This signals the end of the char*[] to drawColumns
|
||||
const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL};
|
||||
|
||||
// Co-ordinates for the center of the compass/circle
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
|
||||
compassY = y + SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5;
|
||||
compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
|
||||
}
|
||||
|
||||
// If our node has a position:
|
||||
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
|
||||
const meshtastic_PositionLite &op = ourNode->position;
|
||||
float myHeading;
|
||||
if (screen->hasHeading())
|
||||
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
|
||||
else
|
||||
myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
screen->drawCompassNorth(display, compassX, compassY, myHeading);
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
drawNodeHeading(display, compassX, compassY, bearingToOther);
|
||||
}
|
||||
|
||||
// If our node doesn't have position
|
||||
else {
|
||||
// ? in the compass
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
|
||||
// ? in the distance field
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
strncpy(distStr, "? mi", sizeof(distStr));
|
||||
else
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
}
|
||||
|
||||
// Undo color-inversion, if set prior to drawing header
|
||||
// Unsure of expected behavior? For now: copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
|
||||
// Draw compass circle
|
||||
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
|
||||
|
||||
// Must be after distStr is populated
|
||||
drawColumns(display, x, y, fields);
|
||||
screen->drawColumns(display, x, y, fields);
|
||||
}
|
||||
|
||||
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
|
||||
@ -1797,8 +1705,6 @@ void Screen::setup()
|
||||
textMessageObserver.observe(textMessageModule);
|
||||
if (inputBroker)
|
||||
inputObserver.observe(inputBroker);
|
||||
if (waypointModule)
|
||||
waypointObserver.observe(waypointModule);
|
||||
|
||||
// Modules can notify screen about refresh
|
||||
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
||||
@ -2130,11 +2036,6 @@ void Screen::setFrames()
|
||||
normalFrames[numframes++] = drawTextMessageFrame;
|
||||
}
|
||||
|
||||
// If we have a waypoint (not expired, not deleted)
|
||||
if (devicestate.has_rx_waypoint && shouldDrawWaypoint(&devicestate.rx_waypoint)) {
|
||||
normalFrames[numframes++] = drawWaypointFrame;
|
||||
}
|
||||
|
||||
// then all the nodes
|
||||
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
|
||||
size_t numToShow = min(numMeshNodes, 4U);
|
||||
@ -2196,7 +2097,7 @@ void Screen::blink()
|
||||
uint8_t count = 10;
|
||||
dispdev->setBrightness(254);
|
||||
while (count > 0) {
|
||||
dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
|
||||
dispdev->display();
|
||||
delay(50);
|
||||
dispdev->clear();
|
||||
@ -2687,13 +2588,6 @@ int Screen::handleInputEvent(const InputEvent *event)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Screen::handleWaypoint(const meshtastic_MeshPacket *arg)
|
||||
{
|
||||
// TODO: move to appropriate frame when redrawing
|
||||
setFrames();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#else
|
||||
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
|
||||
|
@ -82,6 +82,9 @@ class Screen
|
||||
#define SEGMENT_WIDTH 16
|
||||
#define SEGMENT_HEIGHT 4
|
||||
|
||||
/// Convert an integer GPS coords to a floating point
|
||||
#define DegD(i) (i * 1e-7)
|
||||
|
||||
namespace
|
||||
{
|
||||
/// A basic 2D point class for drawing
|
||||
@ -119,31 +122,6 @@ class Point
|
||||
|
||||
} // namespace
|
||||
|
||||
static uint16_t getCompassDiam(OLEDDisplay *display)
|
||||
{
|
||||
uint16_t diam = 0;
|
||||
uint16_t offset = 0;
|
||||
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
|
||||
offset = FONT_HEIGHT_SMALL;
|
||||
|
||||
// get the smaller of the 2 dimensions and subtract 20
|
||||
if (display->getWidth() > (display->getHeight() - offset)) {
|
||||
diam = display->getHeight() - offset;
|
||||
// if 2/3 of the other size would be smaller, use that
|
||||
if (diam > (display->getWidth() * 2 / 3)) {
|
||||
diam = display->getWidth() * 2 / 3;
|
||||
}
|
||||
} else {
|
||||
diam = display->getWidth();
|
||||
if (diam > ((display->getHeight() - offset) * 2 / 3)) {
|
||||
diam = (display->getHeight() - offset) * 2 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
return diam - 20;
|
||||
};
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
@ -188,8 +166,6 @@ class Screen : public concurrency::OSThread
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic_MeshPacket *> textMessageObserver =
|
||||
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleTextMessage);
|
||||
CallbackObserver<Screen, const meshtastic_MeshPacket *> waypointObserver =
|
||||
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleWaypoint);
|
||||
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
|
||||
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent);
|
||||
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
||||
@ -232,27 +208,18 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *);
|
||||
|
||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||
|
||||
// Draw north
|
||||
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
|
||||
{
|
||||
// If north is supposed to be at the top of the compass we want rotation to be +0
|
||||
if (config.display.compass_north_top)
|
||||
myHeading = -0;
|
||||
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading);
|
||||
|
||||
Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f);
|
||||
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
|
||||
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
|
||||
static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// North on compass will be negative of heading
|
||||
rosePoints[i]->rotate(-myHeading);
|
||||
rosePoints[i]->scale(getCompassDiam(display));
|
||||
rosePoints[i]->translate(compassX, compassY);
|
||||
}
|
||||
display->drawLine(N1.x, N1.y, N3.x, N3.y);
|
||||
display->drawLine(N2.x, N2.y, N4.x, N4.y);
|
||||
display->drawLine(N1.x, N1.y, N4.x, N4.y);
|
||||
}
|
||||
float estimatedHeading(double lat, double lon);
|
||||
|
||||
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
|
||||
|
||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
||||
|
||||
/// Handle button press, trackball or swipe action)
|
||||
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
|
||||
@ -421,7 +388,6 @@ class Screen : public concurrency::OSThread
|
||||
int handleTextMessage(const meshtastic_MeshPacket *arg);
|
||||
int handleUIFrameEvent(const UIFrameEvent *arg);
|
||||
int handleInputEvent(const InputEvent *arg);
|
||||
int handleWaypoint(const meshtastic_MeshPacket *arg);
|
||||
|
||||
/// Used to force (super slow) eink displays to draw critical frames
|
||||
void forceDisplay(bool forceUiUpdate = false);
|
||||
@ -444,6 +410,11 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
bool isAUTOOled = false;
|
||||
|
||||
// Screen dimensions (for convenience)
|
||||
// Defined during Screen::setup
|
||||
uint16_t displayWidth = 0;
|
||||
uint16_t displayHeight = 0;
|
||||
|
||||
private:
|
||||
FrameCallback alertFrames[1];
|
||||
struct ScreenCmd {
|
||||
|
@ -2,6 +2,11 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#endif
|
||||
|
||||
WaypointModule *waypointModule;
|
||||
|
||||
@ -11,14 +16,155 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
auto &p = mp.decoded;
|
||||
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
|
||||
#endif
|
||||
|
||||
UIFrameEvent e = {true, true};
|
||||
// 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);
|
||||
notifyObservers(&mp);
|
||||
notifyObservers(&e);
|
||||
|
||||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
bool WaypointModule::shouldDraw()
|
||||
{
|
||||
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
||||
// If no waypoint to show
|
||||
if (!devicestate.has_rx_waypoint)
|
||||
return false;
|
||||
|
||||
// Decode the message, to find the expiration time (is waypoint still valid)
|
||||
// This handles "deletion" as well as expiration
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size,
|
||||
&meshtastic_Waypoint_msg, &wp)) {
|
||||
// Valid waypoint
|
||||
if (wp.expire > getTime())
|
||||
return devicestate.has_rx_waypoint = true;
|
||||
|
||||
// Expired, or deleted
|
||||
else
|
||||
return devicestate.has_rx_waypoint = false;
|
||||
}
|
||||
|
||||
// If decoding failed
|
||||
LOG_ERROR("Failed to decode waypoint\n");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Draw the last waypoint we received
|
||||
void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Prepare to draw
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// Handle inverted display
|
||||
// Unsure of expected behavior: for now, copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
|
||||
// Decode the waypoint
|
||||
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
|
||||
// This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case
|
||||
display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get timestamp info. Will pass as a field to drawColumns
|
||||
static char lastStr[20];
|
||||
screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
|
||||
|
||||
// Will contain distance information, passed as a field to drawColumns
|
||||
static char distStr[20];
|
||||
|
||||
// Get our node, to use our own position
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// Text fields to draw (left of compass)
|
||||
// Last element must be NULL. This signals the end of the char*[] to drawColumns
|
||||
const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL};
|
||||
|
||||
// Dimensions / co-ordinates for the compass/circle
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
|
||||
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + display->getHeight() / 2;
|
||||
} else {
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
|
||||
}
|
||||
|
||||
// If our node has a position:
|
||||
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
|
||||
const meshtastic_PositionLite &op = ourNode->position;
|
||||
float myHeading;
|
||||
if (screen->hasHeading())
|
||||
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
|
||||
else
|
||||
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
screen->drawCompassNorth(display, compassX, compassY, myHeading);
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
}
|
||||
|
||||
// If our node doesn't have position
|
||||
else {
|
||||
// ? in the compass
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
|
||||
// ? in the distance field
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
strncpy(distStr, "? mi", sizeof(distStr));
|
||||
else
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
}
|
||||
|
||||
// Undo color-inversion, if set prior to drawing header
|
||||
// Unsure of expected behavior? For now: copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
|
||||
// Draw compass circle
|
||||
display->drawCircle(compassX, compassY, compassDiam / 2);
|
||||
|
||||
// Must be after distStr is populated
|
||||
screen->drawColumns(display, x, y, fields);
|
||||
}
|
||||
#endif
|
@ -5,21 +5,29 @@
|
||||
/**
|
||||
* Waypoint message handling for meshtastic
|
||||
*/
|
||||
class WaypointModule : public SinglePortModule, public Observable<const meshtastic_MeshPacket *>
|
||||
class WaypointModule : public SinglePortModule, public Observable<const UIFrameEvent *>
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {}
|
||||
|
||||
#if HAS_SCREEN
|
||||
bool shouldDraw();
|
||||
#endif
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
|
||||
it
|
||||
*/
|
||||
|
||||
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
|
||||
#if HAS_SCREEN
|
||||
virtual bool wantUIFrame() override { return this->shouldDraw(); }
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
|
||||
#endif
|
||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
};
|
||||
|
||||
extern WaypointModule *waypointModule;
|
||||
extern WaypointModule *waypointModule;
|
Loading…
Reference in New Issue
Block a user