mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
scroll priority to determine data refreshes
This commit is contained in:
@@ -530,14 +530,33 @@ class DisplayController:
|
||||
|
||||
def _update_modules(self):
|
||||
"""Call update methods on active managers."""
|
||||
if self.weather: self.weather.get_weather()
|
||||
if self.stocks: self.stocks.update_stock_data()
|
||||
if self.news: self.news.update_news_data()
|
||||
if self.odds_ticker: self.odds_ticker.update()
|
||||
if self.calendar: self.calendar.update(time.time())
|
||||
if self.youtube: self.youtube.update()
|
||||
if self.text_display: self.text_display.update()
|
||||
if self.of_the_day: self.of_the_day.update(time.time())
|
||||
# Check if we're currently scrolling and defer updates if so
|
||||
if self.display_manager.is_currently_scrolling():
|
||||
logger.debug("Display is currently scrolling, deferring module updates")
|
||||
# Defer updates for modules that might cause lag during scrolling
|
||||
if self.odds_ticker:
|
||||
self.display_manager.defer_update(self.odds_ticker.update, priority=1)
|
||||
if self.stocks:
|
||||
self.display_manager.defer_update(self.stocks.update_stock_data, priority=2)
|
||||
if self.news:
|
||||
self.display_manager.defer_update(self.news.update_news_data, priority=2)
|
||||
# Continue with non-scrolling-sensitive updates
|
||||
if self.weather: self.weather.get_weather()
|
||||
if self.calendar: self.calendar.update(time.time())
|
||||
if self.youtube: self.youtube.update()
|
||||
if self.text_display: self.text_display.update()
|
||||
if self.of_the_day: self.of_the_day.update(time.time())
|
||||
else:
|
||||
# Not scrolling, perform all updates normally
|
||||
if self.weather: self.weather.get_weather()
|
||||
if self.stocks: self.stocks.update_stock_data()
|
||||
if self.news: self.news.update_news_data()
|
||||
if self.odds_ticker: self.odds_ticker.update()
|
||||
if self.calendar: self.calendar.update(time.time())
|
||||
if self.youtube: self.youtube.update()
|
||||
if self.text_display: self.text_display.update()
|
||||
if self.of_the_day: self.of_the_day.update(time.time())
|
||||
|
||||
# News manager fetches data when displayed, not during updates
|
||||
# if self.news_manager: self.news_manager.fetch_news_data()
|
||||
|
||||
@@ -940,6 +959,9 @@ class DisplayController:
|
||||
# Update data for all modules first
|
||||
self._update_modules()
|
||||
|
||||
# Process any deferred updates that may have accumulated
|
||||
self.display_manager.process_deferred_updates()
|
||||
|
||||
# Update live modes in rotation if needed
|
||||
self._update_live_modes_in_rotation()
|
||||
|
||||
|
||||
@@ -30,6 +30,15 @@ class DisplayManager:
|
||||
self._snapshot_path = "/tmp/led_matrix_preview.png"
|
||||
self._snapshot_min_interval_sec = 0.2 # max ~5 fps
|
||||
self._last_snapshot_ts = 0.0
|
||||
|
||||
# Scrolling state tracking for graceful updates
|
||||
self._scrolling_state = {
|
||||
'is_scrolling': False,
|
||||
'last_scroll_activity': 0,
|
||||
'scroll_inactivity_threshold': 2.0, # seconds of inactivity before considering "not scrolling"
|
||||
'deferred_updates': []
|
||||
}
|
||||
|
||||
self._setup_matrix()
|
||||
logger.info("Matrix setup completed in %.3f seconds", time.time() - start_time)
|
||||
|
||||
@@ -634,6 +643,77 @@ class DisplayManager:
|
||||
|
||||
return dt.strftime(f"%b %-d{suffix}")
|
||||
|
||||
def set_scrolling_state(self, is_scrolling: bool):
|
||||
"""Set the current scrolling state. Call this when a display starts/stops scrolling."""
|
||||
current_time = time.time()
|
||||
self._scrolling_state['is_scrolling'] = is_scrolling
|
||||
if is_scrolling:
|
||||
self._scrolling_state['last_scroll_activity'] = current_time
|
||||
logger.debug(f"Scrolling state set to: {is_scrolling}")
|
||||
|
||||
def is_currently_scrolling(self) -> bool:
|
||||
"""Check if the display is currently in a scrolling state."""
|
||||
current_time = time.time()
|
||||
|
||||
# If explicitly not scrolling, return False
|
||||
if not self._scrolling_state['is_scrolling']:
|
||||
return False
|
||||
|
||||
# If we've been inactive for the threshold period, consider it not scrolling
|
||||
if current_time - self._scrolling_state['last_scroll_activity'] > self._scrolling_state['scroll_inactivity_threshold']:
|
||||
self._scrolling_state['is_scrolling'] = False
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def defer_update(self, update_func, priority: int = 0):
|
||||
"""Defer an update function to be called when not scrolling.
|
||||
|
||||
Args:
|
||||
update_func: Function to call when not scrolling
|
||||
priority: Priority level (lower numbers = higher priority)
|
||||
"""
|
||||
self._scrolling_state['deferred_updates'].append({
|
||||
'func': update_func,
|
||||
'priority': priority,
|
||||
'timestamp': time.time()
|
||||
})
|
||||
# Sort by priority (lower numbers first)
|
||||
self._scrolling_state['deferred_updates'].sort(key=lambda x: x['priority'])
|
||||
logger.debug(f"Deferred update added. Total deferred: {len(self._scrolling_state['deferred_updates'])}")
|
||||
|
||||
def process_deferred_updates(self):
|
||||
"""Process any deferred updates if not currently scrolling."""
|
||||
if self.is_currently_scrolling():
|
||||
return
|
||||
|
||||
if not self._scrolling_state['deferred_updates']:
|
||||
return
|
||||
|
||||
# Process all deferred updates
|
||||
updates_to_process = self._scrolling_state['deferred_updates'].copy()
|
||||
self._scrolling_state['deferred_updates'].clear()
|
||||
|
||||
logger.debug(f"Processing {len(updates_to_process)} deferred updates")
|
||||
|
||||
for update_info in updates_to_process:
|
||||
try:
|
||||
update_info['func']()
|
||||
logger.debug("Deferred update executed successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing deferred update: {e}")
|
||||
# Re-add failed updates for retry
|
||||
self._scrolling_state['deferred_updates'].append(update_info)
|
||||
|
||||
def get_scrolling_stats(self) -> dict:
|
||||
"""Get current scrolling statistics for debugging."""
|
||||
return {
|
||||
'is_scrolling': self._scrolling_state['is_scrolling'],
|
||||
'last_activity': self._scrolling_state['last_scroll_activity'],
|
||||
'deferred_count': len(self._scrolling_state['deferred_updates']),
|
||||
'inactivity_threshold': self._scrolling_state['scroll_inactivity_threshold']
|
||||
}
|
||||
|
||||
def _write_snapshot_if_due(self) -> None:
|
||||
"""Write the current image to a PNG snapshot file at a limited frequency."""
|
||||
try:
|
||||
|
||||
@@ -937,6 +937,16 @@ class OddsTickerManager:
|
||||
logger.debug("Odds ticker is disabled, skipping update")
|
||||
return
|
||||
|
||||
# Check if we're currently scrolling and defer the update if so
|
||||
if self.display_manager.is_currently_scrolling():
|
||||
logger.debug("Odds ticker is currently scrolling, deferring update")
|
||||
self.display_manager.defer_update(self._perform_update, priority=1)
|
||||
return
|
||||
|
||||
self._perform_update()
|
||||
|
||||
def _perform_update(self):
|
||||
"""Internal method to perform the actual update."""
|
||||
current_time = time.time()
|
||||
if current_time - self.last_update < self.update_interval:
|
||||
logger.debug(f"Odds ticker update interval not reached. Next update in {self.update_interval - (current_time - self.last_update)} seconds")
|
||||
@@ -992,8 +1002,18 @@ class OddsTickerManager:
|
||||
try:
|
||||
current_time = time.time()
|
||||
|
||||
# Check if we should be scrolling
|
||||
should_scroll = current_time - self.last_scroll_time >= self.scroll_delay
|
||||
|
||||
# Signal scrolling state to display manager
|
||||
if should_scroll:
|
||||
self.display_manager.set_scrolling_state(True)
|
||||
else:
|
||||
# If we're not scrolling, check if we should process deferred updates
|
||||
self.display_manager.process_deferred_updates()
|
||||
|
||||
# Scroll the image
|
||||
if current_time - self.last_scroll_time >= self.scroll_delay:
|
||||
if should_scroll:
|
||||
self.scroll_position += self.scroll_speed
|
||||
self.last_scroll_time = current_time
|
||||
|
||||
@@ -1010,6 +1030,8 @@ class OddsTickerManager:
|
||||
# Stop scrolling when we reach the end
|
||||
if self.scroll_position >= self.ticker_image.width - width:
|
||||
self.scroll_position = self.ticker_image.width - width
|
||||
# Signal that scrolling has stopped
|
||||
self.display_manager.set_scrolling_state(False)
|
||||
|
||||
# Create the visible part of the image by pasting from the ticker_image
|
||||
visible_image = Image.new('RGB', (width, height))
|
||||
|
||||
@@ -366,32 +366,52 @@ class StockManager:
|
||||
logger.info("Stock display settings changed, clearing cache")
|
||||
|
||||
def update_stock_data(self):
|
||||
"""Update stock and crypto data for all configured symbols."""
|
||||
"""Update stock data from API."""
|
||||
current_time = time.time()
|
||||
update_interval = self.stocks_config.get('update_interval', 300)
|
||||
update_interval = self.stocks_config.get('update_interval', 600)
|
||||
|
||||
# Check if we need to update based on time
|
||||
if current_time - self.last_update > update_interval:
|
||||
stock_symbols = self.stocks_config.get('symbols', [])
|
||||
crypto_symbols = self.crypto_config.get('symbols', []) if self.crypto_config.get('enabled', False) else []
|
||||
# Check if we're currently scrolling and defer the update if so
|
||||
if self.display_manager.is_currently_scrolling():
|
||||
logger.debug("Stock display is currently scrolling, deferring update")
|
||||
self.display_manager.defer_update(self._perform_stock_update, priority=2)
|
||||
return
|
||||
|
||||
if not stock_symbols and not crypto_symbols:
|
||||
logger.warning("No stock or crypto symbols configured")
|
||||
self._perform_stock_update()
|
||||
|
||||
def _perform_stock_update(self):
|
||||
"""Internal method to perform the actual stock update."""
|
||||
current_time = time.time()
|
||||
update_interval = self.stocks_config.get('update_interval', 600)
|
||||
|
||||
if current_time - self.last_update < update_interval:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.debug("Updating stock data")
|
||||
symbols = self.stocks_config.get('symbols', [])
|
||||
|
||||
if not symbols:
|
||||
logger.warning("No stock symbols configured")
|
||||
return
|
||||
|
||||
# Update stocks
|
||||
for symbol in stock_symbols:
|
||||
data = self._fetch_stock_data(symbol, is_crypto=False)
|
||||
if data:
|
||||
self.stock_data[symbol] = data
|
||||
|
||||
# Update crypto
|
||||
for symbol in crypto_symbols:
|
||||
data = self._fetch_stock_data(symbol, is_crypto=True)
|
||||
if data:
|
||||
self.stock_data[symbol] = data
|
||||
|
||||
|
||||
# Fetch stock data
|
||||
for symbol in symbols:
|
||||
try:
|
||||
data = self._fetch_stock_data(symbol)
|
||||
if data:
|
||||
self.stock_data[symbol] = data
|
||||
logger.debug(f"Updated data for {symbol}: {data}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching data for {symbol}: {e}")
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
# Clear cached text to force regeneration
|
||||
self.cached_text = None
|
||||
self.cached_text_image = None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating stock data: {e}")
|
||||
|
||||
def _get_stock_logo(self, symbol: str, is_crypto: bool = False) -> Image.Image:
|
||||
"""Get stock or crypto logo image from local directory."""
|
||||
@@ -696,6 +716,15 @@ class StockManager:
|
||||
width = self.display_manager.matrix.width
|
||||
total_width = self.cached_text_image.width
|
||||
|
||||
# Check if we should be scrolling
|
||||
should_scroll = True # Stock display always scrolls continuously
|
||||
|
||||
# Signal scrolling state to display manager
|
||||
self.display_manager.set_scrolling_state(True)
|
||||
|
||||
# Process any deferred updates (though stocks are always scrolling)
|
||||
self.display_manager.process_deferred_updates()
|
||||
|
||||
# Update scroll position with small increments
|
||||
self.scroll_position = (self.scroll_position + self.scroll_speed) % total_width
|
||||
|
||||
|
||||
@@ -169,50 +169,52 @@ class StockNewsManager:
|
||||
return []
|
||||
|
||||
def update_news_data(self):
|
||||
"""Update news data for all configured stock symbols."""
|
||||
"""Update news data from API."""
|
||||
current_time = time.time()
|
||||
update_interval = self.stock_news_config.get('update_interval', 300)
|
||||
update_interval = self.stock_news_config.get('update_interval', 3600)
|
||||
|
||||
# Check if we need to update based on time
|
||||
if current_time - self.last_update > update_interval:
|
||||
# Check if we're currently scrolling and defer the update if so
|
||||
if self.display_manager.is_currently_scrolling():
|
||||
logger.debug("Stock news display is currently scrolling, deferring update")
|
||||
self.display_manager.defer_update(self._perform_news_update, priority=2)
|
||||
return
|
||||
|
||||
self._perform_news_update()
|
||||
|
||||
def _perform_news_update(self):
|
||||
"""Internal method to perform the actual news update."""
|
||||
current_time = time.time()
|
||||
update_interval = self.stock_news_config.get('update_interval', 3600)
|
||||
|
||||
if current_time - self.last_update < update_interval:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.debug("Updating stock news data")
|
||||
symbols = self.stocks_config.get('symbols', [])
|
||||
|
||||
if not symbols:
|
||||
logger.warning("No stock symbols configured for news")
|
||||
return
|
||||
|
||||
# Get cached data
|
||||
cached_data = self.cache_manager.get('stock_news')
|
||||
|
||||
# Update each symbol
|
||||
new_data = {}
|
||||
success = False
|
||||
|
||||
|
||||
# Fetch news for each symbol
|
||||
for symbol in symbols:
|
||||
# Check if data has changed before fetching
|
||||
if cached_data and symbol in cached_data:
|
||||
current_state = cached_data[symbol]
|
||||
if not self.cache_manager.has_data_changed('stock_news', current_state):
|
||||
logger.info(f"News data hasn't changed for {symbol}, using existing data")
|
||||
new_data[symbol] = current_state
|
||||
success = True
|
||||
continue
|
||||
|
||||
# Add a longer delay between requests to avoid rate limiting
|
||||
time.sleep(random.uniform(1.0, 2.0)) # increased delay between requests
|
||||
news_items = self._fetch_news(symbol)
|
||||
if news_items:
|
||||
new_data[symbol] = news_items
|
||||
success = True
|
||||
try:
|
||||
news = self._fetch_news_for_symbol(symbol)
|
||||
if news:
|
||||
self.news_data[symbol] = news
|
||||
logger.debug(f"Updated news for {symbol}: {len(news)} headlines")
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching news for {symbol}: {e}")
|
||||
|
||||
if success:
|
||||
# Cache the new data
|
||||
self.cache_manager.update_cache('stock_news', new_data)
|
||||
# Only update the displayed data when we have new data
|
||||
self.news_data = new_data
|
||||
self.last_update = current_time
|
||||
logger.info(f"Updated news data for {len(new_data)} symbols")
|
||||
else:
|
||||
logger.error("Failed to fetch news for any configured stocks")
|
||||
self.last_update = current_time
|
||||
|
||||
# Clear cached text to force regeneration
|
||||
self.cached_text = None
|
||||
self.cached_text_image = None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating stock news data: {e}")
|
||||
|
||||
def _create_text_image(self, text: str, color: Tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
|
||||
"""Create an image containing the text for efficient scrolling."""
|
||||
@@ -405,12 +407,23 @@ class StockNewsManager:
|
||||
|
||||
# If total_width is somehow less than screen width, don't scroll
|
||||
if total_width <= width:
|
||||
# Signal that we're not scrolling for this frame
|
||||
self.display_manager.set_scrolling_state(False)
|
||||
# Process any deferred updates
|
||||
self.display_manager.process_deferred_updates()
|
||||
|
||||
self.display_manager.image.paste(self.cached_text_image, (0, 0))
|
||||
self.display_manager.update_display()
|
||||
time.sleep(self.stock_news_config.get('item_display_duration', 5)) # Hold static image
|
||||
self.cached_text_image = None # Force recreation next cycle
|
||||
return True
|
||||
|
||||
# Signal that we're scrolling
|
||||
self.display_manager.set_scrolling_state(True)
|
||||
|
||||
# Process any deferred updates (though news is usually always scrolling)
|
||||
self.display_manager.process_deferred_updates()
|
||||
|
||||
# Update scroll position
|
||||
self.scroll_position += self.scroll_speed
|
||||
if self.scroll_position >= total_width:
|
||||
|
||||
Reference in New Issue
Block a user