Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
13b3798
Infinite calibration loop fix
HarukiToreda Mar 17, 2026
872295a
Save calibration
HarukiToreda Mar 17, 2026
a6d5e05
Screen refresh
HarukiToreda Mar 17, 2026
29f81f1
reduce repeated code
HarukiToreda Mar 22, 2026
e8aba49
Merge branch 'develop' into Compass-calibration
HarukiToreda Mar 22, 2026
288f627
reduce repeated code to reduce flash
HarukiToreda Mar 22, 2026
22c91c5
fix Waypoint compass size and no fix no heading labels
HarukiToreda Mar 22, 2026
d89f704
Don't show compass unless we have a heading and location
HarukiToreda Mar 22, 2026
eb22776
If no calculated heading from moving, we should have no heading
HarukiToreda Mar 22, 2026
d342119
Slow walking calculated heading and auto stale heading when not moving
HarukiToreda Mar 22, 2026
6bdc834
Triming flash space
HarukiToreda Mar 22, 2026
373c031
cleanup
HarukiToreda Mar 23, 2026
9cdc0b7
show "?" when no location or heading for distance and heading screen
HarukiToreda Mar 23, 2026
237bdae
Merge branch 'develop' into Compass-calibration
HarukiToreda Apr 15, 2026
169512d
cleanup
HarukiToreda Apr 15, 2026
e4aeaa2
Stale heading logic
HarukiToreda Apr 15, 2026
c5be756
final trim
HarukiToreda Apr 15, 2026
0b19b43
Compass Calibration screen redesign
HarukiToreda Apr 15, 2026
b71ff8e
Trunk Fix
HarukiToreda Apr 15, 2026
a33fb61
Compile fix
HarukiToreda Apr 15, 2026
04da9ae
patch
HarukiToreda Apr 15, 2026
2fc576f
Update src/motion/MotionSensor.cpp
HarukiToreda Apr 15, 2026
c2cd53f
Update WaypointModule.cpp
HarukiToreda Apr 15, 2026
7872475
Merge branch 'develop' into Compass-calibration
HarukiToreda Apr 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 97 additions & 5 deletions src/graphics/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "main.h"
#include "mesh-pb-constants.h"
#include "mesh/Channels.h"
#include "mesh/Default.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
Expand Down Expand Up @@ -98,6 +99,7 @@ namespace graphics

// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
#define IDLE_FRAMERATE 1 // in fps
#define COMPASS_ACTIVE_FRAMERATE 20

// DEBUG
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
Expand Down Expand Up @@ -135,6 +137,60 @@ static bool heartbeat = false;

extern bool hasUnreadMessage;

static inline float wrapHeading360(float heading)
{
if (heading < 0.0f) {
heading += 360.0f;
} else if (heading >= 360.0f) {
heading -= 360.0f;
}
return heading;
}

void Screen::setHeading(float heading)
{
const float wrappedHeading = wrapHeading360(heading);

if (!hasCompass) {
hasCompass = true;
compassHeading = wrappedHeading;
return;
}

// Interpolate using shortest-path angular delta to avoid jumps around 0/360.
float delta = wrappedHeading - compassHeading;
if (delta > 180.0f) {
delta -= 360.0f;
} else if (delta < -180.0f) {
delta += 360.0f;
}

// Adaptive filtering:
// - Strong damping for tiny deltas (jitter)
// - Faster response for larger turns
const float absDelta = (delta >= 0.0f) ? delta : -delta;
if (absDelta < 1.0f) {
return;
}

float alpha = 0.35f;
if (absDelta > 25.0f) {
alpha = 0.85f;
} else if (absDelta > 10.0f) {
alpha = 0.65f;
}

float step = delta * alpha;
const float maxStep = 12.0f;
if (step > maxStep) {
step = maxStep;
} else if (step < -maxStep) {
step = -maxStep;
}

compassHeading = wrapHeading360(compassHeading + step);
}

// ==============================
// Overlay Alert Banner Renderer
// ==============================
Expand Down Expand Up @@ -272,23 +328,46 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
float Screen::estimatedHeading(double lat, double lon)
{
static double oldLat, oldLon;
static float b;
static float b = -1.0f;
static uint32_t lastHeadingAtMs = 0;
const uint32_t now = millis();
const uint32_t gpsUpdateIntervalSecs =
Default::getConfiguredOrDefault(config.position.gps_update_interval, default_gps_update_interval);
uint32_t effectiveUpdateIntervalSecs = gpsUpdateIntervalSecs;
if (config.position.position_broadcast_smart_enabled) {
const uint32_t smartMinIntervalSecs = Default::getConfiguredOrDefault(
config.position.broadcast_smart_minimum_interval_secs, default_broadcast_smart_minimum_interval_secs);
if (smartMinIntervalSecs > effectiveUpdateIntervalSecs) {
effectiveUpdateIntervalSecs = smartMinIntervalSecs;
}
}
// Two expected update windows; keep arithmetic 32-bit to avoid pulling in larger 64-bit helpers.
const uint32_t headingStaleMs =
(effectiveUpdateIntervalSecs > (UINT32_MAX / 2000U)) ? UINT32_MAX : (effectiveUpdateIntervalSecs * 2000U);

if (oldLat == 0) {
// just prepare for next time
// Need at least two position points before we can infer heading.
oldLat = lat;
oldLon = lon;

return b;
}
Comment thread
jp-bennett marked this conversation as resolved.

float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon);
if (d < 10) // haven't moved enough, just keep current bearing
if (d < 10) { // haven't moved enough, keep previous heading (invalid until first real movement)
if (lastHeadingAtMs != 0 && (now - lastHeadingAtMs) >= headingStaleMs) {
// Heading is stale after prolonged no-movement; force reacquire.
b = -1.0f;
oldLat = lat;
oldLon = lon;
}
return b;
}

b = GeoCoord::bearing(oldLat, oldLon, lat, lon) * RAD_TO_DEG;
oldLat = lat;
oldLon = lon;
lastHeadingAtMs = now;

return b;
}
Expand Down Expand Up @@ -923,9 +1002,22 @@ int32_t Screen::runOnce()
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.

if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) {
uint32_t desiredFramerate = IDLE_FRAMERATE;
#if HAS_GPS && !defined(USE_EINK)
if (showingNormalScreen && hasCompass) {
const uint8_t currentFrame = ui->getUiState()->currentFrame;
if ((framesetInfo.positions.gps != 255 && currentFrame == framesetInfo.positions.gps) ||
(framesetInfo.positions.waypoint != 255 && currentFrame == framesetInfo.positions.waypoint) ||
(framesetInfo.positions.firstFavorite != 255 && currentFrame >= framesetInfo.positions.firstFavorite &&
currentFrame <= framesetInfo.positions.lastFavorite)) {
desiredFramerate = COMPASS_ACTIVE_FRAMERATE;
}
}
#endif

if (targetFramerate != desiredFramerate && ui->getUiState()->frameState == FIXED) {
// oldFrameState = ui->getUiState()->frameState;
targetFramerate = IDLE_FRAMERATE;
targetFramerate = desiredFramerate;

ui->setTargetFPS(targetFramerate);
forceDisplay();
Expand Down
10 changes: 3 additions & 7 deletions src/graphics/Screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,11 @@ class Screen : public concurrency::OSThread

// Function to allow the AccelerometerThread to set the heading if a sensor provides it
// Mutex needed?
void setHeading(long _heading)
{
hasCompass = true;
compassHeading = fmod(_heading, 360);
}
void setHeading(float heading);

bool hasHeading() { return hasCompass; }

long getHeading() { return compassHeading; }
float getHeading() { return compassHeading; }

void setEndCalibration(uint32_t _endCalibrationAt) { endCalibrationAt = _endCalibrationAt; }
uint32_t getEndCalibration() { return endCalibrationAt; }
Expand Down Expand Up @@ -782,4 +778,4 @@ extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString;
extern graphics::Screen *screen;

#endif
#endif
76 changes: 58 additions & 18 deletions src/graphics/draw/CompassRenderer.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
#include "configuration.h"
#if HAS_SCREEN
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include <cmath>
Expand All @@ -21,8 +17,8 @@ struct Point {

void rotate(float angle)
{
float cos_a = cos(angle);
float sin_a = sin(angle);
float cos_a = cosf(angle);
float sin_a = sinf(angle);
float new_x = x * cos_a - y * sin_a;
float new_y = x * sin_a + y * cos_a;
x = new_x;
Expand Down Expand Up @@ -51,21 +47,30 @@ void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY,
if (currentResolution == ScreenResolution::High) {
radius += 4;
}
Point north(0, -radius);
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
north.rotate(-myHeading);
north.translate(compassX, compassY);
float northX = 0.0f;
float northY = -radius;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
const float c = cosf(-myHeading);
const float s = sinf(-myHeading);
const float rx = northX * c - northY * s;
const float ry = northX * s + northY * c;
northX = rx;
northY = ry;
}
northX += compassX;
northY += compassY;

display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setColor(BLACK);
const int16_t nLabelWidth = display->getStringWidth("N");
if (currentResolution == ScreenResolution::High) {
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
display->fillRect(northX - 8, northY - 1, nLabelWidth + 3, FONT_HEIGHT_SMALL - 6);
} else {
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
display->fillRect(northX - 4, northY - 1, nLabelWidth + 2, FONT_HEIGHT_SMALL - 6);
}
display->setColor(WHITE);
display->drawString(north.x, north.y - 3, "N");
display->drawString(northX, northY - 3, "N");
}

void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
Expand Down Expand Up @@ -113,11 +118,46 @@ void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, f
display->fillTriangle(tip.x, tip.y, right.x, right.y, tail.x, tail.y);
}

float estimatedHeading(double lat, double lon)
bool getHeadingRadians(double lat, double lon, float &headingRadian)
{
headingRadian = 0.0f;

if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING)
return true;

if (!screen)
return false;

if (screen->hasHeading()) {
headingRadian = screen->getHeading() * DEG_TO_RAD;
return true;
}

const float estimatedHeadingDeg = screen->estimatedHeading(lat, lon);
if (!(estimatedHeadingDeg >= 0.0f))
return false;

headingRadian = estimatedHeadingDeg * DEG_TO_RAD;
return true;
}

float adjustBearingForCompassMode(float bearingRadian, float headingRadian)
{
// Simple magnetic declination estimation
// This is a very basic implementation - the original might be more sophisticated
return 0.0f; // Return 0 for now, indicating no heading available
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
return bearingRadian - headingRadian;

return bearingRadian;
}

float radiansToDegrees360(float angleRadian)
{
constexpr float fullTurnDeg = 360.0f;
float degrees = angleRadian * RAD_TO_DEG;
if (degrees < 0.0f)
degrees += fullTurnDeg;
else if (degrees >= fullTurnDeg)
degrees -= fullTurnDeg;
return degrees;
}

uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
Expand All @@ -137,4 +177,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)

} // namespace CompassRenderer
} // namespace graphics
#endif
#endif
5 changes: 3 additions & 2 deletions src/graphics/draw/CompassRenderer.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#include "graphics/Screen.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>

Expand All @@ -25,7 +24,9 @@ void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, u
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing);

// Navigation and location functions
float estimatedHeading(double lat, double lon);
bool getHeadingRadians(double lat, double lon, float &headingRadian);
float adjustBearingForCompassMode(float bearingRadian, float headingRadian);
float radiansToDegrees360(float angleRadian);
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);

} // namespace CompassRenderer
Expand Down
Loading
Loading