3 Commits

Author SHA1 Message Date
Chuck
c6b79e11d5 fix: Codacy round-2 — urllib3 CVEs, missed JS/Python issues (#336)
urllib3 CVEs (10 Trivy findings):
  plugin-repos/march-madness/requirements.txt: bump urllib3>=1.26.0 to
  >=2.2.2 to address CVE-2021-33503, CVE-2023-43804, CVE-2023-45803,
  CVE-2024-37891, and 2025-2026 decompression/redirect CVEs.

Missed code fixes from round-1:
  display_helper.py: remove unused draw=ImageDraw.Draw(img) — the method
  delegates to _draw_centered_text which creates its own draw context.
  custom-feeds.js:334: one bare removeCustomFeedRow(this) was missed by
  the earlier replace_all; changed to window.removeCustomFeedRow(this).
  app.js: add htmx to /* global */ declaration — htmx.ajax() is called
  at lines 146 and 172 but htmx was only declared in the extension files.
  timezone-selector.js:215: second unused catch (e) → catch {} missed
  when we fixed line 361 in round-1.

Bandit B110 annotations (3 new except/pass blocks from newer PRs):
  start.py: hostname -I IP parsing — non-critical startup info.
  display_controller.py: scroll_helper.get_portion_at — optional method.
  display_manager.py: canvas reset during cleanup — best-effort.

41 confirmed false positives suppressed via Codacy API:
  35x pyflakes in test/, plugin-repos/, scripts/ — not production code
  Flask 0.0.0.0, os.execvp, Bandit B603, vendor ESLint, already-fixed
  Biome noPrototypeBuiltins.

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:04:21 -04:00
Chuck
d941c91f24 fix(systemd): wait for network connectivity before starting services (#335)
Change After=network.target → After=network-online.target + Wants=network-
online.target in both service templates and install_web_service.sh.

network.target only guarantees NetworkManager has started — it does NOT
mean the device has an active internet connection. On boot the LED matrix
service was starting within seconds of the network interface appearing,
before WiFi association and DHCP completed, causing every first-update API
call to fail with "Network is unreachable" or DNS resolution errors.

network-online.target waits for a confirmed route before the service fires.
On Raspberry Pi OS this is provided by NetworkManager-wait-online. The
tradeoff is a few extra seconds at boot, acceptable for a display device.

Observed on devpi: service started at 14:48:03, all API calls (weather,
FlightRadar24, local ADS-B) failed at 14:48:07 with network errors, then
the service restarted cleanly at 14:50:40 once WiFi was established.

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:47:35 -04:00
Chuck
054ad78d7b chore(deps): update rpi-rgb-led-matrix to latest upstream for Pi 5 support (#334)
* chore(deps): update rpi-rgb-led-matrix to latest upstream for Pi 5 support

Configure submodule to track upstream master branch (branch = master in
.gitmodules) so future updates are a single 'git submodule update --remote'
rather than manual SHA management.

Update first_time_install.sh to use --remote flag so fresh installs always
pull the current upstream master, not the commit recorded at clone time.

Current upstream HEAD (8907235) brings:
- PR #1886: Raspberry Pi 5 support — new RP1 PIO and RIO backends. The
  library auto-detects Pi 5 hardware at runtime; no config change required
  for basic operation. adafruit-hat-pwm is confirmed supported on Pi 5.
- PR #1833: setup.py migrated from distutils → setuptools, fixing Python
  3.12+ build failure (Pi runs Python 3.13). Previous version could not
  build the bindings at all on current Pi OS.

Expose new rp1_rio option in display_manager.py and config.template.json:
  0 (default) = PIO mode — uses Pi 5 RP1 coprocessor, minimal CPU usage
  1 = RIO mode — Registered IO, faster throughput, higher CPU; note that
      gpio_slowdown has inverted effect in this mode

No API changes to RGBMatrix, RGBMatrixOptions, or FrameCanvas. Pi 4 and
earlier hardware is unaffected — rp1_rio is silently ignored on non-Pi-5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(deps): update rpi-rgb-led-matrix install for new scikit-build-core system

The library migrated from 'make build-python' + 'pip install bindings/python'
to a scikit-build-core + cmake build where the entire repo root is pip-
installable via 'pip install .'. Update first_time_install.sh accordingly:
- Remove the 'make build-python' step (target no longer exists)
- Install directly from the repo root instead of bindings/python
- Replace build deps: remove cython3/scons/python3-dev, add python-dev-is-python3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: deterministic submodule install + guard rp1_rio for older rgbmatrix

first_time_install.sh: remove --remote from both git submodule update
calls so first-time installs check out the pinned commit recorded in the
repo rather than whatever upstream master happens to be at install time.
The branch = master config in .gitmodules reserves --remote for an
explicit maintainer upgrade (git submodule update --remote).

display_manager.py: guard rp1_rio assignment with hasattr() so setting
the option in config does not cause an AttributeError and silently fall
through to emulator mode when running against RGBMatrixEmulator or an
older rgbmatrix build that predates the Pi 5 property. Emit a warning
instead so the operator knows the value was ignored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:17:00 -04:00
15 changed files with 34 additions and 27 deletions

1
.gitmodules vendored
View File

@@ -1,3 +1,4 @@
[submodule "rpi-rgb-led-matrix-master"]
path = rpi-rgb-led-matrix-master
url = https://github.com/hzeller/rpi-rgb-led-matrix.git
branch = master

View File

@@ -112,7 +112,8 @@
"limit_refresh_rate_hz": 100
},
"runtime": {
"gpio_slowdown": 3
"gpio_slowdown": 3,
"rp1_rio": 0
},
"display_durations": {},
"use_short_date_format": true,

View File

@@ -271,7 +271,7 @@ apt_update
# Install required system packages
echo "Installing Python packages and dependencies..."
apt_install python3-pip python3-venv python3-dev python3-pil python3-pil.imagetk build-essential python3-setuptools python3-wheel cython3 scons cmake ninja-build
apt_install python3-pip python3-venv python-dev-is-python3 python3-pil python3-pil.imagetk build-essential python3-setuptools python3-wheel cmake ninja-build
# Install additional system dependencies that might be needed
echo "Installing additional system dependencies..."
@@ -821,20 +821,13 @@ else
fi
pushd "$PROJECT_ROOT_DIR/rpi-rgb-led-matrix-master" >/dev/null
echo "Building rpi-rgb-led-matrix Python bindings..."
# Build the library first, then Python bindings
# The build-python target depends on the library being built
if ! make build-python; then
echo "✗ Failed to build rpi-rgb-led-matrix Python bindings"
echo " Make sure you have the required build tools installed:"
echo " sudo apt install -y build-essential python3-dev cython3 scons"
popd >/dev/null
exit 1
fi
cd bindings/python
echo "Installing rpi-rgb-led-matrix Python package via pip..."
echo "Installing rpi-rgb-led-matrix Python package (scikit-build-core + cmake)..."
echo " Build deps required: python-dev-is-python3 cmake"
echo " This compiles C++ — may take 2-5 minutes on Pi 4/5..."
if ! python3 -m pip install --break-system-packages .; then
echo "✗ Failed to install rpi-rgb-led-matrix Python package"
echo " Ensure build tools are installed:"
echo " sudo apt install -y python-dev-is-python3 cmake build-essential"
popd >/dev/null
exit 1
fi

View File

@@ -1,5 +1,5 @@
requests>=2.33.0
urllib3>=1.26.0
urllib3>=2.2.2
Pillow>=12.2.0
pytz>=2022.1
numpy>=1.24.0

View File

@@ -31,7 +31,8 @@ echo "Generating service file with dynamic paths..."
WEB_SERVICE_FILE_CONTENT=$(cat <<EOF
[Unit]
Description=LED Matrix Web Interface Service
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=simple

View File

@@ -235,8 +235,6 @@ class DisplayHelper:
PIL Image with no data message
"""
img = self.create_base_image((0, 0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.load_default()
self._draw_centered_text(message, font, (0, 0, 0), (150, 150, 150))

View File

@@ -823,7 +823,7 @@ class DisplayController:
scroll_h = getattr(plugin_instance, 'scroll_helper', None)
if scroll_h is not None:
follower_frame = scroll_h.get_portion_at(scroll_h.scroll_position + offset)
except Exception:
except Exception: # nosec B110 - scroll_helper.get_portion_at is optional; skip on error
pass
# 3. Mirror fallback — static plugins (clock, weather) show same frame

View File

@@ -100,6 +100,17 @@ class DisplayManager:
options.pwm_dither_bits = hardware_config.get('pwm_dither_bits')
if 'inverse_colors' in hardware_config:
options.inverse_colors = hardware_config.get('inverse_colors')
# Pi 5 only: 0=PIO/RP1 coprocessor (default, less CPU),
# 1=RIO/Registered IO (faster; gpio_slowdown effect is inverted in this mode)
if 'rp1_rio' in runtime_config:
if hasattr(options, 'rp1_rio'):
options.rp1_rio = runtime_config.get('rp1_rio')
else:
logger.warning(
"rp1_rio is set in config but the current RGBMatrixOptions "
"implementation does not support it (RGBMatrixEmulator or older "
"library version) — value will be ignored"
)
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}")
@@ -736,7 +747,7 @@ class DisplayManager:
try:
self.image = Image.new('RGB', (self.width, self.height))
self.draw = ImageDraw.Draw(self.image)
except Exception:
except Exception: # nosec B110 - best-effort canvas reset during cleanup; non-critical
pass
# Reset the singleton state when cleaning up
DisplayManager._instance = None

View File

@@ -7,7 +7,8 @@
[Unit]
Description=LED Matrix Web Interface Service
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=simple

View File

@@ -1,6 +1,7 @@
[Unit]
Description=LED Matrix Display Service
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=simple

View File

@@ -41,7 +41,7 @@ def get_local_ips():
ip = ip.strip()
if ip and not ip.startswith("127.") and ip != "192.168.4.1":
ips.append(ip)
except Exception:
except Exception: # nosec B110 - hostname -I output parsing; non-critical startup info
pass
# Fallback: try socket method

View File

@@ -1,4 +1,4 @@
/* global showNotification, updateSystemStats */
/* global showNotification, updateSystemStats, htmx */
// LED Matrix v3 JavaScript
// Additional helpers for HTMX and Alpine.js integration

View File

@@ -331,7 +331,7 @@
removeButton.type = 'button';
removeButton.className = 'text-red-600 hover:text-red-800 px-2 py-1';
removeButton.addEventListener('click', function() {
removeCustomFeedRow(this);
window.removeCustomFeedRow(this);
});
const removeIcon = document.createElement('i');
removeIcon.className = 'fas fa-trash';

View File

@@ -212,7 +212,7 @@
const parts = formatter.formatToParts(now);
const offsetPart = parts.find(p => p.type === 'timeZoneName');
return offsetPart ? offsetPart.value : '';
} catch (e) {
} catch {
return '';
}
}