mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
refactor(companion): shared shutdown steps, SD channel key by secret, fetch race, small UI fixes
- UITaskBase holds what both UIs' shutdown() and low-battery check share: the saves before the goodbye sound, the power-down (GPS through its provider) and the battery EMA and threshold. - L2 SD archive: a channel's file is keyed by its secret only, so renaming keeps the history; old files are renamed once at boot. - L2 fetch: abandoning a request and the task finishing it are one critical section (it could stay DONE for good); TileCache gets the read-side barrier. - Text from a repeater or sensor no longer rings on the device (nothing to open); a settings switch reads any non-zero value as on; the message toast never cuts a letter in half; ui-new's GPS helpers ask UiCore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,8 +46,14 @@ struct Hdr {
|
||||
uint32_t cap, head, count, next_id;
|
||||
};
|
||||
|
||||
// FNV-1a over the channel's name and secret.
|
||||
// FNV-1a over the channel's secret: renaming a channel keeps its archive.
|
||||
static uint32_t chKey(const ChannelDetails& ch) {
|
||||
uint32_t h = 2166136261u;
|
||||
for (size_t i = 0; i < sizeof(ch.channel.secret); i++) { h ^= ch.channel.secret[i]; h *= 16777619u; }
|
||||
return h;
|
||||
}
|
||||
// Earlier builds hashed the name in too (see migrateKeys()).
|
||||
static uint32_t chKeyNamed(const ChannelDetails& ch) {
|
||||
uint32_t h = 2166136261u;
|
||||
for (const char* p = ch.name; *p; p++) { h ^= (uint8_t)*p; h *= 16777619u; }
|
||||
for (size_t i = 0; i < sizeof(ch.channel.secret); i++) { h ^= ch.channel.secret[i]; h *= 16777619u; }
|
||||
@@ -148,6 +154,7 @@ public:
|
||||
// skipped; a channel's entries get its current slot.
|
||||
void restore(MessageHistory& hist) {
|
||||
if (!ready()) return;
|
||||
migrateKeys();
|
||||
uint32_t keys[MAX_GROUP_CHANNELS];
|
||||
bool have[MAX_GROUP_CHANNELS];
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
@@ -174,6 +181,20 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Files named by the old name+secret key move to the secret-only name, once
|
||||
// (a file already there under the new name wins).
|
||||
void migrateKeys() {
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails ch;
|
||||
if (!the_mesh.getChannel(i, ch) || !ch.name[0]) continue;
|
||||
char from[64], to[64];
|
||||
snprintf(from, sizeof(from), "%s/c_%08lx.bin", DIR_PATH, (unsigned long)chKeyNamed(ch));
|
||||
snprintf(to, sizeof(to), "%s/c_%08lx.bin", DIR_PATH, (unsigned long)chKey(ch));
|
||||
struct stat st;
|
||||
if (stat(from, &st) == 0 && stat(to, &st) != 0) rename(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
bool _ok = false, _tried = false;
|
||||
uint32_t _last_try = 0;
|
||||
uint32_t _keep = KEEP[KEEP_DEFAULT];
|
||||
|
||||
@@ -647,6 +647,7 @@ static uint8_t* s_fbuf = nullptr;
|
||||
static size_t s_fcap = 0;
|
||||
static volatile size_t s_flen = 0;
|
||||
static volatile bool s_fabandoned = false;
|
||||
static portMUX_TYPE s_fmux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static TaskHandle_t s_ftask = nullptr;
|
||||
static uint32_t s_fstart_ms = 0;
|
||||
static const uint32_t FETCH_MAX_MS = 30000; // a body still trickling in after this is cut off
|
||||
@@ -727,8 +728,10 @@ static void fetchTask(void*) {
|
||||
snprintf(s_ferr, sizeof(s_ferr), "bad URL");
|
||||
}
|
||||
s_fcode = code;
|
||||
portENTER_CRITICAL(&s_fmux); // against fetchAbandon() on the UI core: test and set as one
|
||||
if (s_fabandoned) { s_fabandoned = false; s_fstate = F_IDLE; } // nobody wants it any more
|
||||
else s_fstate = code == 200 ? F_DONE : F_FAILED;
|
||||
portEXIT_CRITICAL(&s_fmux);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,8 +759,13 @@ static const uint8_t* fetchData(size_t& len) { len = s_flen; return s_fbuf; }
|
||||
static void fetchRelease() { if (s_fstate == F_DONE || s_fstate == F_FAILED) s_fstate = F_IDLE; }
|
||||
// Stop caring about the request in flight; it completes (or fails) on its own.
|
||||
static void fetchAbandon() {
|
||||
// Locked with the task's finish: a request ending between the test and the
|
||||
// set would otherwise stay DONE with nobody to release it (fetchStart() busy
|
||||
// for good) and leave the flag to drop the next request's answer.
|
||||
portENTER_CRITICAL(&s_fmux);
|
||||
if (s_fstate == F_REQUESTED || s_fstate == F_BUSY) s_fabandoned = true;
|
||||
else fetchRelease();
|
||||
else if (s_fstate == F_DONE || s_fstate == F_FAILED) s_fstate = F_IDLE;
|
||||
portEXIT_CRITICAL(&s_fmux);
|
||||
}
|
||||
static const char* fetchError() { return s_ferr; }
|
||||
// WiFi off: the response buffer (grown to the biggest tile or update chunk,
|
||||
|
||||
@@ -382,9 +382,7 @@ uint32_t UITask::idleMillis(uint32_t lv_next) {
|
||||
|
||||
void UITask::shutdown(bool restart) {
|
||||
s_prefs_dirty_ms = s_prefs_save_at = 0;
|
||||
the_mesh.savePrefs();
|
||||
the_mesh.saveRTCTime();
|
||||
the_mesh.flushDirtyContacts();
|
||||
saveBeforeShutdown();
|
||||
if (!trailJournalTick(true)) _core->trail.onShutdown(); // no card: the internal slot, if enabled
|
||||
#ifdef PIN_BUZZER
|
||||
_buzzer.shutdown(); // the goodbye sound, unless muted
|
||||
@@ -392,17 +390,7 @@ void UITask::shutdown(bool restart) {
|
||||
for (uint32_t t0 = millis(); _buzzer.isPlaying() && millis() - t0 < 2500; ) { _buzzer.loop(); delay(10); }
|
||||
#endif
|
||||
#endif
|
||||
if (restart) {
|
||||
_board->reboot();
|
||||
} else {
|
||||
if (_display) _display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
if (_sensors) {
|
||||
LocationProvider* loc = _sensors->getLocationProvider();
|
||||
if (loc) loc->stop();
|
||||
}
|
||||
_board->powerOff();
|
||||
}
|
||||
powerDown(restart, _display, _sensors);
|
||||
}
|
||||
|
||||
// Settings > Power > Battery shutdown (NodePrefs::low_batt_mv), as
|
||||
@@ -411,9 +399,8 @@ void UITask::checkLowBattery() {
|
||||
if ((int32_t)(millis() - _next_batt_ms) < 0) return;
|
||||
_next_batt_ms = millis() + 8000;
|
||||
uint16_t raw = getBattMilliVolts();
|
||||
if (raw > 0) _batt_mv = _batt_mv == 0 ? raw : (uint16_t)((_batt_mv * 4u + raw) / 5u); // EMA, alpha 0.2
|
||||
uint16_t low = _prefs ? _prefs->low_batt_mv : 0;
|
||||
if (low == 0 || _batt_mv == 0 || _batt_mv >= low || _board->isExternalPowered()) return;
|
||||
if (raw > 0) _batt_mv = battEma(_batt_mv, raw);
|
||||
if (!lowBatteryShutdownDue(_batt_mv, _prefs)) return;
|
||||
wake();
|
||||
showToast("Low battery - shutting down", 3000);
|
||||
lv_timer_handler();
|
||||
|
||||
@@ -73,6 +73,9 @@ public:
|
||||
struct Done { int z; int32_t x, y; TileProvider* src; bool overlay_missed; };
|
||||
bool poll(Done& done) {
|
||||
if (_job_state != JOB_DONE) return false;
|
||||
#if defined(ESP32)
|
||||
__sync_synchronize(); // pairs with runJob()'s: the state before the results it covers
|
||||
#endif
|
||||
done = { _job.z, _job.x, _job.y, _job.src, _job.overlay_missed };
|
||||
bool keep = !_job_stale;
|
||||
if (keep && !_job.present) { // remember the miss without holding a buffer slot
|
||||
|
||||
Reference in New Issue
Block a user