#include "logic/page_selector.h" #include 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; bool takes_over = ((r.mode == TriggerMode::WhileTrue && _rule_active[i]) || (r.mode == TriggerMode::ForSeconds && _rule_timer[i])) && ruleInBlock(r, _block) && (r.over_manual || !_picked); 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++) if (!_slow || !b.items[i].skip_slow_mode) items[count++] = {b.items[i].page, b.items[i].seconds * 1000UL, ShowReason::Schedule, -1}; } if (count == 0) // no schedule, a manual pick, or slow mode skips all the block's pages items[count++] = {_chosen, CHOSEN_PAGE_SECONDS * 1000UL, ShowReason::Chosen, -1}; for (int i = 0; i < rule_count; i++) { const TriggerRule &r = _schedule->rules[i]; if (r.mode == TriggerMode::AddToRotation && _rule_active[i] && ruleInBlock(r, _block) && (r.over_manual || !_picked) && (!_slow || !r.skip_slow_mode)) 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); choice.over_slow_mode = choice.rule >= 0 && choice.rule < rule_count && _schedule->rules[choice.rule].over_slow_mode; // 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; }