Respect live game display duration when no priority is given

This commit is contained in:
Chuck
2025-07-31 21:52:21 -05:00
parent a6f8237069
commit b451d5def8
3 changed files with 156 additions and 72 deletions

View File

@@ -915,7 +915,19 @@ class DisplayController:
# No live_priority takeover, regular rotation
needs_switch = False
if self.current_display_mode.endswith('_live'):
needs_switch = True
# For live modes without live_priority, check if duration has elapsed
if current_time - self.last_switch >= self.get_current_duration():
needs_switch = True
self.current_mode_index = (self.current_mode_index + 1) % len(self.available_modes)
new_mode_after_timer = self.available_modes[self.current_mode_index]
if previous_mode_before_switch == 'music' and self.music_manager and new_mode_after_timer != 'music':
self.music_manager.deactivate_music_display()
if self.current_display_mode != new_mode_after_timer:
logger.info(f"Switching to {new_mode_after_timer} from {self.current_display_mode}")
self.current_display_mode = new_mode_after_timer
# Reset logged duration when mode changes
if hasattr(self, '_last_logged_duration'):
delattr(self, '_last_logged_duration')
elif current_time - self.last_switch >= self.get_current_duration():
if self.current_display_mode == 'calendar' and self.calendar:
self.calendar.advance_event()

View File

@@ -37,92 +37,136 @@ class DisplayManager:
def _setup_matrix(self):
"""Initialize the RGB matrix with configuration settings."""
setup_start = time.time()
options = RGBMatrixOptions()
# Hardware configuration
hardware_config = self.config.get('display', {}).get('hardware', {})
runtime_config = self.config.get('display', {}).get('runtime', {})
# Basic hardware settings
options.rows = hardware_config.get('rows', 32)
options.cols = hardware_config.get('cols', 64)
options.chain_length = hardware_config.get('chain_length', 2)
options.parallel = hardware_config.get('parallel', 1)
options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm')
# Performance and stability settings
options.brightness = hardware_config.get('brightness', 90)
options.pwm_bits = hardware_config.get('pwm_bits', 10)
options.pwm_lsb_nanoseconds = hardware_config.get('pwm_lsb_nanoseconds', 150)
options.led_rgb_sequence = hardware_config.get('led_rgb_sequence', 'RGB')
options.pixel_mapper_config = hardware_config.get('pixel_mapper_config', '')
options.row_address_type = hardware_config.get('row_address_type', 0)
options.multiplexing = hardware_config.get('multiplexing', 0)
options.disable_hardware_pulsing = hardware_config.get('disable_hardware_pulsing', False)
options.show_refresh_rate = hardware_config.get('show_refresh_rate', False)
options.limit_refresh_rate_hz = hardware_config.get('limit_refresh_rate_hz', 90)
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 2)
# Additional settings from config
if 'scan_mode' in hardware_config:
options.scan_mode = hardware_config.get('scan_mode')
if 'pwm_dither_bits' in hardware_config:
options.pwm_dither_bits = hardware_config.get('pwm_dither_bits')
if 'inverse_colors' in hardware_config:
options.inverse_colors = hardware_config.get('inverse_colors')
# Initialize the matrix
self.matrix = RGBMatrix(options=options)
# Create double buffer for smooth updates
self.offscreen_canvas = self.matrix.CreateFrameCanvas()
self.current_canvas = self.matrix.CreateFrameCanvas()
# Create image with full chain width
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.draw = ImageDraw.Draw(self.image)
# Initialize font with Press Start 2P
try:
self.font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8)
logger.info("Initial Press Start 2P font loaded successfully")
options = RGBMatrixOptions()
# Hardware configuration
hardware_config = self.config.get('display', {}).get('hardware', {})
runtime_config = self.config.get('display', {}).get('runtime', {})
# Basic hardware settings
options.rows = hardware_config.get('rows', 32)
options.cols = hardware_config.get('cols', 64)
options.chain_length = hardware_config.get('chain_length', 2)
options.parallel = hardware_config.get('parallel', 1)
options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm')
# Performance and stability settings
options.brightness = hardware_config.get('brightness', 90)
options.pwm_bits = hardware_config.get('pwm_bits', 10)
options.pwm_lsb_nanoseconds = hardware_config.get('pwm_lsb_nanoseconds', 150)
options.led_rgb_sequence = hardware_config.get('led_rgb_sequence', 'RGB')
options.pixel_mapper_config = hardware_config.get('pixel_mapper_config', '')
options.row_address_type = hardware_config.get('row_address_type', 0)
options.multiplexing = hardware_config.get('multiplexing', 0)
options.disable_hardware_pulsing = hardware_config.get('disable_hardware_pulsing', False)
options.show_refresh_rate = hardware_config.get('show_refresh_rate', False)
options.limit_refresh_rate_hz = hardware_config.get('limit_refresh_rate_hz', 90)
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 2)
# Additional settings from config
if 'scan_mode' in hardware_config:
options.scan_mode = hardware_config.get('scan_mode')
if 'pwm_dither_bits' in hardware_config:
options.pwm_dither_bits = hardware_config.get('pwm_dither_bits')
if 'inverse_colors' in hardware_config:
options.inverse_colors = hardware_config.get('inverse_colors')
logger.info(f"Initializing RGB Matrix with settings: rows={options.rows}, cols={options.cols}, chain_length={options.chain_length}, parallel={options.parallel}, hardware_mapping={options.hardware_mapping}")
# Initialize the matrix
self.matrix = RGBMatrix(options=options)
logger.info("RGB Matrix initialized successfully")
# Create double buffer for smooth updates
self.offscreen_canvas = self.matrix.CreateFrameCanvas()
self.current_canvas = self.matrix.CreateFrameCanvas()
logger.info("Frame canvases created successfully")
# Create image with full chain width
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.draw = ImageDraw.Draw(self.image)
logger.info(f"Image canvas created with dimensions: {self.matrix.width}x{self.matrix.height}")
# Initialize font with Press Start 2P
try:
self.font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8)
logger.info("Initial Press Start 2P font loaded successfully")
except Exception as e:
logger.error(f"Failed to load initial font: {e}")
self.font = ImageFont.load_default()
# Draw a test pattern
self._draw_test_pattern()
except Exception as e:
logger.error(f"Failed to load initial font: {e}")
self.font = ImageFont.load_default()
# Draw a test pattern
self._draw_test_pattern()
logger.error(f"Failed to initialize RGB Matrix: {e}", exc_info=True)
# Create a fallback image for web preview
self.matrix = None
self.image = Image.new('RGB', (128, 32)) # Default size
self.draw = ImageDraw.Draw(self.image)
self.draw.text((10, 10), "Matrix Error", fill=(255, 0, 0))
logger.error(f"Matrix initialization failed, using fallback mode. Error: {e}")
raise
@property
def width(self):
"""Get the display width."""
return self.matrix.width if hasattr(self, 'matrix') else 128
if hasattr(self, 'matrix') and self.matrix is not None:
return self.matrix.width
elif hasattr(self, 'image'):
return self.image.width
else:
return 128 # Default fallback width
@property
def height(self):
"""Get the display height."""
return self.matrix.height if hasattr(self, 'matrix') else 32
if hasattr(self, 'matrix') and self.matrix is not None:
return self.matrix.height
elif hasattr(self, 'image'):
return self.image.height
else:
return 32 # Default fallback height
def _draw_test_pattern(self):
"""Draw a test pattern to verify the display is working."""
self.clear()
# Draw a red rectangle border
self.draw.rectangle([0, 0, self.matrix.width-1, self.matrix.height-1], outline=(255, 0, 0))
# Draw a diagonal line
self.draw.line([0, 0, self.matrix.width-1, self.matrix.height-1], fill=(0, 255, 0))
# Draw some text - changed from "TEST" to "Initializing" with smaller font
self.draw.text((10, 10), "Initializing", font=self.font, fill=(0, 0, 255))
# Update the display once after everything is drawn
self.update_display()
time.sleep(0.5) # Reduced from 1 second to 0.5 seconds for faster animation
try:
self.clear()
if self.matrix is None:
# Fallback mode - just draw on the image
self.draw.rectangle([0, 0, self.image.width-1, self.image.height-1], outline=(255, 0, 0))
self.draw.line([0, 0, self.image.width-1, self.image.height-1], fill=(0, 255, 0))
self.draw.text((10, 10), "Simulation", font=self.font, fill=(0, 0, 255))
logger.info("Drew test pattern in fallback mode")
return
# Draw a red rectangle border
self.draw.rectangle([0, 0, self.matrix.width-1, self.matrix.height-1], outline=(255, 0, 0))
# Draw a diagonal line
self.draw.line([0, 0, self.matrix.width-1, self.matrix.height-1], fill=(0, 255, 0))
# Draw some text - changed from "TEST" to "Initializing" with smaller font
self.draw.text((10, 10), "Initializing", font=self.font, fill=(0, 0, 255))
# Update the display once after everything is drawn
self.update_display()
time.sleep(0.5) # Reduced from 1 second to 0.5 seconds for faster animation
except Exception as e:
logger.error(f"Error drawing test pattern: {e}", exc_info=True)
def update_display(self):
"""Update the display using double buffering with proper sync."""
try:
if self.matrix is None:
# Fallback mode - no actual hardware to update
logger.debug("Update display called in fallback mode (no hardware)")
return
# Copy the current image to the offscreen canvas
self.offscreen_canvas.SetImage(self.image)
@@ -137,6 +181,13 @@ class DisplayManager:
def clear(self):
"""Clear the display completely."""
try:
if self.matrix is None:
# Fallback mode - just clear the image
self.image = Image.new('RGB', (self.image.width, self.image.height))
self.draw = ImageDraw.Draw(self.image)
logger.debug("Cleared display in fallback mode")
return
# Create a new black image
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.draw = ImageDraw.Draw(self.image)

View File

@@ -15,6 +15,7 @@ from PIL import Image
import io
import signal
import sys
import logging
app = Flask(__name__)
app.secret_key = os.urandom(24)
@@ -28,6 +29,9 @@ display_running = False
editor_mode = False
current_display_data = {}
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
class DisplayMonitor:
def __init__(self):
self.running = False
@@ -71,7 +75,7 @@ class DisplayMonitor:
socketio.emit('display_update', current_display_data)
except Exception as e:
print(f"Display monitor error: {e}")
logger.error(f"Display monitor error: {e}", exc_info=True)
time.sleep(0.05) # Update 20 times per second for smoother display
@@ -182,7 +186,15 @@ def start_display():
try:
if not display_manager:
config = config_manager.load_config()
display_manager = DisplayManager(config)
try:
display_manager = DisplayManager(config)
logger.info("DisplayManager initialized successfully")
except Exception as dm_error:
logger.error(f"Failed to initialize DisplayManager: {dm_error}")
# Create a fallback display manager for web simulation
display_manager = DisplayManager(config)
logger.info("Using fallback DisplayManager for web simulation")
display_monitor.start()
display_running = True
@@ -192,6 +204,7 @@ def start_display():
'message': 'Display started successfully'
})
except Exception as e:
logger.error(f"Error in start_display: {e}", exc_info=True)
return jsonify({
'status': 'error',
'message': f'Error starting display: {e}'
@@ -235,7 +248,14 @@ def toggle_editor_mode():
# Initialize display manager for editor if needed
if not display_manager:
config = config_manager.load_config()
display_manager = DisplayManager(config)
try:
display_manager = DisplayManager(config)
logger.info("DisplayManager initialized for editor mode")
except Exception as dm_error:
logger.error(f"Failed to initialize DisplayManager for editor: {dm_error}")
# Create a fallback display manager for web simulation
display_manager = DisplayManager(config)
logger.info("Using fallback DisplayManager for editor simulation")
display_monitor.start()
else:
# Resume normal display operation
@@ -247,6 +267,7 @@ def toggle_editor_mode():
'message': f'Editor mode {"enabled" if editor_mode else "disabled"}'
})
except Exception as e:
logger.error(f"Error toggling editor mode: {e}", exc_info=True)
return jsonify({
'status': 'error',
'message': f'Error toggling editor mode: {e}'