Add time-based screen scheduling (e.g. Journey every morning 6-8)
New "Time-based schedule" section on the Rotation settings page: up to
4 rules, each a screen + a daily HH:MM-HH:MM window (crossing midnight
is fine, e.g. 22:00-02:00). While a rule's window is active, that
screen overrides rotation and any manual pin -- without touching
either's underlying state, so things go back to exactly how they were
once the window ends. Navidrome-playing still wins on top of a
schedule rule, same as it already does over everything else.
config.py: new schedules list (get_schedules/set_schedules), each rule
just {screen_id, start, end} -- being in the list *is* being enabled,
no separate flag (a settings row is "disabled" by leaving its screen
dropdown on "-- none --").
scheduler.py: _time_in_window() handles same-day and midnight-wrapping
windows, treats a zero-width window as never-matching (more likely a
blank settings row than an intentional all-day rule).
_active_scheduled_screen() slots into _select_screen() between
Navidrome-priority and the pin/rotation fallback.
Verified: unit tests for _time_in_window's edge cases (wraparound,
zero-width, malformed input) and the priority override, plus an
end-to-end run against the real app -- pinned a screen, added a rule
active right now, confirmed the status page switched to the scheduled
screen within one tick.
This commit is contained in:
1 parent
33b964f1f6
commit
9aacd4936a
5 files changed
+103
-3
No files matched your search
@@ -99,7 +99,7 @@
|
||||
label { display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.85rem; }
|
||||
.hint { font-size: 0.78rem; color: var(--text-muted); margin: 0.35rem 0 0; }
|
||||
|
||||
input[type=text], input[type=password], input[type=number], select, input[type=file] {
|
||||
input[type=text], input[type=password], input[type=number], input[type=time], select, input[type=file] {
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.7rem;
|
||||
font-size: 0.95rem;
|
||||
|
||||
@@ -35,6 +35,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="section-label">Time-based schedule</div>
|
||||
<p class="hint" style="margin: -0.25rem 0 1rem;">
|
||||
Show a specific screen automatically during a daily time window --
|
||||
handy for e.g. checking bus departures every morning. Takes priority
|
||||
over rotation and any manual pin while active (Navidrome priority
|
||||
above still wins over this if enabled), and reverts back on its own
|
||||
once the window ends. A window can cross midnight (e.g. 22:00-02:00).
|
||||
Leave a row's screen on "-- none --" to disable it.
|
||||
</p>
|
||||
{% for i in range(1, max_schedule_rules + 1) %}
|
||||
{% set rule = schedules[i - 1] if i - 1 < schedules|length else None %}
|
||||
<div class="field" style="display: flex; gap: 0.6rem; align-items: flex-end; flex-wrap: wrap;">
|
||||
<div style="flex: 1; min-width: 160px;">
|
||||
<label for="schedule_screen_{{ i }}">Screen</label>
|
||||
<select id="schedule_screen_{{ i }}" name="schedule_screen_{{ i }}">
|
||||
<option value="">-- none --</option>
|
||||
{% for screen_id, screen in screens.items() | sort(attribute="1.display_name") %}
|
||||
<option value="{{ screen_id }}" {{ "selected" if rule and rule.screen_id == screen_id }}>{{ screen.display_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="schedule_start_{{ i }}">From</label>
|
||||
<input type="time" id="schedule_start_{{ i }}" name="schedule_start_{{ i }}" value="{{ rule.start if rule else '' }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="schedule_end_{{ i }}">To</label>
|
||||
<input type="time" id="schedule_end_{{ i }}" name="schedule_end_{{ i }}" value="{{ rule.end if rule else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user