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
@@ -25,10 +25,62 @@ static bool printable(const char *text, size_t size)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool expandEntity(const char *entity, const char *user, char *out, size_t out_size)
|
||||
{
|
||||
const char *at = strstr(entity, PLEX_USER_PLACEHOLDER);
|
||||
int n;
|
||||
if (at)
|
||||
n = snprintf(out, out_size, "%.*s%s%s", (int)(at - entity), entity, user,
|
||||
at + strlen(PLEX_USER_PLACEHOLDER));
|
||||
else
|
||||
n = snprintf(out, out_size, "%s", entity);
|
||||
return n >= 0 && (size_t)n < out_size;
|
||||
}
|
||||
|
||||
void plexUserSlug(const char *name, char *out, size_t out_size)
|
||||
{
|
||||
size_t n = 0;
|
||||
bool gap = false;
|
||||
for (const char *c = name; *c && n + 1 < out_size; c++)
|
||||
{
|
||||
if (isalnum((unsigned char)*c))
|
||||
{
|
||||
if (gap && n > 0 && n + 2 < out_size)
|
||||
out[n++] = '_';
|
||||
gap = false;
|
||||
out[n++] = tolower((unsigned char)*c);
|
||||
}
|
||||
else
|
||||
gap = true;
|
||||
}
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
bool elementUsesHa(const PageElement &element)
|
||||
{
|
||||
switch (element.type)
|
||||
{
|
||||
case ElementType::HaValue:
|
||||
return true;
|
||||
case ElementType::Image:
|
||||
case ElementType::Bar:
|
||||
return element.entity[0] != '\0';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool validEntityId(const char *entity)
|
||||
{
|
||||
if (entity == nullptr)
|
||||
return false;
|
||||
// Checked with a stand-in user name
|
||||
if (strstr(entity, PLEX_USER_PLACEHOLDER))
|
||||
{
|
||||
char full[ELEMENT_ENTITY_SIZE];
|
||||
return strlen(entity) < (size_t)ELEMENT_ENTITY_SIZE && !strstr(strstr(entity, "}") + 1, "{") &&
|
||||
expandEntity(entity, "x", full, sizeof(full)) && validEntityId(full);
|
||||
}
|
||||
const char *dot = strchr(entity, '.');
|
||||
if (dot == nullptr || dot == entity || dot[1] == '\0' || strchr(dot + 1, '.'))
|
||||
return false;
|
||||
@@ -125,6 +177,15 @@ const char *validatePage(const DynamicPage &page)
|
||||
return "Scrolling: window up to 168 dots, speed 1-30 dots per second";
|
||||
if (e.type == ElementType::HaValue && !validEntityId(e.entity))
|
||||
return "Home Assistant values need an entity id like sensor.outside_temperature";
|
||||
if (e.type == ElementType::Bar)
|
||||
{
|
||||
if (!validEntityId(e.entity))
|
||||
return "Progress bars need an entity id like sensor.progress";
|
||||
if (e.bar_width < 1 || e.bar_width > BITMAP_WIDTH || e.bar_height < 1 || e.bar_height > BITMAP_HEIGHT)
|
||||
return "Progress bar: width 1-84, height 1-19 dots";
|
||||
if (!isfinite(e.bar_max) || e.bar_max <= 0)
|
||||
return "Progress bar: the full value must be a number above 0";
|
||||
}
|
||||
if (e.type == ElementType::Countdown && e.target >= MAX_TARGET)
|
||||
return "The countdown date must be before 2100";
|
||||
if (e.type == ElementType::Image)
|
||||
@@ -645,6 +706,7 @@ void elementText(const PageElement &e, const LocalTime &t, const ElementValues &
|
||||
formatStopwatch(e, values.stopwatch_ms, out, size);
|
||||
break;
|
||||
case ElementType::Image:
|
||||
case ElementType::Bar:
|
||||
out[0] = '\0';
|
||||
break;
|
||||
default:
|
||||
@@ -653,6 +715,20 @@ void elementText(const PageElement &e, const LocalTime &t, const ElementValues &
|
||||
}
|
||||
}
|
||||
|
||||
int barFill(const PageElement &e, const char *ha_value, int fill_width)
|
||||
{
|
||||
double v;
|
||||
if (!ha_value || !parseNumber(ha_value, v) || fill_width <= 0)
|
||||
return 0;
|
||||
double part = v * e.factor / e.bar_max;
|
||||
if (!(part > 0)) // also NaN
|
||||
return 0;
|
||||
if (part >= 1)
|
||||
return fill_width;
|
||||
int n = (int)(part * fill_width + 0.5);
|
||||
return n;
|
||||
}
|
||||
|
||||
bool elementShowsSeconds(const PageElement &e, const LocalTime &t, bool stopwatch_running)
|
||||
{
|
||||
switch (e.type)
|
||||
|
||||
Reference in new issue
Block a user