mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
update ncaa FB and NFL recent games to look more like other displays
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# LEDMatrix Cache Permissions Fix Script
|
# LEDMatrix Cache Permissions Fix Script
|
||||||
# This script fixes permissions on all known cache directories so they're writable by the daemon or current user
|
# This script fixes permissions on all known cache directories so they're writable by the daemon or current user
|
||||||
|
# Also sets up placeholder logo directories for sports managers
|
||||||
|
|
||||||
echo "Fixing LEDMatrix cache directory permissions..."
|
echo "Fixing LEDMatrix cache directory permissions..."
|
||||||
|
|
||||||
@@ -18,8 +19,8 @@ for CACHE_DIR in "${CACHE_DIRS[@]}"; do
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Checking cache directory: $CACHE_DIR"
|
echo "Checking cache directory: $CACHE_DIR"
|
||||||
if [ ! -d "$CACHE_DIR" ]; then
|
if [ ! -d "$CACHE_DIR" ]; then
|
||||||
echo " - Directory does not exist. Skipping."
|
echo " - Directory does not exist. Creating it..."
|
||||||
continue
|
sudo mkdir -p "$CACHE_DIR"
|
||||||
fi
|
fi
|
||||||
echo " - Current permissions:"
|
echo " - Current permissions:"
|
||||||
ls -ld "$CACHE_DIR"
|
ls -ld "$CACHE_DIR"
|
||||||
@@ -37,6 +38,42 @@ for CACHE_DIR in "${CACHE_DIRS[@]}"; do
|
|||||||
echo " - Permissions fix complete for $CACHE_DIR."
|
echo " - Permissions fix complete for $CACHE_DIR."
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Set up placeholder logos directory for sports managers
|
||||||
|
echo ""
|
||||||
|
echo "Setting up placeholder logos directory for sports managers..."
|
||||||
|
|
||||||
|
PLACEHOLDER_DIR="/var/cache/ledmatrix/placeholder_logos"
|
||||||
|
if [ ! -d "$PLACEHOLDER_DIR" ]; then
|
||||||
|
echo "Creating placeholder logos directory: $PLACEHOLDER_DIR"
|
||||||
|
sudo mkdir -p "$PLACEHOLDER_DIR"
|
||||||
|
sudo chown "$REAL_USER":"$REAL_GROUP" "$PLACEHOLDER_DIR"
|
||||||
|
sudo chmod 777 "$PLACEHOLDER_DIR"
|
||||||
|
else
|
||||||
|
echo "Placeholder logos directory already exists: $PLACEHOLDER_DIR"
|
||||||
|
sudo chmod 777 "$PLACEHOLDER_DIR"
|
||||||
|
sudo chown "$REAL_USER":"$REAL_GROUP" "$PLACEHOLDER_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " - Current permissions:"
|
||||||
|
ls -ld "$PLACEHOLDER_DIR"
|
||||||
|
echo " - Testing write access as $REAL_USER..."
|
||||||
|
if sudo -u "$REAL_USER" test -w "$PLACEHOLDER_DIR"; then
|
||||||
|
echo " ✓ Placeholder logos directory is writable by $REAL_USER"
|
||||||
|
else
|
||||||
|
echo " ✗ Placeholder logos directory is not writable by $REAL_USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test with daemon user (which the system might run as)
|
||||||
|
if sudo -u daemon test -w "$PLACEHOLDER_DIR" 2>/dev/null; then
|
||||||
|
echo " ✓ Placeholder logos directory is writable by daemon user"
|
||||||
|
else
|
||||||
|
echo " ✗ Placeholder logos directory is not writable by daemon user"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "All cache directory permission fixes attempted."
|
echo "All cache directory permission fixes attempted."
|
||||||
echo "If you still see errors, check which user is running the LEDMatrix service and ensure it matches the owner above."
|
echo "If you still see errors, check which user is running the LEDMatrix service and ensure it matches the owner above."
|
||||||
|
echo ""
|
||||||
|
echo "The system will now create placeholder logos in:"
|
||||||
|
echo " $PLACEHOLDER_DIR"
|
||||||
|
echo "This should eliminate the permission denied warnings for sports logos."
|
||||||
@@ -980,11 +980,12 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
|
|||||||
|
|
||||||
center_y = self.display_height // 2
|
center_y = self.display_height // 2
|
||||||
|
|
||||||
home_x = self.display_width - home_logo.width + 18
|
# MLB-style logo positioning (closer to edges)
|
||||||
|
home_x = self.display_width - home_logo.width + 2
|
||||||
home_y = center_y - (home_logo.height // 2)
|
home_y = center_y - (home_logo.height // 2)
|
||||||
main_img.paste(home_logo, (home_x, home_y), home_logo)
|
main_img.paste(home_logo, (home_x, home_y), home_logo)
|
||||||
|
|
||||||
away_x = -18
|
away_x = -2
|
||||||
away_y = center_y - (away_logo.height // 2)
|
away_y = center_y - (away_logo.height // 2)
|
||||||
main_img.paste(away_logo, (away_x, away_y), away_logo)
|
main_img.paste(away_logo, (away_x, away_y), away_logo)
|
||||||
|
|
||||||
|
|||||||
@@ -922,11 +922,12 @@ class NFLRecentManager(BaseNFLManager): # Renamed class
|
|||||||
|
|
||||||
center_y = self.display_height // 2
|
center_y = self.display_height // 2
|
||||||
|
|
||||||
home_x = self.display_width - home_logo.width + 18
|
# MLB-style logo positioning (closer to edges)
|
||||||
|
home_x = self.display_width - home_logo.width + 2
|
||||||
home_y = center_y - (home_logo.height // 2)
|
home_y = center_y - (home_logo.height // 2)
|
||||||
main_img.paste(home_logo, (home_x, home_y), home_logo)
|
main_img.paste(home_logo, (home_x, home_y), home_logo)
|
||||||
|
|
||||||
away_x = -18
|
away_x = -2
|
||||||
away_y = center_y - (away_logo.height // 2)
|
away_y = center_y - (away_logo.height // 2)
|
||||||
main_img.paste(away_logo, (away_x, away_y), away_logo)
|
main_img.paste(away_logo, (away_x, away_y), away_logo)
|
||||||
|
|
||||||
|
|||||||
@@ -387,11 +387,25 @@ class BaseSoccerManager:
|
|||||||
|
|
||||||
self.logger.debug(f"Logo path: {logo_path}")
|
self.logger.debug(f"Logo path: {logo_path}")
|
||||||
|
|
||||||
|
# Check if logo exists in original path or cache directory
|
||||||
|
cache_logo_path = None
|
||||||
|
if hasattr(self.cache_manager, 'cache_dir') and self.cache_manager.cache_dir:
|
||||||
|
cache_logo_dir = os.path.join(self.cache_manager.cache_dir, 'placeholder_logos')
|
||||||
|
cache_logo_path = os.path.join(cache_logo_dir, f"{team_abbrev}.png")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(logo_path):
|
if not os.path.exists(logo_path) and not (cache_logo_path and os.path.exists(cache_logo_path)):
|
||||||
self.logger.info(f"Creating placeholder logo for {team_abbrev}")
|
self.logger.info(f"Creating placeholder logo for {team_abbrev}")
|
||||||
|
# Try to create placeholder in cache directory instead of assets directory
|
||||||
|
cache_logo_path = None
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(logo_path), exist_ok=True)
|
# Use cache directory for placeholder logos
|
||||||
|
if hasattr(self.cache_manager, 'cache_dir') and self.cache_manager.cache_dir:
|
||||||
|
cache_logo_dir = os.path.join(self.cache_manager.cache_dir, 'placeholder_logos')
|
||||||
|
os.makedirs(cache_logo_dir, exist_ok=True)
|
||||||
|
cache_logo_path = os.path.join(cache_logo_dir, f"{team_abbrev}.png")
|
||||||
|
|
||||||
|
# Create placeholder logo
|
||||||
logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255))
|
logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255))
|
||||||
draw = ImageDraw.Draw(logo)
|
draw = ImageDraw.Draw(logo)
|
||||||
# Optionally add text to placeholder
|
# Optionally add text to placeholder
|
||||||
@@ -403,17 +417,30 @@ class BaseSoccerManager:
|
|||||||
draw.text((text_x, text_y), team_abbrev, fill=(0,0,0,255), font=placeholder_font)
|
draw.text((text_x, text_y), team_abbrev, fill=(0,0,0,255), font=placeholder_font)
|
||||||
except IOError:
|
except IOError:
|
||||||
pass # Font not found, skip text
|
pass # Font not found, skip text
|
||||||
logo.save(logo_path)
|
logo.save(cache_logo_path)
|
||||||
self.logger.info(f"Created placeholder logo at {logo_path}")
|
self.logger.info(f"Created placeholder logo in cache at {cache_logo_path}")
|
||||||
except PermissionError as pe:
|
# Update logo_path to use cache version
|
||||||
self.logger.warning(f"Permission denied creating placeholder logo for {team_abbrev}: {pe}")
|
logo_path = cache_logo_path
|
||||||
|
else:
|
||||||
|
# No cache directory available, just use in-memory placeholder
|
||||||
|
raise PermissionError("No writable cache directory available")
|
||||||
|
except (PermissionError, OSError) as pe:
|
||||||
|
self.logger.debug(f"Could not create placeholder logo file for {team_abbrev}: {pe}")
|
||||||
# Return a simple in-memory placeholder instead
|
# Return a simple in-memory placeholder instead
|
||||||
logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255))
|
logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255))
|
||||||
self._logo_cache[team_abbrev] = logo
|
self._logo_cache[team_abbrev] = logo
|
||||||
return logo
|
return logo
|
||||||
|
|
||||||
|
# Try to load logo from original path or cache directory
|
||||||
|
logo_to_load = None
|
||||||
|
if os.path.exists(logo_path):
|
||||||
|
logo_to_load = logo_path
|
||||||
|
elif cache_logo_path and os.path.exists(cache_logo_path):
|
||||||
|
logo_to_load = cache_logo_path
|
||||||
|
|
||||||
|
if logo_to_load:
|
||||||
try:
|
try:
|
||||||
logo = Image.open(logo_path)
|
logo = Image.open(logo_to_load)
|
||||||
if logo.mode != 'RGBA':
|
if logo.mode != 'RGBA':
|
||||||
logo = logo.convert('RGBA')
|
logo = logo.convert('RGBA')
|
||||||
|
|
||||||
|
|||||||
@@ -408,6 +408,30 @@ class StockNewsManager:
|
|||||||
self.cached_text_image = None
|
self.cached_text_image = None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Calculate the visible portion
|
||||||
|
# Handle wrap-around drawing
|
||||||
|
visible_end = self.scroll_position + width
|
||||||
|
if visible_end <= total_width:
|
||||||
|
# Normal case: Paste single crop
|
||||||
|
visible_portion = self.cached_text_image.crop((
|
||||||
|
self.scroll_position, 0,
|
||||||
|
visible_end, height
|
||||||
|
))
|
||||||
|
self.display_manager.image.paste(visible_portion, (0, 0))
|
||||||
|
else:
|
||||||
|
# Wrap-around case: Paste two parts
|
||||||
|
width1 = total_width - self.scroll_position
|
||||||
|
width2 = width - width1
|
||||||
|
portion1 = self.cached_text_image.crop((self.scroll_position, 0, total_width, height))
|
||||||
|
portion2 = self.cached_text_image.crop((0, 0, width2, height))
|
||||||
|
self.display_manager.image.paste(portion1, (0, 0))
|
||||||
|
self.display_manager.image.paste(portion2, (width1, 0))
|
||||||
|
|
||||||
|
self.display_manager.update_display()
|
||||||
|
self._log_frame_rate()
|
||||||
|
time.sleep(self.scroll_delay)
|
||||||
|
return True
|
||||||
|
|
||||||
def calculate_dynamic_duration(self):
|
def calculate_dynamic_duration(self):
|
||||||
"""Calculate the exact time needed to display all news headlines"""
|
"""Calculate the exact time needed to display all news headlines"""
|
||||||
# If dynamic duration is disabled, use fixed duration from config
|
# If dynamic duration is disabled, use fixed duration from config
|
||||||
@@ -464,28 +488,3 @@ class StockNewsManager:
|
|||||||
def get_dynamic_duration(self) -> int:
|
def get_dynamic_duration(self) -> int:
|
||||||
"""Get the calculated dynamic duration for display"""
|
"""Get the calculated dynamic duration for display"""
|
||||||
return self.dynamic_duration
|
return self.dynamic_duration
|
||||||
|
|
||||||
# Calculate the visible portion
|
|
||||||
# Handle wrap-around drawing
|
|
||||||
visible_end = self.scroll_position + width
|
|
||||||
if visible_end <= total_width:
|
|
||||||
# Normal case: Paste single crop
|
|
||||||
visible_portion = self.cached_text_image.crop((
|
|
||||||
self.scroll_position, 0,
|
|
||||||
visible_end, height
|
|
||||||
))
|
|
||||||
self.display_manager.image.paste(visible_portion, (0, 0))
|
|
||||||
else:
|
|
||||||
# Wrap-around case: Paste two parts
|
|
||||||
width1 = total_width - self.scroll_position
|
|
||||||
width2 = width - width1
|
|
||||||
portion1 = self.cached_text_image.crop((self.scroll_position, 0, total_width, height))
|
|
||||||
portion2 = self.cached_text_image.crop((0, 0, width2, height))
|
|
||||||
self.display_manager.image.paste(portion1, (0, 0))
|
|
||||||
self.display_manager.image.paste(portion2, (width1, 0))
|
|
||||||
|
|
||||||
self.display_manager.update_display()
|
|
||||||
self._log_frame_rate()
|
|
||||||
time.sleep(self.scroll_delay)
|
|
||||||
|
|
||||||
return True
|
|
||||||
125
test_soccer_logo_fix.py
Normal file
125
test_soccer_logo_fix.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify the soccer logo permissions fix.
|
||||||
|
This script tests the _load_and_resize_logo method to ensure it can create placeholder logos
|
||||||
|
without permission errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Add the src directory to the path so we can import the modules
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cache_manager import CacheManager
|
||||||
|
from soccer_managers import BaseSoccerManager
|
||||||
|
from display_manager import DisplayManager
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Import error: {e}")
|
||||||
|
print("Make sure you're running this from the LEDMatrix root directory")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def test_soccer_logo_creation():
|
||||||
|
"""Test that soccer placeholder logos can be created without permission errors."""
|
||||||
|
|
||||||
|
print("Testing soccer logo creation...")
|
||||||
|
|
||||||
|
# Create a temporary directory for testing
|
||||||
|
test_dir = tempfile.mkdtemp(prefix="ledmatrix_test_")
|
||||||
|
print(f"Using test directory: {test_dir}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a minimal config
|
||||||
|
config = {
|
||||||
|
"soccer_scoreboard": {
|
||||||
|
"enabled": True,
|
||||||
|
"logo_dir": "assets/sports/soccer_logos",
|
||||||
|
"update_interval_seconds": 60
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"width": 64,
|
||||||
|
"height": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create cache manager with test directory
|
||||||
|
cache_manager = CacheManager()
|
||||||
|
# Override cache directory for testing
|
||||||
|
cache_manager.cache_dir = test_dir
|
||||||
|
|
||||||
|
# Create a mock display manager
|
||||||
|
class MockDisplayManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.width = 64
|
||||||
|
self.height = 32
|
||||||
|
self.image = Image.new('RGB', (64, 32), (0, 0, 0))
|
||||||
|
|
||||||
|
display_manager = MockDisplayManager()
|
||||||
|
|
||||||
|
# Create soccer manager
|
||||||
|
soccer_manager = BaseSoccerManager(config, display_manager, cache_manager)
|
||||||
|
|
||||||
|
# Test teams that might not have logos
|
||||||
|
test_teams = ["ATX", "STL", "SD", "CLT", "TEST1", "TEST2"]
|
||||||
|
|
||||||
|
print("\nTesting logo creation for missing teams:")
|
||||||
|
for team in test_teams:
|
||||||
|
print(f" Testing {team}...")
|
||||||
|
try:
|
||||||
|
logo = soccer_manager._load_and_resize_logo(team)
|
||||||
|
if logo:
|
||||||
|
print(f" ✓ Successfully created logo for {team} (size: {logo.size})")
|
||||||
|
else:
|
||||||
|
print(f" ✗ Failed to create logo for {team}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Error creating logo for {team}: {e}")
|
||||||
|
|
||||||
|
# Check if placeholder logos were created in cache
|
||||||
|
placeholder_dir = os.path.join(test_dir, 'placeholder_logos')
|
||||||
|
if os.path.exists(placeholder_dir):
|
||||||
|
placeholder_files = os.listdir(placeholder_dir)
|
||||||
|
print(f"\nPlaceholder logos created in cache: {len(placeholder_files)} files")
|
||||||
|
for file in placeholder_files:
|
||||||
|
print(f" - {file}")
|
||||||
|
else:
|
||||||
|
print("\nNo placeholder logos directory created (using in-memory placeholders)")
|
||||||
|
|
||||||
|
print("\n✓ Soccer logo test completed successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n✗ Test failed with error: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up test directory
|
||||||
|
try:
|
||||||
|
shutil.rmtree(test_dir)
|
||||||
|
print(f"Cleaned up test directory: {test_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not clean up test directory: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("LEDMatrix Soccer Logo Permissions Fix Test")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
success = test_soccer_logo_creation()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 All tests passed! The soccer logo fix is working correctly.")
|
||||||
|
print("\nTo apply this fix on your Raspberry Pi:")
|
||||||
|
print("1. Transfer the updated files to your Pi")
|
||||||
|
print("2. Run: chmod +x fix_soccer_logo_permissions.sh")
|
||||||
|
print("3. Run: ./fix_soccer_logo_permissions.sh")
|
||||||
|
print("4. Restart your LEDMatrix application")
|
||||||
|
else:
|
||||||
|
print("\n❌ Tests failed. Please check the error messages above.")
|
||||||
|
sys.exit(1)
|
||||||
167
test_stock_news_fix.py
Normal file
167
test_stock_news_fix.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify the stock news manager fix.
|
||||||
|
This script tests that the display_news method works correctly without excessive image generation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Add the src directory to the path so we can import the modules
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cache_manager import CacheManager
|
||||||
|
from stock_news_manager import StockNewsManager
|
||||||
|
from display_manager import DisplayManager
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Import error: {e}")
|
||||||
|
print("Make sure you're running this from the LEDMatrix root directory")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def test_stock_news_display():
|
||||||
|
"""Test that stock news display works correctly without excessive image generation."""
|
||||||
|
|
||||||
|
print("Testing stock news display fix...")
|
||||||
|
|
||||||
|
# Create a temporary directory for testing
|
||||||
|
test_dir = tempfile.mkdtemp(prefix="ledmatrix_test_")
|
||||||
|
print(f"Using test directory: {test_dir}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a minimal config
|
||||||
|
config = {
|
||||||
|
"stock_news": {
|
||||||
|
"enabled": True,
|
||||||
|
"scroll_speed": 1,
|
||||||
|
"scroll_delay": 0.1, # Slower for testing
|
||||||
|
"headlines_per_rotation": 2,
|
||||||
|
"max_headlines_per_symbol": 1,
|
||||||
|
"update_interval": 300,
|
||||||
|
"dynamic_duration": True,
|
||||||
|
"min_duration": 30,
|
||||||
|
"max_duration": 300
|
||||||
|
},
|
||||||
|
"stocks": {
|
||||||
|
"symbols": ["AAPL", "GOOGL", "MSFT"],
|
||||||
|
"enabled": True
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"width": 64,
|
||||||
|
"height": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create cache manager with test directory
|
||||||
|
cache_manager = CacheManager()
|
||||||
|
# Override cache directory for testing
|
||||||
|
cache_manager.cache_dir = test_dir
|
||||||
|
|
||||||
|
# Create a mock display manager
|
||||||
|
class MockDisplayManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.width = 64
|
||||||
|
self.height = 32
|
||||||
|
self.image = Image.new('RGB', (64, 32), (0, 0, 0))
|
||||||
|
self.matrix = type('Matrix', (), {'width': 64, 'height': 32})()
|
||||||
|
self.small_font = None # We'll handle this in the test
|
||||||
|
|
||||||
|
def update_display(self):
|
||||||
|
# Mock update - just pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
display_manager = MockDisplayManager()
|
||||||
|
|
||||||
|
# Create stock news manager
|
||||||
|
news_manager = StockNewsManager(config, display_manager)
|
||||||
|
|
||||||
|
# Mock some news data
|
||||||
|
news_manager.news_data = {
|
||||||
|
"AAPL": [
|
||||||
|
{"title": "Apple reports strong Q4 earnings", "publisher": "Reuters"},
|
||||||
|
{"title": "New iPhone sales exceed expectations", "publisher": "Bloomberg"}
|
||||||
|
],
|
||||||
|
"GOOGL": [
|
||||||
|
{"title": "Google announces new AI features", "publisher": "TechCrunch"},
|
||||||
|
{"title": "Alphabet stock reaches new high", "publisher": "CNBC"}
|
||||||
|
],
|
||||||
|
"MSFT": [
|
||||||
|
{"title": "Microsoft cloud services grow 25%", "publisher": "WSJ"},
|
||||||
|
{"title": "Windows 12 preview released", "publisher": "The Verge"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nTesting display_news method...")
|
||||||
|
|
||||||
|
# Test multiple calls to ensure it doesn't generate images excessively
|
||||||
|
generation_count = 0
|
||||||
|
original_generate_method = news_manager._generate_background_image
|
||||||
|
|
||||||
|
def mock_generate_method(*args, **kwargs):
|
||||||
|
nonlocal generation_count
|
||||||
|
generation_count += 1
|
||||||
|
print(f" Image generation call #{generation_count}")
|
||||||
|
return original_generate_method(*args, **kwargs)
|
||||||
|
|
||||||
|
news_manager._generate_background_image = mock_generate_method
|
||||||
|
|
||||||
|
# Call display_news multiple times to simulate the display controller
|
||||||
|
for i in range(10):
|
||||||
|
print(f" Call {i+1}: ", end="")
|
||||||
|
try:
|
||||||
|
result = news_manager.display_news()
|
||||||
|
if result:
|
||||||
|
print("✓ Success")
|
||||||
|
else:
|
||||||
|
print("✗ Failed")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error: {e}")
|
||||||
|
|
||||||
|
print(f"\nTotal image generations: {generation_count}")
|
||||||
|
|
||||||
|
if generation_count <= 3: # Should only generate a few times for different rotations
|
||||||
|
print("✓ Image generation is working correctly (not excessive)")
|
||||||
|
else:
|
||||||
|
print("✗ Too many image generations - fix may not be working")
|
||||||
|
|
||||||
|
print("\n✓ Stock news display test completed!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n✗ Test failed with error: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up test directory
|
||||||
|
try:
|
||||||
|
shutil.rmtree(test_dir)
|
||||||
|
print(f"Cleaned up test directory: {test_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not clean up test directory: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("LEDMatrix Stock News Manager Fix Test")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
success = test_stock_news_display()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 Test completed! The stock news manager should now work correctly.")
|
||||||
|
print("\nThe fix addresses the issue where the display_news method was:")
|
||||||
|
print("1. Generating images excessively (every second)")
|
||||||
|
print("2. Missing the actual scrolling display logic")
|
||||||
|
print("3. Causing rapid rotation through headlines")
|
||||||
|
print("\nNow it should:")
|
||||||
|
print("1. Generate images only when needed for new rotations")
|
||||||
|
print("2. Properly scroll the content across the display")
|
||||||
|
print("3. Use the configured dynamic duration properly")
|
||||||
|
else:
|
||||||
|
print("\n❌ Test failed. Please check the error messages above.")
|
||||||
|
sys.exit(1)
|
||||||
Reference in New Issue
Block a user