Implement curfew.

This commit is contained in:
2026-06-09 09:12:57 +02:00
parent d3493f50d1
commit 9002cbbe08
2 changed files with 124 additions and 4 deletions

View File

@ -13,6 +13,12 @@ BUDGET_SECONDS = int(os.environ.get("BUDGET_MINUTES", "60")) * 60 # default 60
STATE_FILE = Path(__file__).parent / ".device_state.json"
TICK_INTERVAL = 10 # seconds between budget checks
# Allowed device hours (24h format)
ALLOWED_WEEKDAY_START = int(os.environ.get("ALLOWED_WEEKDAY_START", "15")) # Mon-Fri start hour
ALLOWED_WEEKDAY_END = int(os.environ.get("ALLOWED_WEEKDAY_END", "20")) # Mon-Fri end hour
ALLOWED_WEEKEND_START = int(os.environ.get("ALLOWED_WEEKEND_START", "8")) # Sat-Sun start hour
ALLOWED_END_MINUTE = int(os.environ.get("ALLOWED_END_MINUTE", "30")) # End minute (same for all days)
# ── Budget state (persisted to disk) ─────────────────────────────────
@ -163,6 +169,94 @@ def _budget_to_minutes(budget_seconds: int) -> int:
return max(0, budget_seconds // 60)
def is_curfew_allowed() -> bool:
"""Check if current time is within allowed hours (devices allowed).
Allowed hours:
Mon-Fri: ALLOWED_WEEKDAY_START:00 to ALLOWED_WEEKDAY_END:ALLOWED_END_MINUTE
Sat-Sun: ALLOWED_WEEKEND_START:00 to ALLOWED_WEEKDAY_END:ALLOWED_END_MINUTE
"""
now = datetime.now()
weekday = now.weekday() # 0=Mon, 6=Sun
current_minutes = now.hour * 60 + now.minute
if weekday < 5: # Mon-Fri
start = ALLOWED_WEEKDAY_START * 60
end = (ALLOWED_WEEKDAY_END * 60) + ALLOWED_END_MINUTE
else: # Sat-Sun
start = ALLOWED_WEEKEND_START * 60
end = (ALLOWED_WEEKDAY_END * 60) + ALLOWED_END_MINUTE
return start <= current_minutes < end
def _minutes_to_time(minutes: int) -> str:
"""Convert minutes since midnight to HH:MM format."""
h = minutes // 60
m = minutes % 60
return f"{h:02d}:{m:02d}"
def curfew_status() -> dict:
"""Get curfew status and time until next transition.
Returns dict with:
- in_curfew: bool (True = devices BLOCKED)
- minutes_left: int (minutes until curfew ends or starts)
- message: str (human-readable description)
- next_time: str (clock time of next transition, e.g. '15:00')
"""
now = datetime.now()
weekday = now.weekday() # 0=Mon, 6=Sun
current_minutes = now.hour * 60 + now.minute
if weekday < 5: # Mon-Fri
start = ALLOWED_WEEKDAY_START * 60
end = (ALLOWED_WEEKDAY_END * 60) + ALLOWED_END_MINUTE
else: # Sat-Sun
start = ALLOWED_WEEKEND_START * 60
end = (ALLOWED_WEEKDAY_END * 60) + ALLOWED_END_MINUTE
in_curfew = not (start <= current_minutes < end) # True when devices are BLOCKED
if in_curfew:
# Devices blocked — show time until allowed hours start
if weekday < 5: # Mon-Fri
if current_minutes < start:
# Before allowed hours start today
minutes_left = start - current_minutes
next_time = _minutes_to_time(start)
else:
# After allowed hours end → next day same schedule
minutes_left = (24 * 60) - current_minutes + start
next_time = _minutes_to_time(start)
else: # Sat-Sun
if current_minutes < start:
# Before allowed hours start today
minutes_left = start - current_minutes
next_time = _minutes_to_time(start)
elif weekday == 5: # Saturday after allowed ends → Sunday
minutes_left = (24 * 60) - current_minutes + start
next_time = _minutes_to_time(start)
else: # Sunday after allowed ends → Monday
minutes_left = (3 * 24 * 60) - current_minutes + ALLOWED_WEEKDAY_START * 60
next_time = _minutes_to_time(ALLOWED_WEEKDAY_START * 60)
message = f"Blocked until {next_time} ({minutes_left} min)"
else:
# Devices allowed — show time until allowed hours end
minutes_left = end - current_minutes
next_time = _minutes_to_time(end)
message = f"Allowed until {_minutes_to_time(end)} ({minutes_left} min)"
return {
"in_curfew": in_curfew,
"minutes_left": minutes_left,
"message": message,
"next_time": next_time,
}
# ── Budget timer (runs every 60s in background) ──────────────────────
_timer_running = False
@ -179,6 +273,10 @@ def _budget_tick():
for dev_id in DEVICES:
_reset_budget(dev_id)
# Only count down budget during allowed curfew hours
if not is_curfew_allowed():
return
with _timer_lock:
for dev_id, dev_info in DEVICES.items():
state = _load_state()
@ -249,6 +347,7 @@ def shutdown_all() -> list[dict]:
def status_all() -> list[dict]:
"""Get current status + budget for all devices. Returns ordered list."""
curfew = curfew_status()
actions = []
for dev_id, dev_info in DEVICES.items():
state = _load_state()
@ -277,4 +376,4 @@ def status_all() -> list[dict]:
"title": dev_info["name"],
"detail": f"{detail} · {budget_minutes} min left",
})
return actions
return actions, curfew