Public Access
Add a page schedule with rotation, Home Assistant trigger rules and a Stopwatch tab
- Schedule: daily blocks from a start time, each with up to 6 rotating pages or bitmaps (own duration), switching with the effect or instantly. A manual pick (Show tab, HA Mode) lasts until the next block; Back to schedule ends it. - Rules: an HA state (=, is not, above, below) shows a page while true, adds it to the rotation while true, or shows it for N seconds when it becomes true. Each rule is polled at its own interval, with a 60 s back-off on errors. - PageSelector (pure logic, tested) decides the shown page; schedule stored in NVS; HA sensor Shown page and button Back to schedule. - Web UI: Schedule and Stopwatch tabs, bitmaps used by pages or rules in their own subsection, schedule in export/import. Version 2.6.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
47ef65ee7d
commit
93b9bec69a
25 files changed
+1217
-50
No files matched your search
+41
-12
@@ -14,15 +14,23 @@ struct Entry
|
||||
String attribute;
|
||||
String value;
|
||||
bool known;
|
||||
bool watched;
|
||||
bool page_watched; // used by the shown page: read every HA_POLL_INTERVAL
|
||||
uint32_t rule_interval; // used by trigger rules: read this often (ms), 0 = not
|
||||
uint32_t next_poll;
|
||||
|
||||
bool watched() const { return page_watched || rule_interval; }
|
||||
uint32_t interval() const
|
||||
{
|
||||
uint32_t page = page_watched ? HA_POLL_INTERVAL : UINT32_MAX;
|
||||
return rule_interval && rule_interval < page ? rule_interval : page;
|
||||
}
|
||||
};
|
||||
|
||||
static String base_url;
|
||||
static String token;
|
||||
static std::vector<Entry> entries;
|
||||
// Kept for the editor's previews, besides the watched ones
|
||||
static const size_t MAX_ENTRIES = 16;
|
||||
static const size_t MAX_ENTRIES = 24;
|
||||
|
||||
void haApiSetSettings(const HomeAssistantSettings &settings)
|
||||
{
|
||||
@@ -52,19 +60,19 @@ static Entry &add(const char *entity, const char *attribute)
|
||||
return *e;
|
||||
if (entries.size() >= MAX_ENTRIES)
|
||||
for (auto it = entries.begin(); it != entries.end(); ++it)
|
||||
if (!it->watched)
|
||||
if (!it->watched())
|
||||
{
|
||||
entries.erase(it);
|
||||
break;
|
||||
}
|
||||
entries.push_back({entity, attribute, "", false, false, millis()});
|
||||
entries.push_back({entity, attribute, "", false, false, 0, millis()});
|
||||
return entries.back();
|
||||
}
|
||||
|
||||
void haApiWatch(const DynamicPage *page)
|
||||
{
|
||||
for (Entry &e : entries)
|
||||
e.watched = false;
|
||||
e.page_watched = false;
|
||||
if (!page)
|
||||
return;
|
||||
for (int i = 0; i < page->element_count; i++)
|
||||
@@ -74,9 +82,27 @@ void haApiWatch(const DynamicPage *page)
|
||||
if (!uses_state || !validEntityId(element.entity))
|
||||
continue;
|
||||
Entry &e = add(element.entity, element.attribute);
|
||||
if (!e.watched)
|
||||
if (!e.watched())
|
||||
e.next_poll = millis();
|
||||
e.watched = true;
|
||||
e.page_watched = true;
|
||||
}
|
||||
}
|
||||
|
||||
void haApiWatchRules(const PageSchedule *schedule)
|
||||
{
|
||||
for (Entry &e : entries)
|
||||
e.rule_interval = 0;
|
||||
if (!schedule)
|
||||
return;
|
||||
for (int i = 0; i < schedule->rule_count; i++)
|
||||
{
|
||||
const TriggerRule &rule = schedule->rules[i];
|
||||
Entry &e = add(rule.entity, rule.attribute);
|
||||
if (!e.watched())
|
||||
e.next_poll = millis();
|
||||
uint32_t ms = rule.interval * 1000UL;
|
||||
if (!e.rule_interval || ms < e.rule_interval)
|
||||
e.rule_interval = ms;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,20 +165,23 @@ bool haApiLoop(bool wifi_connected)
|
||||
uint32_t now = millis();
|
||||
for (Entry &e : entries)
|
||||
{
|
||||
if (!e.watched || (int32_t)(now - e.next_poll) < 0)
|
||||
if (!e.watched() || (int32_t)(now - e.next_poll) < 0)
|
||||
continue;
|
||||
e.next_poll = now + HA_POLL_INTERVAL;
|
||||
e.next_poll = now + e.interval();
|
||||
String value;
|
||||
const char *error = fetch(e.entity, e.attribute, value);
|
||||
if (error)
|
||||
{
|
||||
// Shown as "--" until it can be read again
|
||||
// Shown as "--" until it can be read again. Read less often, so a Home
|
||||
// Assistant that is down doesn't block loop() all the time.
|
||||
Serial.printf("Home Assistant %s: %s\n", e.entity.c_str(), error);
|
||||
bool changed = e.known;
|
||||
if (e.interval() < HA_ERROR_INTERVAL)
|
||||
e.next_poll = now + HA_ERROR_INTERVAL;
|
||||
bool changed = e.known && e.page_watched;
|
||||
e.known = false;
|
||||
return changed;
|
||||
}
|
||||
bool changed = !e.known || e.value != value;
|
||||
bool changed = (!e.known || e.value != value) && e.page_watched;
|
||||
e.value = value;
|
||||
e.known = true;
|
||||
return changed; // one entity per loop()
|
||||
|
||||
Reference in new issue
Block a user