M2 step 5: GNSS App, with the Position and Sky views

Position: the Fix line (or how long it has been searching), latitude and
longitude in decimal degrees or degrees-minutes-seconds (Settings ->
Coordinates), the Maidenhead locator, altitude, speed and course, HDOP
and UTC. Sky: the satellites by azimuth and elevation, coloured by
constellation, filled when used in the Fix, with used/in-view counts.
Tab switches. lib/gnss/geo_format holds the formatting, the locator and
the sky projection, host-tested (6 tests; locators checked against
FN31pr and JN58td).

Verified on the device by a window: 3D Fix from GPS, GLONASS, Galileo
and BeiDou.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-04 22:38:06 +02:00
co-authored by Claude Opus 5.5
parent d408bada07
commit 463ed2dda8
7 changed files with 355 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
#include <unity.h>
#include "geo_format.h"
using namespace roro::gnss;
void setUp() {}
void tearDown() {}
void test_decimal_degrees_with_hemisphere() {
TEST_ASSERT_EQUAL_STRING("50.86920° N", formatDecimal(50.8692034, true).c_str());
TEST_ASSERT_EQUAL_STRING("33.50000° S", formatDecimal(-33.5, true).c_str());
TEST_ASSERT_EQUAL_STRING("4.25350° E", formatDecimal(4.2535, false).c_str());
TEST_ASSERT_EQUAL_STRING("151.21000° W", formatDecimal(-151.21, false).c_str());
}
void test_degrees_minutes_seconds() {
TEST_ASSERT_EQUAL_STRING("50° 52' 09.1\" N", formatDms(50.8692034, true).c_str());
TEST_ASSERT_EQUAL_STRING("4° 15' 12.6\" E", formatDms(4.2535, false).c_str());
TEST_ASSERT_EQUAL_STRING("33° 30' 00.0\" S", formatDms(-33.5, true).c_str());
}
void test_seconds_that_round_up_carry_over() {
TEST_ASSERT_EQUAL_STRING("11° 00' 00.0\" N", formatDms(10.9999999, true).c_str());
}
void test_maidenhead_matches_known_locators() {
TEST_ASSERT_EQUAL_STRING("FN31pr", maidenhead(41.714775, -72.727260).c_str()); // W1AW
TEST_ASSERT_EQUAL_STRING("JN58td", maidenhead(48.14666, 11.60833).c_str()); // Munich
TEST_ASSERT_EQUAL_STRING("JJ00aa", maidenhead(0, 0).c_str());
TEST_ASSERT_EQUAL_STRING("AA00aa", maidenhead(-90, -180).c_str());
}
void test_maidenhead_stays_in_range_at_the_edges() {
TEST_ASSERT_EQUAL_STRING("RR99xx", maidenhead(90, 180).c_str());
}
void test_sky_projection() {
SkyPoint zenith = skyPosition(123, 90, 50, 60, 40);
TEST_ASSERT_EQUAL(50, zenith.x);
TEST_ASSERT_EQUAL(60, zenith.y);
SkyPoint north = skyPosition(0, 0, 50, 60, 40);
TEST_ASSERT_EQUAL(50, north.x);
TEST_ASSERT_EQUAL(20, north.y); // up
SkyPoint east = skyPosition(90, 0, 50, 60, 40);
TEST_ASSERT_EQUAL(90, east.x);
TEST_ASSERT_EQUAL(60, east.y);
SkyPoint south45 = skyPosition(180, 45, 50, 60, 40);
TEST_ASSERT_EQUAL(50, south45.x);
TEST_ASSERT_EQUAL(80, south45.y);
SkyPoint below = skyPosition(270, -5, 50, 60, 40); // just below the horizon: on the edge
TEST_ASSERT_EQUAL(10, below.x);
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_decimal_degrees_with_hemisphere);
RUN_TEST(test_degrees_minutes_seconds);
RUN_TEST(test_seconds_that_round_up_carry_over);
RUN_TEST(test_maidenhead_matches_known_locators);
RUN_TEST(test_maidenhead_stays_in_range_at_the_edges);
RUN_TEST(test_sky_projection);
return UNITY_END();
}