mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
feat(ui-lvgl): GPS on three tabs -- Sky, Signal, Details
Each tab a screenful: the sky plot beside the fix; the bars with a fixed
dB-Hz scale and the constellations; and Details with whatever else the
receiver reports (correction, locator, course, DOPs with their rating,
signal averages, NMEA counts). Sim: sim_open("gps").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// Home > GPS (also Settings > Connections > GPS): what the receiver sees.
|
// Home > GPS (also Settings > Connections > GPS): what the receiver sees, on
|
||||||
// A sky plot of the satellites in view (centre = overhead, rings at 30 and 60
|
// three tabs in the header, each a screenful. Sky: a plot of the satellites
|
||||||
// degrees elevation, filled = used in the fix, colour = signal), the fix
|
// in view (centre = overhead, rings at 30 and 60 degrees elevation, filled =
|
||||||
// state with time to first fix and the dilutions of precision, and a bar per
|
// used in the fix, colour = signal) beside the fix state. Signal: a bar per
|
||||||
// satellite with its C/N0 (dB-Hz) -- the number that tells a weak antenna or
|
// satellite with its C/N0 (dB-Hz) -- the number that tells a weak antenna or
|
||||||
// a noisy board from a sky that just isn't visible. Refreshes every second.
|
// a noisy board from a sky that just isn't visible -- and the constellations.
|
||||||
|
// Details: whatever else the receiver reports, a row each once it does.
|
||||||
|
// Refreshes every second.
|
||||||
//
|
//
|
||||||
// The data is helpers/sensors/GpsSky.h (-D GPS_SKYVIEW), fed by the board's
|
// The data is helpers/sensors/GpsSky.h (-D GPS_SKYVIEW), fed by the board's
|
||||||
// NMEA stream; the sim feeds it made-up NMEA so the parser runs there too.
|
// NMEA stream; the sim feeds it made-up NMEA so the parser runs there too.
|
||||||
@@ -15,20 +17,25 @@
|
|||||||
|
|
||||||
namespace gpsview {
|
namespace gpsview {
|
||||||
|
|
||||||
|
enum : uint8_t { TAB_SKY, TAB_SIGNAL, TAB_DETAILS, TAB_COUNT };
|
||||||
|
static uint8_t s_tab = TAB_SKY; // kept across visits
|
||||||
|
|
||||||
static lv_obj_t* s_sky = nullptr; // sky plot (drawn)
|
static lv_obj_t* s_sky = nullptr; // sky plot (drawn)
|
||||||
static lv_obj_t* s_bars = nullptr; // signal bars (drawn, as wide as the satellites need)
|
static lv_obj_t* s_bars = nullptr; // signal bars (drawn, as wide as the satellites need)
|
||||||
static lv_obj_t* s_status = nullptr;
|
static lv_obj_t* s_status = nullptr;
|
||||||
static lv_obj_t* s_used = nullptr;
|
static lv_obj_t* s_used = nullptr;
|
||||||
// Beside the plot: in view, search time / time to first fix, UTC.
|
|
||||||
static lv_obj_t *s_inview = nullptr, *s_search = nullptr, *s_ttff = nullptr, *s_utc = nullptr;
|
|
||||||
// Under the bars: the position card (hidden without a fix), one pill per
|
|
||||||
// constellation.
|
|
||||||
static lv_obj_t *s_fix_title = nullptr, *s_fix_card = nullptr;
|
|
||||||
static lv_obj_t *s_pos = nullptr, *s_alt = nullptr, *s_speed = nullptr, *s_dop = nullptr;
|
|
||||||
static lv_obj_t* s_sys_pill[GpsSky::SYS_COUNT];
|
|
||||||
static lv_obj_t* s_on_btn = nullptr;
|
static lv_obj_t* s_on_btn = nullptr;
|
||||||
static const int SKY = 150;
|
// Sky, beside the plot: in view, search time / time to first fix, UTC.
|
||||||
static const int BAR_PITCH = 26, BAR_W = 16, BARS_H = 104;
|
static lv_obj_t *s_inview = nullptr, *s_search = nullptr, *s_ttff = nullptr, *s_utc = nullptr;
|
||||||
|
// Signal: one pill per constellation.
|
||||||
|
static lv_obj_t* s_sys_pill[GpsSky::SYS_COUNT];
|
||||||
|
// Details.
|
||||||
|
static lv_obj_t *s_quality = nullptr, *s_pos = nullptr, *s_grid = nullptr, *s_alt = nullptr,
|
||||||
|
*s_speed = nullptr, *s_course = nullptr, *s_hdop = nullptr, *s_vdop = nullptr,
|
||||||
|
*s_pdop = nullptr, *s_cn0 = nullptr, *s_best = nullptr, *s_nmea = nullptr, *s_rx = nullptr;
|
||||||
|
static lv_obj_t *s_d_fix = nullptr, *s_d_ttff = nullptr, *s_d_search = nullptr, *s_d_utc = nullptr;
|
||||||
|
static const int SKY = 168;
|
||||||
|
static const int BAR_PITCH = 26, BAR_W = 16, BARS_H = 120, AXIS_W = 22;
|
||||||
|
|
||||||
// Snapshot the screen draws from, sorted: used first, then by signal.
|
// Snapshot the screen draws from, sorted: used first, then by signal.
|
||||||
static GpsSky::Sat s_sats[GpsSky::MAX_SATS];
|
static GpsSky::Sat s_sats[GpsSky::MAX_SATS];
|
||||||
@@ -170,14 +177,16 @@ static void onGpsBarsDraw(lv_event_t* e) {
|
|||||||
int32_t lh = lv_font_get_line_height(td.font);
|
int32_t lh = lv_font_get_line_height(td.font);
|
||||||
int32_t base = a.y2 - lh - 2, top = a.y1 + lh + 2, full = base - top;
|
int32_t base = a.y2 - lh - 2, top = a.y1 + lh + 2, full = base - top;
|
||||||
|
|
||||||
lv_draw_line_dsc_t ld; // the 30 dB-Hz line: above it, a good signal
|
lv_draw_line_dsc_t ld; // a line every 10 dB-Hz, 30 dashed: above it, a good signal
|
||||||
lv_draw_line_dsc_init(&ld);
|
lv_draw_line_dsc_init(&ld);
|
||||||
ld.color = lv_color_hex(theme::SURFACE_2);
|
|
||||||
ld.width = 1;
|
ld.width = 1;
|
||||||
ld.dash_width = 3; ld.dash_gap = 3;
|
|
||||||
ld.p1.x = a.x1; ld.p2.x = a.x2;
|
ld.p1.x = a.x1; ld.p2.x = a.x2;
|
||||||
ld.p1.y = ld.p2.y = base - full * 30 / 50;
|
for (int v = 10; v <= 50; v += 10) {
|
||||||
|
ld.color = lv_color_hex(v == 30 ? theme::TEXT_MUTED : theme::mix(theme::TEXT_MUTED, theme::BG, 22));
|
||||||
|
ld.dash_width = ld.dash_gap = v == 30 ? 3 : 0;
|
||||||
|
ld.p1.y = ld.p2.y = base - full * v / 50;
|
||||||
lv_draw_line(layer, &ld);
|
lv_draw_line(layer, &ld);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < s_n; i++) {
|
for (int i = 0; i < s_n; i++) {
|
||||||
const GpsSky::Sat& s = s_sats[i];
|
const GpsSky::Sat& s = s_sats[i];
|
||||||
@@ -188,9 +197,9 @@ static void onGpsBarsDraw(lv_event_t* e) {
|
|||||||
lv_draw_rect_dsc_t d;
|
lv_draw_rect_dsc_t d;
|
||||||
lv_draw_rect_dsc_init(&d);
|
lv_draw_rect_dsc_init(&d);
|
||||||
d.radius = 3;
|
d.radius = 3;
|
||||||
d.bg_color = lv_color_hex(snrColor(s.snr));
|
const uint32_t col = snrColor(s.snr); // unused: the colour faded into the page, solid
|
||||||
d.bg_opa = s_used_flag[i] ? LV_OPA_COVER : LV_OPA_30;
|
d.bg_color = lv_color_hex(s_used_flag[i] ? col : theme::mix(col, theme::BG, 30));
|
||||||
d.border_color = d.bg_color;
|
d.border_color = lv_color_hex(col);
|
||||||
d.border_width = s_used_flag[i] ? 0 : 1;
|
d.border_width = s_used_flag[i] ? 0 : 1;
|
||||||
lv_area_t ba = { x, base - h, x + BAR_W - 1, base };
|
lv_area_t ba = { x, base - h, x + BAR_W - 1, base };
|
||||||
lv_draw_rect(layer, &d, &ba);
|
lv_draw_rect(layer, &d, &ba);
|
||||||
@@ -212,6 +221,27 @@ static void onGpsBarsDraw(lv_event_t* e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The bars' scale, left of them (they scroll, it stays): 0 to 50 dB-Hz.
|
||||||
|
static void onGpsAxisDraw(lv_event_t* e) {
|
||||||
|
lv_obj_t* o = (lv_obj_t*)lv_event_get_target(e);
|
||||||
|
lv_layer_t* layer = lv_event_get_layer(e);
|
||||||
|
lv_area_t a;
|
||||||
|
lv_obj_get_coords(o, &a);
|
||||||
|
const int32_t lh = lv_font_get_line_height(THEME_FONT_SMALL);
|
||||||
|
const int32_t base = a.y2 - lh - 2, full = base - (a.y1 + lh + 2);
|
||||||
|
char t[4];
|
||||||
|
for (int v = 0; v <= 50; v += 10) {
|
||||||
|
snprintf(t, sizeof(t), "%d", v);
|
||||||
|
const int32_t y = base - full * v / 50;
|
||||||
|
chart::text(layer, t, a.x1, y - lh / 2, a.x2 - 4, LV_TEXT_ALIGN_RIGHT, theme::TEXT_MUTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onGpsTab(lv_event_t* e) {
|
||||||
|
gpsview::s_tab = (uint8_t)lv_buttonmatrix_get_selected_button((lv_obj_t*)lv_event_get_target(e));
|
||||||
|
s_ui->buildGps();
|
||||||
|
}
|
||||||
|
|
||||||
void UITask::showGps(bool from_settings) {
|
void UITask::showGps(bool from_settings) {
|
||||||
_gps_from_settings = from_settings;
|
_gps_from_settings = from_settings;
|
||||||
_screen = SCR_GPS;
|
_screen = SCR_GPS;
|
||||||
@@ -222,8 +252,20 @@ void UITask::buildGps() {
|
|||||||
using namespace gpsview;
|
using namespace gpsview;
|
||||||
lv_obj_t* body = newScreen("GPS", true);
|
lv_obj_t* body = newScreen("GPS", true);
|
||||||
s_sky = s_bars = s_status = s_used = s_on_btn = nullptr;
|
s_sky = s_bars = s_status = s_used = s_on_btn = nullptr;
|
||||||
|
s_inview = s_search = s_ttff = s_utc = nullptr;
|
||||||
|
s_quality = s_pos = s_grid = s_alt = s_speed = s_course = s_hdop = s_vdop = s_pdop = nullptr;
|
||||||
|
s_cn0 = s_best = s_nmea = s_rx = s_d_fix = s_d_ttff = s_d_search = s_d_utc = nullptr;
|
||||||
|
for (lv_obj_t*& p : s_sys_pill) p = nullptr;
|
||||||
|
lv_obj_set_style_pad_row(body, 6, 0);
|
||||||
|
if (_header) {
|
||||||
|
static const char* TABS[] = { "Sky", "Signal", "Details", "" };
|
||||||
|
lv_obj_t* tabs = segmented(_header, TABS, s_tab, 186, 28, theme::SURFACE);
|
||||||
|
lv_obj_align(tabs, LV_ALIGN_RIGHT_MID, -4, 0);
|
||||||
|
lv_obj_add_event_cb(tabs, onGpsTab, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
// Sky plot left, the fix right.
|
if (s_tab == TAB_SKY) { // the plot left, the fix right
|
||||||
|
lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
lv_obj_t* top = lv_obj_create(body);
|
lv_obj_t* top = lv_obj_create(body);
|
||||||
lv_obj_remove_style_all(top);
|
lv_obj_remove_style_all(top);
|
||||||
lv_obj_set_size(top, LV_PCT(100), LV_SIZE_CONTENT);
|
lv_obj_set_size(top, LV_PCT(100), LV_SIZE_CONTENT);
|
||||||
@@ -258,22 +300,25 @@ void UITask::buildGps() {
|
|||||||
lv_obj_set_height(s_on_btn, 34);
|
lv_obj_set_height(s_on_btn, 34);
|
||||||
lv_obj_set_style_radius(s_on_btn, theme::RADIUS, 0);
|
lv_obj_set_style_radius(s_on_btn, theme::RADIUS, 0);
|
||||||
lv_obj_set_style_shadow_width(s_on_btn, 0, 0);
|
lv_obj_set_style_shadow_width(s_on_btn, 0, 0);
|
||||||
stylePrimary(s_on_btn);
|
|
||||||
lv_obj_center(label(s_on_btn, LV_SYMBOL_GPS " Turn on", THEME_FONT_BODY, theme::TEXT));
|
lv_obj_center(label(s_on_btn, LV_SYMBOL_GPS " Turn on", THEME_FONT_BODY, theme::TEXT));
|
||||||
|
stylePrimary(s_on_btn);
|
||||||
lv_obj_add_event_cb(s_on_btn, onGpsTurnOn, LV_EVENT_CLICKED, NULL);
|
lv_obj_add_event_cb(s_on_btn, onGpsTurnOn, LV_EVENT_CLICKED, NULL);
|
||||||
lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
} else if (s_tab == TAB_SIGNAL) {
|
||||||
s_fix_title = sectionTitle(body, "POSITION");
|
// The scale, then the bars, which scroll sideways when the satellites don't fit.
|
||||||
s_fix_card = infoCard(body);
|
lv_obj_t* row = lv_obj_create(body);
|
||||||
s_pos = infoRow(s_fix_card, "Coordinates", "");
|
lv_obj_remove_style_all(row);
|
||||||
s_alt = infoRow(s_fix_card, "Altitude", "");
|
lv_obj_set_size(row, LV_PCT(100), BARS_H + 8);
|
||||||
s_speed = infoRow(s_fix_card, "Speed", "");
|
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
|
||||||
s_dop = infoRow(s_fix_card, "Precision (DOP)", "");
|
lv_obj_t* ax = lv_obj_create(row);
|
||||||
|
lv_obj_remove_style_all(ax);
|
||||||
sectionTitle(body, "SIGNAL (dB-Hz)");
|
lv_obj_remove_flag(ax, LV_OBJ_FLAG_CLICKABLE);
|
||||||
lv_obj_t* sc = lv_obj_create(body); // scrolls sideways when the satellites don't fit
|
lv_obj_set_size(ax, AXIS_W, BARS_H);
|
||||||
|
lv_obj_add_event_cb(ax, onGpsAxisDraw, LV_EVENT_DRAW_MAIN_END, NULL);
|
||||||
|
lv_obj_t* sc = lv_obj_create(row);
|
||||||
styleSurface(sc, theme::BG);
|
styleSurface(sc, theme::BG);
|
||||||
lv_obj_set_size(sc, LV_PCT(100), BARS_H + 8);
|
lv_obj_set_height(sc, BARS_H + 8);
|
||||||
|
lv_obj_set_flex_grow(sc, 1);
|
||||||
lv_obj_set_scroll_dir(sc, LV_DIR_HOR);
|
lv_obj_set_scroll_dir(sc, LV_DIR_HOR);
|
||||||
lv_obj_set_scrollbar_mode(sc, LV_SCROLLBAR_MODE_AUTO);
|
lv_obj_set_scrollbar_mode(sc, LV_SCROLLBAR_MODE_AUTO);
|
||||||
s_bars = lv_obj_create(sc);
|
s_bars = lv_obj_create(sc);
|
||||||
@@ -300,54 +345,121 @@ void UITask::buildGps() {
|
|||||||
lv_obj_add_flag(p, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_add_flag(p, LV_OBJ_FLAG_HIDDEN);
|
||||||
s_sys_pill[k] = p;
|
s_sys_pill[k] = p;
|
||||||
}
|
}
|
||||||
|
noteLabel(body, "Solid = used in the fix. 30+ is strong; below 25, a fix is unlikely.");
|
||||||
lv_obj_t* note = label(body, "Solid bar = used in the fix. Green is a strong signal (30+ dB-Hz); "
|
} else { // Details: a row once the receiver reports it
|
||||||
"indoors, below 25 a fix is unlikely.", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
lv_obj_t* g = infoCard(body);
|
||||||
lv_label_set_long_mode(note, LV_LABEL_LONG_WRAP);
|
s_d_fix = infoRow(g, "Fix", "");
|
||||||
lv_obj_set_width(note, LV_PCT(100));
|
s_quality = infoRow(g, "Correction", "");
|
||||||
|
s_d_ttff = infoRow(g, "First fix", "");
|
||||||
|
s_d_search = infoRow(g, "Searching", "");
|
||||||
|
s_d_utc = infoRow(g, "UTC", "");
|
||||||
|
sectionTitle(body, "POSITION");
|
||||||
|
g = infoCard(body);
|
||||||
|
s_pos = infoRow(g, "Coordinates", "");
|
||||||
|
s_grid = infoRow(g, "Locator", "");
|
||||||
|
s_alt = infoRow(g, "Altitude", "");
|
||||||
|
s_speed = infoRow(g, "Speed", "");
|
||||||
|
s_course = infoRow(g, "Course", "");
|
||||||
|
sectionTitle(body, "PRECISION");
|
||||||
|
g = infoCard(body);
|
||||||
|
s_hdop = infoRow(g, "Horizontal (HDOP)", "");
|
||||||
|
s_vdop = infoRow(g, "Vertical (VDOP)", "");
|
||||||
|
s_pdop = infoRow(g, "Overall (PDOP)", "");
|
||||||
|
sectionTitle(body, "RECEIVER");
|
||||||
|
g = infoCard(body);
|
||||||
|
s_cn0 = infoRow(g, "Average signal, used", "");
|
||||||
|
s_best = infoRow(g, "Strongest", "");
|
||||||
|
s_nmea = infoRow(g, "NMEA sentences", "");
|
||||||
|
s_rx = infoRow(g, "Data received", "");
|
||||||
|
}
|
||||||
refreshGps();
|
refreshGps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// From loop(), once a second.
|
namespace gpsview {
|
||||||
|
|
||||||
|
// A dilution of precision and what it means (the usual bands).
|
||||||
|
static void dopText(float d, char* out, size_t n) {
|
||||||
|
if (d <= 0) { out[0] = 0; return; }
|
||||||
|
const char* w = d < 1 ? "ideal" : d < 2 ? "excellent" : d < 5 ? "good" : d < 10 ? "moderate" : d < 20 ? "fair" : "poor";
|
||||||
|
snprintf(out, n, "%.1f %s", d, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Maidenhead locator, 6 characters ("JO90ab").
|
||||||
|
static void locator(double lat, double lon, char* out, size_t n) {
|
||||||
|
if (n < 7) { out[0] = 0; return; }
|
||||||
|
double x = lon + 180, y = lat + 90;
|
||||||
|
out[0] = 'A' + (int)(x / 20); out[1] = 'A' + (int)(y / 10);
|
||||||
|
x = fmod(x, 20); y = fmod(y, 10);
|
||||||
|
out[2] = '0' + (int)(x / 2); out[3] = '0' + (int)y;
|
||||||
|
x = fmod(x, 2); y = fmod(y, 1);
|
||||||
|
out[4] = 'a' + (int)(x * 12); out[5] = 'a' + (int)(y * 24);
|
||||||
|
out[6] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Details card with no row to show goes, with its title over it.
|
||||||
|
static void hideEmpty(lv_obj_t* card) {
|
||||||
|
bool any = false;
|
||||||
|
for (uint32_t i = 0; i < lv_obj_get_child_count(card); i++)
|
||||||
|
if (!lv_obj_has_flag(lv_obj_get_child(card, i), LV_OBJ_FLAG_HIDDEN)) { any = true; break; }
|
||||||
|
const int32_t at = lv_obj_get_index(card);
|
||||||
|
lv_obj_t* title = at > 0 ? lv_obj_get_child(lv_obj_get_parent(card), at - 1) : nullptr;
|
||||||
|
for (lv_obj_t* o : { card, title }) {
|
||||||
|
if (!o) continue;
|
||||||
|
if (any) lv_obj_remove_flag(o, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(o, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* qualityName(uint8_t q) {
|
||||||
|
switch (q) {
|
||||||
|
case 1: return "None (GPS)";
|
||||||
|
case 2: return "DGPS / SBAS";
|
||||||
|
case 4: return "RTK fixed";
|
||||||
|
case 5: return "RTK float";
|
||||||
|
case 6: return "Dead reckoning";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpsview
|
||||||
|
|
||||||
|
// From loop(), once a second. Each tab's objects are null on the others.
|
||||||
void UITask::refreshGps() {
|
void UITask::refreshGps() {
|
||||||
using namespace gpsview;
|
using namespace gpsview;
|
||||||
if (_screen != SCR_GPS || !s_status) return;
|
if (_screen != SCR_GPS) return;
|
||||||
GpsSky* g = sky();
|
GpsSky* g = sky();
|
||||||
bool off = _core->gpsAvailable() && !_core->gpsEnabled();
|
bool off = _core->gpsAvailable() && !_core->gpsEnabled();
|
||||||
bool data = g && g->last_ms && millis() - g->last_ms < 3000;
|
bool data = g && g->last_ms && millis() - g->last_ms < 3000;
|
||||||
if (off || !data) s_n = 0;
|
if (off || !data) s_n = 0;
|
||||||
else snapshot(*g);
|
else snapshot(*g);
|
||||||
|
if (s_on_btn) {
|
||||||
if (off) lv_obj_remove_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
if (off) lv_obj_remove_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
||||||
else lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
else lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* status = "Not available";
|
||||||
|
uint32_t status_col = theme::TEXT_MUTED;
|
||||||
char used[48] = "", inview[16] = "", search[16] = "", ttff[16] = "", utc[16] = "";
|
char used[48] = "", inview[16] = "", search[16] = "", ttff[16] = "", utc[16] = "";
|
||||||
char pos[32] = "", alt[16] = "", speed[16] = "", dop[32] = "";
|
char pos[32] = "", grid[8] = "", alt[16] = "", speed[16] = "", course[16] = "";
|
||||||
|
char hdop[24] = "", vdop[24] = "", pdop[24] = "", cn0[16] = "", best[24] = "", nmea[32] = "", rx[24] = "";
|
||||||
bool fix = false;
|
bool fix = false;
|
||||||
if (!g) {
|
if (g && off) status = "GPS is off";
|
||||||
setText(s_status, "Not available");
|
else if (g && !data) {
|
||||||
setTextColor(s_status, theme::TEXT_MUTED);
|
status = "No data";
|
||||||
} else if (off) {
|
status_col = theme::FAIL;
|
||||||
setText(s_status, "GPS is off");
|
|
||||||
setTextColor(s_status, theme::TEXT_MUTED);
|
|
||||||
} else if (!data) {
|
|
||||||
setText(s_status, "No data");
|
|
||||||
setTextColor(s_status, theme::FAIL);
|
|
||||||
#if defined(SEEED_WIO_TRACKER_L2)
|
#if defined(SEEED_WIO_TRACKER_L2)
|
||||||
snprintf(used, sizeof(used), "Nothing from the receiver (%lu B)", (unsigned long)gps.rxChars());
|
snprintf(used, sizeof(used), "Nothing from the receiver (%lu B)", (unsigned long)gps.rxChars());
|
||||||
#else
|
#else
|
||||||
snprintf(used, sizeof(used), "Nothing from the receiver");
|
snprintf(used, sizeof(used), "Nothing from the receiver");
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else if (g) {
|
||||||
int tracked = 0, used_n = 0;
|
int tracked = 0, used_n = 0, cn_sum = 0, best_i = -1;
|
||||||
for (int i = 0; i < s_n; i++) { if (s_sats[i].snr >= 0) tracked++; if (s_used_flag[i]) used_n++; }
|
for (int i = 0; i < s_n; i++) {
|
||||||
fix = g->hasFix();
|
if (s_sats[i].snr >= 0) { tracked++; if (best_i < 0 || s_sats[i].snr > s_sats[best_i].snr) best_i = i; }
|
||||||
if (fix) {
|
if (s_used_flag[i]) { used_n++; if (s_sats[i].snr > 0) cn_sum += s_sats[i].snr; }
|
||||||
setText(s_status, g->fix_mode == 2 ? "2D fix" : "3D fix");
|
|
||||||
setTextColor(s_status, theme::OK);
|
|
||||||
} else {
|
|
||||||
setText(s_status, "Searching");
|
|
||||||
setTextColor(s_status, theme::ACCENT);
|
|
||||||
}
|
}
|
||||||
|
fix = g->hasFix();
|
||||||
|
status = fix ? (g->fix_mode == 2 ? "2D fix" : "3D fix") : "Searching";
|
||||||
|
status_col = fix ? theme::OK : theme::ACCENT;
|
||||||
snprintf(used, sizeof(used), "%d of %d used", fix ? (used_n ? used_n : g->sats_used) : 0, s_n);
|
snprintf(used, sizeof(used), "%d of %d used", fix ? (used_n ? used_n : g->sats_used) : 0, s_n);
|
||||||
snprintf(inview, sizeof(inview), "%d heard", tracked);
|
snprintf(inview, sizeof(inview), "%d heard", tracked);
|
||||||
uint32_t since = g->start_ms ? (millis() - g->start_ms) / 1000 : 0;
|
uint32_t since = g->start_ms ? (millis() - g->start_ms) / 1000 : 0;
|
||||||
@@ -355,25 +467,40 @@ void UITask::refreshGps() {
|
|||||||
else snprintf(search, sizeof(search), "%lu:%02lu", (unsigned long)(since / 60), (unsigned long)(since % 60));
|
else snprintf(search, sizeof(search), "%lu:%02lu", (unsigned long)(since / 60), (unsigned long)(since % 60));
|
||||||
if (g->utc_valid) snprintf(utc, sizeof(utc), "%02u:%02u:%02u", g->utc_h, g->utc_m, g->utc_s);
|
if (g->utc_valid) snprintf(utc, sizeof(utc), "%02u:%02u:%02u", g->utc_h, g->utc_m, g->utc_s);
|
||||||
int32_t lat, lon;
|
int32_t lat, lon;
|
||||||
if (fix && _core->course.currentLocation(lat, lon)) snprintf(pos, sizeof(pos), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
if (fix && _core->course.currentLocation(lat, lon)) {
|
||||||
if (fix && g->alt_valid) {
|
snprintf(pos, sizeof(pos), "%.5f, %.5f", lat / 1e6, lon / 1e6);
|
||||||
snprintf(alt, sizeof(alt), "%.0f m", g->alt_m);
|
locator(lat / 1e6, lon / 1e6, grid, sizeof(grid));
|
||||||
|
}
|
||||||
|
if (fix && g->alt_valid) snprintf(alt, sizeof(alt), "%.0f m", g->alt_m);
|
||||||
|
if (fix && g->rmc_valid) {
|
||||||
snprintf(speed, sizeof(speed), "%.1f km/h", g->speed_kmh);
|
snprintf(speed, sizeof(speed), "%.1f km/h", g->speed_kmh);
|
||||||
|
if (g->speed_kmh >= 2) { // standing still, the course is noise
|
||||||
|
static const char* const C8[8] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
|
||||||
|
snprintf(course, sizeof(course), "%.0f\xC2\xB0 %s", g->course_deg, C8[((int)lroundf(g->course_deg / 45)) & 7]);
|
||||||
}
|
}
|
||||||
if (fix && g->hdop > 0) snprintf(dop, sizeof(dop), "H %.1f V %.1f P %.1f", g->hdop, g->vdop, g->pdop);
|
|
||||||
}
|
}
|
||||||
|
if (fix) { dopText(g->hdop, hdop, sizeof(hdop)); dopText(g->vdop, vdop, sizeof(vdop)); dopText(g->pdop, pdop, sizeof(pdop)); }
|
||||||
|
if (used_n && cn_sum) snprintf(cn0, sizeof(cn0), "%d dB-Hz", cn_sum / used_n);
|
||||||
|
if (best_i >= 0) snprintf(best, sizeof(best), "%c%d %d dB-Hz", GpsSky::sysLetter(s_sats[best_i].sys), s_sats[best_i].prn, s_sats[best_i].snr);
|
||||||
|
if (g->sentences) snprintf(nmea, sizeof(nmea), g->bad_sentences ? "%lu, %lu bad" : "%lu",
|
||||||
|
(unsigned long)g->sentences, (unsigned long)g->bad_sentences);
|
||||||
|
#if defined(SEEED_WIO_TRACKER_L2)
|
||||||
|
const unsigned long b = gps.rxChars();
|
||||||
|
if (b >= 10240) snprintf(rx, sizeof(rx), "%lu KB", b / 1024); else snprintf(rx, sizeof(rx), "%lu B", b);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_status) { // Sky
|
||||||
|
setText(s_status, status);
|
||||||
|
setTextColor(s_status, status_col);
|
||||||
setText(s_used, used);
|
setText(s_used, used);
|
||||||
infoSet(s_inview, inview);
|
infoSet(s_inview, inview);
|
||||||
infoSet(s_search, search);
|
infoSet(s_search, search);
|
||||||
infoSet(s_ttff, ttff);
|
infoSet(s_ttff, ttff);
|
||||||
infoSet(s_utc, utc);
|
infoSet(s_utc, utc);
|
||||||
infoSet(s_pos, pos);
|
lv_obj_invalidate(s_sky);
|
||||||
infoSet(s_alt, alt);
|
}
|
||||||
infoSet(s_speed, speed);
|
if (s_bars) { // Signal
|
||||||
infoSet(s_dop, dop);
|
|
||||||
if (fix && pos[0]) { lv_obj_remove_flag(s_fix_title, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(s_fix_card, LV_OBJ_FLAG_HIDDEN); }
|
|
||||||
else { lv_obj_add_flag(s_fix_title, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(s_fix_card, LV_OBJ_FLAG_HIDDEN); }
|
|
||||||
// Per constellation: used / in view.
|
|
||||||
for (uint8_t k = 0; k < GpsSky::SYS_COUNT; k++) {
|
for (uint8_t k = 0; k < GpsSky::SYS_COUNT; k++) {
|
||||||
int n = 0, u = 0;
|
int n = 0, u = 0;
|
||||||
for (int i = 0; i < s_n; i++) if (s_sats[i].sys == k) { n++; if (s_used_flag[i]) u++; }
|
for (int i = 0; i < s_n; i++) if (s_sats[i].sys == k) { n++; if (s_used_flag[i]) u++; }
|
||||||
@@ -381,10 +508,30 @@ void UITask::refreshGps() {
|
|||||||
setTextFmt(s_sys_pill[k], "%c %s %d/%d", GpsSky::sysLetter(k), GpsSky::sysName(k), u, n);
|
setTextFmt(s_sys_pill[k], "%c %s %d/%d", GpsSky::sysLetter(k), GpsSky::sysName(k), u, n);
|
||||||
lv_obj_remove_flag(s_sys_pill[k], LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(s_sys_pill[k], LV_OBJ_FLAG_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t w = s_n * BAR_PITCH;
|
int32_t w = s_n * BAR_PITCH;
|
||||||
if (w < BAR_PITCH) w = BAR_PITCH;
|
lv_obj_set_width(s_bars, w < BAR_PITCH ? BAR_PITCH : w);
|
||||||
lv_obj_set_width(s_bars, w);
|
|
||||||
lv_obj_invalidate(s_sky);
|
|
||||||
lv_obj_invalidate(s_bars);
|
lv_obj_invalidate(s_bars);
|
||||||
}
|
}
|
||||||
|
if (s_d_fix) { // Details
|
||||||
|
char f[64];
|
||||||
|
snprintf(f, sizeof(f), used[0] ? "%s, %s" : "%s", status, used);
|
||||||
|
infoSet(s_d_fix, f);
|
||||||
|
infoSet(s_quality, fix && g ? qualityName(g->fix_quality) : "");
|
||||||
|
infoSet(s_d_ttff, ttff);
|
||||||
|
infoSet(s_d_search, search);
|
||||||
|
infoSet(s_d_utc, utc);
|
||||||
|
infoSet(s_pos, pos);
|
||||||
|
infoSet(s_grid, grid);
|
||||||
|
infoSet(s_alt, alt);
|
||||||
|
infoSet(s_speed, speed);
|
||||||
|
infoSet(s_course, course);
|
||||||
|
infoSet(s_hdop, hdop);
|
||||||
|
infoSet(s_vdop, vdop);
|
||||||
|
infoSet(s_pdop, pdop);
|
||||||
|
infoSet(s_cn0, cn0);
|
||||||
|
infoSet(s_best, best);
|
||||||
|
infoSet(s_nmea, nmea);
|
||||||
|
infoSet(s_rx, rx);
|
||||||
|
for (lv_obj_t* v : { s_pos, s_hdop, s_cn0 }) hideEmpty(lv_obj_get_parent(lv_obj_get_parent(v)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4629,6 +4629,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) {
|
|||||||
for (auto& s : SCREENS) if (!strcmp(s.n, name)) { (s_ui->*s.fn)(); return; }
|
for (auto& s : SCREENS) if (!strcmp(s.n, name)) { (s_ui->*s.fn)(); return; }
|
||||||
if (!strncmp(name, "page", 4)) { s_ui->showSchemaSettings(atoi(name + 4)); return; }
|
if (!strncmp(name, "page", 4)) { s_ui->showSchemaSettings(atoi(name + 4)); return; }
|
||||||
if (!strcmp(name, "map")) { s_ui->openMap(true); return; }
|
if (!strcmp(name, "map")) { s_ui->openMap(true); return; }
|
||||||
|
if (!strcmp(name, "gps")) { s_ui->showGps(false); return; }
|
||||||
if (!strcmp(name, "nodesmap")) { s_ui->openMap(false); return; } // as from Nodes
|
if (!strcmp(name, "nodesmap")) { s_ui->openMap(false); return; } // as from Nodes
|
||||||
if (!strcmp(name, "battcurve")) { s_ui->showBattCurve(); return; }
|
if (!strcmp(name, "battcurve")) { s_ui->showBattCurve(); return; }
|
||||||
if (!strcmp(name, "homeedit")) { s_ui->showHome(); s_ui->homeEdit(true); return; } // arranging the apps
|
if (!strcmp(name, "homeedit")) { s_ui->showHome(); s_ui->homeEdit(true); return; } // arranging the apps
|
||||||
|
|||||||
Reference in New Issue
Block a user