Public Access
Home Assistant requests in a task, progress bar, Plex page, bitmap cache
- net/ha_api: the HTTP requests run in a FreeRTOS task on core 0; loop()
only hands over copies through queues. The web UI's test button polls.
No more 3-4 s loop() stalls once a minute; timeout raised to 5 s.
- Progress bar page element (page format 4, v3 pages are converted)
- Plex user setting (settings v6): {plex_user} in entity ids
- Plex page (examples): watchers, play/pause icon, title, progress
- Bitmaps cached in RAM: a LittleFS read took ~100 ms on every render
and web preview; long JSON lists sent in 2 KB pieces
- Backup includes the refresh settings and the Plex user
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
090c2bf8ae
commit
490cefc360
42 files changed
+1074
-175
No files matched your search
@@ -2029,6 +2029,141 @@ void test_frame_check_periodic_resync()
|
||||
TEST_ASSERT_EQUAL_UINT32(0, c.stats().repairs); // not counted as a repair
|
||||
}
|
||||
|
||||
static int countOn(const uint8_t *b)
|
||||
{
|
||||
int n = 0;
|
||||
for (int y = 0; y < BITMAP_HEIGHT; y++)
|
||||
for (int x = 0; x < BITMAP_WIDTH; x++)
|
||||
n += bitmapPixel(b, x, y);
|
||||
return n;
|
||||
}
|
||||
|
||||
static DynamicPage barPage()
|
||||
{
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
PageElement &e = page.elements[0];
|
||||
e.type = ElementType::Bar;
|
||||
strcpy(e.entity, "sensor.{plex_user}_progress");
|
||||
e.x = 10;
|
||||
e.y = 14;
|
||||
e.bar_width = 22; // 20 inside the frame
|
||||
e.bar_height = 4;
|
||||
return page;
|
||||
}
|
||||
|
||||
void test_bar_fill()
|
||||
{
|
||||
PageElement e = barPage().elements[0];
|
||||
TEST_ASSERT_EQUAL(0, barFill(e, nullptr, 20));
|
||||
TEST_ASSERT_EQUAL(0, barFill(e, "unavailable", 20));
|
||||
TEST_ASSERT_EQUAL(0, barFill(e, "-5", 20));
|
||||
TEST_ASSERT_EQUAL(0, barFill(e, "0", 20));
|
||||
TEST_ASSERT_EQUAL(10, barFill(e, "50", 20));
|
||||
TEST_ASSERT_EQUAL(7, barFill(e, "33.3", 20));
|
||||
TEST_ASSERT_EQUAL(20, barFill(e, "100", 20));
|
||||
TEST_ASSERT_EQUAL(20, barFill(e, "250", 20));
|
||||
e.bar_max = 1;
|
||||
e.factor = 0.01f; // 0-100 as 0-1
|
||||
TEST_ASSERT_EQUAL(5, barFill(e, "25", 20));
|
||||
e.bar_max = 7200;
|
||||
e.factor = 1;
|
||||
TEST_ASSERT_EQUAL(10, barFill(e, "3600", 20)); // e.g. seconds of a 2 hour film
|
||||
}
|
||||
|
||||
void test_bar_render()
|
||||
{
|
||||
FakeResources res;
|
||||
DynamicPage page = barPage();
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
uint8_t b[BITMAP_BYTES];
|
||||
ElementBox box;
|
||||
res.value = "50";
|
||||
renderPage(page, at(12, 0), 0, res, b, &box);
|
||||
TEST_ASSERT_EQUAL(10, box.x);
|
||||
TEST_ASSERT_EQUAL(14, box.y);
|
||||
TEST_ASSERT_EQUAL(22, box.width);
|
||||
TEST_ASSERT_EQUAL(4, box.height);
|
||||
// Frame: 2 x 22 + 2 x 2 dots; inside 10 of 20 columns x 2 rows
|
||||
TEST_ASSERT_EQUAL(2 * 22 + 2 * 2 + 10 * 2, countOn(b));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 10, 14)); // corner
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 11, 15)); // first filled dot
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 20, 16)); // last filled dot
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 21, 16)); // empty half
|
||||
|
||||
res.value = nullptr; // unknown: only the frame
|
||||
renderPage(page, at(12, 0), 0, res, b);
|
||||
TEST_ASSERT_EQUAL(2 * 22 + 2 * 2, countOn(b));
|
||||
|
||||
// Without frame, right aligned, inverted over a full background
|
||||
PageElement &e = page.elements[0];
|
||||
e.bar_frame = false;
|
||||
e.align = Align::Right;
|
||||
e.x = 84;
|
||||
e.overlay = Overlay::Invert;
|
||||
page.slot_count = 1;
|
||||
strcpy(page.slots[0].bitmap, "Full");
|
||||
res.value = "100";
|
||||
renderPage(page, at(12, 0), 0, res, b, &box);
|
||||
TEST_ASSERT_EQUAL(62, box.x);
|
||||
TEST_ASSERT_EQUAL(BITMAP_WIDTH * BITMAP_HEIGHT - 22 * 4, countOn(b));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(b, 83, 17));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(b, 61, 17));
|
||||
}
|
||||
|
||||
void test_bar_validation()
|
||||
{
|
||||
DynamicPage page = barPage();
|
||||
page.elements[0].entity[0] = '\0';
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = barPage();
|
||||
page.elements[0].bar_width = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = barPage();
|
||||
page.elements[0].bar_height = 20;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = barPage();
|
||||
page.elements[0].bar_max = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = barPage();
|
||||
page.elements[0].bar_max = NAN;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
TEST_ASSERT_TRUE(elementUsesHa(barPage().elements[0]));
|
||||
}
|
||||
|
||||
void test_plex_user_placeholder()
|
||||
{
|
||||
char out[ELEMENT_ENTITY_SIZE];
|
||||
TEST_ASSERT_TRUE(expandEntity("sensor.{plex_user}_state", "mangavalk", out, sizeof(out)));
|
||||
TEST_ASSERT_EQUAL_STRING("sensor.mangavalk_state", out);
|
||||
TEST_ASSERT_TRUE(expandEntity("sensor.tautulli_watching", "mangavalk", out, sizeof(out)));
|
||||
TEST_ASSERT_EQUAL_STRING("sensor.tautulli_watching", out);
|
||||
// No user set: not a valid entity id, so nothing is read
|
||||
TEST_ASSERT_TRUE(expandEntity("sensor.{plex_user}", "", out, sizeof(out)));
|
||||
TEST_ASSERT_FALSE(validEntityId(out));
|
||||
// Too long once filled in
|
||||
char user[PLEX_USER_SIZE];
|
||||
memset(user, 'a', sizeof(user) - 1);
|
||||
user[sizeof(user) - 1] = '\0';
|
||||
TEST_ASSERT_FALSE(expandEntity("sensor.{plex_user}_a_long_name_that_is_too_long", user, out, sizeof(out)));
|
||||
|
||||
TEST_ASSERT_TRUE(validEntityId("sensor.{plex_user}_state"));
|
||||
TEST_ASSERT_TRUE(validEntityId("sensor.{plex_user}"));
|
||||
TEST_ASSERT_FALSE(validEntityId("sensor.{plex_user}{plex_user}"));
|
||||
TEST_ASSERT_FALSE(validEntityId("sensor.{other}_state"));
|
||||
TEST_ASSERT_FALSE(validEntityId("{plex_user}")); // no domain
|
||||
|
||||
plexUserSlug("MangaValk", user, sizeof(user));
|
||||
TEST_ASSERT_EQUAL_STRING("mangavalk", user);
|
||||
plexUserSlug(" Manga Valk-2 ", user, sizeof(user));
|
||||
TEST_ASSERT_EQUAL_STRING("manga_valk_2", user);
|
||||
plexUserSlug("", user, sizeof(user));
|
||||
TEST_ASSERT_EQUAL_STRING("", user);
|
||||
char small[5];
|
||||
plexUserSlug("ab cd ef", small, sizeof(small));
|
||||
TEST_ASSERT_EQUAL_STRING("ab_c", small);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -2158,6 +2293,10 @@ int main()
|
||||
RUN_TEST(test_frame_check_no_reply);
|
||||
RUN_TEST(test_frame_check_errors_and_restarts);
|
||||
RUN_TEST(test_frame_check_periodic_resync);
|
||||
RUN_TEST(test_bar_fill);
|
||||
RUN_TEST(test_bar_render);
|
||||
RUN_TEST(test_bar_validation);
|
||||
RUN_TEST(test_plex_user_placeholder);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user