mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-12 21:43:00 +00:00
Respect live game display duration when no priority is given
This commit is contained in:
@@ -915,7 +915,19 @@ class DisplayController:
|
|||||||
# No live_priority takeover, regular rotation
|
# No live_priority takeover, regular rotation
|
||||||
needs_switch = False
|
needs_switch = False
|
||||||
if self.current_display_mode.endswith('_live'):
|
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():
|
elif current_time - self.last_switch >= self.get_current_duration():
|
||||||
if self.current_display_mode == 'calendar' and self.calendar:
|
if self.current_display_mode == 'calendar' and self.calendar:
|
||||||
self.calendar.advance_event()
|
self.calendar.advance_event()
|
||||||
|
|||||||
@@ -37,92 +37,136 @@ class DisplayManager:
|
|||||||
def _setup_matrix(self):
|
def _setup_matrix(self):
|
||||||
"""Initialize the RGB matrix with configuration settings."""
|
"""Initialize the RGB matrix with configuration settings."""
|
||||||
setup_start = time.time()
|
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:
|
try:
|
||||||
self.font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8)
|
options = RGBMatrixOptions()
|
||||||
logger.info("Initial Press Start 2P font loaded successfully")
|
|
||||||
|
# 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:
|
except Exception as e:
|
||||||
logger.error(f"Failed to load initial font: {e}")
|
logger.error(f"Failed to initialize RGB Matrix: {e}", exc_info=True)
|
||||||
self.font = ImageFont.load_default()
|
# Create a fallback image for web preview
|
||||||
|
self.matrix = None
|
||||||
# Draw a test pattern
|
self.image = Image.new('RGB', (128, 32)) # Default size
|
||||||
self._draw_test_pattern()
|
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
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
"""Get the display width."""
|
"""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
|
@property
|
||||||
def height(self):
|
def height(self):
|
||||||
"""Get the display height."""
|
"""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):
|
def _draw_test_pattern(self):
|
||||||
"""Draw a test pattern to verify the display is working."""
|
"""Draw a test pattern to verify the display is working."""
|
||||||
self.clear()
|
try:
|
||||||
|
self.clear()
|
||||||
# Draw a red rectangle border
|
|
||||||
self.draw.rectangle([0, 0, self.matrix.width-1, self.matrix.height-1], outline=(255, 0, 0))
|
if self.matrix is None:
|
||||||
|
# Fallback mode - just draw on the image
|
||||||
# Draw a diagonal line
|
self.draw.rectangle([0, 0, self.image.width-1, self.image.height-1], outline=(255, 0, 0))
|
||||||
self.draw.line([0, 0, self.matrix.width-1, self.matrix.height-1], fill=(0, 255, 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))
|
||||||
# Draw some text - changed from "TEST" to "Initializing" with smaller font
|
logger.info("Drew test pattern in fallback mode")
|
||||||
self.draw.text((10, 10), "Initializing", font=self.font, fill=(0, 0, 255))
|
return
|
||||||
|
|
||||||
# Update the display once after everything is drawn
|
# Draw a red rectangle border
|
||||||
self.update_display()
|
self.draw.rectangle([0, 0, self.matrix.width-1, self.matrix.height-1], outline=(255, 0, 0))
|
||||||
time.sleep(0.5) # Reduced from 1 second to 0.5 seconds for faster animation
|
|
||||||
|
# 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):
|
def update_display(self):
|
||||||
"""Update the display using double buffering with proper sync."""
|
"""Update the display using double buffering with proper sync."""
|
||||||
try:
|
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
|
# Copy the current image to the offscreen canvas
|
||||||
self.offscreen_canvas.SetImage(self.image)
|
self.offscreen_canvas.SetImage(self.image)
|
||||||
|
|
||||||
@@ -137,6 +181,13 @@ class DisplayManager:
|
|||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clear the display completely."""
|
"""Clear the display completely."""
|
||||||
try:
|
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
|
# Create a new black image
|
||||||
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
|
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
|
||||||
self.draw = ImageDraw.Draw(self.image)
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from PIL import Image
|
|||||||
import io
|
import io
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.urandom(24)
|
app.secret_key = os.urandom(24)
|
||||||
@@ -28,6 +29,9 @@ display_running = False
|
|||||||
editor_mode = False
|
editor_mode = False
|
||||||
current_display_data = {}
|
current_display_data = {}
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
class DisplayMonitor:
|
class DisplayMonitor:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
@@ -71,7 +75,7 @@ class DisplayMonitor:
|
|||||||
socketio.emit('display_update', current_display_data)
|
socketio.emit('display_update', current_display_data)
|
||||||
|
|
||||||
except Exception as e:
|
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
|
time.sleep(0.05) # Update 20 times per second for smoother display
|
||||||
|
|
||||||
@@ -182,7 +186,15 @@ def start_display():
|
|||||||
try:
|
try:
|
||||||
if not display_manager:
|
if not display_manager:
|
||||||
config = config_manager.load_config()
|
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_monitor.start()
|
||||||
|
|
||||||
display_running = True
|
display_running = True
|
||||||
@@ -192,6 +204,7 @@ def start_display():
|
|||||||
'message': 'Display started successfully'
|
'message': 'Display started successfully'
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error in start_display: {e}", exc_info=True)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': f'Error starting display: {e}'
|
'message': f'Error starting display: {e}'
|
||||||
@@ -235,7 +248,14 @@ def toggle_editor_mode():
|
|||||||
# Initialize display manager for editor if needed
|
# Initialize display manager for editor if needed
|
||||||
if not display_manager:
|
if not display_manager:
|
||||||
config = config_manager.load_config()
|
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()
|
display_monitor.start()
|
||||||
else:
|
else:
|
||||||
# Resume normal display operation
|
# Resume normal display operation
|
||||||
@@ -247,6 +267,7 @@ def toggle_editor_mode():
|
|||||||
'message': f'Editor mode {"enabled" if editor_mode else "disabled"}'
|
'message': f'Editor mode {"enabled" if editor_mode else "disabled"}'
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error toggling editor mode: {e}", exc_info=True)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': f'Error toggling editor mode: {e}'
|
'message': f'Error toggling editor mode: {e}'
|
||||||
|
|||||||
Reference in New Issue
Block a user