202 lines
7.5 KiB
C++
202 lines
7.5 KiB
C++
// Actual Sensor UI methods and header; infrastructure init/prefix/metadata only.
|
|
// Hardware, optional status-page and radio dependencies are explicit adapters.
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
static uint32_t now_ms = 0;
|
|
static uint32_t millis() { return now_ms; }
|
|
static constexpr int HIGH = 1;
|
|
enum class UIColor { corp_blue, primary_txt, secondary_txt, warning_txt };
|
|
namespace mesh {
|
|
struct Params { float freq = 915, bw = 125; uint8_t sf = 7, cr = 5; };
|
|
struct RadioProfiles {
|
|
bool enabled() const { return false; }
|
|
bool secondary_temporary = false, primary_temporary = false;
|
|
const Params& params(int) const { static Params value; return value; }
|
|
};
|
|
struct MainBoard {
|
|
bool isExternalPowered() const { return true; }
|
|
bool isUsbHostConnected() const { return true; }
|
|
};
|
|
namespace ui {
|
|
enum class DisplayWake { Button };
|
|
struct RadioProfileSystemStatus {};
|
|
[[maybe_unused]] static bool showSecondaryRadioProfilePage(bool, uint8_t, uint32_t) {
|
|
return false;
|
|
}
|
|
[[maybe_unused]] static bool showRadioProfileSystemStatusPage(bool, uint8_t, uint32_t,
|
|
uint8_t*) { return false; }
|
|
[[maybe_unused]] static const char* radioProfileDisplayTag(int, bool) { return ""; }
|
|
}
|
|
}
|
|
struct DisplayDriver {
|
|
bool on = true, changed = false;
|
|
unsigned frames = 0, logos = 0, homes = 0;
|
|
bool servicePower(bool) { bool result = changed; changed = false; return result; }
|
|
bool isOn() const { return on; }
|
|
void wake(mesh::ui::DisplayWake) { on = true; }
|
|
void startFrame() { ++frames; }
|
|
void endFrame() {}
|
|
void setColor(UIColor) {}
|
|
void setTextSize(float) {}
|
|
void setCursor(int, int) {}
|
|
int width() const { return 128; }
|
|
unsigned getTextWidth(const char* text) const { return std::strlen(text) * 6; }
|
|
void drawXbm(int, int, const uint8_t*, int, int) { ++logos; }
|
|
void print(const char* text) { if (!std::strcmp(text, "normal-node")) ++homes; }
|
|
};
|
|
namespace mesh { namespace ui {
|
|
[[maybe_unused]] static void drawRadioProfileSystemStatusPage(
|
|
DisplayDriver&, const RadioProfileSystemStatus&, uint8_t) {}
|
|
}}
|
|
static mesh::MainBoard board;
|
|
struct Radio { bool timing = false; } radio;
|
|
namespace mesh {
|
|
[[maybe_unused]] static bool morseTimingActive(const Radio* value) { return value->timing; }
|
|
}
|
|
struct SensorMesh {
|
|
Radio* getProfileRadio() { return &radio; }
|
|
bool isDualRadioActive() const { return false; }
|
|
} sensor_mesh;
|
|
[[maybe_unused]] static SensorMesh& activeSensorMesh() { return sensor_mesh; }
|
|
static SensorMesh& the_mesh = sensor_mesh;
|
|
struct NodePrefs {
|
|
char node_name[32] = "normal-node";
|
|
float freq = 915, bw = 125;
|
|
uint8_t sf = 7, cr = 5;
|
|
};
|
|
[[maybe_unused]] static const mesh::RadioProfiles* configuredRadioProfiles() { return nullptr; }
|
|
[[maybe_unused]] static mesh::ui::RadioProfileSystemStatus radioProfileSystemStatus(
|
|
const NodePrefs&) { return {}; }
|
|
[[maybe_unused]] static uint8_t radioProfileStatusPageCount(DisplayDriver&, bool) { return 0; }
|
|
[[maybe_unused]] static const uint8_t meshcore_logo[] = {0};
|
|
#define BOOT_SCREEN_MILLIS 4000
|
|
#include "UITask.h"
|
|
|
|
@METHODS@
|
|
|
|
static const char* scenario;
|
|
static unsigned checks = 0;
|
|
#define CHECK(expression) do { ++checks; if (!(expression)) { \
|
|
std::fprintf(stderr, "FAIL %s: %s (line %d)\n", scenario, #expression, __LINE__); \
|
|
return 1; } } while (0)
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc != 2) return 2;
|
|
scenario = argv[1];
|
|
DisplayDriver display;
|
|
NodePrefs prefs;
|
|
#if TEST_REPEATER
|
|
UITask ui(board, display);
|
|
#else
|
|
UITask ui(display);
|
|
#endif
|
|
#if TEST_SENSOR
|
|
const bool late = !std::strcmp(scenario, "late-begin");
|
|
const bool upper = !std::strcmp(scenario, "upper-begin");
|
|
const bool maximum = !std::strcmp(scenario, "max-begin");
|
|
const bool off_first = !std::strcmp(scenario, "off-first");
|
|
const bool morse_first = !std::strcmp(scenario, "morse-first");
|
|
now_ms = late ? 4000u : upper ? 0x80001000u : maximum ? UINT32_MAX : 0u;
|
|
display.on = !off_first;
|
|
radio.timing = morse_first;
|
|
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
|
ui.loop();
|
|
CHECK(display.logos == unsigned(!late && !upper && !maximum && !off_first && !morse_first));
|
|
CHECK(display.homes == unsigned(late || upper || maximum));
|
|
if (!std::strcmp(scenario, "boundary")) {
|
|
now_ms = 3999;
|
|
display.changed = true;
|
|
ui.loop();
|
|
CHECK(display.logos == 2 && display.homes == 0);
|
|
now_ms = 4000;
|
|
display.changed = true;
|
|
ui.loop();
|
|
CHECK(display.logos == 2 && display.homes == 1);
|
|
} else if (late || upper || maximum) {
|
|
// Re-entering the low boot clock range must not undo begin's late cutoff.
|
|
now_ms = 0;
|
|
display.changed = true;
|
|
ui.loop();
|
|
CHECK(display.logos == 0 && display.homes == 2);
|
|
} else if (!std::strcmp(scenario, "live-wrap")) {
|
|
now_ms = 5000;
|
|
ui.loop();
|
|
CHECK(display.logos == 1 && display.homes == 1);
|
|
for (uint64_t day = 1; day <= 49; ++day) {
|
|
now_ms = static_cast<uint32_t>(day * 86400000ULL);
|
|
ui.loop();
|
|
CHECK(display.logos == 1);
|
|
}
|
|
const unsigned homes = display.homes;
|
|
now_ms = 0; // absolute 2^32, reached after the live checkpoints above
|
|
ui.loop();
|
|
CHECK(display.logos == 1 && display.homes == homes + 1);
|
|
} else if (!std::strcmp(scenario, "off-retirement")
|
|
|| !std::strcmp(scenario, "morse-retirement") || off_first || morse_first) {
|
|
const bool off = off_first || !std::strcmp(scenario, "off-retirement");
|
|
display.on = !off;
|
|
radio.timing = !off;
|
|
const unsigned frames = display.frames, logos = display.logos;
|
|
now_ms = 4000;
|
|
ui.loop();
|
|
CHECK(display.frames == frames);
|
|
now_ms = 0; // full-wrap alias, after observed expiry with no rendered frame
|
|
display.on = true;
|
|
display.changed = true;
|
|
radio.timing = false;
|
|
ui.loop();
|
|
CHECK(display.logos == logos && display.homes == 1);
|
|
} else if (!std::strcmp(scenario, "wake")) {
|
|
now_ms = 4000;
|
|
ui.loop();
|
|
CHECK(display.logos == 1 && display.homes == 1);
|
|
display.on = false;
|
|
now_ms = 12000;
|
|
ui.loop();
|
|
now_ms = 0;
|
|
display.wake(mesh::ui::DisplayWake::Button);
|
|
display.changed = true;
|
|
ui.loop();
|
|
CHECK(display.logos == 1 && display.homes == 2);
|
|
} else if (!std::strcmp(scenario, "rebegin")) {
|
|
now_ms = 4000;
|
|
ui.loop();
|
|
CHECK(display.logos == 1 && display.homes == 1);
|
|
now_ms = 100;
|
|
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
|
ui.loop();
|
|
CHECK(display.logos == 2 && display.homes == 1);
|
|
now_ms = 4100;
|
|
ui.loop();
|
|
CHECK(display.logos == 2 && display.homes == 2);
|
|
now_ms = 5000;
|
|
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
|
ui.loop();
|
|
CHECK(display.logos == 2 && display.homes == 3);
|
|
} else return 3;
|
|
#else
|
|
const bool wrapping = !std::strcmp(scenario, "infra-wrap-start");
|
|
const uint32_t start = wrapping ? UINT32_MAX - 1000u : 1000u;
|
|
now_ms = start;
|
|
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
|
CHECK(ui.testRenderBoot() && ui.testSignatureBoot());
|
|
now_ms = start + 3999u;
|
|
ui.loop();
|
|
CHECK(ui.testRenderBoot() && ui.testSignatureBoot());
|
|
now_ms = start + 4000u;
|
|
ui.loop();
|
|
CHECK(!ui.testRenderBoot() && !ui.testSignatureBoot());
|
|
now_ms = start; // full-wrap alias, same retained boot timestamp
|
|
ui.loop();
|
|
CHECK(!ui.testRenderBoot() && !ui.testSignatureBoot());
|
|
if (!std::strcmp(scenario, "infra-rebegin")) {
|
|
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
|
CHECK(ui.testRenderBoot() && ui.testSignatureBoot());
|
|
} else if (std::strcmp(scenario, "infra-boundary") && !wrapping) return 3;
|
|
#endif
|
|
std::printf("PASS %s (%u checks)\n", scenario, checks);
|
|
}
|