Public Access
Updates from Gitea, step 3: install from Gitea, and IRC steps aside for it
A release downloads straight into the inactive slot through the existing install path: the signature is checked after 160 bytes, before anything is written, the hash at the end. Tried on the device against the real server: a download cut short, a flipped byte in the signature and one in the image are each refused with the running firmware untouched; the real v0.10.0 installed, restarted, and confirmed itself on Probation. A TLS connection to Gitea peaks at about 52 KB of heap whether or not the certificate is verified. With IRC connected (66 KB free) a check left 3 KB and a download 836 bytes. A check, list or install a person asks for now makes IRC step aside (holdForUpdate) and come back after: the lowest free heap during a full download with IRC connected is 38 KB. The daily check never interrupts IRC; with IRC up it waits. A TLS connection starts with 80 KB free (it was 55). Debug Builds get test knobs: update probe <host>, update damage cut|flip, update pretend <version>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -16,7 +16,7 @@ const char* statusText(Status s) {
|
|||||||
case Status::Connecting: return "connecting...";
|
case Status::Connecting: return "connecting...";
|
||||||
case Status::Registering: return "logging in...";
|
case Status::Registering: return "logging in...";
|
||||||
case Status::Online: return "";
|
case Status::Online: return "";
|
||||||
case Status::Paused: return "paused (Wi-Fi monitoring)";
|
case Status::Paused: return "paused (Wi-Fi monitoring or an update)";
|
||||||
case Status::Retrying: return "retrying soon";
|
case Status::Retrying: return "retrying soon";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ void setup() {
|
|||||||
update = new UpdateService(nvs, *wifi, *savedNetworks, *storageService, bus, settings);
|
update = new UpdateService(nvs, *wifi, *savedNetworks, *storageService, bus, settings);
|
||||||
fileOps = new FileOps(*storageService, filesInUse);
|
fileOps = new FileOps(*storageService, filesInUse);
|
||||||
update->enableDailyCheck();
|
update->enableDailyCheck();
|
||||||
|
update->holdMemory = [](bool hold) { irc->holdForUpdate(hold); }; // see UpdateService::holdMemory
|
||||||
irc = new IrcService(nvs, "roro_" + identity::defaultShortName(), *wifi, *storageService, *clockService, bus);
|
irc = new IrcService(nvs, "roro_" + identity::defaultShortName(), *wifi, *storageService, *clockService, bus);
|
||||||
notifier = new Notifier(bus, settings);
|
notifier = new Notifier(bus, settings);
|
||||||
notifier->onShow = [](uint32_t now, uint32_t until) { power->onNotification(now, until); };
|
notifier->onShow = [](uint32_t now, uint32_t until) { power->onNotification(now, until); };
|
||||||
@@ -466,6 +467,13 @@ static void printReleases(bool list) {
|
|||||||
|
|
||||||
static void updateStep() {
|
static void updateStep() {
|
||||||
if (!updateWatch || update->giteaBusy()) return;
|
if (!updateWatch || update->giteaBusy()) return;
|
||||||
|
#ifdef RORO_DEBUG
|
||||||
|
if (updateWatch == 3) {
|
||||||
|
console.printf("update: probe: %s\n", update->probeResult().c_str());
|
||||||
|
updateWatch = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
printReleases(updateWatch == 2);
|
printReleases(updateWatch == 2);
|
||||||
updateWatch = 0;
|
updateWatch = 0;
|
||||||
}
|
}
|
||||||
@@ -492,6 +500,16 @@ static void updateCommand(const String& args) {
|
|||||||
std::string why = update->requestInstall(rest.c_str(), force);
|
std::string why = update->requestInstall(rest.c_str(), force);
|
||||||
console.printf("update: %s\n", why.empty() ? "installing" : why.c_str());
|
console.printf("update: %s\n", why.empty() ? "installing" : why.c_str());
|
||||||
#ifdef RORO_DEBUG
|
#ifdef RORO_DEBUG
|
||||||
|
} else if (args.startsWith("probe ")) { // update probe <host> [path]: is that server's certificate accepted?
|
||||||
|
String rest = args.substring(6);
|
||||||
|
int space = rest.indexOf(' ');
|
||||||
|
update->requestProbe((space < 0 ? rest : rest.substring(0, space)).c_str(), space < 0 ? "/" : rest.substring(space + 1).c_str());
|
||||||
|
updateWatch = 3;
|
||||||
|
} else if (args.startsWith("damage ")) { // update damage cut <bytes> | flip <offset>: for the next download
|
||||||
|
long n = args.substring(args.lastIndexOf(' ') + 1).toInt();
|
||||||
|
args.startsWith("damage cut") ? update->damageNextDownload(n, -1) : update->damageNextDownload(-1, n);
|
||||||
|
console.printf("update: the next download will be %s at byte %ld\n", args.startsWith("damage cut") ? "cut" : "damaged", n);
|
||||||
|
|
||||||
} else if (args.startsWith("pretend ")) { // update pretend v0.9.0 | off: what the comparisons take as running
|
} else if (args.startsWith("pretend ")) { // update pretend v0.9.0 | off: what the comparisons take as running
|
||||||
update->pretendVersion(args.substring(8) == "off" ? "" : args.substring(8).c_str());
|
update->pretendVersion(args.substring(8) == "off" ? "" : args.substring(8).c_str());
|
||||||
console.printf("update: running %s\n", update->runningVersion().c_str());
|
console.printf("update: running %s\n", update->runningVersion().c_str());
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
namespace roro {
|
namespace roro {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr size_t kFloor = 55 * 1024; // Q86, Q172: no connection below this
|
|
||||||
constexpr time_t kClockSetAfter = 1700000000; // 2023-11: anything earlier is the clock's default
|
constexpr time_t kClockSetAfter = 1700000000; // 2023-11: anything earlier is the clock's default
|
||||||
|
|
||||||
// What mbedTLS says in the way it says it, for the cases a person can act on.
|
// What mbedTLS says in the way it says it, for the cases a person can act on.
|
||||||
@@ -30,7 +29,7 @@ std::string whyNotConnected(NetworkClientSecure& tls, const std::string& host) {
|
|||||||
std::string HttpsGet::open(const std::string& host, const std::string& path, const char* accept) {
|
std::string HttpsGet::open(const std::string& host, const std::string& path, const char* accept) {
|
||||||
close();
|
close();
|
||||||
if (time(nullptr) < kClockSetAfter) return "The clock isn't set yet: certificates can't be checked";
|
if (time(nullptr) < kClockSetAfter) return "The clock isn't set yet: certificates can't be checked";
|
||||||
if (esp_get_free_heap_size() < kFloor) return "Not enough memory: close IRC or a Gemini page";
|
if (esp_get_free_heap_size() < kNeedFree) return "Not enough memory for a secure connection";
|
||||||
|
|
||||||
tls_.setCACert(kTrustedRootsPem);
|
tls_.setCACert(kTrustedRootsPem);
|
||||||
tls_.setTimeout(15);
|
tls_.setTimeout(15);
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace roro {
|
|||||||
// release. Redirects aren't followed: the device only goes where it was told to (Q163).
|
// release. Redirects aren't followed: the device only goes where it was told to (Q163).
|
||||||
class HttpsGet {
|
class HttpsGet {
|
||||||
public:
|
public:
|
||||||
|
// A TLS connection to Gitea peaks at about 52 KB of heap (measured: the same with or without
|
||||||
|
// checking the certificate); with Q86's 20 KB to spare, it starts with at least this much free.
|
||||||
|
static constexpr size_t kNeedFree = 80 * 1024;
|
||||||
|
|
||||||
explicit HttpsGet(net::User user) : tls_(user) {}
|
explicit HttpsGet(net::User user) : tls_(user) {}
|
||||||
~HttpsGet() { close(); }
|
~HttpsGet() { close(); }
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class GiteaReleases {
|
|||||||
bool fetchLatest();
|
bool fetchLatest();
|
||||||
bool fetchList();
|
bool fetchList();
|
||||||
|
|
||||||
|
void fail(const std::string& error) { finish(false, error); } // something stopped it before it began
|
||||||
Status status() const;
|
Status status() const;
|
||||||
std::string error() const;
|
std::string error() const;
|
||||||
uint32_t seq() const; // grows with every answer that changed something
|
uint32_t seq() const; // grows with every answer that changed something
|
||||||
|
|||||||
@@ -224,6 +224,14 @@ void IrcService::loop() {
|
|||||||
if (!wanted_) {
|
if (!wanted_) {
|
||||||
if (open_) close("disconnected");
|
if (open_) close("disconnected");
|
||||||
status_ = Status::Stopped;
|
status_ = Status::Stopped;
|
||||||
|
} else if (held_) {
|
||||||
|
if (open_) {
|
||||||
|
conn_->print("QUIT :updating\r\n");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300));
|
||||||
|
close("paused while the device updates");
|
||||||
|
}
|
||||||
|
status_ = Status::Paused;
|
||||||
|
retryAtMs_ = now; // back at once when released
|
||||||
} else if (!wifiUp) {
|
} else if (!wifiUp) {
|
||||||
if (open_) close(monitoring ? "paused for Wi-Fi monitoring" : "Wi-Fi lost");
|
if (open_) close(monitoring ? "paused for Wi-Fi monitoring" : "Wi-Fi lost");
|
||||||
status_ = monitoring ? Status::Paused : Status::WaitingForWifi;
|
status_ = monitoring ? Status::Paused : Status::WaitingForWifi;
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class IrcService : public Service {
|
|||||||
void disconnect();
|
void disconnect();
|
||||||
bool running() const { return wanted_; }
|
bool running() const { return wanted_; }
|
||||||
bool stoppedByUser() const { return stoppedByUser_; }
|
bool stoppedByUser() const { return stoppedByUser_; }
|
||||||
|
|
||||||
|
// A TLS connection to Gitea needs more memory than is left beside this one (#6, Q172): the
|
||||||
|
// Update Service asks IRC to step aside while it talks to the server, and to come back after.
|
||||||
|
// Unlike disconnect(), nothing is remembered as "stopped by the user".
|
||||||
|
void holdForUpdate(bool hold) { held_ = hold; }
|
||||||
Status status() const { return status_; }
|
Status status() const { return status_; }
|
||||||
|
|
||||||
// Read or act on the session while holding its lock: withSession([](IrcSession& s) { ... }).
|
// Read or act on the session while holding its lock: withSession([](IrcSession& s) { ... }).
|
||||||
@@ -86,6 +91,7 @@ class IrcService : public Service {
|
|||||||
volatile bool stopRequested_ = false;
|
volatile bool stopRequested_ = false;
|
||||||
bool quitSent_ = false; // /quit already queued its QUIT
|
bool quitSent_ = false; // /quit already queued its QUIT
|
||||||
volatile bool restart_ = false;
|
volatile bool restart_ = false;
|
||||||
|
volatile bool held_ = false; // stepping aside for an update
|
||||||
volatile Status status_ = Status::Stopped;
|
volatile Status status_ = Status::Stopped;
|
||||||
bool open_ = false;
|
bool open_ = false;
|
||||||
uint32_t retryAtMs_ = 0;
|
uint32_t retryAtMs_ = 0;
|
||||||
|
|||||||
@@ -67,15 +67,21 @@ class FileSource : public UpdateSource {
|
|||||||
// A release being downloaded from Gitea: the same bytes a push or a card would give.
|
// A release being downloaded from Gitea: the same bytes a push or a card would give.
|
||||||
class GiteaSource : public UpdateSource {
|
class GiteaSource : public UpdateSource {
|
||||||
public:
|
public:
|
||||||
explicit GiteaSource(HttpsGet& get) : get_(get) {}
|
GiteaSource(HttpsGet& get, long cutAfter, long flipAt) : get_(get), cut_(cutAfter), flip_(flipAt) {}
|
||||||
int read(uint8_t* buf, size_t len) override { return get_.read(buf, len); }
|
int read(uint8_t* buf, size_t len) override {
|
||||||
|
if (cut_ >= 0 && pos_ >= cut_) return -1; // Debug Builds: the connection "breaks" here
|
||||||
|
int n = get_.read(buf, len);
|
||||||
|
if (n > 0 && flip_ >= pos_ && flip_ < pos_ + n) buf[flip_ - pos_] ^= 1; // and a byte "arrives wrong"
|
||||||
|
if (n > 0) pos_ += n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpsGet& get_;
|
HttpsGet& get_;
|
||||||
|
long cut_, flip_, pos_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr time_t kClockSetAfter = 1700000000; // anything earlier is the clock's default
|
constexpr time_t kClockSetAfter = 1700000000; // anything earlier is the clock's default
|
||||||
constexpr size_t kCheckFloor = 55 * 1024; // Q86, Q172
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -106,7 +112,7 @@ void UpdateService::start() {
|
|||||||
store_.putString("ota_pending", "");
|
store_.putString("ota_pending", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!task_) xTaskCreate(taskEntry, "update", 8192, this, 1, &task_); // TLS to Gitea (#6): measured below
|
if (!task_) xTaskCreate(taskEntry, "update", 7168, this, 1, &task_); // peak 5.4 KB: TLS to Gitea and the signature check (#6)
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateService::bootGuard(KeyValueStore& store) {
|
void UpdateService::bootGuard(KeyValueStore& store) {
|
||||||
@@ -235,7 +241,8 @@ void UpdateService::scheduleDailyCheck(uint32_t nowMs) {
|
|||||||
nextCheckMs_ = nowMs + 3600000; // look again in an hour, in case the day has turned
|
nextCheckMs_ = nowMs + 3600000; // look again in an hour, in case the day has turned
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (esp_get_free_heap_size() < kCheckFloor) {
|
// Never at IRC's expense: with IRC connected there isn't the room (Q172), so this waits.
|
||||||
|
if (esp_get_free_heap_size() < HttpsGet::kNeedFree) {
|
||||||
nextCheckMs_ = nowMs + 600000;
|
nextCheckMs_ = nowMs + 600000;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -243,11 +250,31 @@ void UpdateService::scheduleDailyCheck(uint32_t nowMs) {
|
|||||||
request_ = Request::BackgroundCheck;
|
request_ = Request::BackgroundCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What a person asked for (a check, a list, an install) may take IRC offline for a few seconds:
|
||||||
|
// a TLS connection needs about 80 KB and IRC's own holds 40 KB of what there is (R1, Q172).
|
||||||
|
bool UpdateService::makeRoom() {
|
||||||
|
if (esp_get_free_heap_size() >= HttpsGet::kNeedFree) return true;
|
||||||
|
if (!holdMemory) return false;
|
||||||
|
held_ = true;
|
||||||
|
holdMemory(true);
|
||||||
|
for (int i = 0; i < 40 && esp_get_free_heap_size() < HttpsGet::kNeedFree; i++) delay(100); // its TLS session goes
|
||||||
|
return esp_get_free_heap_size() >= HttpsGet::kNeedFree;
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateService::serve() {
|
void UpdateService::serve() {
|
||||||
Request r = request_;
|
Request r = request_;
|
||||||
if (r == Request::None) return;
|
if (r == Request::None) return;
|
||||||
request_ = Request::None;
|
request_ = Request::None;
|
||||||
serving_ = true;
|
serving_ = true;
|
||||||
|
held_ = false;
|
||||||
|
if (r != Request::BackgroundCheck && r != Request::None) {
|
||||||
|
bool ok = makeRoom();
|
||||||
|
if (!ok) {
|
||||||
|
gitea_.fail("Not enough memory: close a Gemini page");
|
||||||
|
if (r == Request::Install) notify("Update refused: not enough memory", NotificationLevel::Warning);
|
||||||
|
r = Request::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
switch (r) {
|
switch (r) {
|
||||||
case Request::Check:
|
case Request::Check:
|
||||||
case Request::BackgroundCheck: {
|
case Request::BackgroundCheck: {
|
||||||
@@ -268,8 +295,19 @@ void UpdateService::serve() {
|
|||||||
if (gitea_.find(installTag_, release)) installFromGitea(release);
|
if (gitea_.find(installTag_, release)) installFromGitea(release);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#ifdef RORO_DEBUG
|
||||||
|
case Request::Probe: {
|
||||||
|
HttpsGet get(net::User::Updates);
|
||||||
|
std::string why = get.open(probeHost_, probePath_, "*/*");
|
||||||
|
probeResult_ = why.empty() ? "accepted, the server answered 200" : why;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
case Request::None: break;
|
case Request::None: break;
|
||||||
}
|
}
|
||||||
|
// IRC comes back, unless the device is about to restart into an update.
|
||||||
|
if (held_ && phase_ != Phase::Installed && holdMemory) holdMemory(false);
|
||||||
|
held_ = false;
|
||||||
serving_ = false;
|
serving_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +325,12 @@ void UpdateService::installFromGitea(const release::Release& r) {
|
|||||||
notify("Update refused: " + why, NotificationLevel::Warning);
|
notify("Update refused: " + why, NotificationLevel::Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GiteaSource source(get);
|
#ifdef RORO_DEBUG
|
||||||
|
GiteaSource source(get, damageCut_, damageFlip_);
|
||||||
|
damageCut_ = damageFlip_ = -1;
|
||||||
|
#else
|
||||||
|
GiteaSource source(get, -1, -1);
|
||||||
|
#endif
|
||||||
install(source, "Gitea");
|
install(source, "Gitea");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "update_check.h"
|
#include "update_check.h"
|
||||||
@@ -53,12 +54,22 @@ class UpdateService : public Service {
|
|||||||
// it would take its Debug Console away) unless `force`, which only the Debug Console passes.
|
// it would take its Debug Console away) unless `force`, which only the Debug Console passes.
|
||||||
std::string requestInstall(const std::string& tag, bool force = false);
|
std::string requestInstall(const std::string& tag, bool force = false);
|
||||||
bool giteaBusy() const { return request_ != Request::None || serving_; }
|
bool giteaBusy() const { return request_ != Request::None || serving_; }
|
||||||
|
// Asked to make room for a TLS connection (R1, Q172): IRC steps aside while the Update Service
|
||||||
|
// talks to Gitea. Set by the main loop; `true` to step aside, `false` to come back.
|
||||||
|
std::function<void(bool)> holdMemory;
|
||||||
// The daily check (Q165) only runs once the main loop says it may: not in Safe Mode.
|
// The daily check (Q165) only runs once the main loop says it may: not in Safe Mode.
|
||||||
void enableDailyCheck() { dailyCheck_ = true; }
|
void enableDailyCheck() { dailyCheck_ = true; }
|
||||||
// The version that counts as running for comparisons: a Debug Build can pretend to be older.
|
// The version that counts as running for comparisons: a Debug Build can pretend to be older.
|
||||||
std::string runningVersion() const { return fakeVersion_.empty() ? versionString() : fakeVersion_; }
|
std::string runningVersion() const { return fakeVersion_.empty() ? versionString() : fakeVersion_; }
|
||||||
void pretendVersion(const std::string& v) { fakeVersion_ = v; }
|
void pretendVersion(const std::string& v) { fakeVersion_ = v; }
|
||||||
std::string failedVersion() const; // a release that rolled back here, "" if none
|
std::string failedVersion() const; // a release that rolled back here, "" if none
|
||||||
|
#ifdef RORO_DEBUG
|
||||||
|
// Debug Builds only, to try the refusals on the real network path: one HTTPS GET to any host
|
||||||
|
// (is its certificate accepted?), and a download cut short or with one byte flipped.
|
||||||
|
void requestProbe(const std::string& host, const std::string& path) { probeHost_ = host; probePath_ = path; probeResult_ = "..."; request_ = Request::Probe; }
|
||||||
|
std::string probeResult() const { return probeResult_; }
|
||||||
|
void damageNextDownload(long cutAfter, long flipAt) { damageCut_ = cutAfter; damageFlip_ = flipAt; }
|
||||||
|
#endif
|
||||||
|
|
||||||
// The main loop calls this once it has drawn a frame (part of Probation).
|
// The main loop calls this once it has drawn a frame (part of Probation).
|
||||||
void firstFrameDrawn() { firstFrame_ = true; }
|
void firstFrameDrawn() { firstFrame_ = true; }
|
||||||
@@ -66,9 +77,10 @@ class UpdateService : public Service {
|
|||||||
bool rebootPending() const { return phase_ == Phase::Installed; }
|
bool rebootPending() const { return phase_ == Phase::Installed; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Request : uint8_t { None, Check, BackgroundCheck, List, Install };
|
enum class Request : uint8_t { None, Check, BackgroundCheck, List, Install, Probe };
|
||||||
|
|
||||||
void serve(); // on this task: one request
|
void serve(); // on this task: one request
|
||||||
|
bool makeRoom(); // true when a TLS connection can start; may have asked holdMemory
|
||||||
void installFromGitea(const release::Release& release);
|
void installFromGitea(const release::Release& release);
|
||||||
void scheduleDailyCheck(uint32_t nowMs); // from tick()
|
void scheduleDailyCheck(uint32_t nowMs); // from tick()
|
||||||
static void taskEntry(void* self);
|
static void taskEntry(void* self);
|
||||||
@@ -92,9 +104,15 @@ class UpdateService : public Service {
|
|||||||
GiteaReleases gitea_;
|
GiteaReleases gitea_;
|
||||||
volatile Request request_ = Request::None;
|
volatile Request request_ = Request::None;
|
||||||
volatile bool serving_ = false;
|
volatile bool serving_ = false;
|
||||||
|
bool held_ = false; // IRC was asked to step aside, on this task
|
||||||
std::string installTag_, fakeVersion_, announced_;
|
std::string installTag_, fakeVersion_, announced_;
|
||||||
bool dailyCheck_ = false;
|
bool dailyCheck_ = false;
|
||||||
uint32_t nextCheckMs_ = 90000; // not before the device has settled
|
uint32_t nextCheckMs_ = 90000; // not before the device has settled
|
||||||
|
#ifdef RORO_DEBUG
|
||||||
|
std::string probeHost_, probePath_;
|
||||||
|
volatile long damageCut_ = -1, damageFlip_ = -1;
|
||||||
|
std::string probeResult_;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace roro
|
} // namespace roro
|
||||||
|
|||||||
@@ -120,6 +120,18 @@ void test_pause_gnss_for_lora_is_off_by_default() {
|
|||||||
TEST_ASSERT_TRUE(f.settings.getBool(Setting::GnssEnabled)); // the GNSS switch itself is untouched
|
TEST_ASSERT_TRUE(f.settings.getBool(Setting::GnssEnabled)); // the GNSS switch itself is untouched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// R1, Q165: the device looks for a newer release once a day. It installs nothing by itself, so it
|
||||||
|
// is on unless switched off.
|
||||||
|
void test_check_for_updates_is_on_by_default() {
|
||||||
|
Fixture f;
|
||||||
|
int row = f.row(SettingsMenu::Row::CheckUpdates);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Check for updates", f.menu.label(row).c_str());
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Daily", f.menu.value(row).c_str());
|
||||||
|
f.menu.toggle(row);
|
||||||
|
TEST_ASSERT_FALSE(f.settings.getBool(Setting::CheckUpdates));
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Off", f.menu.value(row).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void test_gnss_and_coordinate_rows_toggle() {
|
void test_gnss_and_coordinate_rows_toggle() {
|
||||||
Fixture f;
|
Fixture f;
|
||||||
int gnss = f.row(SettingsMenu::Row::Gnss), coords = f.row(SettingsMenu::Row::Coordinates);
|
int gnss = f.row(SettingsMenu::Row::Gnss), coords = f.row(SettingsMenu::Row::Coordinates);
|
||||||
@@ -145,6 +157,7 @@ int main() {
|
|||||||
RUN_TEST(test_wifi_is_a_page);
|
RUN_TEST(test_wifi_is_a_page);
|
||||||
RUN_TEST(test_names_are_text_rows_with_their_byte_limits);
|
RUN_TEST(test_names_are_text_rows_with_their_byte_limits);
|
||||||
RUN_TEST(test_pause_gnss_for_lora_is_off_by_default);
|
RUN_TEST(test_pause_gnss_for_lora_is_off_by_default);
|
||||||
|
RUN_TEST(test_check_for_updates_is_on_by_default);
|
||||||
RUN_TEST(test_gnss_and_coordinate_rows_toggle);
|
RUN_TEST(test_gnss_and_coordinate_rows_toggle);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user