mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-21 04:13:32 +00:00
Compare commits
4 Commits
fix/pi5-rp
...
update-rgb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c53e4995c4 | ||
|
|
a0f19d8972 | ||
|
|
4f126d6133 | ||
|
|
5dde1125e9 |
11
README.md
11
README.md
@@ -127,15 +127,10 @@ The system supports live, recent, and upcoming game information for multiple spo
|
|||||||
| This project can be finnicky! RGB LED Matrix displays are not built the same or to a high-quality standard. We have seen many displays arrive dead or partially working in our discord. Please purchase from a reputable vendor. |
|
| This project can be finnicky! RGB LED Matrix displays are not built the same or to a high-quality standard. We have seen many displays arrive dead or partially working in our discord. Please purchase from a reputable vendor. |
|
||||||
|
|
||||||
### Raspberry Pi
|
### Raspberry Pi
|
||||||
- Raspberry Pi Zero's don't have enough processing power for this project.
|
- Raspberry Pi Zero's don't have enough processing power for this project and the Pi 5 is unsupported due to new GPIO output.
|
||||||
- **Raspberry Pi 3B, 4, or 5**
|
- **Raspberry Pi 3B or 4 (NOT RPi 5!)**
|
||||||
[Amazon Affiliate Link – Raspberry Pi 4 4GB RAM](https://amzn.to/4dJixuX)
|
[Amazon Affiliate Link – Raspberry Pi 4 4GB RAM](https://amzn.to/4dJixuX)
|
||||||
[Amazon Affiliate Link – Raspberry Pi 4 8GB RAM](https://amzn.to/4qbqY7F)
|
[Amazon Affiliate Link – Raspberry Pi 4 8GB RAM](https://amzn.to/4qbqY7F)
|
||||||
- **Pi 5 users**: the installer automatically detects Pi 5 and builds the `rpi-rgb-led-matrix` library with RP1 support. If you previously installed on a Pi 4 and migrated the SD card, or if you see `mmap` errors in the logs, force a fresh library build:
|
|
||||||
```bash
|
|
||||||
sudo RPI_RGB_FORCE_REBUILD=1 ./first_time_install.sh
|
|
||||||
```
|
|
||||||
- Pi 5 config: leave `rp1_rio` at `0` (PIO mode, default) and set `gpio_slowdown` to `1` or `2`.
|
|
||||||
|
|
||||||
|
|
||||||
### RGB Matrix Bonnet / HAT
|
### RGB Matrix Bonnet / HAT
|
||||||
@@ -587,7 +582,7 @@ These settings control runtime behavior and GPIO timing:
|
|||||||
- **Critical setting**: Must match your Raspberry Pi model for stability
|
- **Critical setting**: Must match your Raspberry Pi model for stability
|
||||||
- **Raspberry Pi 3**: Use 3
|
- **Raspberry Pi 3**: Use 3
|
||||||
- **Raspberry Pi 4**: Use 4
|
- **Raspberry Pi 4**: Use 4
|
||||||
- **Raspberry Pi 5**: Use 1–2 in PIO mode (`rp1_rio: 0`, the default); start with `1` and increase if you see flickering
|
- **Raspberry Pi 5**: Use 5 (or higher if needed)
|
||||||
- **Raspberry Pi Zero/1**: Use 1-2
|
- **Raspberry Pi Zero/1**: Use 1-2
|
||||||
- Incorrect values can cause display corruption, flickering, or system instability
|
- Incorrect values can cause display corruption, flickering, or system instability
|
||||||
- If you experience issues, try adjusting this value up or down by 1
|
- If you experience issues, try adjusting this value up or down by 1
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
{
|
{
|
||||||
"web_display_autostart": true,
|
"web_display_autostart": true,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"mode": "per-day",
|
"mode": "per-day",
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00",
|
"end_time": "23:00",
|
||||||
"days": {
|
"days": {
|
||||||
"monday": {
|
"monday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"tuesday": {
|
"tuesday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"wednesday": {
|
"wednesday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"thursday": {
|
"thursday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"friday": {
|
"friday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"saturday": {
|
"saturday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
},
|
},
|
||||||
"sunday": {
|
"sunday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "07:00",
|
"start_time": "07:00",
|
||||||
"end_time": "23:00"
|
"end_time": "23:00"
|
||||||
}
|
}
|
||||||
@@ -51,46 +51,46 @@
|
|||||||
"end_time": "07:00",
|
"end_time": "07:00",
|
||||||
"days": {
|
"days": {
|
||||||
"monday": {
|
"monday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"tuesday": {
|
"tuesday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"wednesday": {
|
"wednesday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"thursday": {
|
"thursday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"friday": {
|
"friday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"saturday": {
|
"saturday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
},
|
},
|
||||||
"sunday": {
|
"sunday": {
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"start_time": "20:00",
|
"start_time": "20:00",
|
||||||
"end_time": "07:00"
|
"end_time": "07:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timezone": "America/New_York",
|
"timezone": "America/Chicago",
|
||||||
"location": {
|
"location": {
|
||||||
"city": "Tampa",
|
"city": "Dallas",
|
||||||
"state": "Florida",
|
"state": "Texas",
|
||||||
"country": "US"
|
"country": "US"
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
|
|||||||
@@ -36,17 +36,9 @@ if [ -r /proc/device-tree/model ]; then
|
|||||||
DEVICE_MODEL=$(tr -d '\0' </proc/device-tree/model)
|
DEVICE_MODEL=$(tr -d '\0' </proc/device-tree/model)
|
||||||
echo "Detected device: $DEVICE_MODEL"
|
echo "Detected device: $DEVICE_MODEL"
|
||||||
else
|
else
|
||||||
DEVICE_MODEL=""
|
|
||||||
echo "⚠ Could not detect Raspberry Pi model (continuing anyway)"
|
echo "⚠ Could not detect Raspberry Pi model (continuing anyway)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect Pi 5 for hardware-specific install decisions (RP1 library verification)
|
|
||||||
IS_PI5=0
|
|
||||||
if echo "${DEVICE_MODEL:-}" | grep -qi "Raspberry Pi 5"; then
|
|
||||||
IS_PI5=1
|
|
||||||
echo "Raspberry Pi 5 detected — will verify RP1 library support."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check OS version - must be Raspberry Pi OS Lite (Trixie)
|
# Check OS version - must be Raspberry Pi OS Lite (Trixie)
|
||||||
echo ""
|
echo ""
|
||||||
echo "Checking operating system requirements..."
|
echo "Checking operating system requirements..."
|
||||||
@@ -791,26 +783,9 @@ CURRENT_STEP="Build and install rpi-rgb-led-matrix"
|
|||||||
echo "Step 6: Building and installing rpi-rgb-led-matrix..."
|
echo "Step 6: Building and installing rpi-rgb-led-matrix..."
|
||||||
echo "-----------------------------------------------------"
|
echo "-----------------------------------------------------"
|
||||||
|
|
||||||
# On Pi 5, also check that the installed library has rp1_rio support.
|
# If already installed and not forcing rebuild, skip expensive build
|
||||||
# A library built before Pi 5 support was added imports fine but maps to the
|
|
||||||
# Pi 3 peripheral bus address (0x3f000000) instead of the RP1 chip at runtime.
|
|
||||||
_HAS_RP1=0
|
|
||||||
if python3 -c 'from rgbmatrix import RGBMatrixOptions; assert hasattr(RGBMatrixOptions(), "rp1_rio")' >/dev/null 2>&1; then
|
|
||||||
_HAS_RP1=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_SKIP_BUILD=0
|
|
||||||
if python3 -c 'from rgbmatrix import RGBMatrix, RGBMatrixOptions' >/dev/null 2>&1 && [ "${RPI_RGB_FORCE_REBUILD:-0}" != "1" ]; then
|
if python3 -c 'from rgbmatrix import RGBMatrix, RGBMatrixOptions' >/dev/null 2>&1 && [ "${RPI_RGB_FORCE_REBUILD:-0}" != "1" ]; then
|
||||||
if [ "$IS_PI5" = "1" ] && [ "$_HAS_RP1" = "0" ]; then
|
echo "rgbmatrix Python package already available; skipping build (set RPI_RGB_FORCE_REBUILD=1 to force rebuild)."
|
||||||
echo "⚠ Pi 5 detected: installed rgbmatrix lacks rp1_rio support (older build)."
|
|
||||||
echo " Forcing rebuild to get Pi 5 RP1 support..."
|
|
||||||
else
|
|
||||||
_SKIP_BUILD=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$_SKIP_BUILD" = "1" ]; then
|
|
||||||
echo "rgbmatrix already installed${IS_PI5:+ with Pi 5 RP1 support}; skipping build (set RPI_RGB_FORCE_REBUILD=1 to force rebuild)."
|
|
||||||
else
|
else
|
||||||
# Ensure rpi-rgb-led-matrix submodule is initialized
|
# Ensure rpi-rgb-led-matrix submodule is initialized
|
||||||
if [ ! -d "$PROJECT_ROOT_DIR/rpi-rgb-led-matrix-master" ]; then
|
if [ ! -d "$PROJECT_ROOT_DIR/rpi-rgb-led-matrix-master" ]; then
|
||||||
@@ -877,17 +852,6 @@ except Exception as e:
|
|||||||
PY
|
PY
|
||||||
then
|
then
|
||||||
echo "✓ rpi-rgb-led-matrix installed and verified"
|
echo "✓ rpi-rgb-led-matrix installed and verified"
|
||||||
# Pi 5: confirm the freshly-built library has rp1_rio support
|
|
||||||
if [ "$IS_PI5" = "1" ]; then
|
|
||||||
if python3 -c 'from rgbmatrix import RGBMatrixOptions; assert hasattr(RGBMatrixOptions(), "rp1_rio")' >/dev/null 2>&1; then
|
|
||||||
echo "✓ Pi 5 RP1 (rp1_rio) support confirmed"
|
|
||||||
else
|
|
||||||
echo "⚠ rp1_rio not found after rebuild — the submodule may be an older version."
|
|
||||||
echo " Try updating the submodule and rebuilding:"
|
|
||||||
echo " git submodule update --remote rpi-rgb-led-matrix-master"
|
|
||||||
echo " sudo RPI_RGB_FORCE_REBUILD=1 ./first_time_install.sh"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "✗ rpi-rgb-led-matrix import test failed"
|
echo "✗ rpi-rgb-led-matrix import test failed"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
requests>=2.33.0
|
requests>=2.33.0
|
||||||
urllib3>=2.6.3
|
urllib3>=1.26.0
|
||||||
Pillow>=12.2.0
|
Pillow>=12.2.0
|
||||||
pytz>=2022.1
|
pytz>=2022.1
|
||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ class DisplayHelper:
|
|||||||
PIL Image with no data message
|
PIL Image with no data message
|
||||||
"""
|
"""
|
||||||
img = self.create_base_image((0, 0, 0))
|
img = self.create_base_image((0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
font = ImageFont.load_default()
|
font = ImageFont.load_default()
|
||||||
self._draw_centered_text(message, font, (0, 0, 0), (150, 150, 150))
|
self._draw_centered_text(message, font, (0, 0, 0), (150, 150, 150))
|
||||||
|
|
||||||
|
|||||||
@@ -823,7 +823,7 @@ class DisplayController:
|
|||||||
scroll_h = getattr(plugin_instance, 'scroll_helper', None)
|
scroll_h = getattr(plugin_instance, 'scroll_helper', None)
|
||||||
if scroll_h is not None:
|
if scroll_h is not None:
|
||||||
follower_frame = scroll_h.get_portion_at(scroll_h.scroll_position + offset)
|
follower_frame = scroll_h.get_portion_at(scroll_h.scroll_position + offset)
|
||||||
except Exception: # nosec B110 - scroll_helper.get_portion_at is optional; skip on error
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 3. Mirror fallback — static plugins (clock, weather) show same frame
|
# 3. Mirror fallback — static plugins (clock, weather) show same frame
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
if os.getenv("EMULATOR", "false") == "true":
|
if os.getenv("EMULATOR", "false") == "true":
|
||||||
from RGBMatrixEmulator import RGBMatrix, RGBMatrixOptions
|
from RGBMatrixEmulator import RGBMatrix, RGBMatrixOptions
|
||||||
else:
|
else:
|
||||||
@@ -60,7 +58,6 @@ 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):
|
||||||
@@ -90,7 +87,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', 3)
|
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 2)
|
||||||
|
|
||||||
# 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
|
||||||
@@ -110,10 +107,9 @@ class DisplayManager:
|
|||||||
options.rp1_rio = runtime_config.get('rp1_rio')
|
options.rp1_rio = runtime_config.get('rp1_rio')
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"rp1_rio is set in config but the installed rgbmatrix library does "
|
"rp1_rio is set in config but the current RGBMatrixOptions "
|
||||||
"not support it — the library was likely built without Pi 5 RP1 "
|
"implementation does not support it (RGBMatrixEmulator or older "
|
||||||
"support (mmap to 0x3f000000 instead of RP1 chip). "
|
"library version) — value will be ignored"
|
||||||
"Fix: sudo RPI_RGB_FORCE_REBUILD=1 ./first_time_install.sh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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}")
|
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}")
|
||||||
@@ -145,7 +141,6 @@ 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
|
||||||
@@ -169,38 +164,9 @@ 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(
|
logger.error(f"Matrix initialization failed, using fallback mode with size {fallback_width}x{fallback_height}. Error: {e}")
|
||||||
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). gpio_slowdown of 2–3 is typical for Pi 5 PIO mode."
|
|
||||||
)
|
|
||||||
# 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
|
|
||||||
_hw_status = {"ok": self.matrix is not None, "error": _init_error_str}
|
|
||||||
_status_path = "/tmp/led_matrix_hw_status.json" # nosec B108
|
|
||||||
try:
|
|
||||||
if os.path.islink(_status_path):
|
|
||||||
logger.warning("Skipping hardware status write: %s is a symlink", _status_path)
|
|
||||||
else:
|
|
||||||
_fd, _tmp_path = tempfile.mkstemp(dir="/tmp", prefix=".led_hw_") # nosec B108
|
|
||||||
try:
|
|
||||||
with os.fdopen(_fd, "w") as _f:
|
|
||||||
json.dump(_hw_status, _f)
|
|
||||||
_f.flush()
|
|
||||||
os.fsync(_f.fileno())
|
|
||||||
os.chmod(_tmp_path, 0o600)
|
|
||||||
os.replace(_tmp_path, _status_path)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
os.unlink(_tmp_path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
logger.error("Failed to write hardware status file", exc_info=True)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
"""Get the display width."""
|
"""Get the display width."""
|
||||||
@@ -781,8 +747,8 @@ class DisplayManager:
|
|||||||
try:
|
try:
|
||||||
self.image = Image.new('RGB', (self.width, self.height))
|
self.image = Image.new('RGB', (self.width, self.height))
|
||||||
self.draw = ImageDraw.Draw(self.image)
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
except (OSError, RuntimeError, ValueError, MemoryError):
|
except Exception:
|
||||||
logger.debug("Canvas reset during cleanup failed", exc_info=True)
|
pass
|
||||||
# Reset the singleton state when cleaning up
|
# Reset the singleton state when cleaning up
|
||||||
DisplayManager._instance = None
|
DisplayManager._instance = None
|
||||||
DisplayManager._initialized = False
|
DisplayManager._initialized = False
|
||||||
|
|||||||
@@ -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', 'rp1_rio', 'scan_mode', 'disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate',
|
'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',
|
'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,14 +747,6 @@ 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']:
|
||||||
@@ -1535,23 +1527,6 @@ 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 (json.JSONDecodeError, PermissionError):
|
|
||||||
logger.error("Failed to read hardware status file", exc_info=True)
|
|
||||||
return jsonify({"status": "error", "message": "Unable to read hardware status"}), 500
|
|
||||||
except Exception:
|
|
||||||
logger.error("Unexpected error reading hardware status", exc_info=True)
|
|
||||||
return jsonify({"status": "error", "message": "Unable to read hardware status"}), 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"""
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def get_local_ips():
|
|||||||
ip = ip.strip()
|
ip = ip.strip()
|
||||||
if ip and not ip.startswith("127.") and ip != "192.168.4.1":
|
if ip and not ip.startswith("127.") and ip != "192.168.4.1":
|
||||||
ips.append(ip)
|
ips.append(ip)
|
||||||
except Exception: # nosec B110 - hostname -I output parsing; non-critical startup info
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Fallback: try socket method
|
# Fallback: try socket method
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global showNotification, updateSystemStats, htmx */
|
/* global showNotification, updateSystemStats */
|
||||||
// LED Matrix v3 JavaScript
|
// LED Matrix v3 JavaScript
|
||||||
// Additional helpers for HTMX and Alpine.js integration
|
// Additional helpers for HTMX and Alpine.js integration
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@
|
|||||||
removeButton.type = 'button';
|
removeButton.type = 'button';
|
||||||
removeButton.className = 'text-red-600 hover:text-red-800 px-2 py-1';
|
removeButton.className = 'text-red-600 hover:text-red-800 px-2 py-1';
|
||||||
removeButton.addEventListener('click', function() {
|
removeButton.addEventListener('click', function() {
|
||||||
window.removeCustomFeedRow(this);
|
removeCustomFeedRow(this);
|
||||||
});
|
});
|
||||||
const removeIcon = document.createElement('i');
|
const removeIcon = document.createElement('i');
|
||||||
removeIcon.className = 'fas fa-trash';
|
removeIcon.className = 'fas fa-trash';
|
||||||
|
|||||||
@@ -212,7 +212,7 @@
|
|||||||
const parts = formatter.formatToParts(now);
|
const parts = formatter.formatToParts(now);
|
||||||
const offsetPart = parts.find(p => p.type === 'timeZoneName');
|
const offsetPart = parts.find(p => p.type === 'timeZoneName');
|
||||||
return offsetPart ? offsetPart.value : '';
|
return offsetPart ? offsetPart.value : '';
|
||||||
} catch {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,6 @@
|
|||||||
<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 try adjusting <strong>GPIO Slowdown</strong> (start at 3, reduce if the display looks dim or choppy).
|
|
||||||
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"}'
|
||||||
@@ -168,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 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"
|
||||||
@@ -176,20 +157,9 @@
|
|||||||
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="10"
|
max="5"
|
||||||
class="form-control">
|
class="form-control">
|
||||||
<p class="mt-1 text-sm text-gray-600">Pi 3: 1–2 · Pi 4: 2–4 · Pi 5 PIO: 1–3. Increase if display shows garbage; in RIO mode higher values may improve performance.</p>
|
<p class="mt-1 text-sm text-gray-600">GPIO slowdown factor (0-5)</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)|int == 0 %}selected{% endif %}>0 — PIO (default, low CPU)</option>
|
|
||||||
<option value="1" {% if main_config.display.get('runtime', {}).get('rp1_rio', 0)|int == 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