Public Access
Add a web UI to the clock firmware
- Web UI on port 80: live view of the display, choose the page (clock or a bitmap), bitmaps (list, show, live, edit, delete), draw / image upload with conversion in the browser, live mode, settings, Home Assistant - Live view from a shadow buffer in the BROSE9323 driver (shownPixel) - Settings in NVS with the original behaviour as defaults: schedule times, quarter hours, yawning Pikachu, No WiFi screen, flip time, status LED, Home Assistant broker (restart on change) - Bitmaps on LittleFS; new 320 KB partition, ota slots now 1.31 MB - DisplayController: pages, live mode, settings; app.cpp holds the objects - README for the recovery app; docs for the web UI - Tests: settings, time parsing, bitmap format and names, schedule with settings, pages and live mode (95 total) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c9e0800e36
commit
2b1ac12ed7
34 files changed
+1989
-117
No files matched your search
+328
-25
@@ -12,10 +12,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include "config.h"
|
||||
#include "logic/bitmap_format.h"
|
||||
#include "logic/boot_policy.h"
|
||||
#include "logic/display_controller.h"
|
||||
#include "logic/power_timer.h"
|
||||
#include "logic/schedule.h"
|
||||
#include "logic/settings.h"
|
||||
#include "logic/status_led.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -47,12 +49,14 @@ class FakeFrames : public Frames
|
||||
public:
|
||||
std::vector<std::string> shown;
|
||||
|
||||
void clock(int hour, int minute) override
|
||||
void clock(int hour, int minute, bool evening) override
|
||||
{
|
||||
char s[16];
|
||||
snprintf(s, sizeof(s), "clock %02d:%02d", hour, minute);
|
||||
char s[24];
|
||||
snprintf(s, sizeof(s), "clock %02d:%02d%s", hour, minute, evening ? " evening" : "");
|
||||
shown.push_back(s);
|
||||
}
|
||||
void page() override { shown.push_back("page"); }
|
||||
void live() override { shown.push_back("live"); }
|
||||
void noWifi(int hour, int minute) override
|
||||
{
|
||||
char s[16];
|
||||
@@ -65,6 +69,8 @@ public:
|
||||
std::string last() const { return shown.empty() ? "" : shown.back(); }
|
||||
};
|
||||
|
||||
static const Settings defaults;
|
||||
|
||||
static FakePower power;
|
||||
static FakeFrames frames;
|
||||
static DisplayController *controller;
|
||||
@@ -100,42 +106,42 @@ static void run(int hour, int minute, uint32_t ms = 0)
|
||||
|
||||
void test_schedule_night_until_8()
|
||||
{
|
||||
TEST_ASSERT_TRUE(scheduleFor(0, 0) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(7, 0) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(7, 59) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(0, 0, defaults.schedule) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(7, 0, defaults.schedule) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(7, 59, defaults.schedule) == ScheduleMode::Night);
|
||||
}
|
||||
|
||||
void test_schedule_clock_from_8_to_22()
|
||||
{
|
||||
TEST_ASSERT_TRUE(scheduleFor(8, 0) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(12, 34) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(21, 59) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(8, 0, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(12, 34, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(21, 59, defaults.schedule) == ScheduleMode::Clock);
|
||||
}
|
||||
|
||||
void test_schedule_quarter_hours_until_23_30()
|
||||
{
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 0) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 1) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 14) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 15) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 45) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 0) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 29) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 30) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 0, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 1, defaults.schedule) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 14, defaults.schedule) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 15, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 45, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 0, defaults.schedule) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 29, defaults.schedule) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 30, defaults.schedule) == ScheduleMode::Clock);
|
||||
}
|
||||
|
||||
void test_schedule_night_from_23_31()
|
||||
{
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 31) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 45) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 59) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 31, defaults.schedule) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 45, defaults.schedule) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 59, defaults.schedule) == ScheduleMode::Night);
|
||||
}
|
||||
|
||||
void test_evening_from_17()
|
||||
{
|
||||
TEST_ASSERT_FALSE(isEvening(16));
|
||||
TEST_ASSERT_TRUE(isEvening(17));
|
||||
TEST_ASSERT_TRUE(isEvening(23));
|
||||
TEST_ASSERT_FALSE(isEvening(16, 59, defaults.clock));
|
||||
TEST_ASSERT_TRUE(isEvening(17, 0, defaults.clock));
|
||||
TEST_ASSERT_TRUE(isEvening(23, 0, defaults.clock));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -376,7 +382,7 @@ void test_standby_keeps_the_last_time_and_powers_off()
|
||||
run(22, 0);
|
||||
run(22, 1, POWER_OFF_DELAY + 200);
|
||||
TEST_ASSERT_FALSE(power.is_on);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 22:00", frames.last().c_str()); // no blank
|
||||
TEST_ASSERT_EQUAL_STRING("clock 22:00 evening", frames.last().c_str()); // no blank
|
||||
}
|
||||
|
||||
void test_quarter_hour_powers_on_again_and_redraws()
|
||||
@@ -386,7 +392,7 @@ void test_quarter_hour_powers_on_again_and_redraws()
|
||||
TEST_ASSERT_FALSE(power.is_on);
|
||||
run(22, 15);
|
||||
TEST_ASSERT_TRUE(power.is_on);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 22:15", frames.last().c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("clock 22:15 evening", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_clock_cancels_pending_power_off()
|
||||
@@ -515,6 +521,277 @@ void test_night_without_wifi_still_powers_off()
|
||||
TEST_ASSERT_EQUAL_STRING("blank", frames.last().c_str());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_default_settings_are_valid()
|
||||
{
|
||||
TEST_ASSERT_NULL(validateSettings(defaults));
|
||||
}
|
||||
|
||||
void test_settings_times_must_be_in_order()
|
||||
{
|
||||
Settings s;
|
||||
s.schedule.late_start = 7 * 60; // before day_start
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
s = Settings();
|
||||
s.schedule.night_start = 21 * 60; // before late_start
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
s = Settings();
|
||||
s.schedule.night_start = MINUTES_PER_DAY; // never night: fine
|
||||
TEST_ASSERT_NULL(validateSettings(s));
|
||||
s.schedule.night_start = MINUTES_PER_DAY + 1;
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
}
|
||||
|
||||
void test_settings_flip_time_and_port_ranges()
|
||||
{
|
||||
Settings s;
|
||||
s.flip_time = 49;
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
s.flip_time = 2001;
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
s = Settings();
|
||||
s.home_assistant.port = 0;
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
}
|
||||
|
||||
void test_settings_page_must_be_clock_or_bitmap_name()
|
||||
{
|
||||
Settings s;
|
||||
strcpy(s.page, "My bitmap");
|
||||
TEST_ASSERT_NULL(validateSettings(s));
|
||||
strcpy(s.page, "../evil");
|
||||
TEST_ASSERT_NOT_NULL(validateSettings(s));
|
||||
}
|
||||
|
||||
void test_time_parse_and_format()
|
||||
{
|
||||
uint16_t m = 0;
|
||||
TEST_ASSERT_TRUE(parseTime("08:30", m));
|
||||
TEST_ASSERT_EQUAL(510, m);
|
||||
TEST_ASSERT_TRUE(parseTime("24:00", m));
|
||||
TEST_ASSERT_EQUAL(MINUTES_PER_DAY, m);
|
||||
TEST_ASSERT_FALSE(parseTime("24:01", m));
|
||||
TEST_ASSERT_FALSE(parseTime("12:60", m));
|
||||
TEST_ASSERT_FALSE(parseTime("noon", m));
|
||||
TEST_ASSERT_FALSE(parseTime("12:30x", m));
|
||||
TEST_ASSERT_FALSE(parseTime(nullptr, m));
|
||||
char out[6];
|
||||
formatTime(23 * 60 + 31, out);
|
||||
TEST_ASSERT_EQUAL_STRING("23:31", out);
|
||||
formatTime(MINUTES_PER_DAY, out);
|
||||
TEST_ASSERT_EQUAL_STRING("24:00", out);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bitmap format
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_bitmap_is_84_by_19_in_drawbitmap_format()
|
||||
{
|
||||
TEST_ASSERT_EQUAL(11, BITMAP_ROW_BYTES);
|
||||
TEST_ASSERT_EQUAL(209, BITMAP_BYTES);
|
||||
uint8_t b[BITMAP_BYTES] = {};
|
||||
setBitmapPixel(b, 0, 0, true);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x80, b[0]); // leftmost dot = most significant bit
|
||||
setBitmapPixel(b, 83, 18, true);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x10, b[18 * 11 + 10]); // x = 83: bit 3 of byte 10
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 83, 18));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 84, 0)); // outside
|
||||
setBitmapPixel(b, 0, 0, false);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x00, b[0]);
|
||||
}
|
||||
|
||||
void test_bitmap_hex_round_trip()
|
||||
{
|
||||
uint8_t a[BITMAP_BYTES], b[BITMAP_BYTES];
|
||||
for (int i = 0; i < BITMAP_BYTES; i++)
|
||||
a[i] = (uint8_t)(i * 37);
|
||||
char hex[BITMAP_HEX_LENGTH + 1];
|
||||
bitmapToHex(a, hex);
|
||||
TEST_ASSERT_EQUAL(BITMAP_HEX_LENGTH, (int)strlen(hex));
|
||||
TEST_ASSERT_TRUE(bitmapFromHex(hex, b));
|
||||
TEST_ASSERT_EQUAL_MEMORY(a, b, BITMAP_BYTES);
|
||||
}
|
||||
|
||||
void test_bitmap_hex_rejects_wrong_input()
|
||||
{
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
TEST_ASSERT_FALSE(bitmapFromHex("abcd", b));
|
||||
TEST_ASSERT_FALSE(bitmapFromHex(nullptr, b));
|
||||
std::string bad(BITMAP_HEX_LENGTH, 'f');
|
||||
bad[100] = 'x';
|
||||
TEST_ASSERT_FALSE(bitmapFromHex(bad.c_str(), b));
|
||||
std::string upper(BITMAP_HEX_LENGTH, 'F');
|
||||
TEST_ASSERT_TRUE(bitmapFromHex(upper.c_str(), b));
|
||||
}
|
||||
|
||||
void test_bitmap_names()
|
||||
{
|
||||
TEST_ASSERT_TRUE(validBitmapName("Nintendo"));
|
||||
TEST_ASSERT_TRUE(validBitmapName("Happy Birthday 2"));
|
||||
TEST_ASSERT_TRUE(validBitmapName("a-b_c"));
|
||||
TEST_ASSERT_FALSE(validBitmapName(""));
|
||||
TEST_ASSERT_FALSE(validBitmapName(" lead"));
|
||||
TEST_ASSERT_FALSE(validBitmapName("trail "));
|
||||
TEST_ASSERT_FALSE(validBitmapName("../etc"));
|
||||
TEST_ASSERT_FALSE(validBitmapName("a/b"));
|
||||
TEST_ASSERT_FALSE(validBitmapName("Clock")); // the clock page
|
||||
TEST_ASSERT_TRUE(validBitmapName("123456789012345678901234"));
|
||||
TEST_ASSERT_FALSE(validBitmapName("1234567890123456789012345"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Schedule with settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_schedule_disabled_is_always_on()
|
||||
{
|
||||
ScheduleSettings s;
|
||||
s.enabled = false;
|
||||
TEST_ASSERT_TRUE(scheduleFor(3, 0, s) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 7, s) == ScheduleMode::Clock);
|
||||
}
|
||||
|
||||
void test_schedule_custom_times()
|
||||
{
|
||||
ScheduleSettings s;
|
||||
s.day_start = 6 * 60 + 30;
|
||||
s.late_start = 20 * 60;
|
||||
s.night_start = 21 * 60;
|
||||
TEST_ASSERT_TRUE(scheduleFor(6, 29, s) == ScheduleMode::Night);
|
||||
TEST_ASSERT_TRUE(scheduleFor(6, 30, s) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(20, 1, s) == ScheduleMode::Standby);
|
||||
TEST_ASSERT_TRUE(scheduleFor(20, 30, s) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(21, 0, s) == ScheduleMode::Night);
|
||||
}
|
||||
|
||||
void test_schedule_without_quarter_hours_stays_on_until_night()
|
||||
{
|
||||
ScheduleSettings s;
|
||||
s.quarter_hours = false;
|
||||
TEST_ASSERT_TRUE(scheduleFor(22, 7, s) == ScheduleMode::Clock);
|
||||
TEST_ASSERT_TRUE(scheduleFor(23, 31, s) == ScheduleMode::Night);
|
||||
}
|
||||
|
||||
void test_evening_can_be_disabled()
|
||||
{
|
||||
ClockSettings c;
|
||||
c.evening_enabled = false;
|
||||
TEST_ASSERT_FALSE(isEvening(20, 0, c));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pages and live mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_bitmap_page_is_shown_once()
|
||||
{
|
||||
controller->setPage(Page::Bitmap);
|
||||
run(12, 0);
|
||||
run(12, 1, 5000);
|
||||
TEST_ASSERT_EQUAL(1, (int)frames.shown.size());
|
||||
TEST_ASSERT_EQUAL_STRING("page", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_bitmap_page_redrawn_when_changed()
|
||||
{
|
||||
controller->setPage(Page::Bitmap);
|
||||
run(12, 0);
|
||||
controller->pageChanged();
|
||||
run(12, 0);
|
||||
TEST_ASSERT_EQUAL(2, (int)frames.shown.size());
|
||||
}
|
||||
|
||||
void test_switching_back_to_the_clock_page()
|
||||
{
|
||||
controller->setPage(Page::Bitmap);
|
||||
run(12, 0);
|
||||
controller->setPage(Page::Clock);
|
||||
run(12, 0);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 12:00", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_bitmap_page_follows_the_schedule()
|
||||
{
|
||||
controller->setPage(Page::Bitmap);
|
||||
run(23, 30);
|
||||
run(23, 31, POWER_OFF_DELAY + 200);
|
||||
TEST_ASSERT_EQUAL_STRING("blank", frames.last().c_str());
|
||||
TEST_ASSERT_FALSE(power.is_on);
|
||||
}
|
||||
|
||||
void test_evening_clock_redrawn_when_the_evening_starts()
|
||||
{
|
||||
run(16, 59);
|
||||
run(17, 0);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 17:00 evening", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_settings_change_redraws()
|
||||
{
|
||||
run(18, 0);
|
||||
ClockSettings c;
|
||||
c.evening_enabled = false;
|
||||
controller->setSettings(defaults.schedule, c);
|
||||
run(18, 0);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 18:00", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_live_is_shown_right_away_even_at_night()
|
||||
{
|
||||
run(3, 0);
|
||||
controller->showLive(now);
|
||||
run(3, 0);
|
||||
TEST_ASSERT_TRUE(power.is_on);
|
||||
TEST_ASSERT_EQUAL_STRING("live", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_live_works_before_the_time_is_synced()
|
||||
{
|
||||
controller->showLive(now);
|
||||
controller->update(now, false, 0, 0, true);
|
||||
TEST_ASSERT_EQUAL_STRING("live", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_each_live_image_is_drawn()
|
||||
{
|
||||
controller->showLive(now);
|
||||
run(12, 0);
|
||||
controller->showLive(now);
|
||||
run(12, 0);
|
||||
TEST_ASSERT_EQUAL(2, (int)frames.shown.size());
|
||||
}
|
||||
|
||||
void test_stop_live_returns_to_the_page()
|
||||
{
|
||||
controller->showLive(now);
|
||||
run(12, 0);
|
||||
controller->stopLive();
|
||||
run(12, 0);
|
||||
TEST_ASSERT_EQUAL_STRING("clock 12:00", frames.last().c_str());
|
||||
TEST_ASSERT_FALSE(controller->liveActive());
|
||||
}
|
||||
|
||||
void test_live_ends_after_timeout()
|
||||
{
|
||||
controller->showLive(now);
|
||||
run(12, 0, LIVE_TIMEOUT + 200);
|
||||
TEST_ASSERT_FALSE(controller->liveActive());
|
||||
TEST_ASSERT_EQUAL_STRING("clock 12:00", frames.last().c_str());
|
||||
}
|
||||
|
||||
void test_live_respects_the_power_switch()
|
||||
{
|
||||
controller->setPowerEnabled(false);
|
||||
controller->showLive(now);
|
||||
run(12, 0, 5000);
|
||||
TEST_ASSERT_FALSE(power.is_on);
|
||||
TEST_ASSERT_EQUAL(0, (int)std::count(frames.shown.begin(), frames.shown.end(), std::string("live")));
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -570,5 +847,31 @@ int main()
|
||||
RUN_TEST(test_short_wifi_drop_does_not_change_the_display);
|
||||
RUN_TEST(test_night_without_wifi_still_powers_off);
|
||||
|
||||
RUN_TEST(test_default_settings_are_valid);
|
||||
RUN_TEST(test_settings_times_must_be_in_order);
|
||||
RUN_TEST(test_settings_flip_time_and_port_ranges);
|
||||
RUN_TEST(test_settings_page_must_be_clock_or_bitmap_name);
|
||||
RUN_TEST(test_time_parse_and_format);
|
||||
RUN_TEST(test_bitmap_is_84_by_19_in_drawbitmap_format);
|
||||
RUN_TEST(test_bitmap_hex_round_trip);
|
||||
RUN_TEST(test_bitmap_hex_rejects_wrong_input);
|
||||
RUN_TEST(test_bitmap_names);
|
||||
RUN_TEST(test_schedule_disabled_is_always_on);
|
||||
RUN_TEST(test_schedule_custom_times);
|
||||
RUN_TEST(test_schedule_without_quarter_hours_stays_on_until_night);
|
||||
RUN_TEST(test_evening_can_be_disabled);
|
||||
RUN_TEST(test_bitmap_page_is_shown_once);
|
||||
RUN_TEST(test_bitmap_page_redrawn_when_changed);
|
||||
RUN_TEST(test_switching_back_to_the_clock_page);
|
||||
RUN_TEST(test_bitmap_page_follows_the_schedule);
|
||||
RUN_TEST(test_evening_clock_redrawn_when_the_evening_starts);
|
||||
RUN_TEST(test_settings_change_redraws);
|
||||
RUN_TEST(test_live_is_shown_right_away_even_at_night);
|
||||
RUN_TEST(test_live_works_before_the_time_is_synced);
|
||||
RUN_TEST(test_each_live_image_is_drawn);
|
||||
RUN_TEST(test_stop_live_returns_to_the_page);
|
||||
RUN_TEST(test_live_ends_after_timeout);
|
||||
RUN_TEST(test_live_respects_the_power_switch);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user