mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
fix: review pass - Discover's late replies dropped and its way back usable, the Gallery's newest first and faster, the network clock's toast
- Discover: a reply after its try timed out, was stopped or was overtaken went on to the normal handling and became the contact's route (and sent a path back); the last tags sent are kept and a late reply is dropped. Leaving Route on the L2 stops a discovery as it stops a trace. - Discover: a route whose reply came back another way offers that way too (L2 There / Back, L1 a Back row), turned round into a route there, so a trace checks it; the Discover button no longer sits on its group. - Screenshots: numbered one past the highest (one pass over the folder, not a stat per number), so one taken after a delete is still the newest. - Gallery: the newest 300 kept whatever order the folder lists them in; BMP rows read in the file's order and several at a time; the thumbnail buffer sized to the page; the folder read once per visit; a failed open frees or restores the picture. - NTP: the slot the router's server goes in emptied before joining, so a cached address isn't taken for it; "Clock set from the network" judged against the clock a tick before (SNTP has set the system time already). - Sim: sim_scroll up stops at the top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,7 +94,10 @@ on the map. On the **Discover** row, **Left / Right** sets 1, 3 or 5 tries
|
||||
and **Enter** starts it (or stops it). Each try waits for its reply or its
|
||||
timeout, then 20 seconds more, so the floods don't come all at once. Each
|
||||
route found is listed under it with how many times it came; **Enter** on one
|
||||
puts it in the list, ready to **Trace** and save. A repeater you don't have
|
||||
puts it in the list, ready to **Trace** and save. When the reply came back
|
||||
another way, a **Back** row follows it: that way turned round, as a route to
|
||||
the node, so a trace checks it too (a trace goes there and back on the same
|
||||
repeaters). A repeater you don't have
|
||||
as a contact shows as its hash (`?8D`). Nothing is saved until you save it.
|
||||
|
||||
Only a node that answers requests can be asked: a companion whose
|
||||
@@ -119,7 +122,8 @@ straight to the node. The route a node sends back usually replaces it again;
|
||||
> **Discover** is under the route, in **Find the route**, with the number of
|
||||
> tries beside it and where it is (the try waiting for its reply, or the
|
||||
> seconds to the next one). Tap a route found to use it; the way back is under
|
||||
> it.
|
||||
> it. When the way back is another one, **There** and **Back** put either in
|
||||
> the list to trace.
|
||||
|
||||
## Your own contact
|
||||
|
||||
|
||||
@@ -1079,6 +1079,8 @@ uint32_t MyMesh::sendPathDiscovery(const uint8_t* pub_key, DiscoverCallback cb,
|
||||
uint32_t tag, est;
|
||||
if (sendDiscoveryReq(*recipient, tag, est) == MSG_SEND_FAILED) return 0;
|
||||
_ui_discovery = tag;
|
||||
_ui_disc_sent[_ui_disc_next] = tag;
|
||||
_ui_disc_next = (_ui_disc_next + 1) % UI_DISC_KEEP;
|
||||
_discover_cb = cb;
|
||||
if (est_timeout) *est_timeout = est;
|
||||
return tag;
|
||||
@@ -1089,10 +1091,16 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
||||
uint32_t tag;
|
||||
memcpy(&tag, extra, 4);
|
||||
|
||||
if (_ui_discovery && tag == _ui_discovery) { // this device's own (sendPathDiscovery())
|
||||
// This device's own (sendPathDiscovery()): to the editor while it waits
|
||||
// for it; late (timed out, stopped, an earlier try's) only dropped.
|
||||
for (int k = 0; k < UI_DISC_KEEP; k++) {
|
||||
if (!tag || _ui_disc_sent[k] != tag) continue;
|
||||
_ui_disc_sent[k] = 0;
|
||||
if (tag == _ui_discovery) {
|
||||
_ui_discovery = 0;
|
||||
if (_discover_cb && mesh::Packet::isValidPathLen(in_path_len) && mesh::Packet::isValidPathLen(out_path_len))
|
||||
_discover_cb(contact.id.pub_key, out_path, out_path_len, in_path, in_path_len);
|
||||
}
|
||||
return false; // DON'T send reciprocal path, nor take it: the user picks
|
||||
}
|
||||
|
||||
|
||||
@@ -679,6 +679,9 @@ private:
|
||||
uint32_t pending_status;
|
||||
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
|
||||
uint32_t _ui_discovery = 0; // sendPathDiscovery()'s, answered to _discover_cb
|
||||
static const int UI_DISC_KEEP = 6;
|
||||
uint32_t _ui_disc_sent[UI_DISC_KEEP] = {}; // the last ones sent: a late reply is dropped, not taken as the route
|
||||
uint8_t _ui_disc_next = 0;
|
||||
DiscoverCallback _discover_cb = nullptr;
|
||||
int sendDiscoveryReq(ContactInfo& recipient, uint32_t& tag, uint32_t& est_timeout);
|
||||
uint32_t pending_req; // pending _BINARY_REQ
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
// save() -- the list becomes the contact's out_path
|
||||
// discover(tries) -- asks the node by flood which way it's reached (path
|
||||
// discovery), `tries` times with a pause between; the
|
||||
// routes found, useFound() puts one in the list
|
||||
// routes found, useFound() puts one in the list -- the
|
||||
// way there, or the reply's way back turned round
|
||||
//
|
||||
// A TRACE is answered only by the node that hears the last retransmission, so
|
||||
// a route is traced there and back (A B dest B A): every node of it has to
|
||||
@@ -299,30 +300,48 @@ public:
|
||||
int foundCount() const { return _found_n; }
|
||||
const Found& found(int i) const { return _found[i]; }
|
||||
static int hopsOf(uint8_t len) { return len & 63; }
|
||||
// Route i's hops, "Name > Name" (hash "?3F" for one not a contact), cut to fit.
|
||||
void foundText(int i, bool back, char* out, size_t n) const {
|
||||
// Route i's hops, "Name > Name" (hash "?3F" for one not a contact), cut to
|
||||
// fit. The way back as the reply came (from the node), or with `as_route`
|
||||
// turned round, from here: as useFound(i, true) lists it.
|
||||
void foundText(int i, bool back, char* out, size_t n, bool as_route = false) const {
|
||||
const Found& f = _found[i];
|
||||
const uint8_t len = back ? f.back_len : f.out_len;
|
||||
const uint8_t* p = back ? f.back : f.out;
|
||||
const int hops = hopsOf(len), size = (len >> 6) + 1;
|
||||
const int kept = hops < FOUND_HOPS ? hops : FOUND_HOPS;
|
||||
const bool rev = back && as_route;
|
||||
size_t o = 0;
|
||||
out[0] = '\0';
|
||||
if (hops == 0) { snprintf(out, n, "direct"); return; }
|
||||
for (int h = 0; h < hops && h < FOUND_HOPS && o + 4 < n; h++) {
|
||||
for (int h = 0; h < kept && o + 4 < n; h++) {
|
||||
char nm[NAME_LEN];
|
||||
hashName(&p[h * size], size, nm, sizeof(nm));
|
||||
hashName(&p[(rev ? kept - 1 - h : h) * size], size, nm, sizeof(nm));
|
||||
o += snprintf(out + o, n - o, "%s%s", h ? " > " : "", nm);
|
||||
}
|
||||
if (hops > FOUND_HOPS && o + 4 < n) snprintf(out + o, n - o, " ...");
|
||||
}
|
||||
// Route i as the list (its repeaters by key where they're contacts).
|
||||
bool useFound(int i) {
|
||||
// The reply's way back, turned round, is route i itself: one way both ways.
|
||||
bool foundBackSame(int i) const {
|
||||
const Found& f = _found[i];
|
||||
if (f.back_len != f.out_len) return false;
|
||||
const int hops = hopsOf(f.out_len) < FOUND_HOPS ? hopsOf(f.out_len) : FOUND_HOPS, size = (f.out_len >> 6) + 1;
|
||||
for (int h = 0; h < hops; h++)
|
||||
if (memcmp(&f.out[h * size], &f.back[(hops - 1 - h) * size], size) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
static int foundHops(const Found& f, bool back) { return hopsOf(back ? f.back_len : f.out_len); }
|
||||
// Route i as the list (its repeaters by key where they're contacts): the
|
||||
// way there, or the way back turned round -- a route there too, which a
|
||||
// trace then checks out and back like any.
|
||||
bool useFound(int i, bool back = false) {
|
||||
if (i < 0 || i >= _found_n) return false;
|
||||
const Found& f = _found[i];
|
||||
const int hops = hopsOf(f.out_len), size = (f.out_len >> 6) + 1;
|
||||
const uint8_t len = back ? f.back_len : f.out_len;
|
||||
const uint8_t* p = back ? f.back : f.out;
|
||||
const int hops = hopsOf(len), size = (len >> 6) + 1;
|
||||
cancelTrace();
|
||||
_n = 0;
|
||||
for (int h = 0; h < hops && h < MAX_HOPS; h++) loadHash(&f.out[h * size], size);
|
||||
for (int h = 0; h < hops && h < MAX_HOPS; h++) loadHash(&p[(back ? hops - 1 - h : h) * size], size);
|
||||
_too_long = hops > MAX_HOPS;
|
||||
_edited = true;
|
||||
invalidate();
|
||||
|
||||
@@ -26,6 +26,7 @@ static PSRAM_BSS char s_names[SHOTS_MAX][16];
|
||||
static int s_n = 0;
|
||||
static int s_page = 0; // the grid's page, from the newest
|
||||
static uint8_t s_tab = T_SHOTS;
|
||||
static bool s_listed = false; // s_names read since the gallery opened (pages, tabs reuse it)
|
||||
|
||||
// The grid's thumbnails: one buffer per grid, freed with it (a rebuild makes
|
||||
// the new grid before the old one goes, so each frees only its own).
|
||||
@@ -49,12 +50,17 @@ static const int SWIPE_PX = 40;
|
||||
|
||||
static void scan() {
|
||||
s_n = 0;
|
||||
s_listed = true;
|
||||
DIR* d = opendir(SHOTS_DIR);
|
||||
if (!d) return;
|
||||
while (struct dirent* de = readdir(d)) {
|
||||
size_t l = strlen(de->d_name);
|
||||
if (de->d_name[0] == '.' || l < 5 || l >= sizeof(s_names[0]) || strcasecmp(de->d_name + l - 4, ".bmp") != 0) continue;
|
||||
if (s_n < SHOTS_MAX) snprintf(s_names[s_n++], sizeof(s_names[0]), "%s", de->d_name);
|
||||
if (s_n < SHOTS_MAX) { snprintf(s_names[s_n++], sizeof(s_names[0]), "%s", de->d_name); continue; }
|
||||
// Full: the newest SHOTS_MAX stay, whatever order the folder lists them in.
|
||||
int lo = 0;
|
||||
for (int k = 1; k < s_n; k++) if (strcmp(s_names[k], s_names[lo]) < 0) lo = k;
|
||||
if (strcmp(de->d_name, s_names[lo]) > 0) snprintf(s_names[lo], sizeof(s_names[0]), "%s", de->d_name);
|
||||
}
|
||||
closedir(d);
|
||||
// Newest first: scr_NNNN numbers count up.
|
||||
@@ -89,12 +95,17 @@ static void dscInit(lv_image_dsc_t& d, uint16_t* px, int w, int h) {
|
||||
d.data = (const uint8_t*)px;
|
||||
}
|
||||
|
||||
// An uncompressed 24-bit BMP, read a row at a time (top row = 0).
|
||||
static const int CHUNK_ROWS = 16; // rows read at once for the full view
|
||||
|
||||
// An uncompressed 24-bit BMP, read several rows at a time (top row = 0) in
|
||||
// the file's order, so a seek only goes forward and each read is long.
|
||||
struct Bmp {
|
||||
FILE* f = nullptr;
|
||||
int32_t w = 0, h = 0;
|
||||
uint32_t off = 0, stride = 0;
|
||||
bool bottom_up = true;
|
||||
uint8_t* buf = nullptr; // CHUNK_ROWS rows
|
||||
int ch = 1, c0 = 0, cn = 0; // rows read at a time; rows c0..c0+cn-1 in buf
|
||||
bool open(const char* p) {
|
||||
f = fopen(p, "rb");
|
||||
uint8_t hdr[54];
|
||||
@@ -110,13 +121,26 @@ struct Bmp {
|
||||
stride = ((uint32_t)w * 3 + 3) & ~3u;
|
||||
return true;
|
||||
}
|
||||
bool row(int y, uint8_t* out) { // BGR
|
||||
if (fseek(f, (long)(off + (uint32_t)(bottom_up ? h - 1 - y : y) * stride), SEEK_SET) != 0) return false;
|
||||
return fread(out, 1, (size_t)w * 3, f) == (size_t)w * 3;
|
||||
// Rows lo..hi in buf (hi - lo < ch), with the next ones in the file's
|
||||
// order: a bottom-up file is read from its end, upwards on the screen.
|
||||
bool load(int lo, int hi) {
|
||||
if (lo >= c0 && hi < c0 + cn) return true;
|
||||
int a, z;
|
||||
if (bottom_up) { z = hi; a = hi - ch + 1 < 0 ? 0 : hi - ch + 1; }
|
||||
else { a = lo; z = lo + ch - 1 >= h ? h - 1 : lo + ch - 1; }
|
||||
cn = 0;
|
||||
if (fseek(f, (long)(off + (uint32_t)(bottom_up ? h - 1 - z : a) * stride), SEEK_SET) != 0) return false;
|
||||
const size_t n = (size_t)(z - a + 1) * stride;
|
||||
if (fread(buf, 1, n, f) < n - (stride - (uint32_t)w * 3)) return false; // the last row's padding may be missing
|
||||
c0 = a; cn = z - a + 1;
|
||||
return true;
|
||||
}
|
||||
const uint8_t* row(int y) const { // BGR, after load()
|
||||
return buf + (size_t)(bottom_up ? c0 + cn - 1 - y : y - c0) * stride;
|
||||
}
|
||||
~Bmp() { if (f) fclose(f); }
|
||||
};
|
||||
static PSRAM_BSS uint8_t s_row[2][BMP_MAX_W * 3];
|
||||
static PSRAM_BSS uint8_t s_rows[CHUNK_ROWS * ((BMP_MAX_W * 3 + 3) & ~3)];
|
||||
|
||||
// File `i` scaled into w x h: each pixel the mean of the 2x2 under its
|
||||
// spot (a thumbnail), or the one pixel there (the full view).
|
||||
@@ -125,11 +149,14 @@ static bool decode(int i, uint16_t* out, int w, int h, bool smooth) {
|
||||
path(i, p, sizeof(p));
|
||||
Bmp b;
|
||||
if (!b.open(p)) return false;
|
||||
for (int ty = 0; ty < h; ty++) {
|
||||
b.buf = s_rows;
|
||||
b.ch = smooth ? 2 : CHUNK_ROWS; // a thumbnail skips rows: just the pair it means
|
||||
for (int k = 0; k < h; k++) {
|
||||
const int ty = b.bottom_up ? h - 1 - k : k;
|
||||
const int sy = ty * b.h / h, sy2 = smooth && sy + 1 < b.h ? sy + 1 : sy;
|
||||
if (!b.row(sy, s_row[0]) || (sy2 != sy && !b.row(sy2, s_row[1]))) return false;
|
||||
const uint8_t* r1 = s_row[0];
|
||||
const uint8_t* r2 = sy2 != sy ? s_row[1] : s_row[0];
|
||||
if (!b.load(sy, sy2)) return false;
|
||||
const uint8_t* r1 = b.row(sy);
|
||||
const uint8_t* r2 = b.row(sy2);
|
||||
for (int tx = 0; tx < w; tx++) {
|
||||
const int sx = tx * b.w / w, sx2 = smooth && sx + 1 < b.w ? sx + 1 : sx;
|
||||
const uint8_t* a = r1 + sx * 3; const uint8_t* c = r1 + sx2 * 3;
|
||||
@@ -184,6 +211,7 @@ static void onGalleryViewTouch(lv_event_t* e) {
|
||||
void UITask::showGallery() {
|
||||
_screen = SCR_GALLERY;
|
||||
gallery::s_page = 0;
|
||||
gallery::s_listed = false;
|
||||
buildGallery();
|
||||
}
|
||||
|
||||
@@ -230,7 +258,7 @@ void UITask::buildGallery() {
|
||||
}
|
||||
|
||||
if (!card) { noteLabel(list, "No SD card - screenshots are saved on it.", THEME_FONT_BODY); return; }
|
||||
scan();
|
||||
if (!s_listed) scan();
|
||||
if (s_n == 0) {
|
||||
noteLabel(list, "No screenshots yet. Hold the side button and press the top one to take one.", THEME_FONT_BODY);
|
||||
return;
|
||||
@@ -246,7 +274,7 @@ void UITask::buildGallery() {
|
||||
const int first = s_page * PAGE;
|
||||
const int cells = s_n - first < PAGE ? s_n - first : PAGE;
|
||||
|
||||
uint16_t* px = (uint16_t*)psramBuf<uint16_t>((size_t)PAGE * TW * TH);
|
||||
uint16_t* px = (uint16_t*)psramBuf<uint16_t>((size_t)cells * TW * TH);
|
||||
if (!px) { noteLabel(list, "Out of memory", THEME_FONT_BODY); return; }
|
||||
s_thumbs = px;
|
||||
s_cells = cells;
|
||||
@@ -313,7 +341,13 @@ void UITask::galleryOpen(int i) {
|
||||
if (!s_full) s_full = (uint16_t*)psramBuf<uint16_t>((size_t)SW * SH);
|
||||
if (!s_full) { showToast("Out of memory"); return; }
|
||||
if (s_view_img) lv_image_cache_drop(&s_fdsc);
|
||||
if (!decode(i, s_full, SW, SH, false)) { showToast("Can't read this file"); return; }
|
||||
if (!decode(i, s_full, SW, SH, false)) {
|
||||
// The picture shown (if any) was half overwritten: back to it whole.
|
||||
if (s_view) decode(s_cur, s_full, SW, SH, false);
|
||||
else { free(s_full); s_full = nullptr; }
|
||||
showToast("Can't read this file");
|
||||
return;
|
||||
}
|
||||
s_cur = i;
|
||||
dscInit(s_fdsc, s_full, SW, SH);
|
||||
if (!s_view) {
|
||||
|
||||
@@ -625,7 +625,9 @@ static int netState() {
|
||||
// itself; ntpTake() hands the time on to rtc_clock (a hardware RTC, if any).
|
||||
static volatile uint32_t s_ntp_secs = 0;
|
||||
static char s_ntp_dhcp[16];
|
||||
static void ntpAskDhcp() { esp_sntp_servermode_dhcp(true); }
|
||||
// Slot 0 emptied first: it keeps the address it last used (pool.ntp.org's,
|
||||
// or the last network's router), which ntpStart() would take for this one's.
|
||||
static void ntpAskDhcp() { esp_sntp_setserver(0, nullptr); esp_sntp_servermode_dhcp(true); }
|
||||
static void ntpSynced(struct timeval* tv) { s_ntp_secs = (uint32_t)tv->tv_sec; } // the lwIP task
|
||||
static void ntpStart() {
|
||||
if (esp_sntp_enabled()) esp_sntp_stop();
|
||||
|
||||
@@ -146,6 +146,7 @@ void UITask::buildRoute() {
|
||||
_route_note = noteLabel(scroll, "", THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
_route_disc = flexBox(scroll, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_width(_route_disc, LV_PCT(100));
|
||||
lv_obj_set_style_pad_row(_route_disc, theme::GAP, 0); // Discover's button off the group, as the list's rows
|
||||
_route_disc_shown = 0xFFFFFFFF;
|
||||
|
||||
lv_obj_t* bar = buttonBar(body);
|
||||
@@ -321,18 +322,29 @@ void UITask::routeDiscRebuild() {
|
||||
lv_obj_t* l = groupLine(g, false);
|
||||
noteLabel(l, t, THEME_FONT_SMALL, R.discoverFailed() ? theme::FAIL : theme::TEXT_MUTED);
|
||||
}
|
||||
// Code i * 2 + 1: the way back, turned round into a route there to trace.
|
||||
for (int i = 0; i < R.foundCount(); i++) {
|
||||
const PathEditor::Found& f = R.found(i);
|
||||
char route[96], back[64], sub[96];
|
||||
char route[96], back[64], sub[128];
|
||||
R.foundText(i, false, route, sizeof(route));
|
||||
R.foundText(i, true, back, sizeof(back));
|
||||
const int hops = PathEditor::hopsOf(f.out_len);
|
||||
snprintf(sub, sizeof(sub), "%d hop%s%s, found %u time%s. Back: %s", hops, hops == 1 ? "" : "s",
|
||||
hops > PathEditor::MAX_HOPS ? " (too long to use)" : "", (unsigned)f.times, f.times == 1 ? "" : "s", back);
|
||||
lv_obj_t* r = groupLine(g, true);
|
||||
const bool same = R.foundBackSame(i);
|
||||
int o = snprintf(sub, sizeof(sub), "%d hop%s%s, found %u time%s. ", hops, hops == 1 ? "" : "s",
|
||||
hops > PathEditor::MAX_HOPS ? " (too long to use)" : "", (unsigned)f.times, f.times == 1 ? "" : "s");
|
||||
if (same) snprintf(sub + o, sizeof(sub) - o, "Back the same way");
|
||||
else snprintf(sub + o, sizeof(sub) - o, "Back: %s", back);
|
||||
lv_obj_t* r = groupLine(g, same);
|
||||
groupText(r, route, sub);
|
||||
if (same) {
|
||||
label(r, "Use", THEME_FONT_SMALL, theme::ACCENT);
|
||||
lv_obj_add_event_cb(r, onRouteFound, LV_EVENT_CLICKED, (void*)(uintptr_t)i);
|
||||
lv_obj_add_event_cb(r, onRouteFound, LV_EVENT_CLICKED, (void*)(uintptr_t)(i * 2));
|
||||
} else {
|
||||
lv_obj_t* col = flexBox(r, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(col, 4, 0);
|
||||
lv_obj_set_width(pillButton(col, "There", theme::SURFACE_2, onRouteFound, i * 2), 64);
|
||||
lv_obj_set_width(pillButton(col, "Back", theme::SURFACE_2, onRouteFound, i * 2 + 1), 64);
|
||||
}
|
||||
}
|
||||
lv_obj_t* bar = buttonBar(_route_disc);
|
||||
barButton(bar, R.discovering() ? LV_SYMBOL_CLOSE " Stop" : LV_SYMBOL_REFRESH " Discover", onRouteDiscover, 0, !R.discovering());
|
||||
@@ -350,11 +362,14 @@ void UITask::routeDiscTries(int sel) {
|
||||
if (sel >= 0 && sel < 3) routeview::s_tries = sel;
|
||||
}
|
||||
|
||||
void UITask::routeUseFound(int i) {
|
||||
void UITask::routeUseFound(int code) {
|
||||
PathEditor& R = _core->route;
|
||||
if (PathEditor::hopsOf(R.found(i).out_len) > PathEditor::MAX_HOPS) { showToast("Too long: up to 7 repeaters"); return; }
|
||||
if (!R.useFound(i)) return;
|
||||
showToast("In the list: Trace to check it, Save to use it");
|
||||
const int i = code >> 1;
|
||||
const bool back = code & 1;
|
||||
if (i >= R.foundCount()) return;
|
||||
if (PathEditor::foundHops(R.found(i), back) > PathEditor::MAX_HOPS) { showToast("Too long: up to 7 repeaters"); return; }
|
||||
if (!R.useFound(i, back)) return;
|
||||
showToast(back ? "The way back, turned round, in the list: Trace to check it" : "In the list: Trace to check it, Save to use it");
|
||||
routeRebuild();
|
||||
}
|
||||
|
||||
|
||||
@@ -948,14 +948,19 @@ void UITask::takeScreenshot() {
|
||||
if (!lvport::mountStorage()) { showToast("No SD card"); return; }
|
||||
mkdir("/sdcard", 0777); // the sim's MEMFS may not have it yet (the card: fails, harmless)
|
||||
mkdir("/sdcard/screenshots", 0777);
|
||||
char path[48];
|
||||
struct stat st;
|
||||
int n = 1;
|
||||
for (; n <= 9999; n++) {
|
||||
snprintf(path, sizeof(path), "/sdcard/screenshots/scr_%04d.bmp", n);
|
||||
if (stat(path, &st) != 0) break;
|
||||
// One past the highest number: a deleted one's isn't reused, so the newest
|
||||
// stays first in the Gallery (one pass over the folder, not a stat a file).
|
||||
int n = 0;
|
||||
if (DIR* d = opendir("/sdcard/screenshots")) {
|
||||
while (struct dirent* de = readdir(d)) {
|
||||
int k;
|
||||
if (sscanf(de->d_name, "scr_%4d", &k) == 1 && k > n) n = k;
|
||||
}
|
||||
if (n > 9999) { showToast("Screenshots folder full"); return; }
|
||||
closedir(d);
|
||||
}
|
||||
if (++n > 9999) { showToast("Screenshots folder full"); return; }
|
||||
char path[48];
|
||||
snprintf(path, sizeof(path), "/sdcard/screenshots/scr_%04d.bmp", n);
|
||||
int W, H;
|
||||
uint16_t* px = captureFrame(W, H);
|
||||
if (!px) { showToast("Out of memory"); return; }
|
||||
@@ -1249,8 +1254,9 @@ void UITask::back() {
|
||||
case SCR_BOT: if (_nav_overlay) navClosePopup(); else showHome(); break;
|
||||
case SCR_DIAG: if (_nav_overlay) navClosePopup(); else showHome(); break;
|
||||
case SCR_ADMIN_PICK: showHome(); break;
|
||||
case SCR_ROUTE: // back to the node; a trace in flight is dropped
|
||||
case SCR_ROUTE: // back to the node; a trace or discovery in flight is dropped
|
||||
_core->route.cancelTrace();
|
||||
_core->route.stopDiscover();
|
||||
_screen = SCR_NODE;
|
||||
buildNode();
|
||||
break;
|
||||
@@ -1527,8 +1533,9 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_scroll(int dy) {
|
||||
dbgScrollable(lv_layer_top(), o, h);
|
||||
if (!o) dbgScrollable(lv_screen_active(), o, h);
|
||||
if (!o) return -1;
|
||||
int left = lv_obj_get_scroll_bottom(o);
|
||||
lv_obj_scroll_by(o, 0, -(dy < left ? dy : left), LV_ANIM_OFF);
|
||||
int left = dy >= 0 ? lv_obj_get_scroll_bottom(o) : lv_obj_get_scroll_top(o); // up: no further than the top
|
||||
const int step = dy >= 0 ? (dy < left ? dy : left) : (-dy < left ? dy : -left);
|
||||
lv_obj_scroll_by(o, 0, -step, LV_ANIM_OFF);
|
||||
return left;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
uint32_t routeDiscSig();
|
||||
void routeDiscover();
|
||||
void routeDiscTries(int sel);
|
||||
void routeUseFound(int i);
|
||||
void routeUseFound(int code); // found route code >> 1; bit 0: its way back, turned round
|
||||
void routeAct(uint8_t act);
|
||||
void routeOp(int code);
|
||||
void routeKeep();
|
||||
|
||||
@@ -323,9 +323,11 @@ void UITask::wifiSave() {
|
||||
// From loop(), any screen, once a second: SNTP runs while WiFi is connected
|
||||
// (Settings > WiFi, a map download, an update, the app over WiFi), and each
|
||||
// time it answers the clock takes the time -- ahead or behind, as from GPS.
|
||||
// The first answer of a connection says so when it moved the clock.
|
||||
// The first answer of a connection says so when it moved the clock --
|
||||
// judged against the clock a tick before: without a clock chip rtc_clock is
|
||||
// the system time, which SNTP has set already by the time we hear of it.
|
||||
void UITask::wifiClockTick() {
|
||||
static uint32_t next_ms = 0;
|
||||
static uint32_t next_ms = 0, ref_wall = 0, ref_ms = 0;
|
||||
static bool first = false;
|
||||
if ((int32_t)(millis() - next_ms) < 0) return;
|
||||
next_ms = millis() + 1000;
|
||||
@@ -338,9 +340,11 @@ void UITask::wifiClockTick() {
|
||||
const uint32_t t = lvport::ntpTake();
|
||||
if (t > 1700000000UL) {
|
||||
const uint32_t now = rtc_clock.getCurrentTime();
|
||||
const bool moved = t > now + 1 || now > t + 1;
|
||||
if (moved) rtc_clock.setCurrentTime(t);
|
||||
if (first && moved && !_asleep) showToast("Clock set from the network");
|
||||
if (t > now + 1 || now > t + 1) rtc_clock.setCurrentTime(t); // a clock chip: SNTP set only the system time
|
||||
const uint32_t was = ref_wall + (millis() - ref_ms) / 1000;
|
||||
if (first && (t > was + 2 || was > t + 2) && !_asleep) showToast("Clock set from the network");
|
||||
first = false;
|
||||
}
|
||||
ref_wall = rtc_clock.getCurrentTime();
|
||||
ref_ms = millis();
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ class RouteView {
|
||||
bool _seen_disc = false; // a discovery running at the last pass: its end said once
|
||||
uint8_t _tries = 1; // Discover: triesOf(_tries) tries
|
||||
|
||||
// R_FOUND before R_NODE: like the hops, not an action the cursor follows.
|
||||
enum Row : uint8_t { R_HOP, R_FOUND, R_NODE, R_ADD, R_TRACE, R_PROBE, R_TO_NODE, R_SAVE, R_KEEP, R_DISCOVER };
|
||||
// R_FOUND(_BACK) before R_NODE: like the hops, not an action the cursor follows.
|
||||
enum Row : uint8_t { R_HOP, R_FOUND, R_FOUND_BACK, R_NODE, R_ADD, R_TRACE, R_PROBE, R_TO_NODE, R_SAVE, R_KEEP, R_DISCOVER };
|
||||
static int triesOf(int i) { return i == 0 ? 1 : i == 1 ? 3 : 5; }
|
||||
|
||||
PathEditor& R() { return _task->core().route; }
|
||||
@@ -43,14 +43,17 @@ class RouteView {
|
||||
out[k++] = R_SAVE;
|
||||
out[k++] = R_KEEP;
|
||||
out[k++] = R_DISCOVER;
|
||||
for (int i = 0; i < r.foundCount(); i++) out[k++] = R_FOUND;
|
||||
for (int i = 0; i < r.foundCount(); i++) { // a way back of its own: a row to use that too
|
||||
out[k++] = R_FOUND;
|
||||
if (!r.foundBackSame(i)) out[k++] = R_FOUND_BACK;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
static const int ROWS_MAX = PathEditor::MAX_HOPS + 8 + PathEditor::FOUND_MAX;
|
||||
// Which found route row `idx` is.
|
||||
static const int ROWS_MAX = PathEditor::MAX_HOPS + 8 + 2 * PathEditor::FOUND_MAX;
|
||||
// Which found route row `idx` (R_FOUND or the R_FOUND_BACK after it) is of.
|
||||
static int foundIndex(const Row* kind, int idx) {
|
||||
int f = 0;
|
||||
for (int i = 0; i < idx; i++) if (kind[i] == R_FOUND) f++;
|
||||
int f = -1;
|
||||
for (int i = 0; i <= idx; i++) if (kind[i] == R_FOUND) f++;
|
||||
return f;
|
||||
}
|
||||
|
||||
@@ -192,6 +195,12 @@ public:
|
||||
snprintf(right, sizeof(right), "%ux", (unsigned)r.found(f).times);
|
||||
break;
|
||||
}
|
||||
case R_FOUND_BACK: { // the way the reply came, turned round as the list will have it
|
||||
char route[40];
|
||||
r.foundText(foundIndex(kind, idx), true, route, sizeof(route), true);
|
||||
snprintf(left, sizeof(left), " Back %s", route);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const int rw = right[0] ? display.getTextWidth(right) + display.getCharWidth() : 0;
|
||||
display.drawTextEllipsized(2, y, display.width() - reserve - 4 - rw, left, sel);
|
||||
@@ -260,10 +269,11 @@ public:
|
||||
else if (!r.discover(triesOf(_tries))) _task->showAlert("Send failed", 1000);
|
||||
else _seen_disc = true;
|
||||
break;
|
||||
case R_FOUND: {
|
||||
case R_FOUND: case R_FOUND_BACK: {
|
||||
const int f = foundIndex(kind, _sel);
|
||||
if (PathEditor::hopsOf(r.found(f).out_len) > PathEditor::MAX_HOPS) { _task->showAlert("Too long", 1000); break; }
|
||||
if (r.useFound(f)) { _task->showAlert("In list: Trace, Save", 1200); _sel = 0; }
|
||||
const bool back = k == R_FOUND_BACK;
|
||||
if (PathEditor::foundHops(r.found(f), back) > PathEditor::MAX_HOPS) { _task->showAlert("Too long", 1000); break; }
|
||||
if (r.useFound(f, back)) { _task->showAlert(back ? "Way back in list" : "In list: Trace, Save", 1200); _sel = 0; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user