2026-10-15 21:36:56 +02:00
|
|
|
#include "logic/page_selector.h"
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
static void copyName(char *out, const char *name)
|
|
|
|
|
{
|
|
|
|
|
strncpy(out, name ? name : "", PAGE_NAME_SIZE - 1);
|
|
|
|
|
out[PAGE_NAME_SIZE - 1] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PageSelector::setSchedule(const PageSchedule *schedule)
|
|
|
|
|
{
|
|
|
|
|
_schedule = schedule;
|
|
|
|
|
_picked = false;
|
|
|
|
|
_block = -1;
|
|
|
|
|
_last_minute = -1;
|
|
|
|
|
memset(_rule_active, 0, sizeof(_rule_active));
|
|
|
|
|
memset(_rule_timer, 0, sizeof(_rule_timer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PageSelector::setChosen(const char *page)
|
|
|
|
|
{
|
|
|
|
|
copyName(_chosen, page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PageSelector::pick(const char *page)
|
|
|
|
|
{
|
|
|
|
|
setChosen(page);
|
|
|
|
|
_picked = scheduleActive();
|
|
|
|
|
_item = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PageSelector::backToSchedule()
|
|
|
|
|
{
|
|
|
|
|
_picked = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PageSelector::scheduleActive() const
|
|
|
|
|
{
|
|
|
|
|
return _schedule && _schedule->enabled && _schedule->block_count > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PageSelector::_blockAt(int minute) const
|
|
|
|
|
{
|
|
|
|
|
int block = _schedule->block_count - 1; // before the first start: the last block of the day before
|
|
|
|
|
for (int i = 0; i < _schedule->block_count; i++)
|
|
|
|
|
if (_schedule->blocks[i].start <= minute)
|
|
|
|
|
block = i;
|
|
|
|
|
return block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PageSelector::pickedUntil() const
|
|
|
|
|
{
|
|
|
|
|
if (!_picked || !scheduleActive() || _block < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
return _schedule->blocks[(_block + 1) % _schedule->block_count].start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PageSelector::setRuleState(int rule, bool active, uint32_t now)
|
|
|
|
|
{
|
|
|
|
|
if (rule < 0 || rule >= TRIGGER_RULES)
|
|
|
|
|
return;
|
|
|
|
|
if (active && !_rule_active[rule])
|
|
|
|
|
{
|
|
|
|
|
// Becomes true: a ForSeconds rule starts (again)
|
|
|
|
|
_rule_timer[rule] = true;
|
|
|
|
|
_rule_since[rule] = now;
|
|
|
|
|
}
|
|
|
|
|
_rule_active[rule] = active;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PageSelector::ruleActive(int rule) const
|
|
|
|
|
{
|
|
|
|
|
return rule >= 0 && rule < TRIGGER_RULES && _rule_active[rule];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PageChoice PageSelector::update(int minute, uint32_t now)
|
|
|
|
|
{
|
|
|
|
|
PageChoice choice;
|
|
|
|
|
bool schedule = scheduleActive();
|
|
|
|
|
bool effect = true; // switching with the refresh effect
|
|
|
|
|
|
|
|
|
|
// A block starts: it ends a manual pick and starts its rotation from the first page.
|
|
|
|
|
// With one block that is its start time each day.
|
|
|
|
|
if (schedule && minute != _last_minute)
|
|
|
|
|
{
|
|
|
|
|
int block = _blockAt(minute);
|
|
|
|
|
if (block != _block || _schedule->blocks[block].start == minute)
|
|
|
|
|
{
|
|
|
|
|
_picked = false;
|
|
|
|
|
_item = 0;
|
|
|
|
|
_item_since = now;
|
|
|
|
|
}
|
|
|
|
|
_block = block;
|
|
|
|
|
}
|
|
|
|
|
if (!schedule)
|
|
|
|
|
_block = -1;
|
|
|
|
|
_last_minute = minute;
|
|
|
|
|
|
|
|
|
|
// 1. Take-over rules
|
|
|
|
|
int rule_count = _schedule ? _schedule->rule_count : 0;
|
|
|
|
|
for (int i = 0; i < rule_count && choice.rule < 0; i++)
|
|
|
|
|
{
|
|
|
|
|
const TriggerRule &r = _schedule->rules[i];
|
|
|
|
|
if (r.mode == TriggerMode::ForSeconds && _rule_timer[i] && now - _rule_since[i] >= r.seconds * 1000UL)
|
|
|
|
|
_rule_timer[i] = false;
|
2026-10-15 22:25:00 +02:00
|
|
|
bool takes_over = ((r.mode == TriggerMode::WhileTrue && _rule_active[i]) ||
|
|
|
|
|
(r.mode == TriggerMode::ForSeconds && _rule_timer[i])) &&
|
2026-10-16 22:13:23 +02:00
|
|
|
ruleInBlock(r, _block) && (r.over_manual || !_picked);
|
2026-10-15 21:36:56 +02:00
|
|
|
if (takes_over)
|
|
|
|
|
{
|
|
|
|
|
choice.page = r.page;
|
|
|
|
|
choice.reason = ShowReason::Trigger;
|
|
|
|
|
choice.rule = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2-4. The rotation: the block's pages (or the chosen page), then the AddToRotation pages
|
|
|
|
|
if (choice.rule < 0)
|
|
|
|
|
{
|
|
|
|
|
struct Item
|
|
|
|
|
{
|
|
|
|
|
const char *page;
|
|
|
|
|
uint32_t ms;
|
|
|
|
|
ShowReason reason;
|
|
|
|
|
int rule;
|
|
|
|
|
} items[BLOCK_ITEMS + TRIGGER_RULES];
|
|
|
|
|
int count = 0;
|
|
|
|
|
if (schedule && !_picked)
|
|
|
|
|
{
|
|
|
|
|
const ScheduleBlock &b = _schedule->blocks[_block];
|
|
|
|
|
effect = b.effect;
|
|
|
|
|
for (int i = 0; i < b.item_count; i++)
|
|
|
|
|
items[count++] = {b.items[i].page, b.items[i].seconds * 1000UL, ShowReason::Schedule, -1};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
items[count++] = {_chosen, CHOSEN_PAGE_SECONDS * 1000UL, ShowReason::Chosen, -1};
|
|
|
|
|
for (int i = 0; i < rule_count; i++)
|
|
|
|
|
{
|
|
|
|
|
const TriggerRule &r = _schedule->rules[i];
|
2026-10-16 22:13:23 +02:00
|
|
|
if (r.mode == TriggerMode::AddToRotation && _rule_active[i] && ruleInBlock(r, _block) &&
|
|
|
|
|
(r.over_manual || !_picked))
|
2026-10-15 21:36:56 +02:00
|
|
|
items[count++] = {r.page, r.seconds * 1000UL, ShowReason::Rotation, i};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_item >= count)
|
|
|
|
|
{
|
|
|
|
|
_item = 0;
|
|
|
|
|
_item_since = now;
|
|
|
|
|
}
|
|
|
|
|
if (now - _item_since >= items[_item].ms)
|
|
|
|
|
{
|
|
|
|
|
_item = (_item + 1) % count;
|
|
|
|
|
_item_since = now;
|
|
|
|
|
}
|
|
|
|
|
choice.page = items[_item].page;
|
|
|
|
|
choice.reason = items[_item].reason;
|
|
|
|
|
choice.rule = items[_item].rule;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
choice.changed = !sameName(choice.page, _shown);
|
|
|
|
|
// Rules bring their page at once, and leave at once; the schedule as set per block
|
|
|
|
|
choice.instant = choice.changed && (choice.reason == ShowReason::Trigger || _shown_reason == ShowReason::Trigger ||
|
|
|
|
|
!effect);
|
|
|
|
|
copyName(_shown, choice.page);
|
|
|
|
|
_shown_reason = choice.reason;
|
|
|
|
|
return choice;
|
|
|
|
|
}
|