mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
Compare commits
11 Commits
b374bfa8c6
...
40fcd1ed9f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40fcd1ed9f | ||
|
|
33d023bbd5 | ||
|
|
62da1d2b09 | ||
|
|
f4dbde51bd | ||
|
|
38773044e9 | ||
|
|
44cd3e8c2f | ||
|
|
8b838ff366 | ||
|
|
93e2d29af6 | ||
|
|
a62d4529fb | ||
|
|
b577668568 | ||
|
|
2f3433cebc |
@@ -43,39 +43,46 @@ cp ../../.cursor/plugin_templates/*.template .
|
||||
2. **Using dev_plugin_setup.sh**:
|
||||
```bash
|
||||
# Link from GitHub
|
||||
./dev_plugin_setup.sh link-github my-plugin
|
||||
./scripts/dev/dev_plugin_setup.sh link-github my-plugin
|
||||
|
||||
# Link local repo
|
||||
./dev_plugin_setup.sh link my-plugin /path/to/repo
|
||||
./scripts/dev/dev_plugin_setup.sh link my-plugin /path/to/repo
|
||||
```
|
||||
|
||||
### Running Plugins
|
||||
### Running the Display
|
||||
|
||||
```bash
|
||||
# Emulator (development)
|
||||
python run.py --emulator
|
||||
# Emulator mode (development, no hardware required)
|
||||
EMULATOR=true python3 run.py
|
||||
|
||||
# Hardware (production)
|
||||
python run.py
|
||||
# Hardware (production, requires the rpi-rgb-led-matrix submodule built)
|
||||
python3 run.py
|
||||
|
||||
# As service
|
||||
# As a systemd service
|
||||
sudo systemctl start ledmatrix
|
||||
|
||||
# Dev preview server (renders plugins to a browser without running run.py)
|
||||
python3 scripts/dev_server.py # then open http://localhost:5001
|
||||
```
|
||||
|
||||
There is no `--emulator` flag — the emulator is selected via the
|
||||
`EMULATOR=true` environment variable, which `src/display_manager.py:2`
|
||||
checks at import time.
|
||||
|
||||
### Managing Plugins
|
||||
|
||||
```bash
|
||||
# List plugins
|
||||
./dev_plugin_setup.sh list
|
||||
./scripts/dev/dev_plugin_setup.sh list
|
||||
|
||||
# Check status
|
||||
./dev_plugin_setup.sh status
|
||||
./scripts/dev/dev_plugin_setup.sh status
|
||||
|
||||
# Update plugin(s)
|
||||
./dev_plugin_setup.sh update [plugin-name]
|
||||
./scripts/dev/dev_plugin_setup.sh update [plugin-name]
|
||||
|
||||
# Unlink plugin
|
||||
./dev_plugin_setup.sh unlink <plugin-name>
|
||||
./scripts/dev/dev_plugin_setup.sh unlink <plugin-name>
|
||||
```
|
||||
|
||||
## Using These Files with Cursor
|
||||
@@ -118,9 +125,13 @@ Refer to `plugins_guide.md` for:
|
||||
- **Plugin System**: `src/plugin_system/`
|
||||
- **Base Plugin**: `src/plugin_system/base_plugin.py`
|
||||
- **Plugin Manager**: `src/plugin_system/plugin_manager.py`
|
||||
- **Example Plugins**: `plugins/hockey-scoreboard/`, `plugins/football-scoreboard/`
|
||||
- **Example Plugins**: see the
|
||||
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
repo for canonical sources (e.g. `plugins/hockey-scoreboard/`,
|
||||
`plugins/football-scoreboard/`). Installed plugins land in
|
||||
`plugin-repos/` (default) or `plugins/` (dev fallback).
|
||||
- **Architecture Docs**: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
|
||||
- **Development Setup**: `dev_plugin_setup.sh`
|
||||
- **Development Setup**: `scripts/dev/dev_plugin_setup.sh`
|
||||
|
||||
## Getting Help
|
||||
|
||||
|
||||
@@ -156,11 +156,15 @@ def _fetch_data(self):
|
||||
|
||||
### Adding Image Rendering
|
||||
|
||||
There is no `draw_image()` helper on `DisplayManager`. To render an
|
||||
image, paste it directly onto the underlying PIL `Image`
|
||||
(`display_manager.image`) and then call `update_display()`:
|
||||
|
||||
```python
|
||||
def _render_content(self):
|
||||
# Load and render image
|
||||
image = Image.open("assets/logo.png")
|
||||
self.display_manager.draw_image(image, x=0, y=0)
|
||||
# Load and paste image onto the display canvas
|
||||
image = Image.open("assets/logo.png").convert("RGB")
|
||||
self.display_manager.image.paste(image, (0, 0))
|
||||
|
||||
# Draw text overlay
|
||||
self.display_manager.draw_text(
|
||||
@@ -168,8 +172,18 @@ def _render_content(self):
|
||||
x=10, y=20,
|
||||
color=(255, 255, 255)
|
||||
)
|
||||
|
||||
self.display_manager.update_display()
|
||||
```
|
||||
|
||||
For transparency, paste with a mask:
|
||||
|
||||
```python
|
||||
icon = Image.open("assets/icon.png").convert("RGBA")
|
||||
self.display_manager.image.paste(icon, (5, 5), icon)
|
||||
```
|
||||
|
||||
|
||||
### Adding Live Priority
|
||||
|
||||
1. Enable in config:
|
||||
|
||||
@@ -53,13 +53,13 @@ This method is best for plugins stored in separate Git repositories.
|
||||
|
||||
```bash
|
||||
# Link a plugin from GitHub (auto-detects URL)
|
||||
./dev_plugin_setup.sh link-github <plugin-name>
|
||||
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
|
||||
|
||||
# Example: Link hockey-scoreboard plugin
|
||||
./dev_plugin_setup.sh link-github hockey-scoreboard
|
||||
./scripts/dev/dev_plugin_setup.sh link-github hockey-scoreboard
|
||||
|
||||
# With custom URL
|
||||
./dev_plugin_setup.sh link-github <plugin-name> https://github.com/user/repo.git
|
||||
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name> https://github.com/user/repo.git
|
||||
```
|
||||
|
||||
The script will:
|
||||
@@ -71,10 +71,10 @@ The script will:
|
||||
|
||||
```bash
|
||||
# Link a local plugin repository
|
||||
./dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||
|
||||
# Example: Link a local plugin
|
||||
./dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
|
||||
./scripts/dev/dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
|
||||
```
|
||||
|
||||
### Method 2: Manual Plugin Creation
|
||||
@@ -321,7 +321,8 @@ Each plugin has its own section in `config/config.json`:
|
||||
|
||||
### Secrets Management
|
||||
|
||||
Store sensitive data (API keys, tokens) in `config/config_secrets.json`:
|
||||
Store sensitive data (API keys, tokens) in `config/config_secrets.json`
|
||||
under the same plugin id you use in `config/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -331,19 +332,21 @@ Store sensitive data (API keys, tokens) in `config/config_secrets.json`:
|
||||
}
|
||||
```
|
||||
|
||||
Reference secrets in main config:
|
||||
At load time, the config manager deep-merges `config_secrets.json` into
|
||||
the main config (verified at `src/config_manager.py:162-172`). So in
|
||||
your plugin's code:
|
||||
|
||||
```json
|
||||
{
|
||||
"my-plugin": {
|
||||
"enabled": true,
|
||||
"config_secrets": {
|
||||
"api_key": "my-plugin.api_key"
|
||||
}
|
||||
}
|
||||
}
|
||||
```python
|
||||
class MyPlugin(BasePlugin):
|
||||
def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_manager):
|
||||
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
|
||||
self.api_key = config.get("api_key") # already merged from secrets
|
||||
```
|
||||
|
||||
There is no separate `config_secrets` reference field — just put the
|
||||
secret value under the same plugin namespace and read it from the
|
||||
merged config.
|
||||
|
||||
### Plugin Discovery
|
||||
|
||||
Plugins are automatically discovered when:
|
||||
@@ -355,7 +358,7 @@ Check discovered plugins:
|
||||
|
||||
```bash
|
||||
# Using dev_plugin_setup.sh
|
||||
./dev_plugin_setup.sh list
|
||||
./scripts/dev/dev_plugin_setup.sh list
|
||||
|
||||
# Output shows:
|
||||
# ✓ plugin-name (symlink)
|
||||
@@ -368,7 +371,7 @@ Check discovered plugins:
|
||||
Check plugin status and git information:
|
||||
|
||||
```bash
|
||||
./dev_plugin_setup.sh status
|
||||
./scripts/dev/dev_plugin_setup.sh status
|
||||
|
||||
# Output shows:
|
||||
# ✓ plugin-name
|
||||
@@ -391,13 +394,16 @@ cd ledmatrix-my-plugin
|
||||
|
||||
# Link to LEDMatrix project
|
||||
cd /path/to/LEDMatrix
|
||||
./dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
|
||||
./scripts/dev/dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
|
||||
```
|
||||
|
||||
### 2. Development Cycle
|
||||
|
||||
1. **Edit plugin code** in linked repository
|
||||
2. **Test with emulator**: `python run.py --emulator`
|
||||
2. **Test with the dev preview server**:
|
||||
`python3 scripts/dev_server.py` (then open `http://localhost:5001`).
|
||||
Or run the full display in emulator mode with
|
||||
`EMULATOR=true python3 run.py`. There is no `--emulator` flag.
|
||||
3. **Check logs** for errors or warnings
|
||||
4. **Update configuration** in `config/config.json` if needed
|
||||
5. **Iterate** until plugin works correctly
|
||||
@@ -406,30 +412,30 @@ cd /path/to/LEDMatrix
|
||||
|
||||
```bash
|
||||
# Deploy to Raspberry Pi
|
||||
rsync -avz plugins/my-plugin/ pi@raspberrypi:/path/to/LEDMatrix/plugins/my-plugin/
|
||||
rsync -avz plugins/my-plugin/ ledpi@your-pi-ip:/path/to/LEDMatrix/plugins/my-plugin/
|
||||
|
||||
# Or if using git, pull on Pi
|
||||
ssh pi@raspberrypi "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
|
||||
ssh ledpi@your-pi-ip "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
|
||||
|
||||
# Restart service
|
||||
ssh pi@raspberrypi "sudo systemctl restart ledmatrix"
|
||||
ssh ledpi@your-pi-ip "sudo systemctl restart ledmatrix"
|
||||
```
|
||||
|
||||
### 4. Updating Plugins
|
||||
|
||||
```bash
|
||||
# Update single plugin from git
|
||||
./dev_plugin_setup.sh update my-plugin
|
||||
./scripts/dev/dev_plugin_setup.sh update my-plugin
|
||||
|
||||
# Update all linked plugins
|
||||
./dev_plugin_setup.sh update
|
||||
./scripts/dev/dev_plugin_setup.sh update
|
||||
```
|
||||
|
||||
### 5. Unlinking Plugins
|
||||
|
||||
```bash
|
||||
# Remove symlink (preserves repository)
|
||||
./dev_plugin_setup.sh unlink my-plugin
|
||||
./scripts/dev/dev_plugin_setup.sh unlink my-plugin
|
||||
```
|
||||
|
||||
---
|
||||
@@ -625,8 +631,8 @@ python run.py --emulator
|
||||
**Solutions**:
|
||||
1. Check symlink: `ls -la plugins/my-plugin`
|
||||
2. Verify target exists: `readlink -f plugins/my-plugin`
|
||||
3. Update plugin: `./dev_plugin_setup.sh update my-plugin`
|
||||
4. Re-link plugin if needed: `./dev_plugin_setup.sh unlink my-plugin && ./dev_plugin_setup.sh link my-plugin <path>`
|
||||
3. Update plugin: `./scripts/dev/dev_plugin_setup.sh update my-plugin`
|
||||
4. Re-link plugin if needed: `./scripts/dev/dev_plugin_setup.sh unlink my-plugin && ./scripts/dev/dev_plugin_setup.sh link my-plugin <path>`
|
||||
5. Check git status: `cd plugins/my-plugin && git status`
|
||||
|
||||
---
|
||||
@@ -697,22 +703,22 @@ python run.py --emulator
|
||||
|
||||
```bash
|
||||
# Link plugin from GitHub
|
||||
./dev_plugin_setup.sh link-github <name>
|
||||
./scripts/dev/dev_plugin_setup.sh link-github <name>
|
||||
|
||||
# Link local plugin
|
||||
./dev_plugin_setup.sh link <name> <path>
|
||||
./scripts/dev/dev_plugin_setup.sh link <name> <path>
|
||||
|
||||
# List all plugins
|
||||
./dev_plugin_setup.sh list
|
||||
./scripts/dev/dev_plugin_setup.sh list
|
||||
|
||||
# Check plugin status
|
||||
./dev_plugin_setup.sh status
|
||||
./scripts/dev/dev_plugin_setup.sh status
|
||||
|
||||
# Update plugin(s)
|
||||
./dev_plugin_setup.sh update [name]
|
||||
./scripts/dev/dev_plugin_setup.sh update [name]
|
||||
|
||||
# Unlink plugin
|
||||
./dev_plugin_setup.sh unlink <name>
|
||||
./scripts/dev/dev_plugin_setup.sh unlink <name>
|
||||
|
||||
# Run with emulator
|
||||
python run.py --emulator
|
||||
|
||||
60
.cursorrules
60
.cursorrules
@@ -2,7 +2,13 @@
|
||||
|
||||
## Plugin System Overview
|
||||
|
||||
The LEDMatrix project uses a plugin-based architecture. All display functionality (except core calendar) is implemented as plugins that are dynamically loaded from the `plugins/` directory.
|
||||
The LEDMatrix project uses a plugin-based architecture. All display
|
||||
functionality (except core calendar) is implemented as plugins that are
|
||||
dynamically loaded from the directory configured by
|
||||
`plugin_system.plugins_directory` in `config.json` — the default is
|
||||
`plugin-repos/` (per `config/config.template.json:130`), and the loader
|
||||
also falls back to `plugins/` (used by `scripts/dev/dev_plugin_setup.sh`
|
||||
for symlinks).
|
||||
|
||||
## Plugin Structure
|
||||
|
||||
@@ -27,14 +33,15 @@ The LEDMatrix project uses a plugin-based architecture. All display functionalit
|
||||
**Option A: Use dev_plugin_setup.sh (Recommended)**
|
||||
```bash
|
||||
# Link from GitHub
|
||||
./dev_plugin_setup.sh link-github <plugin-name>
|
||||
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
|
||||
|
||||
# Link local repository
|
||||
./dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||
```
|
||||
|
||||
**Option B: Manual Setup**
|
||||
1. Create directory in `plugins/<plugin-id>/`
|
||||
1. Create directory in `plugin-repos/<plugin-id>/` (or `plugins/<plugin-id>/`
|
||||
if you're using the dev fallback location)
|
||||
2. Add `manifest.json` with required fields
|
||||
3. Create `manager.py` with plugin class
|
||||
4. Add `config_schema.json` for configuration
|
||||
@@ -63,7 +70,12 @@ Plugins are configured in `config/config.json`:
|
||||
### 3. Testing Plugins
|
||||
|
||||
**On Development Machine:**
|
||||
- Use emulator: `python run.py --emulator` or `./run_emulator.sh`
|
||||
- Run the dev preview server: `python3 scripts/dev_server.py` (then
|
||||
open `http://localhost:5001`) — renders plugins in the browser
|
||||
without running the full display loop
|
||||
- Or run the full display in emulator mode:
|
||||
`EMULATOR=true python3 run.py` (or `./scripts/dev/run_emulator.sh`).
|
||||
There is no `--emulator` flag.
|
||||
- Test plugin loading: Check logs for plugin discovery and loading
|
||||
- Validate configuration: Ensure config matches `config_schema.json`
|
||||
|
||||
@@ -75,15 +87,22 @@ Plugins are configured in `config/config.json`:
|
||||
### 4. Plugin Development Best Practices
|
||||
|
||||
**Code Organization:**
|
||||
- Keep plugin code in `plugins/<plugin-id>/`
|
||||
- Keep plugin code in `plugin-repos/<plugin-id>/` (or its dev-time
|
||||
symlink in `plugins/<plugin-id>/`)
|
||||
- Use shared assets from `assets/` directory when possible
|
||||
- Follow existing plugin patterns (see `plugins/hockey-scoreboard/` as reference)
|
||||
- Follow existing plugin patterns — canonical sources live in the
|
||||
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
repo (`plugins/hockey-scoreboard/`, `plugins/football-scoreboard/`,
|
||||
`plugins/clock-simple/`, etc.)
|
||||
- Place shared utilities in `src/common/` if reusable across plugins
|
||||
|
||||
**Configuration Management:**
|
||||
- Use `config_schema.json` for validation
|
||||
- Store secrets in `config/config_secrets.json` (not in main config)
|
||||
- Reference secrets via `config_secrets` key in main config
|
||||
- Store secrets in `config/config_secrets.json` under the same plugin
|
||||
id namespace as the main config — they're deep-merged into the main
|
||||
config at load time (`src/config_manager.py:162-172`), so plugin
|
||||
code reads them directly from `config.get(...)` like any other key
|
||||
- There is no separate `config_secrets` reference field
|
||||
- Validate all required fields in `validate_config()`
|
||||
|
||||
**Error Handling:**
|
||||
@@ -138,18 +157,31 @@ Located in: `src/display_manager.py`
|
||||
|
||||
**Key Methods:**
|
||||
- `clear()`: Clear the display
|
||||
- `draw_text(text, x, y, color, font)`: Draw text
|
||||
- `draw_image(image, x, y)`: Draw PIL Image
|
||||
- `update_display()`: Update physical display
|
||||
- `draw_text(text, x, y, color, font, small_font, centered)`: Draw text
|
||||
- `update_display()`: Push the buffer to the physical display
|
||||
- `draw_weather_icon(condition, x, y, size)`: Draw a weather icon
|
||||
- `width`, `height`: Display dimensions
|
||||
|
||||
**Image rendering**: there is no `draw_image()` helper. Paste directly
|
||||
onto the underlying PIL Image:
|
||||
```python
|
||||
self.display_manager.image.paste(pil_image, (x, y))
|
||||
self.display_manager.update_display()
|
||||
```
|
||||
For transparency, paste with a mask: `image.paste(rgba, (x, y), rgba)`.
|
||||
|
||||
### Cache Manager
|
||||
Located in: `src/cache_manager.py`
|
||||
|
||||
**Key Methods:**
|
||||
- `get(key, max_age=None)`: Get cached value
|
||||
- `get(key, max_age=300)`: Get cached value (returns None if missing/stale)
|
||||
- `set(key, value, ttl=None)`: Cache a value
|
||||
- `delete(key)`: Remove cached value
|
||||
- `clear_cache(key=None)`: Remove a cache entry, or all entries if `key`
|
||||
is omitted. There is no `delete()` method.
|
||||
- `get_cached_data_with_strategy(key, data_type)`: Cache get with
|
||||
data-type-aware TTL strategy
|
||||
- `get_background_cached_data(key, sport_key)`: Cache get for the
|
||||
background-fetch service path
|
||||
|
||||
## Plugin Manifest Schema
|
||||
|
||||
|
||||
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +1,84 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
about: Report a problem with LEDMatrix
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!--
|
||||
Before filing: please check existing issues to see if this is already
|
||||
reported. For security issues, see SECURITY.md and report privately.
|
||||
-->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
## Describe the bug
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
## Steps to reproduce
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
## Expected behavior
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
<!-- What you expected to happen. -->
|
||||
|
||||
## Actual behavior
|
||||
|
||||
<!-- What actually happened. Include any error messages. -->
|
||||
|
||||
## Hardware
|
||||
|
||||
- **Raspberry Pi model**: <!-- e.g. Pi 3B+, Pi 4 8GB, Pi Zero 2W -->
|
||||
- **OS / kernel**: <!-- output of `cat /etc/os-release` and `uname -a` -->
|
||||
- **LED matrix panels**: <!-- e.g. 2x Adafruit 64x32, 1x Waveshare 96x48 -->
|
||||
- **HAT / Bonnet**: <!-- e.g. Adafruit RGB Matrix Bonnet, Electrodragon HAT -->
|
||||
- **PWM jumper mod soldered?**: <!-- yes / no -->
|
||||
- **Display chain**: <!-- chain_length × parallel, e.g. "2x1" -->
|
||||
|
||||
## LEDMatrix version
|
||||
|
||||
<!-- Run `git rev-parse HEAD` in the LEDMatrix directory, or paste the
|
||||
release tag if you installed from a release. -->
|
||||
|
||||
```
|
||||
git commit:
|
||||
```
|
||||
|
||||
## Plugin involved (if any)
|
||||
|
||||
- **Plugin id**:
|
||||
- **Plugin version** (from `manifest.json`):
|
||||
|
||||
## Configuration
|
||||
|
||||
<!-- Paste the relevant section from config/config.json. Redact any
|
||||
API keys before pasting. For display issues, the `display.hardware`
|
||||
block is most relevant. For plugin issues, paste that plugin's section. -->
|
||||
|
||||
```json
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
<!-- The first 50 lines of the relevant log are usually enough. Run:
|
||||
sudo journalctl -u ledmatrix -n 100 --no-pager
|
||||
or for the web service:
|
||||
sudo journalctl -u ledmatrix-web -n 100 --no-pager
|
||||
-->
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## Screenshots / video (optional)
|
||||
|
||||
<!-- A photo of the actual display, or a screenshot of the web UI,
|
||||
helps a lot for visual issues. -->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!-- Anything else that might be relevant: when did this start happening,
|
||||
what's different about your setup, what have you already tried, etc. -->
|
||||
|
||||
62
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
62
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Pull Request
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- 1-3 sentences describing what this PR does and why. -->
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Check all that apply. -->
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Documentation
|
||||
- [ ] Refactor (no functional change)
|
||||
- [ ] Build / CI
|
||||
- [ ] Plugin work (link to the plugin)
|
||||
|
||||
## Related issues
|
||||
|
||||
<!-- "Fixes #123" or "Refs #123". Use "Fixes" for bug PRs so the issue
|
||||
auto-closes when this merges. -->
|
||||
|
||||
## Test plan
|
||||
|
||||
<!-- How did you test this? Check all that apply. Add details for any
|
||||
checked box. -->
|
||||
|
||||
- [ ] Ran on a real Raspberry Pi with hardware
|
||||
- [ ] Ran in emulator mode (`EMULATOR=true python3 run.py`)
|
||||
- [ ] Ran the dev preview server (`scripts/dev_server.py`)
|
||||
- [ ] Ran the test suite (`pytest`)
|
||||
- [ ] Manually verified the affected code path in the web UI
|
||||
- [ ] N/A — documentation-only change
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ ] I updated `README.md` if user-facing behavior changed
|
||||
- [ ] I updated the relevant doc in `docs/` if developer behavior changed
|
||||
- [ ] I added/updated docstrings on new public functions
|
||||
- [ ] N/A — no docs needed
|
||||
|
||||
## Plugin compatibility
|
||||
|
||||
<!-- For changes to BasePlugin, the plugin loader, the web UI, or the
|
||||
config schema. -->
|
||||
|
||||
- [ ] No plugin breakage expected
|
||||
- [ ] Some plugins will need updates — listed below
|
||||
- [ ] N/A — change doesn't touch the plugin system
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] My commits follow the message convention in `CONTRIBUTING.md`
|
||||
- [ ] I read `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`
|
||||
- [ ] I've not committed any secrets or hardcoded API keys
|
||||
- [ ] If this adds a new config key, the form in the web UI was
|
||||
verified (the form is generated from `config_schema.json`)
|
||||
|
||||
## Notes for reviewer
|
||||
|
||||
<!-- Anything reviewers should know — gotchas, things you weren't
|
||||
sure about, decisions you'd like a second opinion on. -->
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -4,8 +4,14 @@
|
||||
- `src/plugin_system/` — Plugin loader, manager, store manager, base plugin class
|
||||
- `web_interface/` — Flask web UI (blueprints, templates, static JS)
|
||||
- `config/config.json` — User plugin configuration (persists across plugin reinstalls)
|
||||
- `plugins/` — Installed plugins directory (gitignored)
|
||||
- `plugin-repos/` — Development symlinks to monorepo plugin dirs
|
||||
- `plugin-repos/` — **Default** plugin install directory used by the
|
||||
Plugin Store, set by `plugin_system.plugins_directory` in
|
||||
`config.json` (default per `config/config.template.json:130`).
|
||||
Not gitignored.
|
||||
- `plugins/` — Legacy/dev plugin location. Gitignored (`plugins/*`).
|
||||
Used by `scripts/dev/dev_plugin_setup.sh` for symlinks. The plugin
|
||||
loader falls back to it when something isn't found in `plugin-repos/`
|
||||
(`src/plugin_system/schema_manager.py:77`).
|
||||
|
||||
## Plugin System
|
||||
- Plugins inherit from `BasePlugin` in `src/plugin_system/base_plugin.py`
|
||||
|
||||
137
CODE_OF_CONDUCT.md
Normal file
137
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
This includes the LEDMatrix Discord server, GitHub repositories owned by
|
||||
ChuckBuilds, and any other forums hosted by or affiliated with the project.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement on the
|
||||
[LEDMatrix Discord](https://discord.gg/uW36dVAtcT) (DM a moderator or
|
||||
ChuckBuilds directly) or by opening a private GitHub Security Advisory if
|
||||
the issue involves account safety. All complaints will be reviewed and
|
||||
investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
113
CONTRIBUTING.md
Normal file
113
CONTRIBUTING.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Contributing to LEDMatrix
|
||||
|
||||
Thanks for considering a contribution! LEDMatrix is built with help from
|
||||
the community and we welcome bug reports, plugins, documentation
|
||||
improvements, and code changes.
|
||||
|
||||
## Quick links
|
||||
|
||||
- **Bugs / feature requests**: open an issue using one of the templates
|
||||
in [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/).
|
||||
- **Real-time discussion**: the
|
||||
[LEDMatrix Discord](https://discord.gg/uW36dVAtcT).
|
||||
- **Plugin development**:
|
||||
[`docs/PLUGIN_DEVELOPMENT_GUIDE.md`](docs/PLUGIN_DEVELOPMENT_GUIDE.md)
|
||||
and the [`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
repository.
|
||||
- **Security issues**: see [`SECURITY.md`](SECURITY.md). Please don't
|
||||
open public issues for vulnerabilities.
|
||||
|
||||
## Setting up a development environment
|
||||
|
||||
1. Clone with submodules:
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/ChuckBuilds/LEDMatrix.git
|
||||
cd LEDMatrix
|
||||
```
|
||||
2. For development without hardware, run the dev preview server:
|
||||
```bash
|
||||
python3 scripts/dev_server.py
|
||||
# then open http://localhost:5001
|
||||
```
|
||||
See [`docs/DEV_PREVIEW.md`](docs/DEV_PREVIEW.md) for details.
|
||||
3. To run the full display in emulator mode:
|
||||
```bash
|
||||
EMULATOR=true python3 run.py
|
||||
```
|
||||
4. To target real hardware on a Raspberry Pi, follow the install
|
||||
instructions in the root [`README.md`](README.md).
|
||||
|
||||
## Running the tests
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pytest
|
||||
```
|
||||
|
||||
See [`docs/HOW_TO_RUN_TESTS.md`](docs/HOW_TO_RUN_TESTS.md) for details
|
||||
on test markers, the per-plugin tests, and the web-interface
|
||||
integration tests.
|
||||
|
||||
## Submitting changes
|
||||
|
||||
1. **Open an issue first** for non-trivial changes. This avoids
|
||||
wasted work on PRs that don't fit the project direction.
|
||||
2. **Create a topic branch** off `main`:
|
||||
`feat/<short-description>`, `fix/<short-description>`,
|
||||
`docs/<short-description>`.
|
||||
3. **Keep PRs focused.** One conceptual change per PR. If you find
|
||||
adjacent bugs while working, fix them in a separate PR.
|
||||
4. **Follow the existing code style.** Python code uses standard
|
||||
`black`/`ruff` conventions; HTML/JS in `web_interface/` follows the
|
||||
patterns already in `templates/v3/` and `static/v3/`.
|
||||
5. **Update documentation** alongside code changes. If you add a
|
||||
config key, document it in the relevant `*.md` file (or, for
|
||||
plugins, in `config_schema.json` so the form is auto-generated).
|
||||
6. **Run the tests** locally before opening the PR.
|
||||
7. **Use the PR template** — `.github/PULL_REQUEST_TEMPLATE.md` will
|
||||
prompt you for what we need.
|
||||
|
||||
## Commit message convention
|
||||
|
||||
Conventional Commits is encouraged but not strictly enforced:
|
||||
|
||||
- `feat: add NHL playoff bracket display`
|
||||
- `fix(plugin-loader): handle missing class_name in manifest`
|
||||
- `docs: correct web UI port in TROUBLESHOOTING.md`
|
||||
- `refactor(cache): consolidate strategy lookup`
|
||||
|
||||
Keep the subject under 72 characters; put the why in the body.
|
||||
|
||||
## Contributing a plugin
|
||||
|
||||
LEDMatrix plugins live in their own repository:
|
||||
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins).
|
||||
Plugin contributions go through that repo's
|
||||
[`SUBMISSION.md`](https://github.com/ChuckBuilds/ledmatrix-plugins/blob/main/SUBMISSION.md)
|
||||
process. The
|
||||
[`hello-world` plugin](https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/hello-world)
|
||||
is the canonical starter template.
|
||||
|
||||
## Reviewing pull requests
|
||||
|
||||
Maintainer review is by [@ChuckBuilds](https://github.com/ChuckBuilds).
|
||||
Community review is welcome on any open PR — leave constructive
|
||||
comments, test on your hardware if applicable, and call out anything
|
||||
unclear.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By
|
||||
participating you agree to abide by its terms.
|
||||
|
||||
## License
|
||||
|
||||
LEDMatrix is licensed under the [GNU General Public License v3.0 or
|
||||
later](LICENSE). By submitting a contribution you agree to license it
|
||||
under the same terms (the standard "inbound = outbound" rule that
|
||||
GitHub applies by default).
|
||||
|
||||
LEDMatrix builds on
|
||||
[`rpi-rgb-led-matrix`](https://github.com/hzeller/rpi-rgb-led-matrix),
|
||||
which is GPL-2.0-or-later. The "or later" clause makes it compatible
|
||||
with GPL-3.0 distribution.
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
24
README.md
24
README.md
@@ -878,3 +878,27 @@ sudo systemctl enable ledmatrix-web.service
|
||||
|
||||
|
||||
### If you've read this far — thanks!
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
## License
|
||||
|
||||
LEDMatrix is licensed under the
|
||||
[GNU General Public License v3.0 or later](LICENSE).
|
||||
|
||||
LEDMatrix builds on
|
||||
[`rpi-rgb-led-matrix`](https://github.com/hzeller/rpi-rgb-led-matrix),
|
||||
which is GPL-2.0-or-later. The "or later" clause makes it compatible
|
||||
with GPL-3.0 distribution.
|
||||
|
||||
Plugin contributions in
|
||||
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
are also GPL-3.0-or-later unless individual plugins specify otherwise.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, the PR
|
||||
flow, and how to add a plugin. Bug reports and feature requests go in
|
||||
the [issue tracker](https://github.com/ChuckBuilds/LEDMatrix/issues).
|
||||
Security issues should be reported privately per
|
||||
[SECURITY.md](SECURITY.md).
|
||||
|
||||
86
SECURITY.md
Normal file
86
SECURITY.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you've found a security issue in LEDMatrix, **please don't open a
|
||||
public GitHub issue**. Disclose it privately so we can fix it before it's
|
||||
exploited.
|
||||
|
||||
### How to report
|
||||
|
||||
Use one of these channels, in order of preference:
|
||||
|
||||
1. **GitHub Security Advisories** (preferred). On the LEDMatrix repo,
|
||||
go to **Security → Advisories → Report a vulnerability**. This
|
||||
creates a private discussion thread visible only to you and the
|
||||
maintainer.
|
||||
- Direct link: <https://github.com/ChuckBuilds/LEDMatrix/security/advisories/new>
|
||||
2. **Discord DM**. Send a direct message to a moderator on the
|
||||
[LEDMatrix Discord](https://discord.gg/uW36dVAtcT). Don't post in
|
||||
public channels.
|
||||
|
||||
Please include:
|
||||
|
||||
- A description of the issue
|
||||
- The version / commit hash you're testing against
|
||||
- Steps to reproduce, ideally a minimal proof of concept
|
||||
- The impact you can demonstrate
|
||||
- Any suggested mitigation
|
||||
|
||||
### What to expect
|
||||
|
||||
- An acknowledgement within a few days (this is a hobby project, not
|
||||
a 24/7 ops team).
|
||||
- A discussion of the issue's severity and a plan for the fix.
|
||||
- Credit in the release notes when the fix ships, unless you'd
|
||||
prefer to remain anonymous.
|
||||
- For high-severity issues affecting active deployments, we'll
|
||||
coordinate disclosure timing with you.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope for this policy:
|
||||
|
||||
- The LEDMatrix display controller, web interface, and plugin loader
|
||||
in this repository
|
||||
- The official plugins in
|
||||
[`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
- Installation scripts and systemd unit files
|
||||
|
||||
Out of scope (please report upstream):
|
||||
|
||||
- Vulnerabilities in `rpi-rgb-led-matrix` itself —
|
||||
report to <https://github.com/hzeller/rpi-rgb-led-matrix>
|
||||
- Vulnerabilities in Python packages we depend on — report to the
|
||||
upstream package maintainer
|
||||
- Issues in third-party plugins not in `ledmatrix-plugins` — report
|
||||
to that plugin's repository
|
||||
|
||||
## Known security model
|
||||
|
||||
LEDMatrix is designed for trusted local networks. Several limitations
|
||||
are intentional rather than vulnerabilities:
|
||||
|
||||
- **No web UI authentication.** The web interface assumes the network
|
||||
it's running on is trusted. Don't expose port 5000 to the internet.
|
||||
- **Plugins run unsandboxed.** Installed plugins execute in the same
|
||||
Python process as the display loop with full file-system and
|
||||
network access. Review plugin code (especially third-party plugins
|
||||
from arbitrary GitHub URLs) before installing. The Plugin Store
|
||||
marks community plugins as **Custom** to highlight this.
|
||||
- **The display service runs as root** for hardware GPIO access. This
|
||||
is required by `rpi-rgb-led-matrix`.
|
||||
- **`config_secrets.json` is plaintext.** API keys and tokens are
|
||||
stored unencrypted on the Pi. Lock down filesystem permissions on
|
||||
the config directory if this matters for your deployment.
|
||||
|
||||
These are documented as known limitations rather than bugs. If you
|
||||
have ideas for improving them while keeping the project usable on a
|
||||
Pi, open a discussion — we're interested.
|
||||
|
||||
## Supported versions
|
||||
|
||||
LEDMatrix is rolling-release on `main`. Security fixes land on `main`
|
||||
and become available the next time users run **Update Code** from the
|
||||
web UI's Overview tab (which does a `git pull`). There are no LTS
|
||||
branches.
|
||||
@@ -250,19 +250,29 @@ WARNING - Plugin ID 'Football-Scoreboard' may conflict with 'football-scoreboard
|
||||
|
||||
## Checking Configuration via API
|
||||
|
||||
The API blueprint mounts at `/api/v3` (`web_interface/app.py:144`).
|
||||
|
||||
```bash
|
||||
# Get current config
|
||||
curl http://localhost:5000/api/v3/config
|
||||
# Get full main config (includes all plugin sections)
|
||||
curl http://localhost:5000/api/v3/config/main
|
||||
|
||||
# Get specific plugin config
|
||||
curl http://localhost:5000/api/v3/config/plugin/football-scoreboard
|
||||
|
||||
# Validate config without saving
|
||||
curl -X POST http://localhost:5000/api/v3/config/validate \
|
||||
# Save updated main config
|
||||
curl -X POST http://localhost:5000/api/v3/config/main \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"football-scoreboard": {"enabled": true}}'
|
||||
-d @new-config.json
|
||||
|
||||
# Get config schema for a specific plugin
|
||||
curl "http://localhost:5000/api/v3/plugins/schema?plugin_id=football-scoreboard"
|
||||
|
||||
# Get a single plugin's current config
|
||||
curl "http://localhost:5000/api/v3/plugins/config?plugin_id=football-scoreboard"
|
||||
```
|
||||
|
||||
> There is no dedicated `/config/plugin/<id>` or `/config/validate`
|
||||
> endpoint — config validation runs server-side automatically when you
|
||||
> POST to `/config/main` or `/plugins/config`. See
|
||||
> [REST_API_REFERENCE.md](REST_API_REFERENCE.md) for the full list.
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Manual Backup
|
||||
|
||||
@@ -62,7 +62,7 @@ display_manager.defer_update(lambda: self.update_cache(), priority=0)
|
||||
# Basic caching
|
||||
cached = cache_manager.get("key", max_age=3600)
|
||||
cache_manager.set("key", data)
|
||||
cache_manager.delete("key")
|
||||
cache_manager.clear_cache("key") # there is no delete() method
|
||||
|
||||
# Advanced caching
|
||||
data = cache_manager.get_cached_data_with_strategy("key", data_type="weather")
|
||||
|
||||
@@ -138,7 +138,21 @@ font = self.font_manager.resolve_font(
|
||||
|
||||
## For Plugin Developers
|
||||
|
||||
### Plugin Font Registration
|
||||
> ⚠️ **Status**: the plugin-font registration described below is
|
||||
> implemented in `src/font_manager.py:150` (`register_plugin_fonts()`)
|
||||
> but is **not currently wired into the plugin loader**. Adding a
|
||||
> `"fonts"` block to your plugin's `manifest.json` will silently have
|
||||
> no effect — the FontManager method exists but nothing calls it.
|
||||
>
|
||||
> Until that's connected, plugin authors should ship custom fonts as
|
||||
> regular files inside the plugin directory (e.g., `assets/myfont.ttf`)
|
||||
> and reference them by relative path from the plugin's `manager.py`
|
||||
> via `display_manager.font_manager.resolve_font(...)` or by loading
|
||||
> with PIL directly. The user-facing font override system in the
|
||||
> **Fonts** tab still works for any element that's been registered via
|
||||
> `register_manager_font()`.
|
||||
|
||||
### Plugin Font Registration (planned)
|
||||
|
||||
In your plugin's `manifest.json`:
|
||||
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# LEDMatrix Plugin Architecture Specification
|
||||
|
||||
> **Historical design document.** This spec was written *before* the
|
||||
> plugin system was built. Most of it is still architecturally
|
||||
> accurate, but specific details have drifted from the shipped
|
||||
> implementation:
|
||||
>
|
||||
> - Code paths reference `web_interface_v2.py`; the current web UI is
|
||||
> `web_interface/app.py` with v3 Blueprint-based templates.
|
||||
> - The example Flask routes use `/api/plugins/*`; the real API
|
||||
> blueprint is mounted at `/api/v3` (`web_interface/app.py:144`).
|
||||
> - The default plugin location is `plugin-repos/` (configurable via
|
||||
> `plugin_system.plugins_directory`), not `./plugins/`.
|
||||
> - The "Migration Strategy" and "Implementation Roadmap" sections
|
||||
> describe work that has now shipped.
|
||||
>
|
||||
> For the current system, see:
|
||||
> [PLUGIN_DEVELOPMENT_GUIDE.md](PLUGIN_DEVELOPMENT_GUIDE.md),
|
||||
> [PLUGIN_API_REFERENCE.md](PLUGIN_API_REFERENCE.md), and
|
||||
> [REST_API_REFERENCE.md](REST_API_REFERENCE.md).
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document outlines the transformation of the LEDMatrix project into a modular, plugin-based architecture that enables user-created displays. The goal is to create a flexible, extensible system similar to Home Assistant Community Store (HACS) where users can discover, install, and manage custom display managers from GitHub repositories.
|
||||
@@ -9,7 +28,7 @@ This document outlines the transformation of the LEDMatrix project into a modula
|
||||
1. **Gradual Migration**: Existing managers remain in core while new plugin infrastructure is built
|
||||
2. **Migration Required**: Breaking changes with migration tools provided
|
||||
3. **GitHub-Based Store**: Simple discovery system, packages served from GitHub repos
|
||||
4. **Plugin Location**: `./plugins/` directory in project root
|
||||
4. **Plugin Location**: `./plugins/` directory in project root *(actual default is now `plugin-repos/`)*
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -184,37 +184,45 @@ plugin-repos/
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-plugin",
|
||||
"name": "My Plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin description",
|
||||
"author": "Your Name",
|
||||
"entry_point": "manager.py",
|
||||
"class_name": "MyPlugin",
|
||||
"display_modes": ["my_plugin"],
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean", "default": false},
|
||||
"update_interval": {"type": "integer", "default": 3600}
|
||||
}
|
||||
}
|
||||
"config_schema": "config_schema.json"
|
||||
}
|
||||
```
|
||||
|
||||
The required fields the plugin loader will check for are `id`,
|
||||
`name`, `version`, `class_name`, and `display_modes`. `entry_point`
|
||||
defaults to `manager.py` if omitted. `config_schema` must be a
|
||||
**file path** (relative to the plugin directory) — the schema itself
|
||||
lives in a separate JSON file, not inline in the manifest. The
|
||||
`class_name` value must match the actual class defined in the entry
|
||||
point file **exactly** (case-sensitive, no spaces); otherwise the
|
||||
loader fails with `AttributeError` at load time.
|
||||
|
||||
### Plugin Manager Class
|
||||
|
||||
```python
|
||||
from src.plugin_system.base_plugin import BasePlugin
|
||||
|
||||
class MyPluginManager(BasePlugin):
|
||||
def __init__(self, config, display_manager, cache_manager, font_manager):
|
||||
super().__init__(config, display_manager, cache_manager, font_manager)
|
||||
self.enabled = config.get('enabled', False)
|
||||
class MyPlugin(BasePlugin):
|
||||
def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_manager):
|
||||
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
|
||||
# self.config, self.display_manager, self.cache_manager,
|
||||
# self.plugin_manager, self.logger, and self.enabled are
|
||||
# all set up by BasePlugin.__init__.
|
||||
|
||||
def update(self):
|
||||
"""Update plugin data"""
|
||||
"""Fetch/update data. Called based on update_interval."""
|
||||
pass
|
||||
|
||||
def display(self, force_clear=False):
|
||||
"""Display plugin content"""
|
||||
"""Render plugin content to the LED matrix."""
|
||||
pass
|
||||
|
||||
def get_duration(self):
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Plugin Configuration Tabs
|
||||
|
||||
> **Status note:** this doc was written during the rollout of the
|
||||
> per-plugin configuration tab feature. The feature itself is shipped
|
||||
> and working in the current v3 web interface, but a few file paths
|
||||
> in the "Implementation Details" section below still reference the
|
||||
> pre-v3 file layout (`web_interface_v2.py`, `templates/index_v2.html`).
|
||||
> The current implementation lives in `web_interface/app.py`,
|
||||
> `web_interface/blueprints/api_v3.py`, and `web_interface/templates/v3/`.
|
||||
> The user-facing description (Overview, Features, Form Generation
|
||||
> Process) is still accurate.
|
||||
|
||||
## Overview
|
||||
|
||||
Each installed plugin now gets its own dedicated configuration tab in the web interface. This provides a clean, organized way to configure plugins without cluttering the main Plugins management tab.
|
||||
@@ -198,12 +208,12 @@ Renders as: Dropdown select
|
||||
|
||||
### Form Generation Process
|
||||
|
||||
1. Web UI loads installed plugins via `/api/plugins/installed`
|
||||
1. Web UI loads installed plugins via `/api/v3/plugins/installed`
|
||||
2. For each plugin, the backend loads its `config_schema.json`
|
||||
3. Frontend generates a tab button with plugin name
|
||||
4. Frontend generates a form based on the JSON Schema
|
||||
5. Current config values from `config.json` are populated
|
||||
6. When saved, each field is sent to `/api/plugins/config` endpoint
|
||||
6. When saved, each field is sent to `/api/v3/plugins/config` endpoint
|
||||
|
||||
## Implementation Details
|
||||
|
||||
@@ -211,7 +221,7 @@ Renders as: Dropdown select
|
||||
|
||||
**File**: `web_interface_v2.py`
|
||||
|
||||
- Modified `/api/plugins/installed` endpoint to include `config_schema_data`
|
||||
- Modified `/api/v3/plugins/installed` endpoint to include `config_schema_data`
|
||||
- Loads each plugin's `config_schema.json` if it exists
|
||||
- Returns schema data along with plugin info
|
||||
|
||||
@@ -231,7 +241,7 @@ New Functions:
|
||||
```
|
||||
Page Load
|
||||
→ refreshPlugins()
|
||||
→ /api/plugins/installed
|
||||
→ /api/v3/plugins/installed
|
||||
→ Returns plugins with config_schema_data
|
||||
→ generatePluginTabs()
|
||||
→ Creates tab buttons
|
||||
@@ -245,7 +255,7 @@ User Saves
|
||||
→ savePluginConfiguration()
|
||||
→ Reads form data
|
||||
→ Converts types per schema
|
||||
→ Sends to /api/plugins/config
|
||||
→ Sends to /api/v3/plugins/config
|
||||
→ Updates config.json
|
||||
→ Shows success notification
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Flask Backend │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ /api/plugins/installed │ │
|
||||
│ │ /api/v3/plugins/installed │ │
|
||||
│ │ • Discover plugins in plugins/ directory │ │
|
||||
│ │ • Load manifest.json for each plugin │ │
|
||||
│ │ • Load config_schema.json if exists │ │
|
||||
@@ -40,7 +40,7 @@
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ /api/plugins/config │ │
|
||||
│ │ /api/v3/plugins/config │ │
|
||||
│ │ • Receive key-value pair │ │
|
||||
│ │ • Update config.json │ │
|
||||
│ │ • Return success/error │ │
|
||||
@@ -88,7 +88,7 @@ DOMContentLoaded Event
|
||||
refreshPlugins()
|
||||
│
|
||||
▼
|
||||
GET /api/plugins/installed
|
||||
GET /api/v3/plugins/installed
|
||||
│
|
||||
├─→ For each plugin directory:
|
||||
│ ├─→ Read manifest.json
|
||||
@@ -146,7 +146,7 @@ savePluginConfiguration(pluginId)
|
||||
│ │ • array: split(',')
|
||||
│ │ • string: as-is
|
||||
│ │
|
||||
│ └─→ POST /api/plugins/config
|
||||
│ └─→ POST /api/v3/plugins/config
|
||||
│ {
|
||||
│ plugin_id: "hello-world",
|
||||
│ key: "message",
|
||||
@@ -174,7 +174,7 @@ Refresh Plugins
|
||||
Window Load
|
||||
└── DOMContentLoaded
|
||||
└── refreshPlugins()
|
||||
├── fetch('/api/plugins/installed')
|
||||
├── fetch('/api/v3/plugins/installed')
|
||||
├── renderInstalledPlugins(plugins)
|
||||
└── generatePluginTabs(plugins)
|
||||
└── For each plugin:
|
||||
@@ -198,19 +198,19 @@ User Interactions
|
||||
│ ├── Process form data
|
||||
│ ├── Convert types per schema
|
||||
│ └── For each field:
|
||||
│ └── POST /api/plugins/config
|
||||
│ └── POST /api/v3/plugins/config
|
||||
│
|
||||
└── resetPluginConfig(pluginId)
|
||||
├── Get schema defaults
|
||||
└── For each field:
|
||||
└── POST /api/plugins/config
|
||||
└── POST /api/v3/plugins/config
|
||||
```
|
||||
|
||||
### Backend (Python)
|
||||
|
||||
```
|
||||
Flask Routes
|
||||
├── /api/plugins/installed (GET)
|
||||
├── /api/v3/plugins/installed (GET)
|
||||
│ └── api_plugins_installed()
|
||||
│ ├── PluginManager.discover_plugins()
|
||||
│ ├── For each plugin:
|
||||
@@ -219,7 +219,7 @@ Flask Routes
|
||||
│ │ └── Load config from config.json
|
||||
│ └── Return JSON response
|
||||
│
|
||||
└── /api/plugins/config (POST)
|
||||
└── /api/v3/plugins/config (POST)
|
||||
└── api_plugin_config()
|
||||
├── Parse request JSON
|
||||
├── Load current config
|
||||
@@ -279,7 +279,7 @@ LEDMatrix/
|
||||
### 3. Individual Config Updates
|
||||
|
||||
**Why**: Simplifies backend API
|
||||
**How**: Each field saved separately via `/api/plugins/config`
|
||||
**How**: Each field saved separately via `/api/v3/plugins/config`
|
||||
**Benefit**: Atomic updates, easier error handling
|
||||
|
||||
### 4. Type Conversion in Frontend
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Plugin Custom Icons Guide
|
||||
|
||||
> ⚠️ **Status:** the `icon` field in `manifest.json` is currently
|
||||
> **not honored by the v3 web interface**. Plugin tab icons are
|
||||
> hardcoded to `fas fa-puzzle-piece` in
|
||||
> `web_interface/templates/v3/base.html:515` and `:774`. The icon
|
||||
> field was originally read by a `getPluginIcon()` helper in the v2
|
||||
> templates, but that helper wasn't ported to v3. Setting `icon` in a
|
||||
> manifest is harmless (it's just ignored) so plugin authors can leave
|
||||
> it in place for when this regression is fixed.
|
||||
>
|
||||
> Tracking issue: see the LEDMatrix repo for the open ticket.
|
||||
|
||||
## Overview
|
||||
|
||||
Plugins can specify custom icons that appear next to their name in the web interface tabs. This makes your plugin instantly recognizable and adds visual polish to the UI.
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
# ✅ Plugin Custom Icons Feature - Complete
|
||||
# Plugin Custom Icons Feature
|
||||
|
||||
> ⚠️ **Status:** this doc describes the v2 web interface
|
||||
> implementation of plugin custom icons. The feature **regressed when
|
||||
> the v3 web interface was built** — the `getPluginIcon()` helper
|
||||
> referenced below lived in `templates/index_v2.html` (which is now
|
||||
> archived) and was not ported to the v3 templates. Plugin tab icons
|
||||
> in v3 are hardcoded to `fas fa-puzzle-piece`
|
||||
> (`web_interface/templates/v3/base.html:515` and `:774`). The
|
||||
> `icon` field in `manifest.json` is currently silently ignored.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
@@ -304,7 +313,7 @@ Result: `[logo] Company Metrics` tab
|
||||
|
||||
To test custom icons:
|
||||
|
||||
1. **Open web interface** at `http://your-pi:5001`
|
||||
1. **Open web interface** at `http://your-pi-ip:5000`
|
||||
2. **Check installed plugins**:
|
||||
- Hello World should show 👋
|
||||
- Clock Simple should show 🕐
|
||||
|
||||
@@ -12,6 +12,15 @@ When developing plugins in separate repositories, you need a way to:
|
||||
|
||||
The solution uses **symbolic links** to connect plugin repositories to the `plugins/` directory, combined with a helper script to manage the linking process.
|
||||
|
||||
> **Plugin directory note:** the dev workflow described here puts
|
||||
> symlinks in `plugins/`. The plugin loader's *production* default is
|
||||
> `plugin-repos/` (set by `plugin_system.plugins_directory` in
|
||||
> `config.json`), but it falls back to `plugins/` so the dev symlinks
|
||||
> are picked up automatically. The Plugin Store installs to
|
||||
> `plugin-repos/`. If you want both your dev symlinks *and* store
|
||||
> installs to share the same directory, set `plugins_directory` to
|
||||
> `plugins` in the General tab of the web UI.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Link a Plugin from GitHub
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# LEDMatrix Plugin System - Implementation Summary
|
||||
|
||||
> **Status note:** this is a high-level summary written during the
|
||||
> initial plugin system rollout. Most of it is accurate, but a few
|
||||
> sections describe features that are aspirational or only partially
|
||||
> implemented (per-plugin virtual envs, resource limits, registry
|
||||
> manager). Drift from current reality is called out inline.
|
||||
|
||||
This document provides a comprehensive overview of the plugin architecture implementation, consolidating details from multiple plugin-related implementation summaries.
|
||||
|
||||
## Executive Summary
|
||||
@@ -14,16 +20,25 @@ The LEDMatrix plugin system transforms the project into a modular, extensible pl
|
||||
LEDMatrix/
|
||||
├── src/plugin_system/
|
||||
│ ├── base_plugin.py # Plugin interface contract
|
||||
│ ├── plugin_loader.py # Discovery + dynamic import
|
||||
│ ├── plugin_manager.py # Lifecycle management
|
||||
│ ├── store_manager.py # GitHub integration
|
||||
│ └── registry_manager.py # Plugin discovery
|
||||
├── plugins/ # User-installed plugins
|
||||
│ ├── store_manager.py # GitHub install / store integration
|
||||
│ ├── schema_manager.py # Config schema validation
|
||||
│ ├── health_monitor.py # Plugin health metrics
|
||||
│ ├── operation_queue.py # Async install/update operations
|
||||
│ └── state_manager.py # Persistent plugin state
|
||||
├── plugin-repos/ # Default plugin install location
|
||||
│ ├── football-scoreboard/
|
||||
│ ├── ledmatrix-music/
|
||||
│ └── ledmatrix-stocks/
|
||||
└── config/config.json # Plugin configurations
|
||||
```
|
||||
|
||||
> Earlier drafts of this doc referenced `registry_manager.py`. It was
|
||||
> never created — discovery happens in `plugin_loader.py`. The earlier
|
||||
> default plugin location of `plugins/` has been replaced with
|
||||
> `plugin-repos/` (see `config/config.template.json:130`).
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
✅ **Gradual Migration**: Plugin system added alongside existing managers
|
||||
@@ -77,14 +92,26 @@ LEDMatrix/
|
||||
- **Fallback System**: Default icons when custom ones unavailable
|
||||
|
||||
#### Dependency Management
|
||||
- **Requirements.txt**: Per-plugin dependencies
|
||||
- **Virtual Environments**: Isolated dependency management
|
||||
- **Version Pinning**: Explicit version constraints
|
||||
- **Requirements.txt**: Per-plugin dependencies, installed system-wide
|
||||
via pip on first plugin load
|
||||
- **Version Pinning**: Standard pip version constraints in
|
||||
`requirements.txt`
|
||||
|
||||
#### Permission System
|
||||
- **File Access Control**: Configurable file system permissions
|
||||
- **Network Access**: Controlled API access
|
||||
- **Resource Limits**: CPU and memory constraints
|
||||
> Earlier plans called for per-plugin virtual environments. That isn't
|
||||
> implemented — plugin Python deps install into the system Python
|
||||
> environment (or whatever environment the LEDMatrix service is using).
|
||||
> Conflicting versions across plugins are not auto-resolved.
|
||||
|
||||
#### Health monitoring
|
||||
- **Resource Monitor** (`src/plugin_system/resource_monitor.py`): tracks
|
||||
CPU and memory metrics per plugin and warns about slow plugins
|
||||
- **Health Monitor** (`src/plugin_system/health_monitor.py`): tracks
|
||||
plugin failures and last-success timestamps
|
||||
|
||||
> Earlier plans called for hard CPU/memory limits and a sandboxed
|
||||
> permission system. Neither is implemented. Plugins run in the same
|
||||
> process as the display loop with full file-system and network access
|
||||
> — review third-party plugin code before installing.
|
||||
|
||||
## Plugin Development
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ If the script reboots the Pi (which it recommends), network services may restart
|
||||
# Connect to your WiFi network (replace with your SSID and password)
|
||||
sudo nmcli device wifi connect "YourWiFiSSID" password "YourPassword"
|
||||
|
||||
# Or use the web interface at http://192.168.4.1:5001
|
||||
# Or use the web interface at http://192.168.4.1:5000
|
||||
# Navigate to WiFi tab and connect to your network
|
||||
```
|
||||
|
||||
@@ -177,9 +177,9 @@ sudo systemctl restart NetworkManager
|
||||
|
||||
Even if SSH is unavailable, you can access the web interface:
|
||||
|
||||
1. **Via AP Mode**: Connect to **LEDMatrix-Setup** network and visit `http://192.168.4.1:5001`
|
||||
2. **Via WiFi**: If WiFi is connected, visit `http://<pi-ip-address>:5001`
|
||||
3. **Via Ethernet**: Visit `http://<pi-ip-address>:5001`
|
||||
1. **Via AP Mode**: Connect to **LEDMatrix-Setup** network and visit `http://192.168.4.1:5000`
|
||||
2. **Via WiFi**: If WiFi is connected, visit `http://<pi-ip-address>:5000`
|
||||
3. **Via Ethernet**: Visit `http://<pi-ip-address>:5000`
|
||||
|
||||
The web interface allows you to:
|
||||
- Configure WiFi connections
|
||||
|
||||
@@ -91,7 +91,7 @@ Pixlet is the rendering engine that executes Starlark apps. The plugin will atte
|
||||
|
||||
#### Auto-Install via Web UI
|
||||
|
||||
Navigate to: **Plugins → Starlark Apps → Status → Install Pixlet**
|
||||
Navigate to: **Plugin Manager → Starlark Apps tab (in the second nav row) → Status → Install Pixlet**
|
||||
|
||||
This runs the bundled installation script which downloads the appropriate binary for your platform.
|
||||
|
||||
@@ -110,10 +110,10 @@ Verify installation:
|
||||
|
||||
### 2. Enable the Starlark Apps Plugin
|
||||
|
||||
1. Open the web UI
|
||||
2. Navigate to **Plugins**
|
||||
3. Find **Starlark Apps** in the installed plugins list
|
||||
4. Enable the plugin
|
||||
1. Open the web UI (`http://your-pi-ip:5000`)
|
||||
2. Open the **Plugin Manager** tab
|
||||
3. Find **Starlark Apps** in the **Installed Plugins** list
|
||||
4. Enable the plugin (it then gets its own tab in the second nav row)
|
||||
5. Configure settings:
|
||||
- **Magnify**: Auto-calculated based on your display size (or set manually)
|
||||
- **Render Interval**: How often apps re-render (default: 300s)
|
||||
@@ -122,7 +122,7 @@ Verify installation:
|
||||
|
||||
### 3. Browse and Install Apps
|
||||
|
||||
1. Navigate to **Plugins → Starlark Apps → App Store**
|
||||
1. Navigate to **Plugin Manager → Starlark Apps tab (in the second nav row) → App Store**
|
||||
2. Browse available apps (974+ options)
|
||||
3. Filter by category: Weather, Sports, Finance, Games, Clocks, etc.
|
||||
4. Click **Install** on desired apps
|
||||
@@ -307,7 +307,7 @@ Many apps require API keys for external services:
|
||||
**Symptom**: "Pixlet binary not found" error
|
||||
|
||||
**Solutions**:
|
||||
1. Run auto-installer: **Plugins → Starlark Apps → Install Pixlet**
|
||||
1. Run auto-installer: **Plugin Manager → Starlark Apps tab (in the second nav row) → Install Pixlet**
|
||||
2. Manual install: `bash scripts/download_pixlet.sh`
|
||||
3. Check permissions: `chmod +x bin/pixlet/pixlet-*`
|
||||
4. Verify architecture: `uname -m` matches binary name
|
||||
@@ -338,7 +338,7 @@ Many apps require API keys for external services:
|
||||
**Symptom**: Content appears stretched, squished, or cropped
|
||||
|
||||
**Solutions**:
|
||||
1. Check magnify setting: **Plugins → Starlark Apps → Config**
|
||||
1. Check magnify setting: **Plugin Manager → Starlark Apps tab (in the second nav row) → Config**
|
||||
2. Try `center_small_output=true` to preserve aspect ratio
|
||||
3. Adjust `magnify` manually (1-8) for your display size
|
||||
4. Some apps assume 64×32 - may not scale perfectly to all sizes
|
||||
@@ -349,7 +349,7 @@ Many apps require API keys for external services:
|
||||
|
||||
**Solutions**:
|
||||
1. Check render interval: **App Config → Render Interval** (300s default)
|
||||
2. Force re-render: **Plugins → Starlark Apps → {App} → Render Now**
|
||||
2. Force re-render: **Plugin Manager → Starlark Apps tab (in the second nav row) → {App} → Render Now**
|
||||
3. Clear cache: Restart LEDMatrix service
|
||||
4. API rate limits: Some services throttle requests
|
||||
5. Check app logs for API errors
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
# NBA Logo Downloader
|
||||
|
||||
This script downloads all NBA team logos from the ESPN API and saves them in the `assets/sports/nba_logos/` directory for use with the NBA leaderboard.
|
||||
This script downloads all NBA team logos from the ESPN API and saves
|
||||
them in the `assets/sports/nba_logos/` directory.
|
||||
|
||||
> **Heads up:** the NBA leaderboard and basketball scoreboards now
|
||||
> live as plugins in the
|
||||
> [`ledmatrix-plugins`](https://github.com/ChuckBuilds/ledmatrix-plugins)
|
||||
> repo (`basketball-scoreboard`, `ledmatrix-leaderboard`). Those
|
||||
> plugins download the logos they need automatically on first display.
|
||||
> This standalone script is mainly useful when you want to pre-populate
|
||||
> the assets directory ahead of time, or for development/debugging.
|
||||
|
||||
All commands below should be run from the LEDMatrix project root.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
```bash
|
||||
python download_nba_logos.py
|
||||
python3 scripts/download_nba_logos.py
|
||||
```
|
||||
|
||||
### Force Re-download
|
||||
If you want to re-download all logos (even if they already exist):
|
||||
```bash
|
||||
python download_nba_logos.py --force
|
||||
python3 scripts/download_nba_logos.py --force
|
||||
```
|
||||
|
||||
### Quiet Mode
|
||||
Reduce logging output:
|
||||
```bash
|
||||
python download_nba_logos.py --quiet
|
||||
python3 scripts/download_nba_logos.py --quiet
|
||||
```
|
||||
|
||||
### Combined Options
|
||||
```bash
|
||||
python download_nba_logos.py --force --quiet
|
||||
python3 scripts/download_nba_logos.py --force --quiet
|
||||
```
|
||||
|
||||
## What It Does
|
||||
@@ -82,12 +93,14 @@ assets/sports/nba_logos/
|
||||
└── WAS.png # Washington Wizards
|
||||
```
|
||||
|
||||
## Integration with NBA Leaderboard
|
||||
## Integration with NBA plugins
|
||||
|
||||
Once the logos are downloaded, the NBA leaderboard will:
|
||||
- ✅ Use local logos instantly (no download delays)
|
||||
- ✅ Display team logos in the scrolling leaderboard
|
||||
- ✅ Show proper team branding for all 30 NBA teams
|
||||
Once the logos are in `assets/sports/nba_logos/`, both the
|
||||
`basketball-scoreboard` and `ledmatrix-leaderboard` plugins will pick
|
||||
them up automatically and skip their own first-run download. This is
|
||||
useful if you want to deploy a Pi without internet access to ESPN, or
|
||||
if you want to preview the display on your dev machine without
|
||||
waiting for downloads.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -102,6 +115,6 @@ This is normal - some teams might have temporary API issues or the ESPN API migh
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.7+
|
||||
- `requests` library (should be installed with the project)
|
||||
- Python 3.9+ (matches the project's overall minimum)
|
||||
- `requests` library (already in `requirements.txt`)
|
||||
- Write access to `assets/sports/nba_logos/` directory
|
||||
|
||||
@@ -4,16 +4,26 @@ This directory contains scripts for installing and configuring the LEDMatrix sys
|
||||
|
||||
## Scripts
|
||||
|
||||
- **`one-shot-install.sh`** - Single-command installer; clones the
|
||||
repo, checks prerequisites, then runs `first_time_install.sh`.
|
||||
Invoked via `curl ... | bash` from the project root README.
|
||||
- **`install_service.sh`** - Installs the main LED Matrix display service (systemd)
|
||||
- **`install_web_service.sh`** - Installs the web interface service (systemd)
|
||||
- **`install_wifi_monitor.sh`** - Installs the WiFi monitor daemon service
|
||||
- **`setup_cache.sh`** - Sets up persistent cache directory with proper permissions
|
||||
- **`configure_web_sudo.sh`** - Configures passwordless sudo access for web interface actions
|
||||
- **`configure_wifi_permissions.sh`** - Grants the `ledmatrix` user
|
||||
the WiFi management permissions needed by the web interface and
|
||||
the WiFi monitor service
|
||||
- **`migrate_config.sh`** - Migrates configuration files to new formats (if needed)
|
||||
- **`debug_install.sh`** - Diagnostic helper used when an install
|
||||
fails; collects environment info and recent logs
|
||||
|
||||
## Usage
|
||||
|
||||
These scripts are typically called by `first_time_install.sh` in the project root, but can also be run individually if needed.
|
||||
These scripts are typically called by `first_time_install.sh` in the
|
||||
project root (which itself is invoked by `one-shot-install.sh`), but
|
||||
can also be run individually if needed.
|
||||
|
||||
**Note:** Most installation scripts require `sudo` privileges to install systemd services and configure system settings.
|
||||
|
||||
|
||||
@@ -71,6 +71,17 @@ General-purpose utility functions:
|
||||
- Boolean parsing
|
||||
- Logger creation (deprecated - use `src.logging_config.get_logger()`)
|
||||
|
||||
## Permission Utilities (`permission_utils.py`)
|
||||
|
||||
Helpers for ensuring directory permissions and ownership are correct
|
||||
when running as a service (used by `CacheManager` to set up its
|
||||
persistent cache directory).
|
||||
|
||||
## CLI Helpers (`cli.py`)
|
||||
|
||||
Shared CLI argument parsing helpers used by `scripts/dev/*` and other
|
||||
command-line entry points.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use centralized logging**: Import from `src.logging_config` instead of creating loggers directly
|
||||
|
||||
@@ -28,14 +28,29 @@ These service files are installed by the installation scripts in `scripts/instal
|
||||
|
||||
## Manual Installation
|
||||
|
||||
If you need to install a service manually:
|
||||
|
||||
```bash
|
||||
sudo cp systemd/ledmatrix.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable ledmatrix.service
|
||||
sudo systemctl start ledmatrix.service
|
||||
```
|
||||
> **Important:** the unit files in this directory contain
|
||||
> `__PROJECT_ROOT_DIR__` placeholders that the install scripts replace
|
||||
> with the actual project directory at install time. Do **not** copy
|
||||
> them directly to `/etc/systemd/system/` — the service will fail to
|
||||
> start with `WorkingDirectory=__PROJECT_ROOT_DIR__` errors.
|
||||
>
|
||||
> Always install via the helper script:
|
||||
>
|
||||
> ```bash
|
||||
> sudo ./scripts/install/install_service.sh
|
||||
> ```
|
||||
>
|
||||
> If you really need to do it by hand, substitute the placeholder
|
||||
> first:
|
||||
>
|
||||
> ```bash
|
||||
> PROJECT_ROOT="$(pwd)"
|
||||
> sed "s|__PROJECT_ROOT_DIR__|$PROJECT_ROOT|g" systemd/ledmatrix.service \
|
||||
> | sudo tee /etc/systemd/system/ledmatrix.service > /dev/null
|
||||
> sudo systemctl daemon-reload
|
||||
> sudo systemctl enable ledmatrix.service
|
||||
> sudo systemctl start ledmatrix.service
|
||||
> ```
|
||||
|
||||
## Service Management
|
||||
|
||||
|
||||
@@ -66,38 +66,50 @@ Once running, access the web interface at:
|
||||
|
||||
The web interface reads configuration from:
|
||||
- `config/config.json` - Main configuration
|
||||
- `config/secrets.json` - API keys and secrets
|
||||
- `config/config_secrets.json` - API keys and secrets
|
||||
|
||||
## API Documentation
|
||||
|
||||
The V3 API is available at `/api/v3/` with the following endpoints:
|
||||
The V3 API is mounted at `/api/v3/` (`app.py:144`). For the complete
|
||||
list and request/response formats, see
|
||||
[`docs/REST_API_REFERENCE.md`](../docs/REST_API_REFERENCE.md). Quick
|
||||
reference for the most common endpoints:
|
||||
|
||||
### Configuration
|
||||
- `GET /api/v3/config/main` - Get main configuration
|
||||
- `POST /api/v3/config/main` - Save main configuration
|
||||
- `GET /api/v3/config/secrets` - Get secrets configuration
|
||||
- `POST /api/v3/config/secrets` - Save secrets configuration
|
||||
- `POST /api/v3/config/raw/main` - Save raw main config (Config Editor)
|
||||
- `POST /api/v3/config/raw/secrets` - Save raw secrets
|
||||
|
||||
### Display Control
|
||||
- `POST /api/v3/display/start` - Start display service
|
||||
- `POST /api/v3/display/stop` - Stop display service
|
||||
- `POST /api/v3/display/restart` - Restart display service
|
||||
- `GET /api/v3/display/status` - Get display service status
|
||||
### Display & System Control
|
||||
- `GET /api/v3/system/status` - System status
|
||||
- `POST /api/v3/system/action` - Control display (action body:
|
||||
`start_display`, `stop_display`, `restart_display_service`,
|
||||
`restart_web_service`, `git_pull`, `reboot_system`, `shutdown_system`)
|
||||
- `GET /api/v3/display/current` - Current display frame
|
||||
- `GET /api/v3/display/on-demand/status` - On-demand status
|
||||
- `POST /api/v3/display/on-demand/start` - Trigger on-demand display
|
||||
- `POST /api/v3/display/on-demand/stop` - Clear on-demand
|
||||
|
||||
### Plugins
|
||||
- `GET /api/v3/plugins` - List installed plugins
|
||||
- `GET /api/v3/plugins/<id>` - Get plugin details
|
||||
- `POST /api/v3/plugins/<id>/config` - Update plugin configuration
|
||||
- `GET /api/v3/plugins/<id>/enable` - Enable plugin
|
||||
- `GET /api/v3/plugins/<id>/disable` - Disable plugin
|
||||
- `GET /api/v3/plugins/installed` - List installed plugins
|
||||
- `GET /api/v3/plugins/config?plugin_id=<id>` - Get plugin config
|
||||
- `POST /api/v3/plugins/config` - Update plugin configuration
|
||||
- `GET /api/v3/plugins/schema?plugin_id=<id>` - Get plugin schema
|
||||
- `POST /api/v3/plugins/toggle` - Enable/disable plugin
|
||||
- `POST /api/v3/plugins/install` - Install from registry
|
||||
- `POST /api/v3/plugins/install-from-url` - Install from GitHub URL
|
||||
- `POST /api/v3/plugins/uninstall` - Uninstall plugin
|
||||
- `POST /api/v3/plugins/update` - Update plugin
|
||||
|
||||
### Plugin Store
|
||||
- `GET /api/v3/store/plugins` - List available plugins
|
||||
- `POST /api/v3/store/install/<id>` - Install plugin
|
||||
- `POST /api/v3/store/uninstall/<id>` - Uninstall plugin
|
||||
- `POST /api/v3/store/update/<id>` - Update plugin
|
||||
- `GET /api/v3/plugins/store/list` - List available registry plugins
|
||||
- `POST /api/v3/plugins/store/refresh` - Refresh registry from GitHub
|
||||
|
||||
### Real-time Streams (SSE)
|
||||
SSE stream endpoints are defined directly on the Flask app
|
||||
(`app.py:607-615`), not on the api_v3 blueprint:
|
||||
- `GET /api/v3/stream/stats` - System statistics stream
|
||||
- `GET /api/v3/stream/display` - Display preview stream
|
||||
- `GET /api/v3/stream/logs` - Service logs stream
|
||||
|
||||
@@ -90,6 +90,48 @@ Table-based RSS feed editor with logo uploads.
|
||||
- Enable/disable individual feeds
|
||||
- Automatic row re-indexing
|
||||
|
||||
### Other Built-in Widgets
|
||||
|
||||
In addition to the three documented above, these widgets are
|
||||
registered and ready to use via `x-widget`:
|
||||
|
||||
**Inputs:**
|
||||
- `text-input` — Plain text field with optional length constraints
|
||||
- `textarea` — Multi-line text input
|
||||
- `number-input` — Numeric input with min/max validation
|
||||
- `email-input` — Email field with format validation
|
||||
- `url-input` — URL field with format validation
|
||||
- `password-input` — Password field with show/hide toggle
|
||||
|
||||
**Selectors:**
|
||||
- `select-dropdown` — Single-select dropdown for `enum` fields
|
||||
- `radio-group` — Radio buttons for `enum` fields (alternative to dropdown)
|
||||
- `toggle-switch` — Boolean toggle (alternative to a checkbox)
|
||||
- `slider` — Numeric range slider for `integer`/`number` with `min`/`max`
|
||||
- `color-picker` — RGB color picker; outputs `[r, g, b]` arrays
|
||||
- `font-selector` — Picks from fonts in `assets/fonts/` (TTF + BDF)
|
||||
- `timezone-selector` — IANA timezone picker
|
||||
|
||||
**Date / time / scheduling:**
|
||||
- `date-picker` — Single date input
|
||||
- `day-selector` — Days-of-week multi-select (Mon–Sun checkboxes)
|
||||
- `time-range` — Start/end time pair (e.g. for dim schedules)
|
||||
- `schedule-picker` — Full cron-style or weekday/time schedule editor
|
||||
|
||||
**Composite / data-source:**
|
||||
- `array-table` — Generic table editor for arrays of objects
|
||||
- `google-calendar-picker` — Picks from the user's authenticated Google
|
||||
Calendars (used by the calendar plugin)
|
||||
|
||||
**Internal (typically not used directly by plugins):**
|
||||
- `notification` — Toast notification helper
|
||||
- `base-widget` — Base class other widgets extend
|
||||
|
||||
The canonical source for each widget's exact schema and options is the
|
||||
file in this directory (e.g., `slider.js`, `color-picker.js`). If you
|
||||
need a feature one of these doesn't support, see "Creating Custom
|
||||
Widgets" below.
|
||||
|
||||
## Using Existing Widgets
|
||||
|
||||
To use an existing widget in your plugin's `config_schema.json`, simply add the `x-widget` property to your field definition:
|
||||
|
||||
Reference in New Issue
Block a user