mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-15 01:52:10 +00:00
feat(ui-lvgl): navigation map, clock tools, settings pages, radio
- Home > Map is the navigation map: waypoints (list, add at GPS / by coordinates / long-press spot), live shares, trail recording + tools (save, load, GPX export, track back), live share controls, target bar with ETA; the nodes map opens from Nearby. - Map: smooth panning (no decode during a drag, idle read-ahead, cached coarser stand-ins), overzoom limited to 2 levels, resumable downloads, map credit behind a "©" and in Settings > About; target ring and trail computed in double. - Home in swipeable pages; Clock (alarm, timer, stopwatch, ring card). - Settings rendered from the Core schema (display & power with a brightness slider, messages & contacts, trail / live share / alerts), GPS switch, Radio screen; low-battery shutdown. - WioTrackerL2Display: honour the 0-4 brightness level. - docs: L2 hardware checklist, ui-core notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
// Decoded map tiles (RGB565, 128 KB each) kept for reuse while panning / zooming
|
||||
// back, least-recently-used evicted. Buffers live in PSRAM on the ESP32 and are
|
||||
// allocated on first use, then kept for the session.
|
||||
// allocated on first use, then kept for the session. Tiles found missing are
|
||||
// remembered separately (no buffer), so looking up coarser zoom levels for the
|
||||
// overzoom fallback doesn't push real tiles out.
|
||||
//
|
||||
// Single-TU fragment: included by ui-lvgl/UITask.cpp only.
|
||||
|
||||
@@ -15,7 +17,7 @@ namespace mapview {
|
||||
|
||||
class TileCache {
|
||||
public:
|
||||
static const int SLOTS = 12; // 6 on screen at 320x218 + a ring of neighbours
|
||||
static const int SLOTS = 24; // 6 on screen at 320x218 + the 14-tile read-ahead ring + a few coarser (3 MB)
|
||||
|
||||
struct Slot {
|
||||
int16_t z = -1;
|
||||
@@ -26,10 +28,13 @@ public:
|
||||
lv_image_dsc_t dsc;
|
||||
};
|
||||
|
||||
// The slot holding z/x/y if it was loaded (present or known missing), else nullptr.
|
||||
// The slot holding z/x/y if it was loaded (present, or known missing: a
|
||||
// shared slot with present == false), else nullptr.
|
||||
Slot* find(int z, int x, int y) {
|
||||
for (Slot& s : _slots)
|
||||
if (s.z == z && s.x == x && s.y == y) { s.used = ++_tick; return &s; }
|
||||
for (const Missing& m : _missing)
|
||||
if (m.z == z && m.x == x && m.y == y) return &_miss_slot;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -49,6 +54,12 @@ public:
|
||||
s->z = z; s->x = x; s->y = y;
|
||||
s->used = ++_tick;
|
||||
s->present = src.renderTile(z, x, y, s->px);
|
||||
if (!s->present) { // remember the miss without holding a buffer slot
|
||||
Missing& m = _missing[_miss_next++ % MISSING];
|
||||
m.z = (int16_t)z; m.x = x; m.y = y;
|
||||
s->z = -1; s->used = 0;
|
||||
return &_miss_slot;
|
||||
}
|
||||
memset(&s->dsc, 0, sizeof(s->dsc));
|
||||
s->dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
s->dsc.header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
@@ -61,13 +72,18 @@ public:
|
||||
}
|
||||
|
||||
// Forget the "no tile here" answers (new tiles may have been written).
|
||||
void forgetMissing() { for (Slot& s : _slots) if (s.z >= 0 && !s.present) { s.z = -1; s.used = 0; } }
|
||||
void forgetMissing() { for (Missing& m : _missing) m.z = -1; }
|
||||
|
||||
// Forget everything (e.g. the card was swapped); keeps the buffers.
|
||||
void invalidate() { for (Slot& s : _slots) { s.z = -1; s.used = 0; } }
|
||||
void invalidate() { for (Slot& s : _slots) { s.z = -1; s.used = 0; } forgetMissing(); }
|
||||
|
||||
private:
|
||||
struct Missing { int16_t z = -1; int32_t x = 0, y = 0; };
|
||||
static const int MISSING = 48;
|
||||
Slot _slots[SLOTS];
|
||||
Missing _missing[MISSING];
|
||||
int _miss_next = 0;
|
||||
Slot _miss_slot; // present == false
|
||||
uint32_t _tick = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
// alternative is tools/maps/fetch_tiles.py). Tiles already on the card are
|
||||
// skipped, so a download can be repeated to fill gaps or extend an area.
|
||||
//
|
||||
// The job (area + zoom range) is kept in <root>/.job until it completes, so a
|
||||
// download cut short by a power-off, a lost network or Stop can be resumed:
|
||||
// resuming re-runs the same job, and the tiles already written are skipped.
|
||||
//
|
||||
// Driven by loop() from the UI loop: one small step per call, the HTTP GET
|
||||
// itself runs asynchronously in the board's fetcher (lvport::fetch*), so the
|
||||
// mesh keeps being serviced during a long download.
|
||||
@@ -78,6 +82,20 @@ public:
|
||||
const char* message() const { return _msg; }
|
||||
const char* sourceHost() { loadSource(); return _host; }
|
||||
|
||||
// Unfinished job from <root>/.job (read once, then tracked in memory).
|
||||
bool savedJob(TileArea& a) {
|
||||
if (!_job_checked) { _job_checked = true; _has_job = readJob(_job); }
|
||||
if (_has_job) a = _job;
|
||||
return _has_job;
|
||||
}
|
||||
void discardJob() {
|
||||
char path[64];
|
||||
jobPath(path, sizeof(path));
|
||||
remove(path);
|
||||
_has_job = false;
|
||||
_job_checked = true;
|
||||
}
|
||||
|
||||
bool start(const TileArea& a, const char* ssid, const char* pass) {
|
||||
if (active()) return false;
|
||||
_area = a;
|
||||
@@ -88,6 +106,7 @@ public:
|
||||
if (_total == 0 || _total > MAX_TILES) { fail("Area too large: use the PC tool"); return false; }
|
||||
loadSource();
|
||||
writeAttribution();
|
||||
writeJob(a);
|
||||
_z = a.zmin;
|
||||
tileRange(a, _z, _x0, _y0, _x1, _y1);
|
||||
_x = _x0; _y = _y0;
|
||||
@@ -174,6 +193,8 @@ private:
|
||||
char _attr[96] = "";
|
||||
char _host[48] = "";
|
||||
char _msg[64] = "";
|
||||
TileArea _job = {0, 0, 0, 0, 0, 0};
|
||||
bool _has_job = false, _job_checked = false;
|
||||
|
||||
void fail(const char* m) { snprintf(_msg, sizeof(_msg), "%s", m); _state = FAILED; }
|
||||
|
||||
@@ -181,6 +202,35 @@ private:
|
||||
snprintf(_msg, sizeof(_msg), "%s", m);
|
||||
_state = s;
|
||||
lvport::netEnd();
|
||||
if (s == DONE) discardJob(); // anything else stays resumable
|
||||
}
|
||||
|
||||
void jobPath(char* out, size_t n) const { snprintf(out, n, "%s/.job", _root); }
|
||||
|
||||
void writeJob(const TileArea& a) {
|
||||
char path[64];
|
||||
jobPath(path, sizeof(path));
|
||||
makeParents(path);
|
||||
if (FILE* f = fopen(path, "w")) {
|
||||
fprintf(f, "%.6f %.6f %.6f %.6f %d %d\n", a.lon0, a.lat0, a.lon1, a.lat1, a.zmin, a.zmax);
|
||||
fclose(f);
|
||||
}
|
||||
_job = a;
|
||||
_has_job = true;
|
||||
_job_checked = true;
|
||||
}
|
||||
|
||||
bool readJob(TileArea& a) const {
|
||||
char path[64];
|
||||
jobPath(path, sizeof(path));
|
||||
FILE* f = fopen(path, "r");
|
||||
if (!f) return false;
|
||||
TileArea t;
|
||||
int n = fscanf(f, "%lf %lf %lf %lf %d %d", &t.lon0, &t.lat0, &t.lon1, &t.lat1, &t.zmin, &t.zmax);
|
||||
fclose(f);
|
||||
if (n != 6 || t.zmin < 0 || t.zmax > 20 || t.zmin > t.zmax) return false;
|
||||
a = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
void onFailure(const char* m) {
|
||||
|
||||
Reference in New Issue
Block a user