Add configurable display mode: rolling window or daily reset
New config options: - mode = "window" (rolling hours, default) or "today" (daily reset) - start_time = "06:00" (when today mode resets) - hold_overnight = true (keep overnight collage until first bird) In "today" mode the collage only shows birds since start_time, growing through the day. With hold_overnight=true, the display keeps the previous full collage until the first bird is detected after start_time (no blank screen at dawn). Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
+36
-2
@@ -45,6 +45,9 @@ DEFAULTS = {
|
||||
"bw_days": 7, # BirdWeather lookback window, in days
|
||||
"bw_country": "us", # geocoder country for the ZIP
|
||||
"hours": 24,
|
||||
"mode": "window", # "window" = rolling hours; "today" = daily reset
|
||||
"start_time": "06:00", # "today" mode: collage resets at this time
|
||||
"hold_overnight": True, # "today" mode: keep overnight collage until first bird
|
||||
"image": "", # local PNG written by the shooter
|
||||
"image_url": "", # or a published screenshot URL
|
||||
"shoot": False, # or capture inline (needs a browser; the 3 A+ and Zero 2 W both handle it)
|
||||
@@ -109,6 +112,27 @@ def fetch_species(cfg, auth=None):
|
||||
return fetch_recent(cfg["base_url"], cfg["hours"], cfg["timeout"], auth)
|
||||
|
||||
|
||||
def _today_hours(cfg):
|
||||
"""Compute the hours window for 'today' mode.
|
||||
|
||||
Returns hours since start_time today (or since yesterday's start_time
|
||||
if we haven't reached it yet today).
|
||||
"""
|
||||
now = datetime.now()
|
||||
parts = cfg["start_time"].split(":")
|
||||
start_h, start_m = int(parts[0]), int(parts[1]) if len(parts) > 1 else 0
|
||||
start_today = now.replace(hour=start_h, minute=start_m, second=0, microsecond=0)
|
||||
|
||||
if now < start_today:
|
||||
from datetime import timedelta
|
||||
yesterday_start = start_today - timedelta(days=1)
|
||||
hours = (now - yesterday_start).total_seconds() / 3600
|
||||
return max(1, hours)
|
||||
|
||||
hours = (now - start_today).total_seconds() / 3600
|
||||
return max(1, hours)
|
||||
|
||||
|
||||
# --- image ------------------------------------------------------------------
|
||||
def get_image(src, timeout, auth=None):
|
||||
if re.match(r"^https?://", src):
|
||||
@@ -317,7 +341,7 @@ def in_quiet_hours(cfg, hour):
|
||||
|
||||
|
||||
# --- run --------------------------------------------------------------------
|
||||
def obtain_image(cfg, species=None):
|
||||
def obtain_image(cfg, species=None, window_hours=None):
|
||||
if cfg.get("species_source") == "birdweather":
|
||||
from shoot import shoot_birdweather
|
||||
if species is None: # gate skipped (--no-signature): fetch the list to render
|
||||
@@ -335,6 +359,7 @@ def obtain_image(cfg, species=None):
|
||||
headline_px=cfg["shoot_headline_px"], eyebrow_px=cfg["shoot_eyebrow_px"],
|
||||
lowercase=cfg["shoot_lowercase"], mat=cfg["shoot_mat"],
|
||||
small_floor=cfg["shoot_small_floor"], count_exp=cfg["shoot_count_exp"], timeout_ms=cfg["timeout"] * 1000,
|
||||
window_hours=int(window_hours) if window_hours else None,
|
||||
user=cfg["basic_user"], password=cfg["basic_pass"])
|
||||
return Image.open(out).convert("RGB")
|
||||
src = cfg["image_url"] or cfg["image"]
|
||||
@@ -348,8 +373,17 @@ def run(cfg, preview=None, force=False, use_signature=True, mat_box=False):
|
||||
state = load_state(cfg["state"])
|
||||
sig = None
|
||||
species = None
|
||||
window_hours = None
|
||||
if cfg["mode"] == "today":
|
||||
window_hours = _today_hours(cfg)
|
||||
if use_signature:
|
||||
try:
|
||||
if cfg["mode"] == "today":
|
||||
species = fetch_recent(cfg["base_url"], max(1, int(window_hours + 0.5)), cfg["timeout"], _auth(cfg))
|
||||
if not species and cfg["hold_overnight"]:
|
||||
print("today mode: no birds yet, holding overnight collage")
|
||||
return
|
||||
else:
|
||||
species = fetch_species(cfg, _auth(cfg))
|
||||
sig = signature(species)
|
||||
except Exception as e:
|
||||
@@ -366,7 +400,7 @@ def run(cfg, preview=None, force=False, use_signature=True, mat_box=False):
|
||||
print("refresh:", "changed" if changed else "heal")
|
||||
|
||||
try:
|
||||
img = fit_panel(obtain_image(cfg, species))
|
||||
img = fit_panel(obtain_image(cfg, species, window_hours))
|
||||
except Exception as e:
|
||||
print(f"could not get image: {e}", file=sys.stderr) # keep last panel image
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user