mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-21 20:33:33 +00:00
feat(pi5): expose RP1 backend selector, fix gpio defaults, surface init failures in web UI
- Add rp1_rio select (PIO/RIO) to Display Settings hardware config section; saved via /api/v3/config/main with 0-or-1 validation — previously the key existed in config.json but was not editable from the UI - Update gpio_slowdown help text with per-model guidance (Pi 3: 3, Pi 4: 4, Pi 5: 4–5) and raise max from 5 → 10 to match full library range - Fix gpio_slowdown Python fallback default from 2 → 3 (only affects edge case where the runtime config section is absent; explicit config values are unchanged) - display_manager writes /tmp/led_matrix_hw_status.json at startup: ok/error; Display Settings page fetches it and shows a yellow warning banner when the matrix failed to initialize, including Pi 5 remediation steps - Add GET /api/v3/hardware/status endpoint that reads the status file - Improve fallback error log to include Pi 5 rebuild hint Pi 3/4 users: rp1_rio=0 is set in config but silently ignored by the library on non-RP1 hardware; all other changes are additive or tighten defaults only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ class DisplayManager:
|
|||||||
|
|
||||||
def _setup_matrix(self):
|
def _setup_matrix(self):
|
||||||
"""Initialize the RGB matrix with configuration settings."""
|
"""Initialize the RGB matrix with configuration settings."""
|
||||||
|
_init_error_str = None
|
||||||
try:
|
try:
|
||||||
# Allow callers (e.g., web UI) to force non-hardware fallback mode
|
# Allow callers (e.g., web UI) to force non-hardware fallback mode
|
||||||
if getattr(self, '_force_fallback', False):
|
if getattr(self, '_force_fallback', False):
|
||||||
@@ -87,7 +88,7 @@ class DisplayManager:
|
|||||||
options.disable_hardware_pulsing = hardware_config.get('disable_hardware_pulsing', False)
|
options.disable_hardware_pulsing = hardware_config.get('disable_hardware_pulsing', False)
|
||||||
options.show_refresh_rate = hardware_config.get('show_refresh_rate', 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.limit_refresh_rate_hz = hardware_config.get('limit_refresh_rate_hz', 90)
|
||||||
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 2)
|
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 3)
|
||||||
|
|
||||||
# Disable internal privilege dropping - we manage this via systemd or remain root
|
# Disable internal privilege dropping - we manage this via systemd or remain root
|
||||||
# This prevents the library from dropping to 'daemon' user which breaks file permissions
|
# This prevents the library from dropping to 'daemon' user which breaks file permissions
|
||||||
@@ -141,6 +142,7 @@ class DisplayManager:
|
|||||||
self._draw_test_pattern()
|
self._draw_test_pattern()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
_init_error_str = str(e)
|
||||||
logger.error(f"Failed to initialize RGB Matrix: {e}", exc_info=True)
|
logger.error(f"Failed to initialize RGB Matrix: {e}", exc_info=True)
|
||||||
# Create a fallback image for web preview using configured dimensions when available
|
# Create a fallback image for web preview using configured dimensions when available
|
||||||
self.matrix = None
|
self.matrix = None
|
||||||
@@ -164,9 +166,24 @@ class DisplayManager:
|
|||||||
except Exception: # nosec B110 - best-effort fallback visualization; drawing errors must not crash startup
|
except Exception: # nosec B110 - best-effort fallback visualization; drawing errors must not crash startup
|
||||||
# Best-effort; ignore drawing errors in fallback
|
# Best-effort; ignore drawing errors in fallback
|
||||||
pass
|
pass
|
||||||
logger.error(f"Matrix initialization failed, using fallback mode with size {fallback_width}x{fallback_height}. Error: {e}")
|
logger.error(
|
||||||
|
f"Matrix initialization failed — running in fallback/simulation mode "
|
||||||
|
f"(size {fallback_width}x{fallback_height}). Error: {e}. "
|
||||||
|
"On Raspberry Pi 5: ensure rpi-rgb-led-matrix was built from the latest "
|
||||||
|
"submodule (re-run first_time_install.sh) and that gpio_slowdown is 4 or higher."
|
||||||
|
)
|
||||||
# Do not raise here; allow fallback mode so web preview and non-hardware environments work
|
# Do not raise here; allow fallback mode so web preview and non-hardware environments work
|
||||||
|
|
||||||
|
# Write hardware status file so the web UI can surface init failures
|
||||||
|
try:
|
||||||
|
import json as _json
|
||||||
|
_hw_status = {"ok": self.matrix is not None, "error": _init_error_str}
|
||||||
|
_status_path = "/tmp/led_matrix_hw_status.json" # nosec B108
|
||||||
|
with open(_status_path, "w") as _f:
|
||||||
|
_json.dump(_hw_status, _f)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
"""Get the display width."""
|
"""Get the display width."""
|
||||||
|
|||||||
@@ -699,7 +699,7 @@ def save_main_config():
|
|||||||
|
|
||||||
# Handle display settings
|
# Handle display settings
|
||||||
display_fields = ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'hardware_mapping',
|
display_fields = ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'hardware_mapping',
|
||||||
'gpio_slowdown', 'scan_mode', 'disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate',
|
'gpio_slowdown', 'rp1_rio', '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',
|
'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz', 'use_short_date_format',
|
||||||
'max_dynamic_duration_seconds', 'led_rgb_sequence', 'multiplexing', 'panel_type']
|
'max_dynamic_duration_seconds', 'led_rgb_sequence', 'multiplexing', 'panel_type']
|
||||||
|
|
||||||
@@ -747,6 +747,14 @@ def save_main_config():
|
|||||||
# Handle runtime settings
|
# Handle runtime settings
|
||||||
if 'gpio_slowdown' in data:
|
if 'gpio_slowdown' in data:
|
||||||
current_config['display']['runtime']['gpio_slowdown'] = int(data['gpio_slowdown'])
|
current_config['display']['runtime']['gpio_slowdown'] = int(data['gpio_slowdown'])
|
||||||
|
if 'rp1_rio' in data:
|
||||||
|
try:
|
||||||
|
rp1_val = int(data['rp1_rio'])
|
||||||
|
if rp1_val not in (0, 1):
|
||||||
|
return jsonify({'status': 'error', 'message': "rp1_rio must be 0 (PIO) or 1 (RIO)"}), 400
|
||||||
|
current_config['display']['runtime']['rp1_rio'] = rp1_val
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return jsonify({'status': 'error', 'message': "rp1_rio must be 0 or 1"}), 400
|
||||||
|
|
||||||
# Handle checkboxes - coerce to bool to ensure proper JSON types
|
# Handle checkboxes - coerce to bool to ensure proper JSON types
|
||||||
for checkbox in ['disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate']:
|
for checkbox in ['disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate']:
|
||||||
@@ -1527,6 +1535,19 @@ def execute_system_action():
|
|||||||
print(error_details)
|
print(error_details)
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
||||||
|
|
||||||
|
@api_v3.route('/hardware/status', methods=['GET'])
|
||||||
|
def get_hardware_status():
|
||||||
|
"""Return LED matrix hardware initialization status written by display_manager at startup."""
|
||||||
|
status_path = "/tmp/led_matrix_hw_status.json" # nosec B108
|
||||||
|
try:
|
||||||
|
with open(status_path) as f:
|
||||||
|
hw_data = json.load(f)
|
||||||
|
return jsonify({"status": "success", "data": hw_data})
|
||||||
|
except FileNotFoundError:
|
||||||
|
return jsonify({"status": "success", "data": {"ok": None, "error": "Display service not yet started"}})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"status": "error", "message": str(e)}), 500
|
||||||
|
|
||||||
@api_v3.route('/display/current', methods=['GET'])
|
@api_v3.route('/display/current', methods=['GET'])
|
||||||
def get_display_current():
|
def get_display_current():
|
||||||
"""Get current display state"""
|
"""Get current display state"""
|
||||||
|
|||||||
@@ -4,6 +4,25 @@
|
|||||||
<p class="mt-1 text-sm text-gray-600">Configure LED matrix hardware settings and display options.</p>
|
<p class="mt-1 text-sm text-gray-600">Configure LED matrix hardware settings and display options.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hardware status banner: shown when display service is in fallback/simulation mode -->
|
||||||
|
<div x-data="{ show: false, errorMsg: '' }"
|
||||||
|
x-init="fetch('/api/v3/hardware/status').then(r => r.json()).then(d => {
|
||||||
|
const hw = (d && d.data) || {};
|
||||||
|
if (hw.ok === false) { show = true; errorMsg = hw.error || 'Unknown error'; }
|
||||||
|
}).catch(() => {})"
|
||||||
|
x-show="show"
|
||||||
|
style="display:none"
|
||||||
|
class="bg-yellow-50 border border-yellow-300 rounded-lg p-4 mb-6">
|
||||||
|
<p class="font-semibold text-yellow-800"><i class="fas fa-exclamation-triangle mr-2"></i>LED matrix running in simulation mode</p>
|
||||||
|
<p class="text-sm text-yellow-700 mt-1">Hardware initialization failed: <span x-text="errorMsg" class="font-mono text-xs break-all"></span></p>
|
||||||
|
<p class="text-sm text-yellow-700 mt-2">
|
||||||
|
On Raspberry Pi 5: ensure the library was rebuilt from the latest submodule
|
||||||
|
(<code class="bg-yellow-100 px-1 rounded">first_time_install.sh</code>)
|
||||||
|
and set <strong>GPIO Slowdown to 4 or higher</strong> below.
|
||||||
|
Check the <a href="/v3/logs" class="underline font-medium">Logs tab</a> for the full error.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form hx-post="/api/v3/config/main"
|
<form hx-post="/api/v3/config/main"
|
||||||
hx-ext="json-enc"
|
hx-ext="json-enc"
|
||||||
hx-headers='{"Content-Type": "application/json"}'
|
hx-headers='{"Content-Type": "application/json"}'
|
||||||
@@ -149,7 +168,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="gpio_slowdown" class="block text-sm font-medium text-gray-700">GPIO Slowdown</label>
|
<label for="gpio_slowdown" class="block text-sm font-medium text-gray-700">GPIO Slowdown</label>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
@@ -157,9 +176,20 @@
|
|||||||
name="gpio_slowdown"
|
name="gpio_slowdown"
|
||||||
value="{{ main_config.display.runtime.gpio_slowdown or 3 }}"
|
value="{{ main_config.display.runtime.gpio_slowdown or 3 }}"
|
||||||
min="0"
|
min="0"
|
||||||
max="5"
|
max="10"
|
||||||
class="form-control">
|
class="form-control">
|
||||||
<p class="mt-1 text-sm text-gray-600">GPIO slowdown factor (0-5)</p>
|
<p class="mt-1 text-sm text-gray-600">Pi 3: 3 · Pi 4: 4 · Pi 5: 4–5 (in RIO mode, lower values may work better)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="rp1_rio" class="block text-sm font-medium text-gray-700">
|
||||||
|
RP1 Backend <span class="text-xs text-gray-400 font-normal">(Pi 5 only)</span>
|
||||||
|
</label>
|
||||||
|
<select id="rp1_rio" name="rp1_rio" class="form-control">
|
||||||
|
<option value="0" {% if main_config.display.get('runtime', {}).get('rp1_rio', 0) == 0 %}selected{% endif %}>0 — PIO (default, low CPU)</option>
|
||||||
|
<option value="1" {% if main_config.display.get('runtime', {}).get('rp1_rio', 0) == 1 %}selected{% endif %}>1 — RIO (higher throughput; slowdown inverted)</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-sm text-gray-600">Pi 5 RP1 coprocessor mode. Ignored on Pi 3/4.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
Reference in New Issue
Block a user