set ytm to be on-demand, disable other displays for debugging

This commit is contained in:
ChuckBuilds
2025-05-25 14:53:21 -05:00
parent 7da676f636
commit 8f8587f856
3 changed files with 90 additions and 61 deletions

View File

@@ -63,7 +63,7 @@
} }
}, },
"clock": { "clock": {
"enabled": true, "enabled": false,
"format": "%H:%M:%S", "format": "%H:%M:%S",
"update_interval": 1 "update_interval": 1
}, },
@@ -98,7 +98,7 @@
"headlines_per_rotation": 2 "headlines_per_rotation": 2
}, },
"calendar": { "calendar": {
"enabled": true, "enabled": false,
"credentials_file": "credentials.json", "credentials_file": "credentials.json",
"token_file": "token.pickle", "token_file": "token.pickle",
"update_interval": 3600, "update_interval": 3600,
@@ -106,7 +106,7 @@
"calendars": ["birthdays"] "calendars": ["birthdays"]
}, },
"nhl_scoreboard": { "nhl_scoreboard": {
"enabled": true, "enabled": false,
"test_mode": false, "test_mode": false,
"update_interval_seconds": 300, "update_interval_seconds": 300,
"live_update_interval": 15, "live_update_interval": 15,
@@ -168,7 +168,7 @@
} }
}, },
"ncaam_scoreboard": { "ncaam_scoreboard": {
"enabled": true, "enabled": false,
"test_mode": false, "test_mode": false,
"update_interval_seconds": 3600, "update_interval_seconds": 3600,
"live_update_interval": 15, "live_update_interval": 15,
@@ -182,7 +182,7 @@
} }
}, },
"ncaa_bb_scoreboard": { "ncaa_bb_scoreboard": {
"enabled": true, "enabled": false,
"test_mode": false, "test_mode": false,
"update_interval_seconds": 3600, "update_interval_seconds": 3600,
"live_update_interval": 20, "live_update_interval": 20,
@@ -202,7 +202,7 @@
"update_interval": 3600 "update_interval": 3600
}, },
"mlb": { "mlb": {
"enabled": true, "enabled": false,
"test_mode": false, "test_mode": false,
"update_interval_seconds": 300, "update_interval_seconds": 300,
"live_update_interval": 20, "live_update_interval": 20,
@@ -220,10 +220,10 @@
"text_display": { "text_display": {
"enabled": true, "enabled": true,
"text": "Subscribe to ChuckBuilds", "text": "Subscribe to ChuckBuilds",
"font_path": "assets/fonts/5x7.bdf", "font_path": "assets/fonts/press-start-2p.ttf",
"font_size": 7, "font_size": 8,
"scroll": false, "scroll": true,
"scroll_speed": 25, "scroll_speed": 10,
"text_color": [255, 0, 0], "text_color": [255, 0, 0],
"background_color": [0, 0, 0] "background_color": [0, 0, 0]
}, },

View File

@@ -48,6 +48,7 @@ class MusicManager:
self.scroll_position_artist = 0 self.scroll_position_artist = 0
self.title_scroll_tick = 0 self.title_scroll_tick = 0
self.artist_scroll_tick = 0 self.artist_scroll_tick = 0
self.is_music_display_active = False # New state variable
self._load_config() # Load config first self._load_config() # Load config first
self._initialize_clients() # Initialize based on loaded config self._initialize_clients() # Initialize based on loaded config
@@ -115,11 +116,8 @@ class MusicManager:
if self.preferred_source in ["auto", "ytm"]: if self.preferred_source in ["auto", "ytm"]:
try: try:
self.ytm = YTMClient(update_callback=self._handle_ytm_direct_update) self.ytm = YTMClient(update_callback=self._handle_ytm_direct_update)
if not self.ytm.is_available(): # We no longer check is_available() or connect here. Connection is on-demand.
logging.warning(f"YTM Companion server not reachable at {self.ytm.base_url}. YTM features disabled.") logging.info(f"YTMClient initialized. Connection will be managed on-demand. Configured URL: {self.ytm.base_url}")
self.ytm = None
else:
logging.info(f"YTM Companion server connected at {self.ytm.base_url}.")
except Exception as e: except Exception as e:
logging.error(f"Failed to initialize YTM client: {e}") logging.error(f"Failed to initialize YTM client: {e}")
self.ytm = None self.ytm = None
@@ -127,11 +125,33 @@ class MusicManager:
logging.info("YTM client initialization skipped due to preferred_source setting.") logging.info("YTM client initialization skipped due to preferred_source setting.")
self.ytm = None self.ytm = None
def activate_music_display(self):
logger.info("Music display activated.")
self.is_music_display_active = True
if self.ytm and self.preferred_source in ["auto", "ytm"]:
if not self.ytm.is_connected():
logger.info("Attempting to connect YTM client due to music display activation.")
# Pass a reasonable timeout for on-demand connection
if self.ytm.connect_client(timeout=10):
logger.info("YTM client connected successfully on display activation.")
else:
logger.warning("YTM client failed to connect on display activation.")
else:
logger.debug("YTM client already connected during music display activation.")
def deactivate_music_display(self):
logger.info("Music display deactivated.")
self.is_music_display_active = False
if self.ytm and self.ytm.is_connected():
logger.info("Disconnecting YTM client due to music display deactivation.")
self.ytm.disconnect_client()
def _handle_ytm_direct_update(self, ytm_data): def _handle_ytm_direct_update(self, ytm_data):
"""Handles a direct state update from YTMClient.""" """Handles a direct state update from YTMClient."""
logger.debug(f"MusicManager received direct YTM update: {ytm_data.get('track', {}).get('title') if ytm_data else 'No Data'}") logger.debug(f"MusicManager received direct YTM update: {ytm_data.get('track', {}).get('title') if ytm_data else 'No Data'}")
if not self.enabled: if not self.enabled or not self.is_music_display_active: # Check if display is active
logger.debug("Skipping YTM direct update: Manager disabled or music display not active.")
return return
# Only process if YTM is the preferred source, or if auto and Spotify isn't actively playing. # Only process if YTM is the preferred source, or if auto and Spotify isn't actively playing.
@@ -271,10 +291,11 @@ class MusicManager:
if should_poll_ytm_now: if should_poll_ytm_now:
# Re-check availability just before polling # Re-check availability just before polling
if self.ytm.is_available(): if self.ytm and self.ytm.is_connected(): # Check if connected instead of is_available()
try: try:
ytm_track = self.ytm.get_current_track() ytm_track = self.ytm.get_current_track()
if ytm_track and not ytm_track.get('player', {}).get('isPaused'): # Ensure ytm_track is not None before trying to access its player info
if ytm_track and ytm_track.get('player') and not ytm_track.get('player', {}).get('isPaused'):
# If YTM is preferred, it overrides Spotify even if Spotify was playing # If YTM is preferred, it overrides Spotify even if Spotify was playing
if self.preferred_source == "ytm" or not is_playing: if self.preferred_source == "ytm" or not is_playing:
polled_track_info = ytm_track polled_track_info = ytm_track
@@ -282,11 +303,11 @@ class MusicManager:
is_playing = True is_playing = True
logging.debug(f"Polling YTM: Active track - {ytm_track.get('track', {}).get('title')}") logging.debug(f"Polling YTM: Active track - {ytm_track.get('track', {}).get('title')}")
else: else:
logging.debug("Polling YTM: No active track or player paused.") logging.debug("Polling YTM: No active track or player paused (or track data missing player info).")
except Exception as e: except Exception as e:
logging.error(f"Error polling YTM: {e}") logging.error(f"Error polling YTM: {e}")
else: else:
logging.debug("Skipping YTM poll: Server not available.") logging.debug("Skipping YTM poll: Client not initialized or not connected.")
# Consider setting self.ytm = None if it becomes unavailable repeatedly? # Consider setting self.ytm = None if it becomes unavailable repeatedly?
# --- Consolidate and Check for Changes --- # --- Consolidate and Check for Changes ---
@@ -420,12 +441,16 @@ class MusicManager:
else: else:
logger.info("Music manager: Polling thread stopped.") logger.info("Music manager: Polling thread stopped.")
self.poll_thread = None # Clear the thread object self.poll_thread = None # Clear the thread object
# Also ensure YTM client is disconnected when polling stops completely
self.deactivate_music_display() # This will also handle YTM disconnect if needed
# Method moved from DisplayController and renamed # Method moved from DisplayController and renamed
def display(self, force_clear: bool = False): def display(self, force_clear: bool = False):
if force_clear: # Removed self.force_clear as it's passed directly if force_clear: # Removed self.force_clear as it's passed directly
self.display_manager.clear() self.display_manager.clear()
# self.force_clear = False # Not needed here # self.force_clear = False # Not needed here
# If forcing clear, it implies the music display might be stopping or resetting.
self.deactivate_music_display() # Deactivate YTM if display is forced clear.
# Use self.current_track_info which is updated by _poll_music_data # Use self.current_track_info which is updated by _poll_music_data
display_info = self.current_track_info display_info = self.current_track_info
@@ -437,6 +462,9 @@ class MusicManager:
logger.info("Music Screen (MusicManager): Nothing playing or info unavailable.") logger.info("Music Screen (MusicManager): Nothing playing or info unavailable.")
self._last_nothing_playing_log_time = time.time() self._last_nothing_playing_log_time = time.time()
if self.is_music_display_active: # If we were showing music and now we are not
self.deactivate_music_display()
self.display_manager.clear() # Clear before drawing "Nothing Playing" self.display_manager.clear() # Clear before drawing "Nothing Playing"
text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font) text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font)
x_pos = (self.display_manager.matrix.width - text_width) // 2 x_pos = (self.display_manager.matrix.width - text_width) // 2
@@ -451,6 +479,11 @@ class MusicManager:
self.last_album_art_url = None # Also clear the URL self.last_album_art_url = None # Also clear the URL
return return
# If we've reached here, it means we are about to display actual music info.
# Ensure YTM is active if it's a potential source.
if not self.is_music_display_active:
self.activate_music_display()
# Ensure screen is cleared if not force_clear but needed (e.g. transition from "Nothing Playing") # Ensure screen is cleared if not force_clear but needed (e.g. transition from "Nothing Playing")
# This might be handled by DisplayController's force_clear logic, but can be an internal check too. # This might be handled by DisplayController's force_clear logic, but can be an internal check too.
# For now, assuming DisplayController manages the initial clear for a new mode. # For now, assuming DisplayController manages the initial clear for a new mode.

View File

@@ -114,75 +114,71 @@ class YTMClient:
else: else:
logging.warning(f"YTM auth file not found at {YTM_AUTH_CONFIG_PATH}. Run the authentication script to generate it. YTM features will be disabled.") logging.warning(f"YTM auth file not found at {YTM_AUTH_CONFIG_PATH}. Run the authentication script to generate it. YTM features will be disabled.")
def _ensure_connected(self, timeout=5): def connect_client(self, timeout=10):
if not self.ytm_token: if not self.ytm_token:
# No token, so cannot authenticate or connect. Log this clearly. logging.warning("No YTM token loaded. Cannot connect to Socket.IO. Run authentication script.")
# load_config already warns if token file or token itself is missing.
# logging.warning("No YTM token loaded. Cannot connect to Socket.IO. Run authentication script.")
self.is_connected = False self.is_connected = False
return False return False
if not self.is_connected: if self.is_connected:
logging.info(f"Attempting to connect to YTM Socket.IO server: {self.base_url} on namespace /api/v1/realtime") logging.debug("YTM client already connected.")
auth_payload = {"token": self.ytm_token} return True
try: logging.info(f"Attempting to connect to YTM Socket.IO server: {self.base_url} on namespace /api/v1/realtime")
self._connection_event.clear() # Clear event before attempting connection auth_payload = {"token": self.ytm_token}
self.sio.connect(
self.base_url, try:
transports=['websocket'], self._connection_event.clear()
wait_timeout=timeout, self.sio.connect(
namespaces=['/api/v1/realtime'], self.base_url,
auth=auth_payload transports=['websocket'],
) wait_timeout=timeout,
# self._connection_event.clear() # No longer clear here namespaces=['/api/v1/realtime'],
# Use a slightly longer timeout for the event wait than the connect call itself auth=auth_payload
# to ensure the connect event has time to be processed. )
event_wait_timeout = timeout + 5 # e.g., if connect timeout is 10s, wait 15s for the event event_wait_timeout = timeout + 5
if not self._connection_event.wait(timeout=event_wait_timeout): if not self._connection_event.wait(timeout=event_wait_timeout):
logging.warning(f"YTM Socket.IO connection event not received within {event_wait_timeout}s (connect timeout was {timeout}s).") logging.warning(f"YTM Socket.IO connection event not received within {event_wait_timeout}s (connect timeout was {timeout}s).")
self.is_connected = False # Ensure is_connected is false on timeout
return False
return self.is_connected # This should be true if connect event fired and no timeout
except socketio.exceptions.ConnectionError as e:
logging.error(f"YTM Socket.IO connection error: {e}")
self.is_connected = False self.is_connected = False
return False return False
except Exception as e: logging.info(f"YTM Socket.IO connection successful: {self.is_connected}")
logging.error(f"Unexpected error during YTM Socket.IO connection: {e}") return self.is_connected
self.is_connected = False except socketio.exceptions.ConnectionError as e:
return False logging.error(f"YTM Socket.IO connection error: {e}")
return True # Already connected self.is_connected = False
return False
except Exception as e:
logging.error(f"Unexpected error during YTM Socket.IO connection: {e}")
self.is_connected = False
return False
def is_available(self): def is_available(self):
if not self.ytm_token: # Quick check: if no token, definitely not available. if not self.ytm_token:
return False return False
if not self.is_connected: return self.is_connected
return self._ensure_connected(timeout=10)
return True
def get_current_track(self): def get_current_track(self):
if not self.is_available(): # is_available will attempt to connect if not connected and token exists if not self.is_connected:
# logging.warning("YTM client not available, cannot get current track.") # is_available() or _ensure_connected() already logs issues.
return None return None
with self._data_lock: with self._data_lock:
if self.last_known_track_data: if self.last_known_track_data:
return self.last_known_track_data return self.last_known_track_data
else: else:
# This is a normal state if no music is playing or just connected
# logging.debug("No track data received yet from YTM Companion Socket.IO.")
return None return None
def disconnect_client(self): def disconnect_client(self):
if self.is_connected: if self.is_connected:
self.sio.disconnect() self.sio.disconnect()
logging.info("YTM Socket.IO client disconnected.") logging.info("YTM Socket.IO client disconnected.")
self.is_connected = False
else:
logging.debug("YTM Socket.IO client already disconnected or not connected.")
# Example Usage (for testing - needs to be adapted for Socket.IO async nature) # Example Usage (for testing - needs to be adapted for Socket.IO async nature)
# if __name__ == '__main__': # if __name__ == '__main__':
# client = YTMClient() # client = YTMClient()
# if client.is_available(): # if client.connect_client():
# print("YTM Server is available (Socket.IO).") # print("YTM Server is available (Socket.IO).")
# try: # try:
# for _ in range(10): # Poll for a few seconds # for _ in range(10): # Poll for a few seconds