feat: add timezone support for schedules and dim schedule feature (#218)

* feat: add timezone support for schedules and dim schedule feature

- Fix timezone handling in _check_schedule() to use configured timezone
  instead of system time (addresses schedule offset issues)
- Add dim schedule feature for automatic brightness dimming:
  - New dim_schedule config section with brightness level and time windows
  - Smart interaction: dim schedule won't turn display on if it's off
  - Supports both global and per-day modes like on/off schedule
- Add set_brightness() and get_brightness() methods to DisplayManager
  for runtime brightness control
- Add REST API endpoints: GET/POST /api/v3/config/dim-schedule
- Add web UI for dim schedule configuration in schedule settings page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: normalize per-day mode and validate dim_brightness input

- Normalize mode string in _check_dim_schedule to handle both "per-day"
  and "per_day" variants
- Add try/except around dim_brightness int conversion to handle invalid
  input gracefully

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: improve error handling in brightness and dim schedule endpoints

- display_manager.py: Add fail-fast input validation, catch specific
  exceptions (AttributeError, TypeError, ValueError), add [BRIGHTNESS]
  context tags, include stack traces in error logs
- api_v3.py: Catch specific config exceptions (FileNotFoundError,
  JSONDecodeError, IOError), add [DIM SCHEDULE] context tags for
  Pi debugging, include stack traces

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-01-29 18:12:45 -05:00
committed by GitHub
parent ddd300a117
commit 14c50f316e
6 changed files with 599 additions and 5 deletions

View File

@@ -174,6 +174,57 @@ class DisplayManager:
else:
return 32 # Default fallback height
def set_brightness(self, brightness: int) -> bool:
"""
Set display brightness at runtime.
Args:
brightness: Brightness level (0-100)
Returns:
True if brightness was set successfully, False otherwise
"""
# Fail fast: validate input type
if not isinstance(brightness, (int, float)):
logger.error(f"[BRIGHTNESS] Invalid brightness type: {type(brightness).__name__}, expected int")
return False
if self.matrix is None:
logger.warning("[BRIGHTNESS] Cannot set brightness in fallback mode")
return False
# Clamp to valid range
brightness = max(0, min(100, int(brightness)))
try:
# RGBMatrix accepts brightness as a property
self.matrix.brightness = brightness
logger.info(f"[BRIGHTNESS] Display brightness set to {brightness}%")
return True
except AttributeError as e:
logger.error(f"[BRIGHTNESS] Matrix does not support brightness property: {e}", exc_info=True)
return False
except (TypeError, ValueError) as e:
logger.error(f"[BRIGHTNESS] Invalid brightness value rejected by hardware: {e}", exc_info=True)
return False
def get_brightness(self) -> int:
"""
Get current display brightness.
Returns:
Current brightness level (0-100), or -1 if unavailable
"""
if self.matrix is None:
logger.debug("[BRIGHTNESS] Cannot get brightness in fallback mode")
return -1
try:
return self.matrix.brightness
except AttributeError as e:
logger.warning(f"[BRIGHTNESS] Matrix does not support brightness property: {e}", exc_info=True)
return -1
def _draw_test_pattern(self):
"""Draw a test pattern to verify the display is working."""
try: