import time import logging import sys from pytz import timezone from datetime import datetime, time as time_obj # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s.%(msecs)03d - %(levelname)s:%(name)s:%(message)s', datefmt='%H:%M:%S', stream=sys.stdout ) from src.clock import Clock from src.display_manager import DisplayManager from src.config_manager import ConfigManager from src.cache_manager import CacheManager from src.eojhl_managers import EOJHLLiveManager, EOJHLRecentManager, EOJHLUpcomingManager from src.leaderboard_manager import LeaderboardManager from src.sponsor_manager import EOJHLSponsorsManager logger = logging.getLogger(__name__) class DisplayController: def __init__(self): start_time = time.time() logger.info("Starting DisplayController initialization") # Load config self.config_manager = ConfigManager() self.config = self.config_manager.load_config() self.cache_manager = CacheManager() logger.info("Config loaded in %.3f seconds", time.time() - start_time) # Initialize display manager self.display_manager = DisplayManager(self.config) logger.info("DisplayManager initialized") # Initialize clock if enabled init_time = time.time() self.clock = Clock(self.display_manager, self.config) if self.config.get('clock', {}).get('enabled', True) else None logger.info("Display modes initialized in %.3f seconds", time.time() - init_time) self.is_display_active = True self._load_schedule_config() # --- League Managers Dictionary --- self.league_managers = {} # EOJHL Managers if self.config.get('eojhl_scoreboard', {}).get('enabled', False): eojhl_recent = EOJHLRecentManager(self.config, self.display_manager, self.cache_manager) eojhl_upcoming = EOJHLUpcomingManager(self.config, self.display_manager, self.cache_manager) eojhl_live = EOJHLLiveManager(self.config, self.display_manager, self.cache_manager) self.league_managers["eojhl"] = { "recent": eojhl_recent, "upcoming": eojhl_upcoming, "live": eojhl_live, "leaderboard": LeaderboardManager(self.config, self.display_manager, eojhl_recent.get_league_data()) } # TODO: Add CCHL managers in the same pattern when ready # Sponsors self.sponsors = EOJHLSponsorsManager(self.config, self.display_manager) if self.config.get('sponsors', {}).get('enabled', False) else None # Build available modes list from league_order self.available_modes = [] if self.clock: self.available_modes.append("clock") for league in sorted(self.config.get("league_order", {}), key=lambda k: self.config["league_order"][k]): managers = self.league_managers.get(league, {}) # Only add leaderboard if enabled in config if managers.get("leaderboard") and self.config["leaderboard"]["enabled_sports"].get(league, {}).get("enabled", False): self.available_modes.append(f"{league}_leaderboard") if managers.get("recent"): self.available_modes.append(f"{league}_recent") if managers.get("upcoming"): self.available_modes.append(f"{league}_upcoming") # live is handled separately if you want to prioritize it if self.sponsors: self.available_modes.append("sponsors") if self.config.get("team_info", {}).get("enabled", False): self.available_modes.append("team_info") self.current_mode_index = 0 self.last_switch = time.time() self.force_clear = True self.update_interval = 0.01 logger.info(f"DisplayController initialized with modes: {self.available_modes}") # --- SCHEDULING --- self.is_display_active = True self._load_schedule_config() # Load schedule config once at startup def run_transition(self, when="before"): """Run transition effect either before or after a mode.""" tcfg = self.config.get("transitions", {}) ttype = tcfg.get("type", "fade") if ttype == "fade": dur = tcfg.get("fade_duration", 0.4) steps = tcfg.get("fade_steps", 6) easing = tcfg.get("easing", True) if when == "before": self.display_manager.fade_in(duration=dur, steps=steps, easing=easing) else: self.display_manager.fade_out(duration=dur, steps=steps, easing=easing) elif ttype == "cut": dur = tcfg.get("cut_duration", 0.2) if when == "before": # nothing to fade in, just show the mode return else: self.display_manager.cut_to_black(duration=dur) def _load_schedule_config(self): """Load schedule configuration once at startup.""" schedule_config = self.config.get('schedule', {}) self.schedule_enabled = schedule_config.get('enabled', False) try: self.start_time = datetime.strptime(schedule_config.get('start_time', '07:00'), '%H:%M').time() self.end_time = datetime.strptime(schedule_config.get('end_time', '22:00'), '%H:%M').time() logger.info(f"Schedule loaded: enabled={self.schedule_enabled}, start={self.start_time}, end={self.end_time}") except (ValueError, TypeError): logger.warning("Invalid time format in schedule config. Using defaults.") self.start_time = time_obj(7, 0) self.end_time = time_obj(22, 0) def _check_schedule(self): """Check if the display should be active based on the schedule.""" if not self.schedule_enabled: if not self.is_display_active: logger.info("Schedule is disabled. Activating display.") self.is_display_active = True return local_tz = timezone(self.config.get("timezone", "America/Toronto")) now_time = datetime.now(local_tz).time() if self.start_time <= self.end_time: should_be_active = self.start_time <= now_time < self.end_time else: should_be_active = now_time >= self.start_time or now_time < self.end_time if should_be_active and not self.is_display_active: logger.info("Within scheduled time. Activating display.") self.is_display_active = True self.force_clear = True elif not should_be_active and self.is_display_active: logger.info("Outside of scheduled time. Deactivating display.") self.display_manager.clear() self.is_display_active = False def main(): controller = DisplayController() logger.info("Entering main display loop") try: while True: controller._check_schedule() if not controller.is_display_active: controller.display_manager.clear() time.sleep(5) continue if not controller.available_modes: logger.warning("No display modes available") time.sleep(5) continue # Check for live games with priority for each league live_game_shown = False for league in controller.league_managers: league_config = controller.config.get(f"{league}_scoreboard", {}) if league_config.get("live_priority", False): live_mgr = controller.league_managers[league].get("live") if live_mgr: # Update live manager to check for live games live_mgr.update() if live_mgr.live_games: logger.info(f"Live {league.upper()} games detected with priority - displaying live games") live_update_interval = league_config.get("live_update_interval", 30) # Display live games continuously until they end while live_mgr.live_games: controller._check_schedule() if not controller.is_display_active: break live_mgr.display() time.sleep(1) # Small sleep for display refresh # Check if it's time to update live data current_time = time.time() if current_time - live_mgr.last_update >= live_update_interval: live_mgr.update() if not live_mgr.live_games: logger.info(f"No more live {league.upper()} games - returning to normal rotation") break live_game_shown = True break # Exit league loop after showing live games if live_game_shown: continue # Skip normal rotation and check for live games again mode = controller.available_modes[controller.current_mode_index] duration = controller.config.get("display", {}).get("display_durations", {}).get(mode.split("_")[-1], 10) logger.info(f"Displaying mode: {mode} for {duration} seconds") # Fade out current mode controller.run_transition("before") if mode == "clock" and controller.clock: controller.clock.display_time() time.sleep(duration) elif mode.endswith("_leaderboard"): league = mode.split("_")[0] controller.league_managers[league]["leaderboard"].display() elif mode.endswith("_recent"): league = mode.split("_")[0] mgr = controller.league_managers[league]["recent"] mgr.update() start = time.time() while time.time() - start < duration * mgr.recent_games_to_show: mgr.display() time.sleep(1) elif mode.endswith("_upcoming"): league = mode.split("_")[0] mgr = controller.league_managers[league]["upcoming"] mgr.update() start = time.time() while time.time() - start < duration * mgr.upcoming_games_to_show: mgr.display() time.sleep(1) elif mode == "sponsors" and controller.sponsors: # Get batch of sponsors to display sponsors_to_show = controller.sponsors.get_next_sponsors() if sponsors_to_show: cfg = controller.config.get("sponsors", {}) # Show title slide once at the beginning controller.sponsors.render_title() time.sleep(cfg.get("title_duration", 5)) # Loop through each sponsor in the batch for sponsor in sponsors_to_show: # Show logo controller.sponsors.render_logo(sponsor) time.sleep(cfg.get("logo_duration", 10)) # Show details if enabled details_dur = controller.sponsors.render_details(sponsor) if details_dur > 0: time.sleep(details_dur) elif mode == "team_info": cfg = controller.config.get("team_info", {}) controller.display_manager.render_team_info(cfg) #time.sleep(cfg.get("slide3_duration", 10)) controller.run_transition("after") # Move to next mode controller.current_mode_index = (controller.current_mode_index + 1) % len(controller.available_modes) except KeyboardInterrupt: logger.info("Shutting down display loop gracefully") if __name__ == "__main__": main()