mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 13:23:00 +00:00
Compare commits
3 Commits
82370a0253
...
bc8568604a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc8568604a | ||
|
|
878f339fb3 | ||
|
|
51616f1bc4 |
@@ -82,6 +82,7 @@ class DisplayManager:
|
||||
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.panel_type = hardware_config.get('panel_type', '')
|
||||
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)
|
||||
|
||||
@@ -148,7 +148,13 @@ class LogoDownloader:
|
||||
|
||||
def get_logo_directory(self, league: str) -> str:
|
||||
"""Get the logo directory for a given league."""
|
||||
directory = LogoDownloader.LOGO_DIRECTORIES.get(league, f'assets/sports/{league}_logos')
|
||||
directory = LogoDownloader.LOGO_DIRECTORIES.get(league)
|
||||
if not directory:
|
||||
# Custom soccer leagues share the same logo directory as predefined ones
|
||||
if league.startswith('soccer_'):
|
||||
directory = 'assets/sports/soccer_logos'
|
||||
else:
|
||||
directory = f'assets/sports/{league}_logos'
|
||||
path = Path(directory)
|
||||
if not path.is_absolute():
|
||||
project_root = Path(__file__).resolve().parents[1]
|
||||
@@ -238,9 +244,18 @@ class LogoDownloader:
|
||||
logger.error(f"Unexpected error downloading logo for {team_abbreviation}: {e}")
|
||||
return False
|
||||
|
||||
def _resolve_api_url(self, league: str) -> Optional[str]:
|
||||
"""Resolve the ESPN API teams URL for a league, with dynamic fallback for custom soccer leagues."""
|
||||
api_url = self.API_ENDPOINTS.get(league)
|
||||
if not api_url and league.startswith('soccer_'):
|
||||
league_code = league[len('soccer_'):]
|
||||
api_url = f'https://site.api.espn.com/apis/site/v2/sports/soccer/{league_code}/teams'
|
||||
logger.info(f"Using dynamic ESPN endpoint for custom soccer league: {league}")
|
||||
return api_url
|
||||
|
||||
def fetch_teams_data(self, league: str) -> Optional[Dict]:
|
||||
"""Fetch team data from ESPN API for a specific league."""
|
||||
api_url = self.API_ENDPOINTS.get(league)
|
||||
api_url = self._resolve_api_url(league)
|
||||
if not api_url:
|
||||
logger.error(f"No API endpoint configured for league: {league}")
|
||||
return None
|
||||
@@ -263,7 +278,7 @@ class LogoDownloader:
|
||||
|
||||
def fetch_single_team(self, league: str, team_id: str) -> Optional[Dict]:
|
||||
"""Fetch team data from ESPN API for a specific league."""
|
||||
api_url = self.API_ENDPOINTS.get(league)
|
||||
api_url = self._resolve_api_url(league)
|
||||
if not api_url:
|
||||
logger.error(f"No API endpoint configured for league: {league}")
|
||||
return None
|
||||
@@ -570,7 +585,7 @@ class LogoDownloader:
|
||||
total_failed = 0
|
||||
|
||||
for league in leagues:
|
||||
if league not in self.API_ENDPOINTS:
|
||||
if not self._resolve_api_url(league):
|
||||
logger.warning(f"Skipping unknown league: {league}")
|
||||
continue
|
||||
|
||||
|
||||
@@ -702,7 +702,7 @@ def save_main_config():
|
||||
display_fields = ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'hardware_mapping',
|
||||
'gpio_slowdown', 'scan_mode', 'disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate',
|
||||
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz', 'use_short_date_format',
|
||||
'max_dynamic_duration_seconds']
|
||||
'max_dynamic_duration_seconds', 'led_rgb_sequence', 'multiplexing', 'panel_type']
|
||||
|
||||
if any(k in data for k in display_fields):
|
||||
if 'display' not in current_config:
|
||||
@@ -712,12 +712,35 @@ def save_main_config():
|
||||
if 'runtime' not in current_config['display']:
|
||||
current_config['display']['runtime'] = {}
|
||||
|
||||
# Allowed values for validated string fields
|
||||
LED_RGB_ALLOWED = {'RGB', 'RBG', 'GRB', 'GBR', 'BRG', 'BGR'}
|
||||
PANEL_TYPE_ALLOWED = {'', 'FM6126A', 'FM6127'}
|
||||
|
||||
# Validate led_rgb_sequence
|
||||
if 'led_rgb_sequence' in data and data['led_rgb_sequence'] not in LED_RGB_ALLOWED:
|
||||
return jsonify({'status': 'error', 'message': f"Invalid LED RGB sequence '{data['led_rgb_sequence']}'. Allowed values: {', '.join(sorted(LED_RGB_ALLOWED))}"}), 400
|
||||
|
||||
# Validate panel_type
|
||||
if 'panel_type' in data and data['panel_type'] not in PANEL_TYPE_ALLOWED:
|
||||
return jsonify({'status': 'error', 'message': f"Invalid panel type '{data['panel_type']}'. Allowed values: Standard (empty), FM6126A, FM6127"}), 400
|
||||
|
||||
# Validate multiplexing
|
||||
if 'multiplexing' in data:
|
||||
try:
|
||||
mux_val = int(data['multiplexing'])
|
||||
if mux_val < 0 or mux_val > 22:
|
||||
return jsonify({'status': 'error', 'message': f"Invalid multiplexing value '{data['multiplexing']}'. Must be an integer from 0 to 22."}), 400
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({'status': 'error', 'message': f"Invalid multiplexing value '{data['multiplexing']}'. Must be an integer from 0 to 22."}), 400
|
||||
|
||||
# Handle hardware settings
|
||||
for field in ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'hardware_mapping', 'scan_mode',
|
||||
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz']:
|
||||
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz',
|
||||
'led_rgb_sequence', 'multiplexing', 'panel_type']:
|
||||
if field in data:
|
||||
if field in ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'scan_mode',
|
||||
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz']:
|
||||
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz',
|
||||
'multiplexing']:
|
||||
current_config['display']['hardware'][field] = int(data[field])
|
||||
else:
|
||||
current_config['display']['hardware'][field] = data[field]
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
/* Dark Mode - Utility Class Overrides */
|
||||
[data-theme="dark"] .bg-white { background-color: var(--color-surface); }
|
||||
[data-theme="dark"] .bg-gray-50 { background-color: var(--color-background); }
|
||||
[data-theme="dark"] .bg-gray-100 { background-color: var(--color-surface); }
|
||||
[data-theme="dark"] .bg-gray-200 { background-color: var(--color-border); }
|
||||
|
||||
[data-theme="dark"] .text-gray-900 { color: var(--color-text-primary); }
|
||||
@@ -79,6 +80,7 @@
|
||||
[data-theme="dark"] .border-gray-300 { border-color: #4b5563; }
|
||||
|
||||
[data-theme="dark"] .hover\:bg-gray-50:hover { background-color: var(--color-border); }
|
||||
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: #4b5563; }
|
||||
[data-theme="dark"] .hover\:text-gray-700:hover { color: #e5e7eb; }
|
||||
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: #6b7280; }
|
||||
|
||||
@@ -100,6 +102,7 @@ body {
|
||||
|
||||
/* Utility classes */
|
||||
.bg-gray-50 { background-color: #f9fafb; }
|
||||
.bg-gray-100 { background-color: #f3f4f6; }
|
||||
.bg-white { background-color: #ffffff; }
|
||||
.bg-gray-800 { background-color: #1f2937; }
|
||||
.bg-gray-900 { background-color: #111827; }
|
||||
|
||||
@@ -94,6 +94,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-group">
|
||||
<label for="led_rgb_sequence" class="block text-sm font-medium text-gray-700">LED RGB Sequence</label>
|
||||
<select id="led_rgb_sequence" name="led_rgb_sequence" class="form-control">
|
||||
<option value="RGB" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "RGB" %}selected{% endif %}>RGB</option>
|
||||
<option value="RBG" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "RBG" %}selected{% endif %}>RBG</option>
|
||||
<option value="GRB" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "GRB" %}selected{% endif %}>GRB</option>
|
||||
<option value="GBR" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "GBR" %}selected{% endif %}>GBR</option>
|
||||
<option value="BRG" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "BRG" %}selected{% endif %}>BRG</option>
|
||||
<option value="BGR" {% if main_config.display.hardware.get('led_rgb_sequence', 'RGB') == "BGR" %}selected{% endif %}>BGR</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-600">Color channel order for your LED panels</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="multiplexing" class="block text-sm font-medium text-gray-700">Multiplexing</label>
|
||||
<select id="multiplexing" name="multiplexing" class="form-control">
|
||||
<option value="0" {% if main_config.display.hardware.get('multiplexing', 0)|int == 0 %}selected{% endif %}>0 - Direct</option>
|
||||
<option value="1" {% if main_config.display.hardware.get('multiplexing', 0)|int == 1 %}selected{% endif %}>1 - Stripe</option>
|
||||
<option value="2" {% if main_config.display.hardware.get('multiplexing', 0)|int == 2 %}selected{% endif %}>2 - Checkered</option>
|
||||
<option value="3" {% if main_config.display.hardware.get('multiplexing', 0)|int == 3 %}selected{% endif %}>3 - Spiral</option>
|
||||
<option value="4" {% if main_config.display.hardware.get('multiplexing', 0)|int == 4 %}selected{% endif %}>4 - ZStripe</option>
|
||||
<option value="5" {% if main_config.display.hardware.get('multiplexing', 0)|int == 5 %}selected{% endif %}>5 - ZnMirrorZStripe</option>
|
||||
<option value="6" {% if main_config.display.hardware.get('multiplexing', 0)|int == 6 %}selected{% endif %}>6 - Coreman</option>
|
||||
<option value="7" {% if main_config.display.hardware.get('multiplexing', 0)|int == 7 %}selected{% endif %}>7 - Kaler2Scan</option>
|
||||
<option value="8" {% if main_config.display.hardware.get('multiplexing', 0)|int == 8 %}selected{% endif %}>8 - ZStripeUneven</option>
|
||||
<option value="9" {% if main_config.display.hardware.get('multiplexing', 0)|int == 9 %}selected{% endif %}>9 - P10-128x4-Z</option>
|
||||
<option value="10" {% if main_config.display.hardware.get('multiplexing', 0)|int == 10 %}selected{% endif %}>10 - QiangLiQ8</option>
|
||||
<option value="11" {% if main_config.display.hardware.get('multiplexing', 0)|int == 11 %}selected{% endif %}>11 - InversedZStripe</option>
|
||||
<option value="12" {% if main_config.display.hardware.get('multiplexing', 0)|int == 12 %}selected{% endif %}>12 - P10Outdoor1R1G1B v1</option>
|
||||
<option value="13" {% if main_config.display.hardware.get('multiplexing', 0)|int == 13 %}selected{% endif %}>13 - P10Outdoor1R1G1B v2</option>
|
||||
<option value="14" {% if main_config.display.hardware.get('multiplexing', 0)|int == 14 %}selected{% endif %}>14 - P10Outdoor1R1G1B v3</option>
|
||||
<option value="15" {% if main_config.display.hardware.get('multiplexing', 0)|int == 15 %}selected{% endif %}>15 - P10CoremanMapper</option>
|
||||
<option value="16" {% if main_config.display.hardware.get('multiplexing', 0)|int == 16 %}selected{% endif %}>16 - P8Outdoor1R1G1B</option>
|
||||
<option value="17" {% if main_config.display.hardware.get('multiplexing', 0)|int == 17 %}selected{% endif %}>17 - FlippedStripe</option>
|
||||
<option value="18" {% if main_config.display.hardware.get('multiplexing', 0)|int == 18 %}selected{% endif %}>18 - P10-32x16-HalfScan</option>
|
||||
<option value="19" {% if main_config.display.hardware.get('multiplexing', 0)|int == 19 %}selected{% endif %}>19 - P10-32x16-QuarterScan</option>
|
||||
<option value="20" {% if main_config.display.hardware.get('multiplexing', 0)|int == 20 %}selected{% endif %}>20 - P3Outdoor-64x64</option>
|
||||
<option value="21" {% if main_config.display.hardware.get('multiplexing', 0)|int == 21 %}selected{% endif %}>21 - DoubleZMultiplex</option>
|
||||
<option value="22" {% if main_config.display.hardware.get('multiplexing', 0)|int == 22 %}selected{% endif %}>22 - P4Outdoor-80x40</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-600">Multiplexing scheme for your LED panels</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="panel_type" class="block text-sm font-medium text-gray-700">Panel Type</label>
|
||||
<select id="panel_type" name="panel_type" class="form-control">
|
||||
<option value="" {% if not main_config.display.hardware.get('panel_type', '') %}selected{% endif %}>Standard</option>
|
||||
<option value="FM6126A" {% if main_config.display.hardware.get('panel_type', '') == "FM6126A" %}selected{% endif %}>FM6126A</option>
|
||||
<option value="FM6127" {% if main_config.display.hardware.get('panel_type', '') == "FM6127" %}selected{% endif %}>FM6127</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-600">Special panel chipset initialization (use Standard unless your panel requires it)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-group">
|
||||
<label for="gpio_slowdown" class="block text-sm font-medium text-gray-700">GPIO Slowdown</label>
|
||||
|
||||
Reference in New Issue
Block a user