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_days": 7, # BirdWeather lookback window, in days
|
||||||
"bw_country": "us", # geocoder country for the ZIP
|
"bw_country": "us", # geocoder country for the ZIP
|
||||||
"hours": 24,
|
"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": "", # local PNG written by the shooter
|
||||||
"image_url": "", # or a published screenshot URL
|
"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)
|
"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)
|
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 ------------------------------------------------------------------
|
# --- image ------------------------------------------------------------------
|
||||||
def get_image(src, timeout, auth=None):
|
def get_image(src, timeout, auth=None):
|
||||||
if re.match(r"^https?://", src):
|
if re.match(r"^https?://", src):
|
||||||
@@ -317,7 +341,7 @@ def in_quiet_hours(cfg, hour):
|
|||||||
|
|
||||||
|
|
||||||
# --- run --------------------------------------------------------------------
|
# --- run --------------------------------------------------------------------
|
||||||
def obtain_image(cfg, species=None):
|
def obtain_image(cfg, species=None, window_hours=None):
|
||||||
if cfg.get("species_source") == "birdweather":
|
if cfg.get("species_source") == "birdweather":
|
||||||
from shoot import shoot_birdweather
|
from shoot import shoot_birdweather
|
||||||
if species is None: # gate skipped (--no-signature): fetch the list to render
|
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"],
|
headline_px=cfg["shoot_headline_px"], eyebrow_px=cfg["shoot_eyebrow_px"],
|
||||||
lowercase=cfg["shoot_lowercase"], mat=cfg["shoot_mat"],
|
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,
|
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"])
|
user=cfg["basic_user"], password=cfg["basic_pass"])
|
||||||
return Image.open(out).convert("RGB")
|
return Image.open(out).convert("RGB")
|
||||||
src = cfg["image_url"] or cfg["image"]
|
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"])
|
state = load_state(cfg["state"])
|
||||||
sig = None
|
sig = None
|
||||||
species = None
|
species = None
|
||||||
|
window_hours = None
|
||||||
|
if cfg["mode"] == "today":
|
||||||
|
window_hours = _today_hours(cfg)
|
||||||
if use_signature:
|
if use_signature:
|
||||||
try:
|
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))
|
species = fetch_species(cfg, _auth(cfg))
|
||||||
sig = signature(species)
|
sig = signature(species)
|
||||||
except Exception as e:
|
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")
|
print("refresh:", "changed" if changed else "heal")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
img = fit_panel(obtain_image(cfg, species))
|
img = fit_panel(obtain_image(cfg, species, window_hours))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"could not get image: {e}", file=sys.stderr) # keep last panel image
|
print(f"could not get image: {e}", file=sys.stderr) # keep last panel image
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user