Files
MeshCore/test/fixtures/sensor_ui_polling.cpp
T

249 lines
8.2 KiB
C++

// Production header, begin and loop; only hardware/render boundaries are fake.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
static uint32_t now_ms = 0, read_delay = 0, render_delay = 0, end_delay = 0;
static unsigned reads = 0, renders = 0;
static constexpr int HIGH = 1, LOW = 0, USER_BTN_PRESSED = LOW;
static constexpr int BUTTON_EVENT_NONE = 0;
static constexpr uint32_t BOOT_SCREEN_MILLIS = 4000;
static int button_level = HIGH;
static uint32_t millis() { return now_ms; }
[[maybe_unused]] static int digitalRead(int pin) {
if (pin != 7) std::abort();
++reads;
now_ms += read_delay;
return button_level;
}
struct Radio { bool timing = false; } radio;
namespace mesh {
static bool morseTimingActive(const Radio* r) { return r && r->timing; }
namespace ui { enum class DisplayWake { Button }; }
}
struct Board {
bool external = false, usb = false;
bool isExternalPowered() const { return external; }
bool isUsbHostConnected() const { return usb; }
} board;
struct SensorMesh {
Radio* getProfileRadio() { return &radio; }
bool isDualRadioActive() const { return false; }
} sensor_mesh;
static SensorMesh& activeSensorMesh() { return sensor_mesh; }
struct DisplayDriver {
bool on = true, changed = false;
unsigned frames = 0, wakes = 0;
bool servicePower(bool) { bool result = changed; changed = false; return result; }
bool isOn() const { return on; }
void wake(mesh::ui::DisplayWake) { on = true; ++wakes; }
void startFrame() { ++frames; }
void endFrame() { now_ms += end_delay; }
};
struct MomentaryButton {
int event = BUTTON_EVENT_NONE;
bool held = false;
void begin() { event = BUTTON_EVENT_NONE; }
int check() { int result = event; event = BUTTON_EVENT_NONE; return result; }
bool isWakeHoldActive() const { return held; }
} user_btn;
struct NodePrefs {};
#include "UITask.h"
@BEGIN@
@RESET@
@LOOP@
void UITask::renderCurrScreen() { ++renders; now_ms += render_delay; }
static const char* scenario;
static unsigned checks = 0;
#define CHECK(expr) do { ++checks; if (!(expr)) { \
std::fprintf(stderr, "FAIL %s: %s (line %d)\n", scenario, #expr, __LINE__); \
return 1; } } while (0)
int main(int argc, char** argv) {
if (argc != 2) return 2;
scenario = argv[1];
DisplayDriver display;
NodePrefs prefs;
UITask ui(display);
now_ms = 10000;
if (!std::strcmp(scenario, "zero")) now_ms = 0;
else if (!std::strcmp(scenario, "upper-half")) now_ms = 0x80001000u;
else if (!std::strcmp(scenario, "max")) now_ms = UINT32_MAX;
else if (!std::strcmp(scenario, "wrapped-future")) now_ms = UINT32_MAX - 99u;
else if (!std::strcmp(scenario, "zero-boundary")) now_ms = UINT32_MAX - 199u;
else if (!std::strcmp(scenario, "overdue-wrap") || !std::strcmp(scenario, "morse-wrap"))
now_ms = UINT32_MAX - 1999u;
else if (!std::strcmp(scenario, "forced-refresh")) now_ms = 0x80001000u;
else if (!std::strcmp(scenario, "forced-zero")
|| !std::strcmp(scenario, "momentary-zero-wake")
|| !std::strcmp(scenario, "momentary-zero-morse")) now_ms = UINT32_MAX - 99u;
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
if (!std::strcmp(scenario, "completion-anchor")) {
read_delay = 10;
render_delay = 20;
end_delay = 30;
}
ui.loop();
CHECK(display.frames == 1 && renders == 1);
CHECK(reads == unsigned(TEST_RAW_BUTTON));
if (!std::strcmp(scenario, "zero") || !std::strcmp(scenario, "upper-half")
|| !std::strcmp(scenario, "max") || !std::strcmp(scenario, "wrapped-future")) {
++now_ms;
ui.loop();
CHECK(display.frames == 1 && reads == unsigned(TEST_RAW_BUTTON));
} else if (!std::strcmp(scenario, "cadence")) {
now_ms += 199;
ui.loop();
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
++now_ms;
ui.loop();
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
now_ms += 799;
ui.loop();
CHECK(display.frames == 1);
++now_ms;
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "zero-boundary")) {
now_ms = UINT32_MAX;
ui.loop();
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
now_ms = 0;
ui.loop();
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
now_ms = 799;
ui.loop();
CHECK(display.frames == 1);
++now_ms;
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "overdue-wrap")) {
now_ms = 100;
ui.loop();
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
ui.loop();
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
} else if (!std::strcmp(scenario, "morse-wrap")) {
radio.timing = true;
now_ms += 1500;
ui.loop();
CHECK(display.frames == 1 && reads == 2u * TEST_RAW_BUTTON);
now_ms += 1000;
ui.loop();
CHECK(display.frames == 1 && reads == 3u * TEST_RAW_BUTTON);
radio.timing = false;
ui.loop();
CHECK(display.frames == 2 && reads == 3u * TEST_RAW_BUTTON);
} else if (!std::strcmp(scenario, "forced-refresh")) {
++now_ms;
display.changed = true;
ui.loop();
CHECK(display.frames == 2);
ui.loop();
CHECK(display.frames == 2);
now_ms = UINT32_MAX;
display.changed = true;
ui.loop();
CHECK(display.frames == 3);
now_ms = 0;
ui.loop();
CHECK(display.frames == 3);
} else if (!std::strcmp(scenario, "forced-zero")) {
now_ms = 0;
display.changed = true;
ui.loop();
CHECK(display.frames == 2);
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "forced-morse")) {
++now_ms;
display.changed = true;
radio.timing = true;
ui.loop();
CHECK(display.frames == 1);
++now_ms;
radio.timing = false;
ui.loop();
CHECK(display.frames == 2);
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "momentary-zero-wake")
|| !std::strcmp(scenario, "momentary-zero-morse")) {
const bool during_morse = !std::strcmp(scenario, "momentary-zero-morse");
display.on = false;
radio.timing = during_morse;
now_ms = 0;
user_btn.event = 1;
ui.loop();
#if TEST_MOMENTARY_BUTTON
CHECK(display.on && display.wakes == 2);
CHECK(display.frames == (during_morse ? 1u : 2u));
now_ms = 1;
radio.timing = false;
ui.loop();
CHECK(display.frames == 2);
ui.loop();
CHECK(display.frames == 2);
#else
CHECK(!display.on && display.frames == 1 && display.wakes == 0);
#endif
} else if (!std::strcmp(scenario, "long-display-off")) {
display.on = false;
for (unsigned day = 1; day <= 26; ++day) {
now_ms = 10000u + day * 86400000u;
ui.loop();
CHECK(display.frames == 1);
}
display.on = true; // No force event: elapsed work must remain due.
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "completion-anchor")) {
const uint32_t poll_finished = 10000u + (TEST_RAW_BUTTON ? 10u : 0u);
const uint32_t frame_finished = poll_finished + 50u;
read_delay = render_delay = end_delay = 0;
now_ms = poll_finished + 199u;
ui.loop();
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
++now_ms;
ui.loop();
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
now_ms = frame_finished + 999u;
ui.loop();
CHECK(display.frames == 1);
++now_ms;
ui.loop();
CHECK(display.frames == 2);
} else if (!std::strcmp(scenario, "rebegin")) {
++now_ms;
ui.begin(&prefs, "16 Oct 2026", "v1.2.4-test");
ui.loop();
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
} else if (!std::strcmp(scenario, "button-wake")) {
display.on = false;
now_ms += 200;
button_level = LOW;
user_btn.event = 1;
ui.loop();
#if TEST_RAW_BUTTON
CHECK(display.on && display.wakes == 2 && reads == 2);
// Existing raw-button policy keeps the one-second render throttle.
CHECK(display.frames == 1);
#elif TEST_MOMENTARY_BUTTON
CHECK(display.on && display.wakes == 2 && display.frames == 2);
user_btn.held = true;
ui.loop();
CHECK(display.wakes == 3 && display.frames == 2);
#else
CHECK(!display.on && display.wakes == 0 && display.frames == 1);
#endif
} else {
std::fprintf(stderr, "unknown scenario: %s\n", scenario);
return 2;
}
std::printf("PASS %s: %u checks\n", scenario, checks);
}