Files
MeshCore/test/fixtures/rgb_led_rollover.cpp
T

423 lines
15 KiB
C++

// Full actual handler, output enum, ownership setters and RGB writers are injected.
// The clock is uint32_t even on hosts where unsigned long has 64 bits.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace mesh { namespace notify {
@OUTPUT_ENUM@
} }
@CONSTANTS@
static uint32_t clock_ms = 0;
uint32_t millis() { return clock_ms; }
static unsigned writes = 0, batt_reads = 0, power_reads = 0, charge_reads = 0;
static uint32_t batt_delay = 0, power_delay = 0;
#ifdef EXT_CHRG_DETECT
static uint32_t charge_delay = 0;
#endif
static const char* active_case = "startup";
static unsigned checks = 0;
#define CHECK(value) do { \
++checks; \
if (!(value)) { \
std::fprintf(stderr, "FAIL %s: %s (line %d)\n", active_case, #value, __LINE__); \
std::exit(1); \
} \
} while (0)
struct Board {
uint16_t battery = 4000;
bool external = false;
uint16_t getBattMilliVolts() {
++batt_reads;
clock_ms += batt_delay;
return battery;
}
bool isExternalPowered() {
++power_reads;
clock_ms += power_delay;
return external;
}
};
#ifdef STATUS_LED_RGB
static unsigned red = 0, green = 0, blue = 0, channel = 0;
void analogWrite(unsigned pin, unsigned level) {
CHECK(level <= 255);
if (channel == 0) { CHECK(pin == PIN_STATUS_LED_R); red = level; }
else if (channel == 1) { CHECK(pin == PIN_STATUS_LED_G); green = level; }
else { CHECK(pin == PIN_STATUS_LED_B); blue = level; ++writes; }
channel = (channel + 1) % 3;
}
@WRITERS@
#endif
#ifdef EXT_CHRG_DETECT
constexpr unsigned LOW = 0;
static unsigned charge_level = 1;
int digitalRead(unsigned pin) {
CHECK(pin == EXT_CHRG_DETECT);
++charge_reads;
clock_ms += charge_delay;
return int(charge_level);
}
#endif
class UITask {
public:
@DEFAULTS@
@SETTERS@
int _msgcount = 0;
bool _connected = false;
Board board;
Board* _board = &board;
void userLedHandler();
};
@HANDLER@
static void tick(UITask& ui, uint32_t stamp) {
clock_ms = stamp;
ui.userLedHandler();
}
#ifdef STATUS_LED_RGB
static void color(unsigned r, unsigned g, unsigned b) {
CHECK(channel == 0);
CHECK(red == r);
CHECK(green == g);
CHECK(blue == b);
}
static uint32_t boot(UITask& ui, uint32_t start = 100) {
const uint32_t duration = ui._msgcount > 0 ? 800 : 1200;
tick(ui, start);
CHECK(writes == 1);
CHECK(batt_reads == 0);
CHECK(power_reads == 0);
tick(ui, start + duration);
CHECK(writes == 2);
color(0, 0, 0);
if (start + duration != 0) {
CHECK(batt_reads == 1);
#ifdef EXT_CHRG_DETECT
CHECK(power_reads == 1);
#endif
}
return start + duration;
}
static uint32_t heartbeat(UITask& ui, uint32_t start = 100) {
const uint32_t end = boot(ui, start);
tick(ui, end + 1);
CHECK(writes == 3);
return end + 1;
}
static void veto(UITask& ui, bool owner, bool enabled) {
if (owner) ui.setNotificationLedOwnership(enabled);
else ui.setNotificationOutputs(enabled ? uint8_t(31 & ~mesh::notify::Led) : 31);
}
static void suppressLong(UITask& ui, bool owner, bool on) {
uint32_t stamp = heartbeat(ui);
if (!on) { stamp += 21; tick(ui, stamp); }
const unsigned before = writes, battery_before = batt_reads, power_before = power_reads;
veto(ui, owner, true);
constexpr uint32_t idle = 26u * 24u * 60u * 60u * 1000u;
tick(ui, stamp + idle);
CHECK(writes == before);
CHECK(batt_reads == battery_before);
CHECK(power_reads == power_before);
veto(ui, owner, false);
tick(ui, stamp + idle);
CHECK(writes == before + 1);
if (on) color(0, 0, 0); else color(0, 255, 0);
CHECK(batt_reads == battery_before + 1);
#ifdef EXT_CHRG_DETECT
CHECK(power_reads == power_before + 1);
#endif
tick(ui, stamp + idle);
CHECK(writes == before + 1);
}
#endif
// A fresh process for every case leaves actual function statics untouched.
int main(int argc, char** argv) {
CHECK(argc == 2);
active_case = argv[1];
const char* name = argv[1];
UITask ui;
#ifdef STATUS_LED_RGB
if (!std::strcmp(name, "boot-zero") || !std::strcmp(name, "boot-upper") ||
!std::strcmp(name, "boot-wrap") || !std::strcmp(name, "boot-zero-end")) {
const uint32_t start = !std::strcmp(name, "boot-zero") ? 0u :
!std::strcmp(name, "boot-upper") ? 0x80000000u :
!std::strcmp(name, "boot-wrap") ? UINT32_MAX - 10u : 0u - 1200u;
tick(ui, start);
CHECK(writes == 1);
if (start == 0) color(255, 0, 0);
tick(ui, start + 1199);
CHECK(writes == 2);
CHECK(batt_reads == 0);
CHECK(power_reads == 0);
tick(ui, start + 1200);
CHECK(writes == 3);
color(0, 0, 0);
CHECK(batt_reads == unsigned(start + 1200 != 0));
tick(ui, start + 1200);
CHECK(writes == 3);
tick(ui, start + 1201);
CHECK(writes == 4);
color(0, 255, 0);
CHECK(batt_reads == 1);
} else if (!std::strcmp(name, "wheel-colors")) {
tick(ui, 0); color(255, 0, 0);
tick(ui, 399); color(3, 252, 0);
tick(ui, 400); color(0, 255, 0);
tick(ui, 800); color(0, 0, 255);
tick(ui, 1199); color(252, 0, 3);
CHECK(batt_reads == 0); CHECK(power_reads == 0);
} else if (!std::strcmp(name, "message-first")) {
ui._msgcount = 1;
const uint32_t end = boot(ui, UINT32_MAX - 400u);
tick(ui, end + 1);
CHECK(writes == 3);
color(255, 90, 0);
} else if (!std::strcmp(name, "message") || !std::strcmp(name, "message-wrap") ||
!std::strcmp(name, "message-retrigger") || !std::strcmp(name, "message-decrease")) {
boot(ui);
const uint32_t start = !std::strcmp(name, "message-wrap") ? UINT32_MAX - 100u : 2000u;
ui._msgcount = 1;
tick(ui, start);
CHECK(writes == 3);
const unsigned battery_before = batt_reads;
tick(ui, start + 799);
CHECK(writes == 4);
CHECK(batt_reads == battery_before);
uint32_t end = start + 800;
if (!std::strcmp(name, "message-retrigger")) {
ui._msgcount = 2;
tick(ui, start + 799);
CHECK(writes == 5);
end = start + 1599;
tick(ui, end - 1);
CHECK(writes == 6);
} else if (!std::strcmp(name, "message-decrease")) {
ui._msgcount = 0;
}
const unsigned before = writes;
tick(ui, end);
CHECK(writes == before + 1);
color(0, 0, 0);
tick(ui, end + 1);
CHECK(writes == before + 2);
if (ui._msgcount) color(255, 90, 0); else color(0, 255, 0);
} else if (!std::strcmp(name, "normal") || !std::strcmp(name, "unread") ||
!std::strcmp(name, "connected") || !std::strcmp(name, "low-battery") ||
!std::strcmp(name, "threshold")) {
if (!std::strcmp(name, "unread")) ui._msgcount = 1;
if (!std::strcmp(name, "connected")) ui._connected = true;
if (!std::strcmp(name, "low-battery")) {
ui.board.battery = 3499;
ui._connected = true;
ui._msgcount = 1;
}
if (!std::strcmp(name, "threshold")) ui.board.battery = 3500;
const uint32_t on = heartbeat(ui);
if (ui.board.battery < 3500) color(255, 0, 0);
else if (ui._msgcount) color(255, 90, 0);
else if (ui._connected) color(0, 0, 255);
else color(0, 255, 0);
const uint32_t duration = ui._msgcount ? 200 : 20;
tick(ui, on + duration); CHECK(writes == 3);
tick(ui, on + duration + 1); CHECK(writes == 4); color(0, 0, 0);
tick(ui, on + 4001); CHECK(writes == 4);
tick(ui, on + 4002); CHECK(writes == 5);
} else if (!std::strcmp(name, "heartbeat-wrap-on") || !std::strcmp(name, "heartbeat-wrap-off") ||
!std::strcmp(name, "heartbeat-zero-stamp")) {
const uint32_t on = heartbeat(ui);
tick(ui, on + 21); CHECK(writes == 4);
const bool zero = !std::strcmp(name, "heartbeat-zero-stamp");
const uint32_t near = zero ? 0u - 21u : UINT32_MAX - 10u;
tick(ui, near); CHECK(writes == 5); color(0, 255, 0);
if (!std::strcmp(name, "heartbeat-wrap-off")) {
tick(ui, 100); CHECK(writes == 6); color(0, 0, 0);
tick(ui, 4080); CHECK(writes == 6);
tick(ui, 4081); CHECK(writes == 7);
} else {
tick(ui, near + 20); CHECK(writes == 5);
tick(ui, near + 21); CHECK(writes == 6); color(0, 0, 0);
tick(ui, near + 4001); CHECK(writes == 6);
tick(ui, near + 4002); CHECK(writes == 7);
}
} else if (!std::strcmp(name, "heartbeat-long")) {
const uint32_t on = heartbeat(ui);
tick(ui, on + 100000); CHECK(writes == 4); color(0, 0, 0);
tick(ui, on + 100000); CHECK(writes == 4);
tick(ui, on + 103980); CHECK(writes == 4);
tick(ui, on + 103981); CHECK(writes == 5);
} else if (!std::strcmp(name, "heartbeat-msg-phase")) {
ui._msgcount = 1;
const uint32_t on = heartbeat(ui);
ui._msgcount = 0;
tick(ui, on + 200); CHECK(writes == 3);
tick(ui, on + 201); CHECK(writes == 4);
tick(ui, on + 4001); CHECK(writes == 4);
tick(ui, on + 4002); CHECK(writes == 5); color(0, 255, 0);
tick(ui, on + 4022); CHECK(writes == 5);
tick(ui, on + 4023); CHECK(writes == 6);
} else if (!std::strcmp(name, "battery-cadence")) {
const uint32_t end = boot(ui);
tick(ui, end + 60000); CHECK(batt_reads == 1);
tick(ui, end + 60001); CHECK(batt_reads == 2);
tick(ui, end + 60001); CHECK(batt_reads == 2);
} else if (!std::strcmp(name, "poll-wrap-before") || !std::strcmp(name, "poll-wrap-after")) {
const bool before = !std::strcmp(name, "poll-wrap-before");
const uint32_t end = boot(ui, UINT32_MAX - (before ? 2000u : 62000u));
tick(ui, before ? end + 1 : 100u);
CHECK(batt_reads == unsigned(before ? 1 : 2));
#ifdef EXT_CHRG_DETECT
CHECK(power_reads == unsigned(before ? 1 : 2));
#endif
} else if (!std::strcmp(name, "poll-zero-stamp")) {
const uint32_t end = boot(ui, UINT32_MAX - 62000u);
CHECK(end != 0);
tick(ui, 0); CHECK(batt_reads == 2);
tick(ui, 1); CHECK(batt_reads == 2);
tick(ui, 60000); CHECK(batt_reads == 2);
tick(ui, 60001); CHECK(batt_reads == 3);
#ifdef EXT_CHRG_DETECT
CHECK(power_reads == 3);
#endif
} else if (!std::strcmp(name, "poll-long") || !std::strcmp(name, "poll-near-full")) {
const uint32_t end = boot(ui);
const uint32_t gap = !std::strcmp(name, "poll-long") ?
26u * 24u * 60u * 60u * 1000u : UINT32_MAX - 100u;
tick(ui, end + gap); CHECK(batt_reads == 2);
#ifdef EXT_CHRG_DETECT
CHECK(power_reads == 2);
#endif
tick(ui, end + gap); CHECK(batt_reads == 2);
} else if (!std::strcmp(name, "poll-read-delays")) {
batt_delay = 25;
power_delay = 17;
#ifdef EXT_CHRG_DETECT
charge_delay = 9;
#endif
const uint32_t end = boot(ui);
tick(ui, end + 60001); CHECK(batt_reads == 2);
uint32_t expected_clock = end + 60001 + 25;
#ifdef EXT_CHRG_DETECT
expected_clock += 17;
#endif
CHECK(clock_ms == expected_clock);
tick(ui, end + 120002); CHECK(batt_reads == 3);
#ifdef EXT_CHRG_DETECT
const unsigned before = power_reads;
tick(ui, end + 121002); CHECK(power_reads == before);
tick(ui, end + 121003); CHECK(power_reads == before + 1);
#endif
} else if (!std::strcmp(name, "owner-fresh") || !std::strcmp(name, "mask-fresh")) {
const bool owner = !std::strcmp(name, "owner-fresh");
veto(ui, owner, true);
tick(ui, 0); tick(ui, 0x80000000u);
CHECK(batt_reads == 0); CHECK(power_reads == 0); CHECK(writes == 0);
veto(ui, owner, false);
boot(ui, 0x80000000u);
} else if (!std::strcmp(name, "owner-flourish-short") || !std::strcmp(name, "mask-flourish-long")) {
const bool short_gap = !std::strcmp(name, "owner-flourish-short");
tick(ui, 100); CHECK(writes == 1);
veto(ui, short_gap, true);
const uint32_t now = short_gap ? 500u : 0x80000000u;
tick(ui, now); CHECK(writes == 1); CHECK(batt_reads == 0);
veto(ui, short_gap, false);
tick(ui, now); CHECK(writes == 2);
if (short_gap) {
CHECK(batt_reads == 0);
tick(ui, 1300); CHECK(writes == 3); color(0, 0, 0);
} else { CHECK(batt_reads == 1); color(0, 0, 0); }
} else if (!std::strcmp(name, "owner-on-long")) {
suppressLong(ui, true, true);
} else if (!std::strcmp(name, "owner-off-long")) {
suppressLong(ui, true, false);
} else if (!std::strcmp(name, "mask-on-long")) {
suppressLong(ui, false, true);
} else if (!std::strcmp(name, "mask-off-long")) {
suppressLong(ui, false, false);
} else if (!std::strcmp(name, "both-vetoes")) {
ui.setNotificationLedOwnership(true); ui.setNotificationOutputs(0);
tick(ui, 100); CHECK(writes == 0);
ui.setNotificationLedOwnership(false);
tick(ui, 200); CHECK(writes == 0);
ui.setNotificationOutputs(31);
boot(ui, 200);
#ifdef EXT_CHRG_DETECT
} else if (!std::strcmp(name, "power-cadence")) {
const uint32_t end = boot(ui);
tick(ui, end + 1000); CHECK(power_reads == 1);
tick(ui, end + 1001); CHECK(power_reads == 2);
tick(ui, end + 1001); CHECK(power_reads == 2);
} else if (!std::strcmp(name, "charging") || !std::strcmp(name, "charged")) {
boot(ui);
ui.board.external = true;
charge_level = !std::strcmp(name, "charging") ? 0 : 1;
tick(ui, 3000); CHECK(charge_reads == 1);
if (charge_level) color(0, 40, 0); else color(255, 89, 0);
tick(ui, 3500); CHECK(charge_reads == 1);
if (charge_level) color(0, 40, 0); else color(127, 44, 0);
} else if (!std::strcmp(name, "charge-unread")) {
boot(ui);
ui.board.external = true; charge_level = 0;
tick(ui, 3000); color(255, 89, 0);
ui._msgcount = 1;
tick(ui, 3100);
tick(ui, 3900); color(0, 0, 0);
tick(ui, 3901); color(255, 90, 0);
} else if (!std::strcmp(name, "charge-to-heartbeat")) {
boot(ui);
ui.board.external = true;
tick(ui, 3000); color(0, 40, 0);
ui.board.external = false;
tick(ui, 4001); color(0, 255, 0);
const unsigned before = writes;
tick(ui, 4021); CHECK(writes == before);
tick(ui, 4022); CHECK(writes == before + 1); color(0, 0, 0);
} else if (!std::strcmp(name, "power-unpowered-charge-skip")) {
boot(ui);
tick(ui, 3000);
CHECK(power_reads == 2); CHECK(charge_reads == 0);
} else if (!std::strcmp(name, "poll-powered-read-delays")) {
boot(ui);
ui.board.external = true; charge_level = 0;
batt_delay = 25; power_delay = 17; charge_delay = 9;
tick(ui, 3000); CHECK(power_reads == 2); CHECK(charge_reads == 1);
CHECK(clock_ms == 3026); color(255, 89, 0);
tick(ui, 4000); CHECK(power_reads == 2); CHECK(charge_reads == 1);
tick(ui, 4001); CHECK(power_reads == 3); CHECK(charge_reads == 2);
CHECK(clock_ms == 4027);
#endif
} else {
std::fprintf(stderr, "unknown case: %s\n", name);
return 2;
}
#else
CHECK(std::strcmp(name, "no-led") == 0);
const uint32_t stamps[] = {0u, 1u, 0x7fffffffu, 0x80000000u, UINT32_MAX};
for (uint32_t stamp : stamps) {
tick(ui, stamp);
ui.setNotificationLedOwnership(true); tick(ui, stamp);
ui.setNotificationLedOwnership(false); ui.setNotificationOutputs(0); tick(ui, stamp);
ui.setNotificationOutputs(31);
}
CHECK(writes == 0); CHECK(batt_reads == 0); CHECK(power_reads == 0); CHECK(charge_reads == 0);
#endif
#ifndef EXT_CHRG_DETECT
CHECK(charge_reads == 0);
#endif
std::printf("PASS %s: %u checks\n", name, checks);
}