Compare commits
359 Commits
v2.2
...
6812dfe7a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6812dfe7a6 | ||
|
|
efe6b1fe23 | ||
|
|
5ea2acd897 | ||
|
|
68a0fe1182 | ||
|
|
7afc2c0670 | ||
|
|
ee4149dc49 | ||
|
|
5ddf8b1aea | ||
|
|
35df06b8e1 | ||
|
|
77e9eba294 | ||
|
|
6eccb74415 | ||
|
|
2c2fca2219 | ||
|
|
640a4c1706 | ||
|
|
81a022dbe8 | ||
|
|
48ff624a85 | ||
|
|
31ed854d4e | ||
|
|
442638dd2c | ||
|
|
8391832c90 | ||
|
|
c8737d1a6c | ||
|
|
28a374485f | ||
|
|
fa92bfbdd8 | ||
|
|
f3e7c639ba | ||
|
|
f718305886 | ||
|
|
f0dc094cd6 | ||
|
|
178dfb0c2a | ||
|
|
76c5bf5781 | ||
|
|
feee1dffde | ||
|
|
f05c357d57 | ||
|
|
fe5c1d0d5e | ||
|
|
3e50fa5b1d | ||
|
|
8ae82321ce | ||
|
|
eb143c44fa | ||
|
|
275fed402e | ||
|
|
38a9c1ed1b | ||
|
|
23f0176c18 | ||
|
|
9465fcda6e | ||
|
|
976c10c4ac | ||
|
|
b92ff3dfbd | ||
|
|
4c4efd614a | ||
|
|
14b6a0c6a3 | ||
|
|
c2763d6447 | ||
|
|
1f0de9b354 | ||
|
|
ed90654bf2 | ||
|
|
302235a357 | ||
|
|
636d0e181c | ||
|
|
963c4d3b91 | ||
|
|
22c495ea7c | ||
|
|
5b0ad5ab71 | ||
|
|
bc8568604a | ||
|
|
878f339fb3 | ||
|
|
51616f1bc4 | ||
|
|
82370a0253 | ||
|
|
3975940cff | ||
|
|
158e07c82b | ||
|
|
9a72adbde1 | ||
|
|
9d3bc55c18 | ||
|
|
df3cf9bb56 | ||
|
|
448a15c1e6 | ||
|
|
b99be88cec | ||
|
|
4a9fc2df3a | ||
|
|
d207e7c6dd | ||
|
|
7e98fa9bd8 | ||
|
|
0d5510d8f7 | ||
|
|
18fecd3cda | ||
|
|
1c3269c0f3 | ||
|
|
ea61331d46 | ||
|
|
8fb2800495 | ||
|
|
8912501604 | ||
|
|
68c4259370 | ||
|
|
7f5c7399fb | ||
|
|
14c50f316e | ||
|
|
ddd300a117 | ||
|
|
7524747e44 | ||
|
|
10d70d911a | ||
|
|
a8c85dd015 | ||
|
|
0203c5c1b5 | ||
|
|
384ed096ff | ||
|
|
f9de9fa29e | ||
|
|
d0ad2031c8 | ||
|
|
1833e30c1d | ||
|
|
2381ead03f | ||
|
|
bc23b7c75c | ||
|
|
bff16d3e00 | ||
|
|
23ada60544 | ||
|
|
fadcf0f407 | ||
|
|
71584d4361 | ||
|
|
3b8910ac09 | ||
|
|
94d5a38358 | ||
|
|
4a63ff87cb | ||
|
|
fdf09fabd2 | ||
|
|
75a8219a29 | ||
|
|
a9798e1a7f | ||
|
|
c35769cefb | ||
|
|
f1f33989b2 | ||
|
|
f9e21c6033 | ||
|
|
0f4dbb6c1a | ||
|
|
b9f839af3d | ||
|
|
f438f9dfe3 | ||
|
|
7f230f625d | ||
|
|
3fa032f7f6 | ||
|
|
5f4839b4f6 | ||
|
|
20d58754b8 | ||
|
|
f7d72f88b5 | ||
|
|
a13bd971b3 | ||
|
|
9f1711f9a3 | ||
|
|
67197635c9 | ||
|
|
a5c10d6f78 | ||
|
|
cca495f306 | ||
|
|
24c34c5a40 | ||
|
|
1815a5b791 | ||
|
|
947a0fbe8f | ||
|
|
00a1b9dc90 | ||
|
|
f22910207b | ||
|
|
97a301a1a9 | ||
|
|
f7e8266e64 | ||
|
|
7ec4323ff4 | ||
|
|
d14b5ffb8f | ||
|
|
33e4f3680c | ||
|
|
b0d65581df | ||
|
|
f412350110 | ||
|
|
f8a2f687ba | ||
|
|
4be4caae08 | ||
|
|
e4976b65c6 | ||
|
|
7d71656cf1 | ||
|
|
711482d59a | ||
|
|
694c7cec10 | ||
|
|
bb76f01c05 | ||
|
|
3382cabe25 | ||
|
|
c06893f3e0 | ||
|
|
5c97b4baae | ||
|
|
cd0c43fb3a | ||
|
|
0a312cc912 | ||
|
|
0dc44c94b3 | ||
|
|
ce79ccaec2 | ||
|
|
9c313cdac3 | ||
|
|
ed22f5cd01 | ||
|
|
98d3ed7d91 | ||
|
|
584a4f258e | ||
|
|
7a61ecff7b | ||
|
|
58bbb5fe6f | ||
|
|
ee650820a9 | ||
|
|
fb38a5a814 | ||
|
|
3406234e00 | ||
|
|
9ba76b56d7 | ||
|
|
f3d02e07ea | ||
|
|
a115a1ed6b | ||
|
|
d83ff6a121 | ||
|
|
f6e2881f2f | ||
|
|
6c493e8329 | ||
|
|
e749925c0a | ||
|
|
f0b61e3bd9 | ||
|
|
83f76dbdce | ||
|
|
4adb7b49ba | ||
|
|
2d6c238ea0 | ||
|
|
e584026bda | ||
|
|
1b52928e1a | ||
|
|
38c1bff9dc | ||
|
|
08af182380 | ||
|
|
1ee805901f | ||
|
|
a98760f4d9 | ||
|
|
fc06493990 | ||
|
|
1bc4e531ae | ||
|
|
abceb8205c | ||
|
|
5bfcdaf4ff | ||
|
|
ad8a652183 | ||
|
|
76a9e98ba7 | ||
|
|
42e14f99b0 | ||
|
|
b1295047e2 | ||
|
|
7c18b5126e | ||
|
|
b792401e07 | ||
|
|
80a5e491ed | ||
|
|
635fd4de73 | ||
|
|
a08951ab56 | ||
|
|
1a746e965e | ||
|
|
8cbdef3949 | ||
|
|
569576d4e8 | ||
|
|
b0b5af8e21 | ||
|
|
cf15409973 | ||
|
|
1f2c1bfe0b | ||
|
|
8cad607d98 | ||
|
|
1dcda5613a | ||
|
|
31ec0018be | ||
|
|
86049ca679 | ||
|
|
9dc1118d79 | ||
|
|
6fcc93cab8 | ||
|
|
379e2c3714 | ||
|
|
3cbb32fec6 | ||
|
|
29ead0f7d9 | ||
|
|
7022a5c572 | ||
|
|
7ba7d5de13 | ||
|
|
007006feb2 | ||
|
|
b4b21604da | ||
|
|
688b09b95e | ||
|
|
e894c40ff4 | ||
|
|
a23a749c59 | ||
|
|
2d8f1b60a3 | ||
|
|
8c03e65104 | ||
|
|
764d80e818 | ||
|
|
854c236a60 | ||
|
|
4b1b343a8f | ||
|
|
65f04bff63 | ||
|
|
8055856137 | ||
|
|
17a79976dd | ||
|
|
38062d0bee | ||
|
|
2ce252059e | ||
|
|
c7ee946871 | ||
|
|
3afcbb759c | ||
|
|
bc18202736 | ||
|
|
a0973a2ad8 | ||
|
|
c18ab3f91f | ||
|
|
9718595017 | ||
|
|
5f803f346b | ||
|
|
0320830725 | ||
|
|
9dd744254a | ||
|
|
67b6a6fd68 | ||
|
|
ca62fd714f | ||
|
|
49346f9a6d | ||
|
|
9200c9cab3 | ||
|
|
dbdb730b4d | ||
|
|
91211d5c86 | ||
|
|
d78c592d6a | ||
|
|
4771ec8b3b | ||
|
|
60f68ff2a3 | ||
|
|
c7634cbf3a | ||
|
|
96cd383436 | ||
|
|
e39dd1e0a3 | ||
|
|
7b1339631c | ||
|
|
0579b3b860 | ||
|
|
e7e76eea4c | ||
|
|
3f431a54d4 | ||
|
|
d0f8785936 | ||
|
|
7618eafaa6 | ||
|
|
f8f4539015 | ||
|
|
0ab978d543 | ||
|
|
c4a51d0f80 | ||
|
|
b20c3880b2 | ||
|
|
652461a819 | ||
|
|
691d39675d | ||
|
|
9bc0cd5629 | ||
|
|
625a501da5 | ||
|
|
28c2dcd2f7 | ||
|
|
c55511c099 | ||
|
|
b96f1e3957 | ||
|
|
fcbc67464d | ||
|
|
4b36937a55 | ||
|
|
8ead8ad893 | ||
|
|
515ae2c7e9 | ||
|
|
a5a9398c5c | ||
|
|
b82e904cb1 | ||
|
|
fbff65fbad | ||
|
|
19edd9ace0 | ||
|
|
0982ef78dd | ||
|
|
5695d8e017 | ||
|
|
06ad446925 | ||
|
|
6b5a9cdff7 | ||
|
|
efb66118e4 | ||
|
|
2444aa2fc9 | ||
|
|
93f6173efa | ||
|
|
dc81d48ab1 | ||
|
|
5c32be929e | ||
|
|
105f60f57e | ||
|
|
a52130cedb | ||
|
|
d08e7953f2 | ||
|
|
d7544b04dd | ||
|
|
0291540df4 | ||
|
|
27b9b0267b | ||
|
|
cffef0d161 | ||
|
|
b5cb71b68d | ||
|
|
7685586508 | ||
|
|
b8aaa56b4b | ||
|
|
4aa307c8dd | ||
|
|
12b99024a4 | ||
|
|
22f0e29315 | ||
|
|
6eeba92350 | ||
|
|
286ba2b044 | ||
|
|
4b5a1e41d8 | ||
|
|
008705b75c | ||
|
|
5937f968ef | ||
|
|
4fe5547bf8 | ||
|
|
96f6749516 | ||
|
|
bc3883df14 | ||
|
|
32b1b8020a | ||
|
|
9280295ed3 | ||
|
|
e5a29d4668 | ||
|
|
4cfaa5ca59 | ||
|
|
6f894a587b | ||
|
|
35eb9cbdb5 | ||
|
|
2d41c5ca31 | ||
|
|
14f7a8b502 | ||
|
|
5101795cbf | ||
|
|
6d0632acee | ||
|
|
9298eff554 | ||
|
|
335ab8cce0 | ||
|
|
32daced427 | ||
|
|
153edcc2e1 | ||
|
|
548bc00e00 | ||
|
|
f8ab022da9 | ||
|
|
cd80745dcb | ||
|
|
56dfbda40a | ||
|
|
591555c3c7 | ||
|
|
bbabad3135 | ||
|
|
3329822a46 | ||
|
|
413a1fa38c | ||
|
|
3ee7821353 | ||
|
|
20a816f3e6 | ||
|
|
457f9f9eb5 | ||
|
|
39519dbfff | ||
|
|
61a56560f3 | ||
|
|
c1ccd6f44a | ||
|
|
2123f78dad | ||
|
|
a1914980c8 | ||
|
|
a3dbc6a4a8 | ||
|
|
8a0fdb005d | ||
|
|
f13ad238eb | ||
|
|
92071237c1 | ||
|
|
52c2d61dcf | ||
|
|
73036c33cb | ||
|
|
d6eb7a778c | ||
|
|
62b50cc06f | ||
|
|
05bee6ce84 | ||
|
|
2c2d24c0a8 | ||
|
|
b252229e03 | ||
|
|
8f7aeee546 | ||
|
|
b7fa2f1df6 | ||
|
|
ba3b79dd72 | ||
|
|
e4e058ff97 | ||
|
|
0b42cec902 | ||
|
|
35ad842ba1 | ||
|
|
08cf4152f7 | ||
|
|
71a392737e | ||
|
|
2b93eafcdf | ||
|
|
3c1706d4e8 | ||
|
|
e4b3adb867 | ||
|
|
9f00124fad | ||
|
|
68416d0293 | ||
|
|
e63198dc49 | ||
|
|
a5ce721733 | ||
|
|
e3b65588a2 | ||
|
|
f13e9306c9 | ||
|
|
ef82610a06 | ||
|
|
18145edbf1 | ||
|
|
54635fee3c | ||
|
|
6152969340 | ||
|
|
8770e5a327 | ||
|
|
822d9909ed | ||
|
|
d179700c6c | ||
|
|
dfecc6f8a0 | ||
|
|
0d8d4084a9 | ||
|
|
6bc1039ed6 | ||
|
|
8e1b04550b | ||
|
|
33e1f05f77 | ||
|
|
c4113367f7 | ||
|
|
4b906b3a92 | ||
|
|
06d8360922 | ||
|
|
9d4082665a | ||
|
|
a3481f3674 | ||
|
|
e36d92340e | ||
|
|
6225189b3c | ||
|
|
6c658c23c4 | ||
|
|
30cf8ee2e8 |
145
.cursor/README.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Cursor Helper Files for LEDMatrix Plugin Development
|
||||||
|
|
||||||
|
This directory contains Cursor-specific helper files to assist with plugin development in the LEDMatrix project.
|
||||||
|
|
||||||
|
## Files Overview
|
||||||
|
|
||||||
|
### `.cursorrules`
|
||||||
|
Comprehensive rules file that Cursor uses to understand plugin development patterns, best practices, and workflows. This file is automatically loaded by Cursor and helps guide AI-assisted development.
|
||||||
|
|
||||||
|
### `plugins_guide.md`
|
||||||
|
Detailed guide covering:
|
||||||
|
- Plugin system overview
|
||||||
|
- Creating new plugins
|
||||||
|
- Running plugins (emulator and hardware)
|
||||||
|
- Loading and configuring plugins
|
||||||
|
- Development workflow
|
||||||
|
- Testing strategies
|
||||||
|
- Troubleshooting
|
||||||
|
|
||||||
|
### `plugin_templates/`
|
||||||
|
Template files for quick plugin creation:
|
||||||
|
- `manifest.json.template` - Plugin metadata template
|
||||||
|
- `manager.py.template` - Plugin class template
|
||||||
|
- `config_schema.json.template` - Configuration schema template
|
||||||
|
- `README.md.template` - Plugin documentation template
|
||||||
|
- `requirements.txt.template` - Dependencies template
|
||||||
|
- `QUICK_START.md` - Quick start guide for using templates
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Creating a New Plugin
|
||||||
|
|
||||||
|
1. **Using templates** (recommended):
|
||||||
|
```bash
|
||||||
|
# See QUICK_START.md in plugin_templates/
|
||||||
|
cd plugins
|
||||||
|
mkdir my-plugin
|
||||||
|
cd my-plugin
|
||||||
|
cp ../../.cursor/plugin_templates/*.template .
|
||||||
|
# Edit files, replacing PLUGIN_ID and other placeholders
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Using dev_plugin_setup.sh**:
|
||||||
|
```bash
|
||||||
|
# Link from GitHub
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github my-plugin
|
||||||
|
|
||||||
|
# Link local repo
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link my-plugin /path/to/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Display
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Emulator mode (development, no hardware required)
|
||||||
|
python3 run.py --emulator
|
||||||
|
# (equivalent: EMULATOR=true python3 run.py)
|
||||||
|
|
||||||
|
# Hardware (production, requires the rpi-rgb-led-matrix submodule built)
|
||||||
|
python3 run.py
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-e`/`--emulator` CLI flag is defined in `run.py:19-20` and
|
||||||
|
sets `os.environ["EMULATOR"] = "true"` before any display imports,
|
||||||
|
which `src/display_manager.py:2` then reads to switch between the
|
||||||
|
hardware and emulator backends.
|
||||||
|
|
||||||
|
### Managing Plugins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List plugins
|
||||||
|
./scripts/dev/dev_plugin_setup.sh list
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
./scripts/dev/dev_plugin_setup.sh status
|
||||||
|
|
||||||
|
# Update plugin(s)
|
||||||
|
./scripts/dev/dev_plugin_setup.sh update [plugin-name]
|
||||||
|
|
||||||
|
# Unlink plugin
|
||||||
|
./scripts/dev/dev_plugin_setup.sh unlink <plugin-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using These Files with Cursor
|
||||||
|
|
||||||
|
### `.cursorrules`
|
||||||
|
Cursor automatically reads this file to understand:
|
||||||
|
- Plugin structure and requirements
|
||||||
|
- Development workflows
|
||||||
|
- Best practices
|
||||||
|
- Common patterns
|
||||||
|
- API reference
|
||||||
|
|
||||||
|
When asking Cursor to help with plugins, it will use this context to provide better assistance.
|
||||||
|
|
||||||
|
### Plugin Templates
|
||||||
|
Use templates when creating new plugins:
|
||||||
|
1. Copy templates from `.cursor/plugin_templates/`
|
||||||
|
2. Replace placeholders (PLUGIN_ID, PluginClassName, etc.)
|
||||||
|
3. Customize for your plugin's needs
|
||||||
|
4. Follow the guide in `plugins_guide.md`
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
Refer to `plugins_guide.md` for:
|
||||||
|
- Detailed explanations
|
||||||
|
- Troubleshooting steps
|
||||||
|
- Best practices
|
||||||
|
- Examples and patterns
|
||||||
|
|
||||||
|
## Plugin Development Workflow
|
||||||
|
|
||||||
|
1. **Plan**: Determine plugin functionality and requirements
|
||||||
|
2. **Create**: Use templates or dev_plugin_setup.sh to create plugin structure
|
||||||
|
3. **Develop**: Implement plugin logic following BasePlugin interface
|
||||||
|
4. **Test**: Test with emulator first, then on hardware
|
||||||
|
5. **Configure**: Add plugin config to config/config.json
|
||||||
|
6. **Iterate**: Refine based on testing and feedback
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- **Plugin System**: `src/plugin_system/`
|
||||||
|
- **Base Plugin**: `src/plugin_system/base_plugin.py`
|
||||||
|
- **Plugin Manager**: `src/plugin_system/plugin_manager.py`
|
||||||
|
- **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**: `scripts/dev/dev_plugin_setup.sh`
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
1. Check `plugins_guide.md` for detailed documentation
|
||||||
|
2. Review `.cursorrules` for development patterns
|
||||||
|
3. Look at existing plugins for examples
|
||||||
|
4. Check logs for error messages
|
||||||
|
5. Review plugin system code in `src/plugin_system/`
|
||||||
|
|
||||||
202
.cursor/plans/implement_audit_fixes_plan.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# Implementation Plan: Fix Config Schema Validation Issues
|
||||||
|
|
||||||
|
Based on audit results showing 186 issues across 20 plugins.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Three priority fixes identified from audit:
|
||||||
|
1. **Priority 1 (HIGH)**: Remove core properties from required array - will fix ~150 issues
|
||||||
|
2. **Priority 2 (MEDIUM)**: Verify default merging logic - will fix remaining required field issues
|
||||||
|
3. **Priority 3 (LOW)**: Calendar plugin schema cleanup - will fix 3 extra field warnings
|
||||||
|
|
||||||
|
## Priority 1: Remove Core Properties from Required Array
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Core properties (`enabled`, `display_duration`, `live_priority`) are system-managed but listed in schema `required` arrays. SchemaManager injects them into properties but doesn't remove them from `required`, causing validation failures.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
**File**: `src/plugin_system/schema_manager.py`
|
||||||
|
**Location**: `validate_config_against_schema()` method, after line 295
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. **Add code to remove core properties from required array**:
|
||||||
|
```python
|
||||||
|
# After injecting core properties (around line 295), add:
|
||||||
|
# Remove core properties from required array (they're system-managed)
|
||||||
|
if "required" in enhanced_schema:
|
||||||
|
core_prop_names = list(core_properties.keys())
|
||||||
|
enhanced_schema["required"] = [
|
||||||
|
field for field in enhanced_schema["required"]
|
||||||
|
if field not in core_prop_names
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add logging for debugging** (optional but helpful):
|
||||||
|
```python
|
||||||
|
if "required" in enhanced_schema and core_prop_names:
|
||||||
|
removed_from_required = [
|
||||||
|
field for field in enhanced_schema.get("required", [])
|
||||||
|
if field in core_prop_names
|
||||||
|
]
|
||||||
|
if removed_from_required and plugin_id:
|
||||||
|
self.logger.debug(
|
||||||
|
f"Removed core properties from required array for {plugin_id}: {removed_from_required}"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test the fix**:
|
||||||
|
- Run audit script: `python scripts/audit_plugin_configs.py`
|
||||||
|
- Expected: Issue count drops from 186 to ~30-40
|
||||||
|
- All "enabled" related errors should be eliminated
|
||||||
|
|
||||||
|
### Expected Outcome
|
||||||
|
- All 20 plugins should no longer fail validation due to missing `enabled` field
|
||||||
|
- ~150 issues resolved (all enabled-related validation errors)
|
||||||
|
|
||||||
|
## Priority 2: Verify Default Merging Logic
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Some plugins have required fields with defaults that should be applied before validation. Need to verify the default merging happens correctly and handles nested objects.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
**File**: `web_interface/blueprints/api_v3.py`
|
||||||
|
**Location**: `save_plugin_config()` method, around lines 3218-3221
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. **Review current default merging logic**:
|
||||||
|
- Check that `merge_with_defaults()` is called before validation (line 3220)
|
||||||
|
- Verify it's called after preserving enabled state but before validation
|
||||||
|
|
||||||
|
2. **Verify merge_with_defaults handles nested objects**:
|
||||||
|
- Check `src/plugin_system/schema_manager.py` → `merge_with_defaults()` method
|
||||||
|
- Ensure it recursively merges nested objects (it does use deep_merge)
|
||||||
|
- Test with plugins that have nested required fields
|
||||||
|
|
||||||
|
3. **Check if defaults are applied for nested required fields**:
|
||||||
|
- Review how `generate_default_config()` extracts defaults from nested schemas
|
||||||
|
- Verify nested required fields with defaults are included
|
||||||
|
|
||||||
|
4. **Test with problematic plugins**:
|
||||||
|
- `ledmatrix-weather`: required fields `api_key`, `location_city` (check if defaults exist)
|
||||||
|
- `mqtt-notifications`: required field `mqtt` object (check if default exists)
|
||||||
|
- `text-display`: required field `text` (check if default exists)
|
||||||
|
- `ledmatrix-music`: required field `preferred_source` (check if default exists)
|
||||||
|
|
||||||
|
5. **If defaults don't exist in schemas**:
|
||||||
|
- Either add defaults to schemas, OR
|
||||||
|
- Make fields optional in schemas if they're truly optional
|
||||||
|
|
||||||
|
### Expected Outcome
|
||||||
|
- Plugins with required fields that have schema defaults should pass validation
|
||||||
|
- Issue count further reduced from ~30-40 to ~5-10
|
||||||
|
|
||||||
|
## Priority 3: Calendar Plugin Schema Cleanup
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Calendar plugin config has fields not in schema:
|
||||||
|
- `show_all_day` (config) but schema has `show_all_day_events` (field name mismatch)
|
||||||
|
- `date_format` (not in schema, not used in manager.py)
|
||||||
|
- `time_format` (not in schema, not used in manager.py)
|
||||||
|
|
||||||
|
### Investigation Results
|
||||||
|
- Schema defines: `show_all_day_events` (boolean, default: true)
|
||||||
|
- Manager.py uses: `show_all_day_events` (line 82: `config.get('show_all_day_events', True)`)
|
||||||
|
- Config has: `show_all_day` (wrong field name - should be `show_all_day_events`)
|
||||||
|
- `date_format` and `time_format` appear to be deprecated (not used in manager.py)
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
|
||||||
|
**File**: `config/config.json` → `calendar` section
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. **Fix field name mismatch**:
|
||||||
|
- Rename `show_all_day` → `show_all_day_events` in config.json
|
||||||
|
- This matches the schema and manager.py code
|
||||||
|
|
||||||
|
2. **Remove deprecated fields**:
|
||||||
|
- Remove `date_format` from config (not used in code)
|
||||||
|
- Remove `time_format` from config (not used in code)
|
||||||
|
|
||||||
|
3. **Alternative (if fields are needed)**: Add `date_format` and `time_format` to schema
|
||||||
|
- Only if these fields should be supported
|
||||||
|
- Check if they're used anywhere else in the codebase
|
||||||
|
|
||||||
|
4. **Test calendar plugin**:
|
||||||
|
- Run audit for calendar plugin specifically
|
||||||
|
- Verify no extra field warnings remain
|
||||||
|
- Test calendar plugin functionality to ensure it still works
|
||||||
|
|
||||||
|
### Expected Outcome
|
||||||
|
- Calendar plugin shows 0 extra field warnings
|
||||||
|
- Final issue count: ~3-5 (only edge cases remain)
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### After Each Priority Fix
|
||||||
|
|
||||||
|
1. **Run local audit**:
|
||||||
|
```bash
|
||||||
|
python scripts/audit_plugin_configs.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check issue count reduction**:
|
||||||
|
- Priority 1: Should drop from 186 to ~30-40
|
||||||
|
- Priority 2: Should drop from ~30-40 to ~5-10
|
||||||
|
- Priority 3: Should drop from ~5-10 to ~3-5
|
||||||
|
|
||||||
|
3. **Review specific plugin results**:
|
||||||
|
```bash
|
||||||
|
python scripts/audit_plugin_configs.py --plugin <plugin-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
### After All Fixes
|
||||||
|
|
||||||
|
1. **Full audit run**:
|
||||||
|
```bash
|
||||||
|
python scripts/audit_plugin_configs.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy to Pi**:
|
||||||
|
```bash
|
||||||
|
./scripts/deploy_to_pi.sh src/plugin_system/schema_manager.py web_interface/blueprints/api_v3.py
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run audit on Pi**:
|
||||||
|
```bash
|
||||||
|
./scripts/run_audit_on_pi.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Manual web interface testing**:
|
||||||
|
- Access each problematic plugin's config page
|
||||||
|
- Try saving configuration
|
||||||
|
- Verify no validation errors appear
|
||||||
|
- Check that configs save successfully
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Priority 1: All "enabled" related validation errors eliminated
|
||||||
|
- [ ] Priority 1: Issue count reduced from 186 to ~30-40
|
||||||
|
- [ ] Priority 2: Plugins with required fields + defaults pass validation
|
||||||
|
- [ ] Priority 2: Issue count reduced to ~5-10
|
||||||
|
- [ ] Priority 3: Calendar plugin extra field warnings resolved
|
||||||
|
- [ ] Priority 3: Final issue count at ~3-5 (only edge cases)
|
||||||
|
- [ ] All fixes work on Pi (not just local)
|
||||||
|
- [ ] Web interface saves configs without validation errors
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
1. `src/plugin_system/schema_manager.py` - Remove core properties from required array
|
||||||
|
2. `plugins/calendar/config_schema.json` OR `config/config.json` - Calendar cleanup (if needed)
|
||||||
|
3. `web_interface/blueprints/api_v3.py` - May need minor adjustments for default merging (if needed)
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
**Priority 1**: Low risk - Only affects validation logic, doesn't change behavior
|
||||||
|
**Priority 2**: Low risk - Only ensures defaults are applied (already intended behavior)
|
||||||
|
**Priority 3**: Very low risk - Only affects calendar plugin, cosmetic issue
|
||||||
|
|
||||||
|
All changes are backward compatible and improve the system rather than changing core functionality.
|
||||||
|
|
||||||
247
.cursor/plugin_templates/QUICK_START.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# Quick Start: Creating a New Plugin
|
||||||
|
|
||||||
|
This guide will help you create a new plugin using the templates in `.cursor/plugin_templates/`.
|
||||||
|
|
||||||
|
## Step 1: Create Plugin Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/LEDMatrix
|
||||||
|
mkdir -p plugins/my-plugin
|
||||||
|
cd plugins/my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Copy Templates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy all template files
|
||||||
|
cp ../../.cursor/plugin_templates/manifest.json.template ./manifest.json
|
||||||
|
cp ../../.cursor/plugin_templates/manager.py.template ./manager.py
|
||||||
|
cp ../../.cursor/plugin_templates/config_schema.json.template ./config_schema.json
|
||||||
|
cp ../../.cursor/plugin_templates/README.md.template ./README.md
|
||||||
|
cp ../../.cursor/plugin_templates/requirements.txt.template ./requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Customize Files
|
||||||
|
|
||||||
|
### manifest.json
|
||||||
|
|
||||||
|
Replace placeholders:
|
||||||
|
- `PLUGIN_ID` → `my-plugin` (lowercase, use hyphens)
|
||||||
|
- `Plugin Name` → Your plugin's display name
|
||||||
|
- `PluginClassName` → `MyPlugin` (PascalCase)
|
||||||
|
- Update description, author, homepage, etc.
|
||||||
|
|
||||||
|
### manager.py
|
||||||
|
|
||||||
|
Replace placeholders:
|
||||||
|
- `PluginClassName` → `MyPlugin` (must match manifest)
|
||||||
|
- Implement `_fetch_data()` method
|
||||||
|
- Implement `_render_content()` method
|
||||||
|
- Add any custom validation in `validate_config()`
|
||||||
|
|
||||||
|
### config_schema.json
|
||||||
|
|
||||||
|
Customize:
|
||||||
|
- Update description
|
||||||
|
- Add/remove configuration properties
|
||||||
|
- Set default values
|
||||||
|
- Add validation rules
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
|
||||||
|
Replace placeholders:
|
||||||
|
- `PLUGIN_ID` → `my-plugin`
|
||||||
|
- `Plugin Name` → Your plugin's name
|
||||||
|
- Fill in features, installation, configuration sections
|
||||||
|
|
||||||
|
### requirements.txt
|
||||||
|
|
||||||
|
Add your plugin's dependencies:
|
||||||
|
```txt
|
||||||
|
requests>=2.28.0
|
||||||
|
pillow>=9.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Enable Plugin
|
||||||
|
|
||||||
|
Edit `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"my-plugin": {
|
||||||
|
"enabled": true,
|
||||||
|
"display_duration": 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Test Plugin
|
||||||
|
|
||||||
|
### Test with Emulator
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/LEDMatrix
|
||||||
|
python run.py --emulator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Plugin Loading
|
||||||
|
|
||||||
|
Look for logs like:
|
||||||
|
```
|
||||||
|
[INFO] Discovered 1 plugin(s)
|
||||||
|
[INFO] Loaded plugin: my-plugin v1.0.0
|
||||||
|
[INFO] Added plugin mode: my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Plugin Display
|
||||||
|
|
||||||
|
The plugin should appear in the display rotation. Check logs for any errors.
|
||||||
|
|
||||||
|
## Step 6: Develop and Iterate
|
||||||
|
|
||||||
|
1. Edit `manager.py` to implement your plugin logic
|
||||||
|
2. Test with emulator: `python run.py --emulator`
|
||||||
|
3. Check logs for errors
|
||||||
|
4. Iterate until working correctly
|
||||||
|
|
||||||
|
## Step 7: Test on Hardware (Optional)
|
||||||
|
|
||||||
|
When ready, test on Raspberry Pi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy to Pi
|
||||||
|
rsync -avz plugins/my-plugin/ pi@raspberrypi:/path/to/LEDMatrix/plugins/my-plugin/
|
||||||
|
|
||||||
|
# Or if using git
|
||||||
|
ssh pi@raspberrypi "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
ssh pi@raspberrypi "sudo systemctl restart ledmatrix"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Customizations
|
||||||
|
|
||||||
|
### Adding API Integration
|
||||||
|
|
||||||
|
1. Add API key to `config_schema.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"api_key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "API key for service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Implement API call in `_fetch_data()`:
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def _fetch_data(self):
|
||||||
|
response = requests.get(
|
||||||
|
"https://api.example.com/data",
|
||||||
|
headers={"Authorization": f"Bearer {self.api_key}"}
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Store API key in `config/config_secrets.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"my-plugin": {
|
||||||
|
"api_key": "your-secret-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 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(
|
||||||
|
"Text",
|
||||||
|
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:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"my-plugin": {
|
||||||
|
"live_priority": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Implement `has_live_content()`:
|
||||||
|
```python
|
||||||
|
def has_live_content(self) -> bool:
|
||||||
|
return self.data and self.data.get("is_live", False)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Override `get_live_modes()` if needed:
|
||||||
|
```python
|
||||||
|
def get_live_modes(self) -> list:
|
||||||
|
return ["my_plugin_live_mode"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Plugin Not Loading
|
||||||
|
|
||||||
|
- Check `manifest.json` syntax (must be valid JSON)
|
||||||
|
- Verify `entry_point` file exists
|
||||||
|
- Ensure `class_name` matches class name in manager.py
|
||||||
|
- Check for import errors in logs
|
||||||
|
|
||||||
|
### Configuration Errors
|
||||||
|
|
||||||
|
- Validate config against `config_schema.json`
|
||||||
|
- Check required fields are present
|
||||||
|
- Verify data types match schema
|
||||||
|
|
||||||
|
### Display Issues
|
||||||
|
|
||||||
|
- Check display dimensions: `display_manager.width`, `display_manager.height`
|
||||||
|
- Verify coordinates are within bounds
|
||||||
|
- Ensure `update_display()` is called
|
||||||
|
- Test with emulator first
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Review existing plugins for patterns:
|
||||||
|
- `plugins/hockey-scoreboard/` - Sports scoreboard example
|
||||||
|
- `plugins/ledmatrix-music/` - Real-time data example
|
||||||
|
- `plugins/ledmatrix-stocks/` - Data display example
|
||||||
|
|
||||||
|
- Read full documentation:
|
||||||
|
- `.cursor/plugins_guide.md` - Comprehensive guide
|
||||||
|
- `docs/PLUGIN_ARCHITECTURE_SPEC.md` - Architecture details
|
||||||
|
- `.cursorrules` - Development rules
|
||||||
|
|
||||||
|
- Check plugin system code:
|
||||||
|
- `src/plugin_system/base_plugin.py` - Base class
|
||||||
|
- `src/plugin_system/plugin_manager.py` - Plugin manager
|
||||||
|
|
||||||
156
.cursor/plugin_templates/README.md.template
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# Plugin Name
|
||||||
|
|
||||||
|
Brief description of what this plugin does.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Feature 1
|
||||||
|
- Feature 2
|
||||||
|
- Feature 3
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Link the plugin to your LEDMatrix installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/LEDMatrix
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github PLUGIN_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for local development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link PLUGIN_ID /path/to/plugin/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r plugins/PLUGIN_ID/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Configure the plugin in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"PLUGIN_ID": {
|
||||||
|
"enabled": true,
|
||||||
|
"display_duration": 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** API keys and other sensitive credentials must be stored in `config/config_secrets.json`, not in `config/config.json`.
|
||||||
|
|
||||||
|
4. Store API keys in `config/config_secrets.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"PLUGIN_ID": {
|
||||||
|
"api_key": "your-secret-api-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Required Settings
|
||||||
|
|
||||||
|
- `enabled` (boolean): Enable or disable the plugin
|
||||||
|
- `api_key` (string): API key for external service (if required)
|
||||||
|
|
||||||
|
### Optional Settings
|
||||||
|
|
||||||
|
- `display_duration` (number): How long to display this plugin (default: 15 seconds)
|
||||||
|
- `refresh_interval` (integer): How often to refresh data in seconds (default: 60)
|
||||||
|
- `live_priority` (boolean): Enable live priority takeover (default: false)
|
||||||
|
|
||||||
|
## Display Modes
|
||||||
|
|
||||||
|
This plugin provides the following display modes:
|
||||||
|
|
||||||
|
- `PLUGIN_ID`: Main display mode
|
||||||
|
|
||||||
|
## API Requirements
|
||||||
|
|
||||||
|
This plugin requires:
|
||||||
|
|
||||||
|
- **API Name**: Description of API requirements
|
||||||
|
- URL: https://api.example.com
|
||||||
|
- Rate Limit: X requests per minute
|
||||||
|
- Authentication: API key required
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd plugins/PLUGIN_ID
|
||||||
|
python test_PLUGIN_ID.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with Emulator
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/LEDMatrix
|
||||||
|
python run.py --emulator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
Enable debug logging in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"level": "DEBUG"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Check logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On Raspberry Pi (if running as service)
|
||||||
|
journalctl -u ledmatrix -f
|
||||||
|
|
||||||
|
# Direct execution
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Plugin Not Loading
|
||||||
|
|
||||||
|
1. Check that `manifest.json` exists and is valid
|
||||||
|
2. Verify `entry_point` file exists
|
||||||
|
3. Check that `class_name` matches the class in manager.py
|
||||||
|
4. Review logs for import errors
|
||||||
|
|
||||||
|
### Configuration Errors
|
||||||
|
|
||||||
|
1. Validate config against `config_schema.json`
|
||||||
|
2. Check required fields are present
|
||||||
|
3. Verify data types match schema
|
||||||
|
|
||||||
|
### API Errors
|
||||||
|
|
||||||
|
1. Verify API key is correct
|
||||||
|
2. Check API rate limits
|
||||||
|
3. Review network connectivity
|
||||||
|
4. Check API service status
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[License information]
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Your Name
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- GitHub: https://github.com/username/ledmatrix-PLUGIN_ID
|
||||||
|
- Documentation: [Link to docs]
|
||||||
|
- Issues: https://github.com/username/ledmatrix-PLUGIN_ID/issues
|
||||||
|
|
||||||
44
.cursor/plugin_templates/config_schema.json.template
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "Plugin Configuration Schema",
|
||||||
|
"description": "Configuration schema for Plugin Name",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable or disable this plugin"
|
||||||
|
},
|
||||||
|
"display_duration": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 15,
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 300,
|
||||||
|
"description": "How long to display this plugin in seconds"
|
||||||
|
},
|
||||||
|
"live_priority": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable live priority takeover when plugin has live content"
|
||||||
|
},
|
||||||
|
"refresh_interval": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 60,
|
||||||
|
"minimum": 1,
|
||||||
|
"description": "How often to refresh data in seconds"
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "API key for external service (store in config_secrets.json)",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"custom_setting": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Example custom setting - replace with your plugin's settings",
|
||||||
|
"default": "default_value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["enabled"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
|
||||||
226
.cursor/plugin_templates/manager.py.template
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
"""
|
||||||
|
Plugin Name
|
||||||
|
|
||||||
|
Brief description of what this plugin does.
|
||||||
|
|
||||||
|
API Version: 1.0.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.plugin_system.base_plugin import BasePlugin
|
||||||
|
from PIL import Image
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class PluginClassName(BasePlugin):
|
||||||
|
"""
|
||||||
|
Plugin class that inherits from BasePlugin.
|
||||||
|
|
||||||
|
This plugin demonstrates the basic structure and common patterns
|
||||||
|
for LEDMatrix plugins.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
plugin_id: str,
|
||||||
|
config: Dict[str, Any],
|
||||||
|
display_manager,
|
||||||
|
cache_manager,
|
||||||
|
plugin_manager,
|
||||||
|
):
|
||||||
|
"""Initialize the plugin."""
|
||||||
|
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
|
||||||
|
|
||||||
|
# Initialize plugin-specific data
|
||||||
|
self.data = None
|
||||||
|
self.last_update_time = None
|
||||||
|
|
||||||
|
# Load configuration values
|
||||||
|
self.api_key = config.get("api_key", "")
|
||||||
|
self.refresh_interval = config.get("refresh_interval", 60)
|
||||||
|
|
||||||
|
self.logger.info(f"Plugin {plugin_id} initialized")
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
"""
|
||||||
|
Fetch/update data for this plugin.
|
||||||
|
|
||||||
|
This method is called periodically based on update_interval
|
||||||
|
specified in the manifest. Use cache_manager to avoid
|
||||||
|
excessive API calls.
|
||||||
|
"""
|
||||||
|
cache_key = f"{self.plugin_id}_data"
|
||||||
|
|
||||||
|
# Check cache first
|
||||||
|
cached = self.cache_manager.get(cache_key, max_age=self.refresh_interval)
|
||||||
|
if cached:
|
||||||
|
self.data = cached
|
||||||
|
self.logger.debug("Using cached data")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Fetch new data
|
||||||
|
self.data = self._fetch_data()
|
||||||
|
|
||||||
|
# Cache the data
|
||||||
|
self.cache_manager.set(cache_key, self.data, ttl=self.refresh_interval)
|
||||||
|
self.last_update_time = time.time()
|
||||||
|
|
||||||
|
self.logger.info("Data updated successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to update data: {e}")
|
||||||
|
# Use cached data if available, even if expired
|
||||||
|
# Use a very large max_age (1 year) to effectively bypass expiration for fallback
|
||||||
|
expired_cached = self.cache_manager.get(cache_key, max_age=31536000)
|
||||||
|
if expired_cached:
|
||||||
|
self.data = expired_cached
|
||||||
|
self.logger.warning("Using expired cache due to update failure")
|
||||||
|
|
||||||
|
def display(self, force_clear: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Render this plugin's display.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force_clear: If True, clear display before rendering
|
||||||
|
"""
|
||||||
|
if force_clear:
|
||||||
|
self.display_manager.clear()
|
||||||
|
|
||||||
|
# Check if we have data to display
|
||||||
|
if not self.data:
|
||||||
|
self._display_error("No data available")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Render plugin content
|
||||||
|
self._render_content()
|
||||||
|
|
||||||
|
# Update the display
|
||||||
|
self.display_manager.update_display()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Display error: {e}")
|
||||||
|
self._display_error("Display error")
|
||||||
|
|
||||||
|
def _fetch_data(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Fetch data from external source.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing fetched data
|
||||||
|
"""
|
||||||
|
# TODO: Implement data fetching logic
|
||||||
|
# Example:
|
||||||
|
# import requests
|
||||||
|
# response = requests.get("https://api.example.com/data",
|
||||||
|
# headers={"Authorization": f"Bearer {self.api_key}"})
|
||||||
|
# return response.json()
|
||||||
|
|
||||||
|
# Placeholder
|
||||||
|
return {
|
||||||
|
"message": "Hello, World!",
|
||||||
|
"timestamp": time.time()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _render_content(self) -> None:
|
||||||
|
"""Render the plugin content on the display."""
|
||||||
|
# Get display dimensions
|
||||||
|
width = self.display_manager.width
|
||||||
|
height = self.display_manager.height
|
||||||
|
|
||||||
|
# Example: Draw text
|
||||||
|
text = self.data.get("message", "No data")
|
||||||
|
x = 5
|
||||||
|
y = height // 2
|
||||||
|
|
||||||
|
self.display_manager.draw_text(
|
||||||
|
text,
|
||||||
|
x=x,
|
||||||
|
y=y,
|
||||||
|
color=(255, 255, 255) # White
|
||||||
|
)
|
||||||
|
|
||||||
|
# Example: Draw image
|
||||||
|
# if hasattr(self, 'logo_image'):
|
||||||
|
# self.display_manager.draw_image(
|
||||||
|
# self.logo_image,
|
||||||
|
# x=0,
|
||||||
|
# y=0
|
||||||
|
# )
|
||||||
|
|
||||||
|
def _display_error(self, message: str) -> None:
|
||||||
|
"""Display an error message."""
|
||||||
|
self.display_manager.clear()
|
||||||
|
width = self.display_manager.width
|
||||||
|
height = self.display_manager.height
|
||||||
|
|
||||||
|
self.display_manager.draw_text(
|
||||||
|
message,
|
||||||
|
x=5,
|
||||||
|
y=height // 2,
|
||||||
|
color=(255, 0, 0) # Red
|
||||||
|
)
|
||||||
|
self.display_manager.update_display()
|
||||||
|
|
||||||
|
def validate_config(self) -> bool:
|
||||||
|
"""
|
||||||
|
Validate plugin configuration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if config is valid, False otherwise
|
||||||
|
"""
|
||||||
|
# Call parent validation first
|
||||||
|
if not super().validate_config():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Add custom validation
|
||||||
|
# Example: Check for required API key
|
||||||
|
# if self.config.get("require_api_key", True):
|
||||||
|
# if not self.api_key:
|
||||||
|
# self.logger.error("API key is required but not provided")
|
||||||
|
# return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def has_live_content(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check if plugin has live content to display.
|
||||||
|
|
||||||
|
Override this method to enable live priority features.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if plugin has live content, False otherwise
|
||||||
|
"""
|
||||||
|
# Example: Check if there's live data
|
||||||
|
# return self.data and self.data.get("is_live", False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_info(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Return plugin info for display in web UI.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with plugin information
|
||||||
|
"""
|
||||||
|
info = super().get_info()
|
||||||
|
|
||||||
|
# Add plugin-specific info
|
||||||
|
info.update({
|
||||||
|
"data_available": self.data is not None,
|
||||||
|
"last_update": self.last_update_time,
|
||||||
|
# Add more info as needed
|
||||||
|
})
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
def cleanup(self) -> None:
|
||||||
|
"""Cleanup resources when plugin is unloaded."""
|
||||||
|
# Clean up any resources (threads, connections, etc.)
|
||||||
|
# Example:
|
||||||
|
# if hasattr(self, 'api_client'):
|
||||||
|
# self.api_client.close()
|
||||||
|
|
||||||
|
super().cleanup()
|
||||||
|
|
||||||
55
.cursor/plugin_templates/manifest.json.template
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"id": "PLUGIN_ID",
|
||||||
|
"name": "Plugin Name",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Your Name",
|
||||||
|
"description": "Brief description of what this plugin does",
|
||||||
|
"homepage": "https://github.com/username/ledmatrix-PLUGIN_ID",
|
||||||
|
"entry_point": "manager.py",
|
||||||
|
"class_name": "PluginClassName",
|
||||||
|
"category": "custom",
|
||||||
|
"tags": ["custom", "example"],
|
||||||
|
"icon": "fas fa-icon-name",
|
||||||
|
"compatible_versions": [">=2.0.0"],
|
||||||
|
"min_ledmatrix_version": "2.0.0",
|
||||||
|
"max_ledmatrix_version": "3.0.0",
|
||||||
|
"requires": {
|
||||||
|
"python": ">=3.9",
|
||||||
|
"display_size": {
|
||||||
|
"min_width": 64,
|
||||||
|
"min_height": 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config_schema": "config_schema.json",
|
||||||
|
"assets": {
|
||||||
|
"logos": "Optional: Description of asset requirements"
|
||||||
|
},
|
||||||
|
"update_interval": 60,
|
||||||
|
"default_duration": 15,
|
||||||
|
"display_modes": [
|
||||||
|
"PLUGIN_ID"
|
||||||
|
],
|
||||||
|
"api_requirements": [
|
||||||
|
{
|
||||||
|
"name": "API Name",
|
||||||
|
"required": false,
|
||||||
|
"description": "Description of API requirements",
|
||||||
|
"url": "https://api.example.com",
|
||||||
|
"rate_limit": "Rate limit information"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"download_url_template": "https://github.com/username/ledmatrix-PLUGIN_ID/archive/refs/tags/v{version}.zip",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"released": "2025-01-01",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"ledmatrix_min_version": "2.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"last_updated": "2025-01-01",
|
||||||
|
"stars": 0,
|
||||||
|
"downloads": 0,
|
||||||
|
"verified": false,
|
||||||
|
"screenshot": ""
|
||||||
|
}
|
||||||
|
|
||||||
13
.cursor/plugin_templates/requirements.txt.template
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Plugin Dependencies
|
||||||
|
# Add your plugin's Python dependencies here
|
||||||
|
|
||||||
|
# Example dependencies (uncomment and modify as needed):
|
||||||
|
# requests>=2.28.0
|
||||||
|
# pillow>=9.0.0
|
||||||
|
# python-dateutil>=2.8.0
|
||||||
|
|
||||||
|
# Note: Core LEDMatrix dependencies are already available:
|
||||||
|
# - PIL/Pillow (for image handling)
|
||||||
|
# - Core plugin system classes
|
||||||
|
# - Display manager, cache manager, config manager
|
||||||
|
|
||||||
136
.cursor/plugin_templates/test_manager.py.template
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
Test file for Plugin Name plugin.
|
||||||
|
|
||||||
|
This file provides example unit tests for your plugin.
|
||||||
|
Run tests with: python -m pytest test_manager.py
|
||||||
|
Or: python test_manager.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
|
if str(PROJECT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
from src.plugin_system.testing import PluginTestCase
|
||||||
|
from manager import PluginClassName
|
||||||
|
|
||||||
|
|
||||||
|
class TestPluginClassName(PluginTestCase):
|
||||||
|
"""Test cases for PluginClassName plugin."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test fixtures."""
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# Update plugin_id to match the plugin being tested
|
||||||
|
self.plugin_id = 'PLUGIN_ID'
|
||||||
|
|
||||||
|
# Create plugin instance
|
||||||
|
self.plugin = self.create_plugin_instance(
|
||||||
|
PluginClassName,
|
||||||
|
plugin_id='PLUGIN_ID',
|
||||||
|
config=self.get_mock_config()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_plugin_initialization(self):
|
||||||
|
"""Test that plugin initializes correctly."""
|
||||||
|
self.assert_plugin_initialized(self.plugin)
|
||||||
|
self.assertTrue(self.plugin.enabled)
|
||||||
|
|
||||||
|
def test_config_validation(self):
|
||||||
|
"""Test configuration validation."""
|
||||||
|
# Valid config should pass
|
||||||
|
self.assertTrue(self.plugin.validate_config())
|
||||||
|
|
||||||
|
# Test with invalid config if applicable
|
||||||
|
# invalid_config = self.get_mock_config(enabled='not-a-boolean')
|
||||||
|
# invalid_plugin = self.create_plugin_instance(
|
||||||
|
# PluginClassName,
|
||||||
|
# config=invalid_config
|
||||||
|
# )
|
||||||
|
# self.assertFalse(invalid_plugin.validate_config())
|
||||||
|
|
||||||
|
def test_update_method(self):
|
||||||
|
"""Test the update() method."""
|
||||||
|
# Reset mocks
|
||||||
|
self.cache_manager.reset()
|
||||||
|
|
||||||
|
# Call update
|
||||||
|
self.plugin.update()
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
# Example: Check that cache was used
|
||||||
|
# self.assert_cache_get('PLUGIN_ID_data')
|
||||||
|
|
||||||
|
# Example: Check that data was fetched and cached
|
||||||
|
# self.assert_cache_set('PLUGIN_ID_data')
|
||||||
|
|
||||||
|
def test_display_method(self):
|
||||||
|
"""Test the display() method."""
|
||||||
|
# Ensure plugin has data (call update first if needed)
|
||||||
|
# self.plugin.update()
|
||||||
|
|
||||||
|
# Call display
|
||||||
|
self.plugin.display(force_clear=True)
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
self.assert_display_cleared()
|
||||||
|
self.assert_display_updated()
|
||||||
|
|
||||||
|
# Example: Check that text was drawn
|
||||||
|
# self.assert_text_drawn("Expected Text")
|
||||||
|
|
||||||
|
# Example: Check that image was drawn
|
||||||
|
# self.assert_image_drawn()
|
||||||
|
|
||||||
|
def test_display_without_data(self):
|
||||||
|
"""Test display() behavior when no data is available."""
|
||||||
|
# Clear any cached data
|
||||||
|
self.cache_manager.reset()
|
||||||
|
|
||||||
|
# Call display
|
||||||
|
self.plugin.display()
|
||||||
|
|
||||||
|
# Should handle gracefully (no exceptions)
|
||||||
|
# May show error message or fallback content
|
||||||
|
self.assert_display_updated()
|
||||||
|
|
||||||
|
def test_get_display_duration(self):
|
||||||
|
"""Test display duration configuration."""
|
||||||
|
duration = self.plugin.get_display_duration()
|
||||||
|
self.assertIsInstance(duration, (int, float))
|
||||||
|
self.assertGreater(duration, 0)
|
||||||
|
|
||||||
|
# Test with custom duration
|
||||||
|
custom_config = self.get_mock_config(display_duration=30.0)
|
||||||
|
custom_plugin = self.create_plugin_instance(
|
||||||
|
PluginClassName,
|
||||||
|
config=custom_config
|
||||||
|
)
|
||||||
|
self.assertEqual(custom_plugin.get_display_duration(), 30.0)
|
||||||
|
|
||||||
|
def test_enable_disable(self):
|
||||||
|
"""Test plugin enable/disable functionality."""
|
||||||
|
self.assertTrue(self.plugin.enabled)
|
||||||
|
|
||||||
|
self.plugin.on_disable()
|
||||||
|
self.assertFalse(self.plugin.enabled)
|
||||||
|
|
||||||
|
self.plugin.on_enable()
|
||||||
|
self.assertTrue(self.plugin.enabled)
|
||||||
|
|
||||||
|
def test_config_change(self):
|
||||||
|
"""Test configuration change handling."""
|
||||||
|
new_config = self.get_mock_config(display_duration=20.0)
|
||||||
|
self.plugin.on_config_change(new_config)
|
||||||
|
|
||||||
|
self.assertEqual(self.plugin.config.get('display_duration'), 20.0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
751
.cursor/plugins_guide.md
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
# LEDMatrix Plugin Development Guide
|
||||||
|
|
||||||
|
This guide provides comprehensive instructions for creating, running, and loading plugins in the LEDMatrix project.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Plugin System Overview](#plugin-system-overview)
|
||||||
|
2. [Creating a New Plugin](#creating-a-new-plugin)
|
||||||
|
3. [Running Plugins](#running-plugins)
|
||||||
|
4. [Loading Plugins](#loading-plugins)
|
||||||
|
5. [Plugin Development Workflow](#plugin-development-workflow)
|
||||||
|
6. [Testing Plugins](#testing-plugins)
|
||||||
|
7. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin System Overview
|
||||||
|
|
||||||
|
The LEDMatrix project uses a plugin-based architecture where all display functionality (except core calendar) is implemented as plugins. Plugins are dynamically loaded from the `plugins/` directory and integrated into the display rotation.
|
||||||
|
|
||||||
|
### Plugin Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
LEDMatrix Core
|
||||||
|
├── Plugin Manager (discovers, loads, manages plugins)
|
||||||
|
├── Display Manager (handles LED matrix rendering)
|
||||||
|
├── Cache Manager (data persistence)
|
||||||
|
├── Config Manager (configuration management)
|
||||||
|
└── Plugins/ (plugin directory)
|
||||||
|
├── plugin-1/
|
||||||
|
├── plugin-2/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Lifecycle
|
||||||
|
|
||||||
|
1. **Discovery**: PluginManager scans `plugins/` for directories with `manifest.json`
|
||||||
|
2. **Loading**: Plugin module is imported and class is instantiated
|
||||||
|
3. **Configuration**: Plugin config is loaded from `config/config.json`
|
||||||
|
4. **Validation**: `validate_config()` is called to verify configuration
|
||||||
|
5. **Registration**: Plugin is added to available display modes
|
||||||
|
6. **Execution**: `update()` is called periodically, `display()` is called during rotation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creating a New Plugin
|
||||||
|
|
||||||
|
### Method 1: Using dev_plugin_setup.sh (Recommended)
|
||||||
|
|
||||||
|
This method is best for plugins stored in separate Git repositories.
|
||||||
|
|
||||||
|
#### From GitHub Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Link a plugin from GitHub (auto-detects URL)
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
|
||||||
|
|
||||||
|
# Example: Link hockey-scoreboard plugin
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github hockey-scoreboard
|
||||||
|
|
||||||
|
# With custom URL
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name> https://github.com/user/repo.git
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Clone the repository to `~/.ledmatrix-dev-plugins/` (or configured directory)
|
||||||
|
- Create a symlink in `plugins/<plugin-name>/` pointing to the cloned repo
|
||||||
|
- Validate the plugin structure
|
||||||
|
|
||||||
|
#### From Local Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Link a local plugin repository
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||||
|
|
||||||
|
# Example: Link a local plugin
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link my-plugin ../ledmatrix-my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Manual Plugin Creation
|
||||||
|
|
||||||
|
1. **Create Plugin Directory**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p plugins/my-plugin
|
||||||
|
cd plugins/my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create manifest.json**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "my-plugin",
|
||||||
|
"name": "My Plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Your Name",
|
||||||
|
"description": "Description of what this plugin does",
|
||||||
|
"entry_point": "manager.py",
|
||||||
|
"class_name": "MyPlugin",
|
||||||
|
"category": "custom",
|
||||||
|
"tags": ["custom", "example"],
|
||||||
|
"display_modes": ["my_plugin"],
|
||||||
|
"update_interval": 60,
|
||||||
|
"default_duration": 15,
|
||||||
|
"requires": {
|
||||||
|
"python": ">=3.9"
|
||||||
|
},
|
||||||
|
"config_schema": "config_schema.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Create manager.py**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system.base_plugin import BasePlugin
|
||||||
|
from PIL import Image
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class MyPlugin(BasePlugin):
|
||||||
|
"""My custom plugin implementation."""
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Fetch/update data for this plugin."""
|
||||||
|
# Fetch data from API, files, etc.
|
||||||
|
# Use self.cache_manager for caching
|
||||||
|
cache_key = f"{self.plugin_id}_data"
|
||||||
|
cached = self.cache_manager.get(cache_key, max_age=3600)
|
||||||
|
if cached:
|
||||||
|
self.data = cached
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fetch new data
|
||||||
|
self.data = self._fetch_data()
|
||||||
|
self.cache_manager.set(cache_key, self.data)
|
||||||
|
|
||||||
|
def display(self, force_clear=False):
|
||||||
|
"""Render this plugin's display."""
|
||||||
|
if force_clear:
|
||||||
|
self.display_manager.clear()
|
||||||
|
|
||||||
|
# Render content using display_manager
|
||||||
|
self.display_manager.draw_text(
|
||||||
|
"Hello, World!",
|
||||||
|
x=10, y=15,
|
||||||
|
color=(255, 255, 255)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.display_manager.update_display()
|
||||||
|
|
||||||
|
def _fetch_data(self):
|
||||||
|
"""Fetch data from external source."""
|
||||||
|
# Implement your data fetching logic
|
||||||
|
return {"message": "Hello, World!"}
|
||||||
|
|
||||||
|
def validate_config(self):
|
||||||
|
"""Validate plugin configuration."""
|
||||||
|
# Check required config fields
|
||||||
|
if not super().validate_config():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Add custom validation
|
||||||
|
required_fields = ['api_key'] # Example
|
||||||
|
for field in required_fields:
|
||||||
|
if field not in self.config:
|
||||||
|
self.logger.error(f"Missing required field: {field}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create config_schema.json**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable or disable this plugin"
|
||||||
|
},
|
||||||
|
"display_duration": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 15,
|
||||||
|
"minimum": 1,
|
||||||
|
"description": "How long to display this plugin (seconds)"
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "API key for external service"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["enabled"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Create requirements.txt** (if needed)
|
||||||
|
|
||||||
|
```
|
||||||
|
requests>=2.28.0
|
||||||
|
pillow>=9.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Create README.md**
|
||||||
|
|
||||||
|
Document your plugin's functionality, configuration options, and usage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Plugins
|
||||||
|
|
||||||
|
### Development Mode (Emulator)
|
||||||
|
|
||||||
|
Run the LEDMatrix system with emulator for plugin testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using run.py
|
||||||
|
python run.py --emulator
|
||||||
|
|
||||||
|
# Using emulator script
|
||||||
|
./run_emulator.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The emulator will:
|
||||||
|
- Load all enabled plugins
|
||||||
|
- Display plugin content in a window (simulating LED matrix)
|
||||||
|
- Show logs for plugin loading and execution
|
||||||
|
- Allow testing without Raspberry Pi hardware
|
||||||
|
|
||||||
|
### Production Mode (Raspberry Pi)
|
||||||
|
|
||||||
|
Run on actual Raspberry Pi hardware:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Direct execution
|
||||||
|
python run.py
|
||||||
|
|
||||||
|
# As systemd service
|
||||||
|
sudo systemctl start ledmatrix
|
||||||
|
sudo systemctl status ledmatrix
|
||||||
|
sudo journalctl -u ledmatrix -f # View logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin-Specific Testing
|
||||||
|
|
||||||
|
Test individual plugin loading:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# test_my_plugin.py
|
||||||
|
from src.plugin_system.plugin_manager import PluginManager
|
||||||
|
from src.config_manager import ConfigManager
|
||||||
|
from src.display_manager import DisplayManager
|
||||||
|
from src.cache_manager import CacheManager
|
||||||
|
|
||||||
|
# Initialize managers
|
||||||
|
config_manager = ConfigManager()
|
||||||
|
config = config_manager.load_config()
|
||||||
|
display_manager = DisplayManager(config)
|
||||||
|
cache_manager = CacheManager()
|
||||||
|
|
||||||
|
# Initialize plugin manager
|
||||||
|
plugin_manager = PluginManager(
|
||||||
|
plugins_dir="plugins",
|
||||||
|
config_manager=config_manager,
|
||||||
|
display_manager=display_manager,
|
||||||
|
cache_manager=cache_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
# Discover and load plugin
|
||||||
|
plugins = plugin_manager.discover_plugins()
|
||||||
|
print(f"Discovered plugins: {plugins}")
|
||||||
|
|
||||||
|
if "my-plugin" in plugins:
|
||||||
|
if plugin_manager.load_plugin("my-plugin"):
|
||||||
|
plugin = plugin_manager.get_plugin("my-plugin")
|
||||||
|
plugin.update()
|
||||||
|
plugin.display()
|
||||||
|
print("Plugin loaded and displayed successfully!")
|
||||||
|
else:
|
||||||
|
print("Failed to load plugin")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loading Plugins
|
||||||
|
|
||||||
|
### Enabling Plugins
|
||||||
|
|
||||||
|
Plugins are enabled/disabled in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"my-plugin": {
|
||||||
|
"enabled": true,
|
||||||
|
"display_duration": 15,
|
||||||
|
"api_key": "your-api-key-here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Configuration Structure
|
||||||
|
|
||||||
|
Each plugin has its own section in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"<plugin-id>": {
|
||||||
|
"enabled": true, // Enable/disable plugin
|
||||||
|
"display_duration": 15, // Display duration in seconds
|
||||||
|
"live_priority": false, // Enable live priority takeover
|
||||||
|
"high_performance_transitions": false, // Use 120 FPS transitions
|
||||||
|
"transition": { // Transition configuration
|
||||||
|
"type": "redraw", // Transition type
|
||||||
|
"speed": 2, // Transition speed
|
||||||
|
"enabled": true // Enable transitions
|
||||||
|
},
|
||||||
|
// ... plugin-specific configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets Management
|
||||||
|
|
||||||
|
Store sensitive data (API keys, tokens) in `config/config_secrets.json`
|
||||||
|
under the same plugin id you use in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"my-plugin": {
|
||||||
|
"api_key": "secret-api-key-here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
- Directory exists in `plugins/`
|
||||||
|
- Directory contains `manifest.json`
|
||||||
|
- Manifest has required fields (`id`, `entry_point`, `class_name`)
|
||||||
|
|
||||||
|
Check discovered plugins:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using dev_plugin_setup.sh
|
||||||
|
./scripts/dev/dev_plugin_setup.sh list
|
||||||
|
|
||||||
|
# Output shows:
|
||||||
|
# ✓ plugin-name (symlink)
|
||||||
|
# → /path/to/repo
|
||||||
|
# ✓ Git repo is clean (branch: main)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Status
|
||||||
|
|
||||||
|
Check plugin status and git information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/dev/dev_plugin_setup.sh status
|
||||||
|
|
||||||
|
# Output shows:
|
||||||
|
# ✓ plugin-name
|
||||||
|
# Path: /path/to/repo
|
||||||
|
# Branch: main
|
||||||
|
# Remote: https://github.com/user/repo.git
|
||||||
|
# Status: Clean and up to date
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin Development Workflow
|
||||||
|
|
||||||
|
### 1. Initial Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create or clone plugin repository
|
||||||
|
git clone https://github.com/user/ledmatrix-my-plugin.git
|
||||||
|
cd ledmatrix-my-plugin
|
||||||
|
|
||||||
|
# Link to LEDMatrix project
|
||||||
|
cd /path/to/LEDMatrix
|
||||||
|
./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 the dev preview server**:
|
||||||
|
`python3 scripts/dev_server.py` (then open `http://localhost:5001`).
|
||||||
|
Or run the full display in emulator mode with
|
||||||
|
`python3 run.py --emulator` (or equivalently
|
||||||
|
`EMULATOR=true python3 run.py`). The `-e`/`--emulator` CLI flag is
|
||||||
|
defined in `run.py:19-20` and sets the same `EMULATOR` environment
|
||||||
|
variable internally.
|
||||||
|
3. **Check logs** for errors or warnings
|
||||||
|
4. **Update configuration** in `config/config.json` if needed
|
||||||
|
5. **Iterate** until plugin works correctly
|
||||||
|
|
||||||
|
### 3. Testing on Hardware
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy to Raspberry Pi
|
||||||
|
rsync -avz plugins/my-plugin/ ledpi@your-pi-ip:/path/to/LEDMatrix/plugins/my-plugin/
|
||||||
|
|
||||||
|
# Or if using git, pull on Pi
|
||||||
|
ssh ledpi@your-pi-ip "cd /path/to/LEDMatrix/plugins/my-plugin && git pull"
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
ssh ledpi@your-pi-ip "sudo systemctl restart ledmatrix"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Updating Plugins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update single plugin from git
|
||||||
|
./scripts/dev/dev_plugin_setup.sh update my-plugin
|
||||||
|
|
||||||
|
# Update all linked plugins
|
||||||
|
./scripts/dev/dev_plugin_setup.sh update
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Unlinking Plugins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove symlink (preserves repository)
|
||||||
|
./scripts/dev/dev_plugin_setup.sh unlink my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Plugins
|
||||||
|
|
||||||
|
### Unit Testing
|
||||||
|
|
||||||
|
Create test files in plugin directory:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# plugins/my-plugin/test_my_plugin.py
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import Mock, MagicMock
|
||||||
|
from manager import MyPlugin
|
||||||
|
|
||||||
|
class TestMyPlugin(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.config = {"enabled": True}
|
||||||
|
self.display_manager = Mock()
|
||||||
|
self.cache_manager = Mock()
|
||||||
|
self.plugin_manager = Mock()
|
||||||
|
|
||||||
|
self.plugin = MyPlugin(
|
||||||
|
plugin_id="my-plugin",
|
||||||
|
config=self.config,
|
||||||
|
display_manager=self.display_manager,
|
||||||
|
cache_manager=self.cache_manager,
|
||||||
|
plugin_manager=self.plugin_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_plugin_initialization(self):
|
||||||
|
self.assertEqual(self.plugin.plugin_id, "my-plugin")
|
||||||
|
self.assertTrue(self.plugin.enabled)
|
||||||
|
|
||||||
|
def test_config_validation(self):
|
||||||
|
self.assertTrue(self.plugin.validate_config())
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
self.cache_manager.get.return_value = None
|
||||||
|
self.plugin.update()
|
||||||
|
# Assert data was fetched and cached
|
||||||
|
|
||||||
|
def test_display(self):
|
||||||
|
self.plugin.display()
|
||||||
|
self.display_manager.draw_text.assert_called()
|
||||||
|
self.display_manager.update_display.assert_called()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd plugins/my-plugin
|
||||||
|
python -m pytest test_my_plugin.py
|
||||||
|
# or
|
||||||
|
python test_my_plugin.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
|
||||||
|
Test plugin with actual managers:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# test_plugin_integration.py
|
||||||
|
from src.plugin_system.plugin_manager import PluginManager
|
||||||
|
from src.config_manager import ConfigManager
|
||||||
|
from src.display_manager import DisplayManager
|
||||||
|
from src.cache_manager import CacheManager
|
||||||
|
|
||||||
|
def test_plugin_loading():
|
||||||
|
config_manager = ConfigManager()
|
||||||
|
config = config_manager.load_config()
|
||||||
|
display_manager = DisplayManager(config)
|
||||||
|
cache_manager = CacheManager()
|
||||||
|
|
||||||
|
plugin_manager = PluginManager(
|
||||||
|
plugins_dir="plugins",
|
||||||
|
config_manager=config_manager,
|
||||||
|
display_manager=display_manager,
|
||||||
|
cache_manager=cache_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
plugins = plugin_manager.discover_plugins()
|
||||||
|
assert "my-plugin" in plugins
|
||||||
|
|
||||||
|
assert plugin_manager.load_plugin("my-plugin")
|
||||||
|
plugin = plugin_manager.get_plugin("my-plugin")
|
||||||
|
assert plugin is not None
|
||||||
|
assert plugin.enabled
|
||||||
|
|
||||||
|
plugin.update()
|
||||||
|
plugin.display()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emulator Testing
|
||||||
|
|
||||||
|
Test plugin rendering visually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run with emulator
|
||||||
|
python run.py --emulator
|
||||||
|
|
||||||
|
# Plugin should appear in display rotation
|
||||||
|
# Check logs for plugin loading and execution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hardware Testing
|
||||||
|
|
||||||
|
1. Deploy plugin to Raspberry Pi
|
||||||
|
2. Enable in `config/config.json`
|
||||||
|
3. Restart LEDMatrix service
|
||||||
|
4. Observe LED matrix display
|
||||||
|
5. Check logs: `journalctl -u ledmatrix -f`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Plugin Not Loading
|
||||||
|
|
||||||
|
**Symptoms**: Plugin doesn't appear in available modes, no logs about plugin
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check plugin directory exists: `ls plugins/my-plugin/`
|
||||||
|
2. Verify `manifest.json` exists and is valid JSON
|
||||||
|
3. Check manifest has required fields: `id`, `entry_point`, `class_name`
|
||||||
|
4. Verify entry_point file exists: `ls plugins/my-plugin/manager.py`
|
||||||
|
5. Check class name matches: `grep "class.*Plugin" plugins/my-plugin/manager.py`
|
||||||
|
6. Review logs for import errors
|
||||||
|
|
||||||
|
### Plugin Loading but Not Displaying
|
||||||
|
|
||||||
|
**Symptoms**: Plugin loads successfully but doesn't appear in rotation
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check plugin is enabled: `config/config.json` has `"enabled": true`
|
||||||
|
2. Verify display_modes in manifest match config
|
||||||
|
3. Check plugin is in rotation schedule
|
||||||
|
4. Review `display()` method for errors
|
||||||
|
5. Check logs for runtime errors
|
||||||
|
|
||||||
|
### Configuration Errors
|
||||||
|
|
||||||
|
**Symptoms**: Plugin fails to load, validation errors in logs
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Validate config against `config_schema.json`
|
||||||
|
2. Check required fields are present
|
||||||
|
3. Verify data types match schema
|
||||||
|
4. Check for typos in config keys
|
||||||
|
5. Review `validate_config()` method
|
||||||
|
|
||||||
|
### Import Errors
|
||||||
|
|
||||||
|
**Symptoms**: ModuleNotFoundError or ImportError in logs
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Install plugin dependencies: `pip install -r plugins/my-plugin/requirements.txt`
|
||||||
|
2. Check Python path includes plugin directory
|
||||||
|
3. Verify relative imports are correct
|
||||||
|
4. Check for circular import issues
|
||||||
|
5. Ensure all dependencies are in requirements.txt
|
||||||
|
|
||||||
|
### Display Issues
|
||||||
|
|
||||||
|
**Symptoms**: Plugin renders incorrectly or not at all
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check display dimensions: `display_manager.width`, `display_manager.height`
|
||||||
|
2. Verify coordinates are within display bounds
|
||||||
|
3. Check color values are valid (0-255)
|
||||||
|
4. Ensure `update_display()` is called after rendering
|
||||||
|
5. Test with emulator first to debug rendering
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
|
||||||
|
**Symptoms**: Slow display updates, high CPU usage
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Use `cache_manager` to avoid excessive API calls
|
||||||
|
2. Implement background data fetching
|
||||||
|
3. Optimize rendering code
|
||||||
|
4. Consider using `high_performance_transitions`
|
||||||
|
5. Profile plugin code to identify bottlenecks
|
||||||
|
|
||||||
|
### Git/Symlink Issues
|
||||||
|
|
||||||
|
**Symptoms**: Plugin changes not appearing, broken symlinks
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check symlink: `ls -la plugins/my-plugin`
|
||||||
|
2. Verify target exists: `readlink -f plugins/my-plugin`
|
||||||
|
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`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Code Organization
|
||||||
|
|
||||||
|
- Keep plugin code in `plugins/<plugin-id>/` directory
|
||||||
|
- Use descriptive class and method names
|
||||||
|
- Follow existing plugin patterns
|
||||||
|
- Place shared utilities in `src/common/` if reusable
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Always use `config_schema.json` for validation
|
||||||
|
- Store secrets in `config_secrets.json`
|
||||||
|
- Provide sensible defaults
|
||||||
|
- Document all configuration options in README
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- Use plugin logger for all logging
|
||||||
|
- Handle API failures gracefully
|
||||||
|
- Provide fallback displays when data unavailable
|
||||||
|
- Cache data to avoid excessive requests
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Cache API responses appropriately
|
||||||
|
- Use background data fetching for long operations
|
||||||
|
- Optimize rendering for Pi's limited resources
|
||||||
|
- Test performance on actual hardware
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Write unit tests for core logic
|
||||||
|
- Test with emulator before hardware
|
||||||
|
- Test on Raspberry Pi before deploying
|
||||||
|
- Test with other plugins enabled
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Document plugin functionality in README
|
||||||
|
- Include configuration examples
|
||||||
|
- Document API requirements and rate limits
|
||||||
|
- Provide usage examples
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- **Plugin System Documentation**: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
|
||||||
|
- **Base Plugin Class**: `src/plugin_system/base_plugin.py`
|
||||||
|
- **Plugin Manager**: `src/plugin_system/plugin_manager.py`
|
||||||
|
- **Example Plugins**:
|
||||||
|
- `plugins/hockey-scoreboard/` - Sports scoreboard example
|
||||||
|
- `plugins/football-scoreboard/` - Complex multi-league example
|
||||||
|
- `plugins/ledmatrix-music/` - Real-time data example
|
||||||
|
- **Development Setup**: `dev_plugin_setup.sh`
|
||||||
|
- **Example Config**: `dev_plugins.json.example`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Link plugin from GitHub
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github <name>
|
||||||
|
|
||||||
|
# Link local plugin
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link <name> <path>
|
||||||
|
|
||||||
|
# List all plugins
|
||||||
|
./scripts/dev/dev_plugin_setup.sh list
|
||||||
|
|
||||||
|
# Check plugin status
|
||||||
|
./scripts/dev/dev_plugin_setup.sh status
|
||||||
|
|
||||||
|
# Update plugin(s)
|
||||||
|
./scripts/dev/dev_plugin_setup.sh update [name]
|
||||||
|
|
||||||
|
# Unlink plugin
|
||||||
|
./scripts/dev/dev_plugin_setup.sh unlink <name>
|
||||||
|
|
||||||
|
# Run with emulator
|
||||||
|
python run.py --emulator
|
||||||
|
|
||||||
|
# Run on Pi
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
plugins/my-plugin/
|
||||||
|
├── manifest.json # Required: Plugin metadata
|
||||||
|
├── manager.py # Required: Plugin class
|
||||||
|
├── config_schema.json # Required: Config validation
|
||||||
|
├── requirements.txt # Optional: Dependencies
|
||||||
|
├── README.md # Optional: Documentation
|
||||||
|
└── ... # Plugin-specific files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required Manifest Fields
|
||||||
|
|
||||||
|
- `id`: Plugin identifier
|
||||||
|
- `entry_point`: Python file (usually "manager.py")
|
||||||
|
- `class_name`: Plugin class name
|
||||||
|
- `display_modes`: Array of mode names
|
||||||
|
|
||||||
38
.cursor/rules/coding-standards.mdc
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
globs: *.py
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python Coding Standards
|
||||||
|
|
||||||
|
## Code Quality Principles
|
||||||
|
- **Simplicity First**: Prefer clear, readable code over clever optimizations
|
||||||
|
- **Explicit over Implicit**: Make intentions clear through naming and structure
|
||||||
|
- **Fail Fast**: Validate inputs and handle errors early
|
||||||
|
- **Documentation**: Use docstrings for classes and complex functions
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
- **Classes**: PascalCase (e.g., `NHLRecentManager`)
|
||||||
|
- **Functions/Variables**: snake_case (e.g., `fetch_game_data`)
|
||||||
|
- **Constants**: UPPER_SNAKE_CASE (e.g., `ESPN_NHL_SCOREBOARD_URL`)
|
||||||
|
- **Private methods**: Leading underscore (e.g., `_fetch_data`)
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
- **Logging**: Use structured logging with context (e.g., `[NHL Recent]`)
|
||||||
|
- **Exceptions**: Catch specific exceptions, not bare `except:`
|
||||||
|
- **User-friendly messages**: Explain what went wrong and potential solutions
|
||||||
|
- **Graceful degradation**: Continue operation when non-critical features fail
|
||||||
|
|
||||||
|
## Manager Pattern
|
||||||
|
All sports managers should follow this structure:
|
||||||
|
```python
|
||||||
|
class BaseManager:
|
||||||
|
def __init__(self, config, display_manager, cache_manager)
|
||||||
|
def update(self) # Fetch and process data
|
||||||
|
def display(self, force_clear=False) # Render to display
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Management
|
||||||
|
- **Type hints**: Use for function parameters and return values
|
||||||
|
- **Configuration validation**: Check required fields on initialization
|
||||||
|
- **Default values**: Provide sensible defaults in code, not config
|
||||||
|
- **Environment awareness**: Handle different deployment contexts
|
||||||
42
.cursor/rules/configuration-management.mdc
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
globs: config/*.json,src/*.py
|
||||||
|
---
|
||||||
|
|
||||||
|
# Configuration Management
|
||||||
|
|
||||||
|
## Configuration Structure
|
||||||
|
- **Main config**: [config/config.json](mdc:config/config.json) - Primary configuration
|
||||||
|
- **Secrets**: [config/config_secrets.json](mdc:config/config_secrets.json) - API keys and sensitive data
|
||||||
|
- **Templates**: [config/config.template.json](mdc:config/config.template.json) - Default values
|
||||||
|
|
||||||
|
## Configuration Principles
|
||||||
|
- **Validation**: Check required fields and data types on startup
|
||||||
|
- **Defaults**: Provide sensible defaults in code, not just config
|
||||||
|
- **Environment awareness**: Handle development vs production differences
|
||||||
|
- **Security**: Never commit secrets to version control
|
||||||
|
|
||||||
|
## Manager Configuration Pattern
|
||||||
|
```python
|
||||||
|
def __init__(self, config, display_manager, cache_manager):
|
||||||
|
self.mode_config = config.get("sport_scoreboard", {})
|
||||||
|
self.favorite_teams = self.mode_config.get("favorite_teams", [])
|
||||||
|
self.show_favorite_only = self.mode_config.get("show_favorite_teams_only", False)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Configuration Sections
|
||||||
|
- **Display settings**: Update intervals, display durations
|
||||||
|
- **API settings**: Timeouts, retry logic, rate limiting
|
||||||
|
- **Background service**: Threading, caching, priority settings
|
||||||
|
- **Team preferences**: Favorite teams, filtering options
|
||||||
|
|
||||||
|
## Configuration Validation
|
||||||
|
- **Type checking**: Ensure numeric values are numbers, lists are lists
|
||||||
|
- **Range validation**: Check that intervals are reasonable
|
||||||
|
- **Dependency checking**: Verify required services are available
|
||||||
|
- **Fallback values**: Provide defaults when config is missing or invalid
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- **Documentation**: Comment complex configuration options
|
||||||
|
- **Examples**: Provide working examples in templates
|
||||||
|
- **Migration**: Handle configuration changes between versions
|
||||||
|
- **Testing**: Validate configuration in test environments
|
||||||
50
.cursor/rules/error-handling-logging.mdc
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
globs: src/*.py
|
||||||
|
---
|
||||||
|
|
||||||
|
# Error Handling and Logging
|
||||||
|
|
||||||
|
## Logging Standards
|
||||||
|
- **Structured prefixes**: Use consistent tags like `[NHL Recent]`, `[NFL Live]`
|
||||||
|
- **Context information**: Include relevant details (team names, game status, dates)
|
||||||
|
- **Appropriate levels**:
|
||||||
|
- `info`: Normal operations and status updates
|
||||||
|
- `debug`: Detailed information for troubleshooting
|
||||||
|
- `warning`: Non-critical issues that should be noted
|
||||||
|
- `error`: Problems that need attention
|
||||||
|
|
||||||
|
## Error Handling Patterns
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
data = self._fetch_data()
|
||||||
|
if not data or 'events' not in data:
|
||||||
|
self.logger.warning("[Manager] No events found in API response")
|
||||||
|
return
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.error(f"[Manager] API error: {e}")
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
## User-Friendly Messages
|
||||||
|
- **Explain the situation**: "No games available during off-season"
|
||||||
|
- **Provide context**: "NHL season typically runs October-June"
|
||||||
|
- **Suggest solutions**: "Check back when season starts"
|
||||||
|
- **Distinguish issues**: API problems vs no data vs filtering results
|
||||||
|
|
||||||
|
## Graceful Degradation
|
||||||
|
- **Fallback content**: Show alternative games when favorites unavailable
|
||||||
|
- **Cached data**: Use cached data when API fails
|
||||||
|
- **Service continuity**: Continue operation when non-critical features fail
|
||||||
|
- **Clear communication**: Explain what's happening to users
|
||||||
|
|
||||||
|
## Debugging Support
|
||||||
|
- **Comprehensive logging**: Log API responses, filtering results, display updates
|
||||||
|
- **State tracking**: Log current state and transitions
|
||||||
|
- **Performance monitoring**: Track timing and resource usage
|
||||||
|
- **Error context**: Include stack traces for debugging
|
||||||
|
|
||||||
|
## Off-Season Awareness
|
||||||
|
- **Seasonal messaging**: Different messages for different times of year
|
||||||
|
- **Helpful context**: Explain why no games are available
|
||||||
|
- **Future planning**: Mention when season starts
|
||||||
|
- **Realistic expectations**: Set appropriate expectations during off-season
|
||||||
51
.cursor/rules/git-workflow.mdc
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Workflow and Branching
|
||||||
|
|
||||||
|
## Branch Naming Conventions
|
||||||
|
- **Features**: `feature/description-of-feature` (e.g., `feature/weather-forecast-improvements`)
|
||||||
|
- **Bug fixes**: `fix/description-of-bug` (e.g., `fix/nhl-manager-improvements`)
|
||||||
|
- **Hotfixes**: `hotfix/critical-issue-description`
|
||||||
|
- **Refactoring**: `refactor/description-of-refactor`
|
||||||
|
|
||||||
|
## Commit Message Format
|
||||||
|
```
|
||||||
|
type(scope): description
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Types**: feat, fix, docs, style, refactor, test, chore
|
||||||
|
**Examples**:
|
||||||
|
- `feat(nhl): Add enhanced logging for data visibility`
|
||||||
|
- `fix(display): Resolve rendering performance issue`
|
||||||
|
- `docs(api): Update ESPN API integration guide`
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
- **Self-review**: Review your own PR before requesting review
|
||||||
|
- **Testing**: Test thoroughly on Raspberry Pi hardware
|
||||||
|
- **Documentation**: Update relevant documentation if needed
|
||||||
|
- **Clean history**: Squash commits if necessary for clean history
|
||||||
|
|
||||||
|
## Code Review Checklist
|
||||||
|
- **Code Quality**: Proper error handling, logging, type hints
|
||||||
|
- **Architecture**: Follows project patterns, doesn't break existing functionality
|
||||||
|
- **Performance**: No negative impact on display performance
|
||||||
|
- **Testing**: Works on Raspberry Pi hardware
|
||||||
|
- **Documentation**: Comments added for complex logic
|
||||||
|
|
||||||
|
## Merge Strategies
|
||||||
|
- **Squash and Merge**: Preferred for feature branches and bug fixes
|
||||||
|
- **Merge Commit**: For complex features with multiple logical commits
|
||||||
|
- **Rebase and Merge**: For simple, single-commit changes
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- **Keep branches small and focused**
|
||||||
|
- **Commit frequently with meaningful messages**
|
||||||
|
- **Update branch regularly with main**
|
||||||
|
- **Test changes incrementally**
|
||||||
|
- **Delete feature branches after merge**
|
||||||
213
.cursor/rules/github-branches-rule.mdc
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
---
|
||||||
|
description: GitHub branching and pull request best practices for LEDMatrix project
|
||||||
|
globs: ["**/*.py", "**/*.md", "**/*.json", "**/*.sh"]
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitHub Branching and Pull Request Guidelines
|
||||||
|
|
||||||
|
## Branch Naming Conventions
|
||||||
|
|
||||||
|
### Feature Branches
|
||||||
|
- **Format**: `feature/description-of-feature`
|
||||||
|
- **Examples**:
|
||||||
|
- `feature/weather-forecast-improvements`
|
||||||
|
- `feature/stock-api-integration`
|
||||||
|
- `feature/nba-live-scores`
|
||||||
|
|
||||||
|
### Bug Fix Branches
|
||||||
|
- **Format**: `fix/description-of-bug`
|
||||||
|
- **Examples**:
|
||||||
|
- `fix/leaderboard-scrolling-performance`
|
||||||
|
- `fix/weather-api-timeout`
|
||||||
|
- `fix/display-rendering-issue`
|
||||||
|
|
||||||
|
### Hotfix Branches
|
||||||
|
- **Format**: `hotfix/critical-issue-description`
|
||||||
|
- **Examples**:
|
||||||
|
- `hotfix/display-crash-fix`
|
||||||
|
- `hotfix/api-rate-limit-fix`
|
||||||
|
|
||||||
|
### Refactoring Branches
|
||||||
|
- **Format**: `refactor/description-of-refactor`
|
||||||
|
- **Examples**:
|
||||||
|
- `refactor/sports-manager-architecture`
|
||||||
|
- `refactor/cache-management-system`
|
||||||
|
|
||||||
|
## Branch Management Rules
|
||||||
|
|
||||||
|
### Main Branch Protection
|
||||||
|
- **`main`** branch is protected and requires PR reviews
|
||||||
|
- Never commit directly to `main`
|
||||||
|
- All changes must go through pull requests
|
||||||
|
|
||||||
|
### Branch Lifecycle
|
||||||
|
1. **Create** branch from `main` when starting work
|
||||||
|
2. **Keep** branch up-to-date with `main` regularly
|
||||||
|
3. **Test** thoroughly before creating PR
|
||||||
|
4. **Delete** branch after successful merge
|
||||||
|
|
||||||
|
### Branch Updates
|
||||||
|
```bash
|
||||||
|
# Before starting new work
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Create new branch
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
|
||||||
|
# Keep branch updated during development
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git checkout feature/your-feature-name
|
||||||
|
git merge main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
### PR Title Format
|
||||||
|
- **Feature**: `feat: Add weather forecast improvements`
|
||||||
|
- **Fix**: `fix: Resolve leaderboard scrolling performance issue`
|
||||||
|
- **Refactor**: `refactor: Improve sports manager architecture`
|
||||||
|
- **Docs**: `docs: Update API integration guide`
|
||||||
|
- **Test**: `test: Add unit tests for weather manager`
|
||||||
|
|
||||||
|
### PR Description Template
|
||||||
|
```markdown
|
||||||
|
## Description
|
||||||
|
Brief description of changes and motivation.
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
- [ ] Bug fix (non-breaking change)
|
||||||
|
- [ ] New feature (non-breaking change)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Performance improvement
|
||||||
|
- [ ] Refactoring
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- [ ] Tested on Raspberry Pi hardware
|
||||||
|
- [ ] Verified display rendering works correctly
|
||||||
|
- [ ] Checked API integration functionality
|
||||||
|
- [ ] Tested error handling scenarios
|
||||||
|
|
||||||
|
## Screenshots/Videos
|
||||||
|
(If applicable, add screenshots or videos of the changes)
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] Code follows project style guidelines
|
||||||
|
- [ ] Self-review completed
|
||||||
|
- [ ] Comments added for complex logic
|
||||||
|
- [ ] No hardcoded values or API keys
|
||||||
|
- [ ] Error handling implemented
|
||||||
|
- [ ] Logging added where appropriate
|
||||||
|
```
|
||||||
|
|
||||||
|
### PR Review Requirements
|
||||||
|
|
||||||
|
#### For Reviewers
|
||||||
|
- **Code Quality**: Check for proper error handling, logging, and type hints
|
||||||
|
- **Architecture**: Ensure changes follow project patterns and don't break existing functionality
|
||||||
|
- **Performance**: Verify changes don't negatively impact display performance
|
||||||
|
- **Testing**: Confirm changes work on Raspberry Pi hardware
|
||||||
|
- **Documentation**: Check if documentation needs updates
|
||||||
|
|
||||||
|
#### For Authors
|
||||||
|
- **Self-Review**: Review your own PR before requesting review
|
||||||
|
- **Testing**: Test thoroughly on Pi hardware before submitting
|
||||||
|
- **Documentation**: Update relevant documentation if needed
|
||||||
|
- **Clean History**: Squash commits if necessary for clean history
|
||||||
|
|
||||||
|
## Commit Message Guidelines
|
||||||
|
|
||||||
|
### Format
|
||||||
|
```
|
||||||
|
type(scope): description
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Types
|
||||||
|
- **feat**: New feature
|
||||||
|
- **fix**: Bug fix
|
||||||
|
- **docs**: Documentation changes
|
||||||
|
- **style**: Code style changes (formatting, etc.)
|
||||||
|
- **refactor**: Code refactoring
|
||||||
|
- **test**: Adding or updating tests
|
||||||
|
- **chore**: Maintenance tasks
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
feat(weather): Add hourly forecast display
|
||||||
|
fix(nba): Resolve live score update issue
|
||||||
|
docs(api): Update ESPN API integration guide
|
||||||
|
refactor(sports): Improve base class architecture
|
||||||
|
```
|
||||||
|
|
||||||
|
## Merge Strategies
|
||||||
|
|
||||||
|
### Squash and Merge (Preferred)
|
||||||
|
- Use for feature branches and bug fixes
|
||||||
|
- Creates clean, linear history
|
||||||
|
- Combines all commits into single commit
|
||||||
|
|
||||||
|
### Merge Commit
|
||||||
|
- Use for complex features with multiple logical commits
|
||||||
|
- Preserves commit history
|
||||||
|
- Use when commit messages are meaningful
|
||||||
|
|
||||||
|
### Rebase and Merge
|
||||||
|
- Use sparingly for simple, single-commit changes
|
||||||
|
- Creates linear history without merge commits
|
||||||
|
|
||||||
|
## Release Management
|
||||||
|
|
||||||
|
### Version Tags
|
||||||
|
- Use semantic versioning: `v1.2.3`
|
||||||
|
- Tag releases on `main` branch
|
||||||
|
- Create release notes with technical details
|
||||||
|
|
||||||
|
### Release Branches
|
||||||
|
- **Format**: `release/v1.2.3`
|
||||||
|
- Use for release preparation
|
||||||
|
- Include version bumps and final testing
|
||||||
|
|
||||||
|
## Emergency Procedures
|
||||||
|
|
||||||
|
### Hotfix Process
|
||||||
|
1. Create `hotfix/` branch from `main`
|
||||||
|
2. Make minimal fix
|
||||||
|
3. Test thoroughly
|
||||||
|
4. Create PR with expedited review
|
||||||
|
5. Merge to `main` and tag release
|
||||||
|
6. Cherry-pick to other branches if needed
|
||||||
|
|
||||||
|
### Rollback Process
|
||||||
|
1. Identify last known good commit
|
||||||
|
2. Create revert PR if possible
|
||||||
|
3. Use `git revert` for clean rollback
|
||||||
|
4. Tag rollback release
|
||||||
|
5. Document issue and resolution
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Before Creating PR
|
||||||
|
- [ ] Run all tests locally
|
||||||
|
- [ ] Test on Raspberry Pi hardware
|
||||||
|
- [ ] Check for linting errors
|
||||||
|
- [ ] Update documentation if needed
|
||||||
|
- [ ] Ensure commit messages are clear
|
||||||
|
|
||||||
|
### During Development
|
||||||
|
- [ ] Keep branches small and focused
|
||||||
|
- [ ] Commit frequently with meaningful messages
|
||||||
|
- [ ] Update branch regularly with main
|
||||||
|
- [ ] Test changes incrementally
|
||||||
|
|
||||||
|
### After PR Approval
|
||||||
|
- [ ] Delete feature branch after merge
|
||||||
|
- [ ] Update local main branch
|
||||||
|
- [ ] Verify changes work in production
|
||||||
|
- [ ] Update any related documentation
|
||||||
23
.cursor/rules/project-structure.mdc
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# LEDMatrix Project Structure
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
- **Main entry point**: [run.py](mdc:run.py) - Primary application launcher
|
||||||
|
- **Configuration**: [config/config.json](mdc:config/config.json) - Main configuration file
|
||||||
|
- **Display management**: [src/display_controller.py](mdc:src/display_controller.py) - Core display logic
|
||||||
|
- **Web interface**: [web_interface_v2.py](mdc:web_interface_v2.py) - Modern web UI
|
||||||
|
|
||||||
|
## Source Code Organization
|
||||||
|
- **Managers**: [src/](mdc:src/) - All sports/weather/stock managers
|
||||||
|
- **Assets**: [assets/](mdc:assets/) - Logos, fonts, and static resources
|
||||||
|
- **Tests**: [test/](mdc:test/) - Unit and integration tests
|
||||||
|
- **Documentation**: [LEDMatrix.wiki/](mdc:LEDMatrix.wiki/) - Comprehensive guides
|
||||||
|
|
||||||
|
## Key Design Principles
|
||||||
|
- **Single Responsibility**: Each manager handles one sport/domain
|
||||||
|
- **Consistent Patterns**: All managers follow similar structure
|
||||||
|
- **Configuration-Driven**: Behavior controlled via [config/config.json](mdc:config/config.json)
|
||||||
|
- **Raspberry Pi Focus**: Optimized for Pi hardware, not Windows development
|
||||||
41
.cursor/rules/raspberry-pi-development.mdc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raspberry Pi Development Guidelines
|
||||||
|
|
||||||
|
## Hardware Constraints
|
||||||
|
- **Pi-only execution**: Code must run on Raspberry Pi, not Windows development machine
|
||||||
|
- **LED matrix library**: Uses [rpi-rgb-led-matrix-master/](mdc:rpi-rgb-led-matrix-master/) for hardware control
|
||||||
|
- **Memory limitations**: Optimize for Pi's limited RAM
|
||||||
|
- **Performance**: Consider Pi's CPU capabilities in design
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
- **Local development**: Write and test code on Windows
|
||||||
|
- **Pi deployment**: Deploy and test on actual Pi hardware
|
||||||
|
- **SSH access**: Use SSH for Pi-based testing and debugging
|
||||||
|
- **Service management**: Use systemd services for production deployment
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
- **Unit tests**: Test logic without hardware dependencies
|
||||||
|
- **Integration tests**: Test with mock display managers
|
||||||
|
- **Hardware tests**: Validate on actual Pi with LED matrix
|
||||||
|
- **Performance tests**: Monitor memory and CPU usage
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
- **Service files**: [ledmatrix.service](mdc:ledmatrix.service), [ledmatrix-web.service](mdc:ledmatrix-web.service)
|
||||||
|
- **Installation scripts**: [first_time_install.sh](mdc:first_time_install.sh), [install_service.sh](mdc:install_service.sh)
|
||||||
|
- **Dependencies**: [requirements.txt](mdc:requirements.txt) for Pi environment
|
||||||
|
- **Permissions**: Handle file permissions for Pi user
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
- **Caching**: Use [src/cache_manager.py](mdc:src/cache_manager.py) for data persistence
|
||||||
|
- **Background services**: Non-blocking data fetching
|
||||||
|
- **Memory management**: Clean up resources regularly
|
||||||
|
- **Display optimization**: Minimize unnecessary redraws
|
||||||
|
|
||||||
|
## Debugging on Pi
|
||||||
|
- **Logging**: Comprehensive logging for remote debugging
|
||||||
|
- **Error reporting**: Clear error messages for troubleshooting
|
||||||
|
- **Status monitoring**: Health checks and status reporting
|
||||||
|
- **Remote access**: Web interface for configuration and monitoring
|
||||||
42
.cursor/rules/sports-managers.mdc
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
globs: src/*_managers.py
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sports Manager Development
|
||||||
|
|
||||||
|
## Manager Architecture
|
||||||
|
All sports managers inherit from base classes and follow consistent patterns:
|
||||||
|
- **Base classes**: [src/nhl_managers.py](mdc:src/nhl_managers.py), [src/nfl_managers.py](mdc:src/nfl_managers.py)
|
||||||
|
- **Common functionality**: Data fetching, caching, display rendering
|
||||||
|
- **Configuration-driven**: Behavior controlled via config sections
|
||||||
|
|
||||||
|
## Required Methods
|
||||||
|
```python
|
||||||
|
def __init__(self, config, display_manager, cache_manager)
|
||||||
|
def update(self) # Fetch fresh data
|
||||||
|
def display(self, force_clear=False) # Render current data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Flow Pattern
|
||||||
|
1. **Fetch**: Get data from API (with caching)
|
||||||
|
2. **Process**: Extract relevant game information
|
||||||
|
3. **Filter**: Apply favorite team preferences
|
||||||
|
4. **Display**: Render to LED matrix
|
||||||
|
|
||||||
|
## Logging Standards
|
||||||
|
- **Structured prefixes**: `[NHL Recent]`, `[NFL Live]`, etc.
|
||||||
|
- **Context information**: Include team names, game status, dates
|
||||||
|
- **Debug levels**: Use appropriate log levels (info, debug, warning, error)
|
||||||
|
- **User-friendly messages**: Explain what's happening and why
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
- **API failures**: Log and continue with cached data if available
|
||||||
|
- **No data scenarios**: Distinguish between API issues vs no games available
|
||||||
|
- **Off-season awareness**: Provide helpful context during non-active periods
|
||||||
|
- **Fallback behavior**: Show alternative content when preferred content unavailable
|
||||||
|
|
||||||
|
## Configuration Integration
|
||||||
|
- **Required settings**: Validate on initialization
|
||||||
|
- **Optional settings**: Provide sensible defaults
|
||||||
|
- **Background service**: Use for non-blocking data fetching
|
||||||
|
- **Caching strategy**: Implement intelligent cache management
|
||||||
51
.cursor/rules/testing-standards.mdc
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
globs: test/*.py,src/*.py
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Standards
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
- **Test directory**: [test/](mdc:test/) - All test files
|
||||||
|
- **Unit tests**: Test individual components in isolation
|
||||||
|
- **Integration tests**: Test component interactions
|
||||||
|
- **Hardware tests**: Validate on Raspberry Pi with actual LED matrix
|
||||||
|
|
||||||
|
## Testing Principles
|
||||||
|
- **Test behavior, not implementation**: Focus on what the code does, not how
|
||||||
|
- **Mock external dependencies**: Use mocks for APIs, display managers, cache
|
||||||
|
- **Test edge cases**: Empty data, API failures, configuration errors
|
||||||
|
- **Pi-specific testing**: Validate hardware integration
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
```python
|
||||||
|
def test_manager_initialization():
|
||||||
|
"""Test that manager initializes with valid config"""
|
||||||
|
config = {"sport_scoreboard": {"enabled": True}}
|
||||||
|
manager = ManagerClass(config, mock_display, mock_cache)
|
||||||
|
assert manager.enabled == True
|
||||||
|
|
||||||
|
def test_api_failure_handling():
|
||||||
|
"""Test graceful handling of API failures"""
|
||||||
|
# Test that system continues when API fails
|
||||||
|
# Verify fallback to cached data
|
||||||
|
# Check appropriate error logging
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mock Patterns
|
||||||
|
- **Display Manager**: Mock for testing without hardware
|
||||||
|
- **Cache Manager**: Mock for testing data persistence
|
||||||
|
- **API responses**: Mock for consistent test data
|
||||||
|
- **Configuration**: Use test-specific configs
|
||||||
|
|
||||||
|
## Test Categories
|
||||||
|
- **Unit tests**: Individual manager methods
|
||||||
|
- **Integration tests**: Manager interactions with services
|
||||||
|
- **Configuration tests**: Validate config loading and validation
|
||||||
|
- **Error handling tests**: API failures, invalid data, edge cases
|
||||||
|
|
||||||
|
## Testing Best Practices
|
||||||
|
- **Descriptive names**: Test names should explain what they test
|
||||||
|
- **Single responsibility**: Each test should verify one thing
|
||||||
|
- **Independent tests**: Tests should not depend on each other
|
||||||
|
- **Clean setup/teardown**: Reset state between tests
|
||||||
|
- **Pi compatibility**: Ensure tests work in Pi environment
|
||||||
1
.cursorignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
||||||
363
.cursorrules
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
# LEDMatrix Plugin Development Rules
|
||||||
|
|
||||||
|
## 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 directory configured by
|
||||||
|
`plugin_system.plugins_directory` in `config.json` — the default is
|
||||||
|
`plugin-repos/` (per `config/config.template.json:130`).
|
||||||
|
|
||||||
|
> **Fallback note (scoped):** `PluginManager.discover_plugins()`
|
||||||
|
> (`src/plugin_system/plugin_manager.py:154`) only scans the
|
||||||
|
> configured directory — there is no fallback to `plugins/` in the
|
||||||
|
> main discovery path. A fallback to `plugins/` does exist in two
|
||||||
|
> narrower places:
|
||||||
|
> - `store_manager.py:1700-1718` — store operations (install/update/
|
||||||
|
> uninstall) check `plugins/` if the plugin isn't found in the
|
||||||
|
> configured directory, so plugin-store flows work even when your
|
||||||
|
> dev symlinks live in `plugins/`.
|
||||||
|
> - `schema_manager.py:70-80` — `get_schema_path()` probes both
|
||||||
|
> `plugins/` and `plugin-repos/` for `config_schema.json` so the
|
||||||
|
> web UI form generation finds the schema regardless of where the
|
||||||
|
> plugin lives.
|
||||||
|
>
|
||||||
|
> The dev workflow in `scripts/dev/dev_plugin_setup.sh` creates
|
||||||
|
> symlinks under `plugins/`, which is why the store and schema
|
||||||
|
> fallbacks exist. For day-to-day development, set
|
||||||
|
> `plugin_system.plugins_directory` to `plugins` so the main
|
||||||
|
> discovery path picks up your symlinks.
|
||||||
|
|
||||||
|
## Plugin Structure
|
||||||
|
|
||||||
|
### Required Files
|
||||||
|
- **manifest.json**: Plugin metadata, entry point, class name, dependencies
|
||||||
|
- **manager.py**: Main plugin class (must inherit from `BasePlugin`)
|
||||||
|
- **config_schema.json**: JSON schema for plugin configuration validation
|
||||||
|
- **requirements.txt**: Python dependencies (if any)
|
||||||
|
- **README.md**: Plugin documentation
|
||||||
|
|
||||||
|
### Plugin Class Requirements
|
||||||
|
- Must inherit from `src.plugin_system.base_plugin.BasePlugin`
|
||||||
|
- Must implement `update()` method for data fetching
|
||||||
|
- Must implement `display()` method for rendering
|
||||||
|
- Should implement `validate_config()` for configuration validation
|
||||||
|
- Optional: Override `has_live_content()` for live priority features
|
||||||
|
|
||||||
|
## Plugin Development Workflow
|
||||||
|
|
||||||
|
### 1. Creating a New Plugin
|
||||||
|
|
||||||
|
**Option A: Use dev_plugin_setup.sh (Recommended)**
|
||||||
|
```bash
|
||||||
|
# Link from GitHub
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link-github <plugin-name>
|
||||||
|
|
||||||
|
# Link local repository
|
||||||
|
./scripts/dev/dev_plugin_setup.sh link <plugin-name> <path-to-repo>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Manual Setup**
|
||||||
|
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
|
||||||
|
5. Enable plugin in `config/config.json` under `"<plugin-id>": {"enabled": true}`
|
||||||
|
|
||||||
|
### 2. Plugin Configuration
|
||||||
|
|
||||||
|
Plugins are configured in `config/config.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"<plugin-id>": {
|
||||||
|
"enabled": true,
|
||||||
|
"display_duration": 15,
|
||||||
|
"live_priority": false,
|
||||||
|
"high_performance_transitions": false,
|
||||||
|
"transition": {
|
||||||
|
"type": "redraw",
|
||||||
|
"speed": 2,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
// ... plugin-specific config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Testing Plugins
|
||||||
|
|
||||||
|
**On Development Machine:**
|
||||||
|
- 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:
|
||||||
|
`python3 run.py --emulator` (or equivalently
|
||||||
|
`EMULATOR=true python3 run.py`, or `./scripts/dev/run_emulator.sh`).
|
||||||
|
The `-e`/`--emulator` CLI flag is defined in `run.py:19-20`.
|
||||||
|
- Test plugin loading: Check logs for plugin discovery and loading
|
||||||
|
- Validate configuration: Ensure config matches `config_schema.json`
|
||||||
|
|
||||||
|
**On Raspberry Pi:**
|
||||||
|
- Deploy and test on actual hardware
|
||||||
|
- Monitor logs: `journalctl -u ledmatrix -f` (if running as service)
|
||||||
|
- Check plugin status in web interface
|
||||||
|
|
||||||
|
### 4. Plugin Development Best Practices
|
||||||
|
|
||||||
|
**Code Organization:**
|
||||||
|
- 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 — 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` 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:**
|
||||||
|
- Use plugin's logger: `self.logger.info/error/warning()`
|
||||||
|
- Handle API failures gracefully
|
||||||
|
- Cache data to avoid excessive API calls
|
||||||
|
- Provide fallback displays when data unavailable
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Use `cache_manager` for API response caching
|
||||||
|
- Implement background data fetching if needed
|
||||||
|
- Use `high_performance_transitions` for smoother animations
|
||||||
|
- Optimize rendering for Pi's limited resources
|
||||||
|
|
||||||
|
**Display Rendering:**
|
||||||
|
- Use `display_manager` for all drawing operations
|
||||||
|
- Support different display sizes (check `display_manager.width/height`)
|
||||||
|
- Use `apply_transition()` for smooth transitions between displays
|
||||||
|
- Clear display before rendering: `display_manager.clear()`
|
||||||
|
- Always call `display_manager.update_display()` after rendering
|
||||||
|
|
||||||
|
## Plugin API Reference
|
||||||
|
|
||||||
|
### BasePlugin Class
|
||||||
|
Located in: `src/plugin_system/base_plugin.py`
|
||||||
|
|
||||||
|
**Required Methods:**
|
||||||
|
- `update()`: Fetch/update data (called based on `update_interval` in manifest)
|
||||||
|
- `display(force_clear=False)`: Render plugin content
|
||||||
|
|
||||||
|
**Optional Methods:**
|
||||||
|
- `validate_config()`: Validate plugin configuration
|
||||||
|
- `has_live_content()`: Return True if plugin has live/urgent content
|
||||||
|
- `get_live_modes()`: Return list of modes for live priority
|
||||||
|
- `cleanup()`: Clean up resources on unload
|
||||||
|
- `on_config_change(new_config)`: Handle config updates
|
||||||
|
- `on_enable()`: Called when plugin enabled
|
||||||
|
- `on_disable()`: Called when plugin disabled
|
||||||
|
|
||||||
|
**Available Properties:**
|
||||||
|
- `self.plugin_id`: Plugin identifier
|
||||||
|
- `self.config`: Plugin configuration dict
|
||||||
|
- `self.display_manager`: Display manager instance
|
||||||
|
- `self.cache_manager`: Cache manager instance
|
||||||
|
- `self.plugin_manager`: Plugin manager reference
|
||||||
|
- `self.logger`: Plugin-specific logger
|
||||||
|
- `self.enabled`: Boolean enabled status
|
||||||
|
- `self.transition_manager`: Transition system (if available)
|
||||||
|
|
||||||
|
### Display Manager
|
||||||
|
Located in: `src/display_manager.py`
|
||||||
|
|
||||||
|
**Key Methods:**
|
||||||
|
- `clear()`: Clear the 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=300)`: Get cached value (returns None if missing/stale)
|
||||||
|
- `set(key, value, ttl=None)`: Cache a 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
|
||||||
|
|
||||||
|
Required fields in `manifest.json`:
|
||||||
|
- `id`: Unique plugin identifier (matches directory name)
|
||||||
|
- `name`: Human-readable plugin name
|
||||||
|
- `version`: Semantic version (e.g., "1.0.0")
|
||||||
|
- `entry_point`: Python file (usually "manager.py")
|
||||||
|
- `class_name`: Plugin class name (must match class in entry_point)
|
||||||
|
- `display_modes`: Array of mode names this plugin provides
|
||||||
|
|
||||||
|
Common optional fields:
|
||||||
|
- `description`: Plugin description
|
||||||
|
- `author`: Plugin author
|
||||||
|
- `homepage`: Plugin homepage URL
|
||||||
|
- `category`: Plugin category (e.g., "sports", "weather")
|
||||||
|
- `tags`: Array of tags
|
||||||
|
- `update_interval`: Seconds between update() calls (default: 60)
|
||||||
|
- `default_duration`: Default display duration (default: 15)
|
||||||
|
- `requires`: Python version, display size requirements
|
||||||
|
- `config_schema`: Path to config schema file
|
||||||
|
- `api_requirements`: API dependencies and rate limits
|
||||||
|
|
||||||
|
## Plugin Loading Process
|
||||||
|
|
||||||
|
1. **Discovery**: PluginManager scans `plugins/` directory for directories containing `manifest.json`
|
||||||
|
2. **Validation**: Validates manifest structure and required fields
|
||||||
|
3. **Loading**: Imports plugin module and instantiates plugin class
|
||||||
|
4. **Configuration**: Loads plugin config from `config/config.json`
|
||||||
|
5. **Validation**: Calls `validate_config()` on plugin instance
|
||||||
|
6. **Registration**: Adds plugin to available modes and stores instance
|
||||||
|
7. **Enablement**: Calls `on_enable()` if plugin is enabled
|
||||||
|
|
||||||
|
## Common Plugin Patterns
|
||||||
|
|
||||||
|
### Sports Scoreboard Plugin
|
||||||
|
- Use `background_data_service.py` pattern for API fetching
|
||||||
|
- Implement live/recent/upcoming game modes
|
||||||
|
- Use `scoreboard_renderer.py` for consistent rendering
|
||||||
|
- Support team filtering and game filtering
|
||||||
|
- Use shared sports logos from `assets/sports/`
|
||||||
|
|
||||||
|
### Data Display Plugin
|
||||||
|
- Fetch data in `update()` method
|
||||||
|
- Cache API responses using `cache_manager`
|
||||||
|
- Render in `display()` method
|
||||||
|
- Handle API errors gracefully
|
||||||
|
- Provide configuration for refresh intervals
|
||||||
|
|
||||||
|
### Real-time Content Plugin
|
||||||
|
- Implement `has_live_content()` for live priority
|
||||||
|
- Use `get_live_modes()` to specify which modes are live
|
||||||
|
- Set `live_priority: true` in config to enable live takeover
|
||||||
|
- Update data frequently when live content exists
|
||||||
|
|
||||||
|
## Debugging Plugins
|
||||||
|
|
||||||
|
**Check Plugin Loading:**
|
||||||
|
- Review logs for plugin discovery messages
|
||||||
|
- Verify manifest.json syntax is valid JSON
|
||||||
|
- Check that class_name matches actual class name
|
||||||
|
- Ensure entry_point file exists and is importable
|
||||||
|
|
||||||
|
**Check Plugin Execution:**
|
||||||
|
- Add logging statements in `update()` and `display()`
|
||||||
|
- Use `self.logger` for plugin-specific logging
|
||||||
|
- Check cache_manager for cached data
|
||||||
|
- Verify display_manager is rendering correctly
|
||||||
|
|
||||||
|
**Common Issues:**
|
||||||
|
- Import errors: Check Python path and dependencies
|
||||||
|
- Config errors: Validate against config_schema.json
|
||||||
|
- Display issues: Check display dimensions and coordinate calculations
|
||||||
|
- Performance: Monitor CPU/memory usage on Pi
|
||||||
|
|
||||||
|
## Plugin Testing
|
||||||
|
|
||||||
|
**Unit Tests:**
|
||||||
|
- Test plugin class instantiation
|
||||||
|
- Test `update()` data fetching logic
|
||||||
|
- Test `display()` rendering logic
|
||||||
|
- Test `validate_config()` with various configs
|
||||||
|
- Mock `display_manager` and `cache_manager` for testing
|
||||||
|
|
||||||
|
**Integration Tests:**
|
||||||
|
- Test plugin loading via PluginManager
|
||||||
|
- Test plugin with actual config
|
||||||
|
- Test plugin with emulator display
|
||||||
|
- Test plugin with cache_manager
|
||||||
|
|
||||||
|
**Hardware Tests:**
|
||||||
|
- Test on Raspberry Pi with LED matrix
|
||||||
|
- Verify display rendering on actual hardware
|
||||||
|
- Test performance under load
|
||||||
|
- Test with other plugins enabled
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
plugins/
|
||||||
|
<plugin-id>/
|
||||||
|
manifest.json # Plugin metadata
|
||||||
|
manager.py # Main plugin class
|
||||||
|
config_schema.json # Config validation schema
|
||||||
|
requirements.txt # Python dependencies
|
||||||
|
README.md # Plugin documentation
|
||||||
|
# Plugin-specific files
|
||||||
|
data_manager.py
|
||||||
|
renderer.py
|
||||||
|
etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git Workflow for Plugins
|
||||||
|
|
||||||
|
**Plugin Development:**
|
||||||
|
- Plugins are typically separate repositories
|
||||||
|
- Use `dev_plugin_setup.sh` to link plugins for development
|
||||||
|
- Symlinks are used to connect plugin repos to `plugins/` directory
|
||||||
|
- Plugin repos follow naming: `ledmatrix-<plugin-name>`
|
||||||
|
|
||||||
|
**Branching:**
|
||||||
|
- Develop plugins in feature branches
|
||||||
|
- Follow project branching conventions
|
||||||
|
- Test plugins before merging to main
|
||||||
|
|
||||||
|
**Automatic Version Bumping:**
|
||||||
|
- **Automatic Version Management**: Version bumping is handled automatically via the pre-push git hook - no manual version bumping is required for normal development workflows
|
||||||
|
- **GitHub as Source of Truth**: Plugin store always fetches latest versions from GitHub (releases/tags/manifest/commit)
|
||||||
|
- **Pre-Push Hook**: Automatically bumps patch version and creates git tags when pushing code changes
|
||||||
|
- The hook is self-contained (no external dependencies) and works on any dev machine
|
||||||
|
- Installation: Copy the hook from LEDMatrix repo to your plugin repo:
|
||||||
|
```bash
|
||||||
|
# From your plugin repository directory
|
||||||
|
cp /path/to/LEDMatrix/scripts/git-hooks/pre-push-plugin-version .git/hooks/pre-push
|
||||||
|
chmod +x .git/hooks/pre-push
|
||||||
|
```
|
||||||
|
- Or use the installer script from the main LEDMatrix repo (one-time setup)
|
||||||
|
- The hook automatically:
|
||||||
|
1. Bumps the patch version (x.y.Z) in manifest.json when code changes are detected
|
||||||
|
2. Creates a git tag (v{version}) for the new version
|
||||||
|
3. Stages manifest.json for commit
|
||||||
|
- Skip auto-tagging: Set `SKIP_TAG=1` environment variable before pushing
|
||||||
|
- **Manual Version Bumping (Edge Cases Only)**: Manual version bumps are only needed in rare circumstances:
|
||||||
|
- CI/CD pipelines that bypass git hooks
|
||||||
|
- Forked repositories without the pre-push hook installed
|
||||||
|
- Major/minor version bumps (hook only handles patch versions)
|
||||||
|
- When skipping auto-tagging but still needing a version bump
|
||||||
|
- For manual bumps, use the standalone script: `scripts/bump_plugin_version.py`
|
||||||
|
- **Registry**: The plugin registry (plugins.json) stores only metadata (name, description, repo URL) - no versions
|
||||||
|
- **Version Priority**: Plugin store checks versions in this order: GitHub Releases → GitHub Tags → Manifest from branch → Git commit hash
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- Plugin System Docs: `docs/PLUGIN_ARCHITECTURE_SPEC.md`
|
||||||
|
- Plugin Examples: `plugins/hockey-scoreboard/`, `plugins/football-scoreboard/`
|
||||||
|
- Base Plugin: `src/plugin_system/base_plugin.py`
|
||||||
|
- Plugin Manager: `src/plugin_system/plugin_manager.py`
|
||||||
|
- Development Setup: `dev_plugin_setup.sh`
|
||||||
|
- Example Config: `dev_plugins.json.example`
|
||||||
|
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github: ChuckBuilds
|
||||||
|
buy_me_a_coffee: chuckbuilds
|
||||||
|
ko_fi: chuckbuilds
|
||||||
|
|
||||||
84
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a problem with LEDMatrix
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Before filing: please check existing issues to see if this is already
|
||||||
|
reported. For security issues, see SECURITY.md and report privately.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Describe the bug
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
|
||||||
|
<!-- 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. -->
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
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. -->
|
||||||
28
.gitignore
vendored
@@ -5,6 +5,9 @@ __pycache__/
|
|||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
config/config_secrets.json
|
config/config_secrets.json
|
||||||
|
config/config.json
|
||||||
|
config/config.json.backup
|
||||||
|
config/wifi_config.json
|
||||||
credentials.json
|
credentials.json
|
||||||
token.pickle
|
token.pickle
|
||||||
|
|
||||||
@@ -13,6 +16,7 @@ token.pickle
|
|||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
|
venv*/
|
||||||
ENV/
|
ENV/
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
@@ -20,6 +24,26 @@ ENV/
|
|||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
emulator_config.json
|
||||||
|
|
||||||
# Cache directory
|
# Testing
|
||||||
cache/
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
# Cache directory (root level only, not src/cache which is source code)
|
||||||
|
/cache/
|
||||||
|
|
||||||
|
# Development plugins directory
|
||||||
|
# Plugins are managed as separate repositories via multi-root workspace
|
||||||
|
# See docs/MULTI_ROOT_WORKSPACE_SETUP.md for details
|
||||||
|
plugins/*
|
||||||
|
!plugins/.gitkeep
|
||||||
|
|
||||||
|
# Binary files and backups
|
||||||
|
bin/pixlet/
|
||||||
|
config/backups/
|
||||||
|
|
||||||
|
# Starlark apps runtime storage (installed .star files and cached renders)
|
||||||
|
/starlark-apps/
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "rpi-rgb-led-matrix-master"]
|
||||||
|
path = rpi-rgb-led-matrix-master
|
||||||
|
url = https://github.com/hzeller/rpi-rgb-led-matrix.git
|
||||||
64
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Pre-commit hooks for LEDMatrix
|
||||||
|
# Install: pip install pre-commit && pre-commit install
|
||||||
|
# Run manually: pre-commit run --all-files
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-json
|
||||||
|
- id: check-added-large-files
|
||||||
|
args: ['--maxkb=1000']
|
||||||
|
- id: check-merge-conflict
|
||||||
|
|
||||||
|
- repo: https://github.com/PyCQA/flake8
|
||||||
|
rev: 7.0.0
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
args: ['--select=E9,F63,F7,F82,B', '--ignore=E501']
|
||||||
|
additional_dependencies: [flake8-bugbear]
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: no-bare-except
|
||||||
|
name: Check for bare except clauses
|
||||||
|
entry: bash -c 'if grep -rn "except:\s*pass" src/; then echo "Found bare except:pass - please handle exceptions properly"; exit 1; fi'
|
||||||
|
language: system
|
||||||
|
types: [python]
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
|
- id: no-hardcoded-paths
|
||||||
|
name: Check for hardcoded user paths
|
||||||
|
entry: bash -c 'if grep -rn "/home/chuck/" src/; then echo "Found hardcoded user paths - please use relative paths or config"; exit 1; fi'
|
||||||
|
language: system
|
||||||
|
types: [python]
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.8.0
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
additional_dependencies: [types-requests, types-pytz]
|
||||||
|
args: [--ignore-missing-imports, --no-error-summary]
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^src/
|
||||||
|
|
||||||
|
- repo: https://github.com/PyCQA/bandit
|
||||||
|
rev: 1.8.3
|
||||||
|
hooks:
|
||||||
|
- id: bandit
|
||||||
|
args:
|
||||||
|
- '-r'
|
||||||
|
- '-ll'
|
||||||
|
- '-c'
|
||||||
|
- 'bandit.yaml'
|
||||||
|
- '-x'
|
||||||
|
- './tests,./test,./venv,./.venv,./scripts/prove_security.py,./rpi-rgb-led-matrix-master'
|
||||||
|
|
||||||
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
|
rev: v8.24.3
|
||||||
|
hooks:
|
||||||
|
- id: gitleaks
|
||||||
37
CLAUDE.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# LEDMatrix
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- `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)
|
||||||
|
- `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`
|
||||||
|
- Required abstract methods: `update()`, `display(force_clear=False)`
|
||||||
|
- Each plugin needs: `manifest.json`, `config_schema.json`, `manager.py`, `requirements.txt`
|
||||||
|
- Plugin instantiation args: `plugin_id, config, display_manager, cache_manager, plugin_manager`
|
||||||
|
- Config schemas use JSON Schema Draft-7
|
||||||
|
- Display dimensions: always read dynamically from `self.display_manager.matrix.width/height`
|
||||||
|
|
||||||
|
## Plugin Store Architecture
|
||||||
|
- Official plugins live in the `ledmatrix-plugins` monorepo (not individual repos)
|
||||||
|
- Plugin repo naming convention: `ledmatrix-<plugin-id>` (e.g., `ledmatrix-football-scoreboard`)
|
||||||
|
- `plugins.json` registry at `https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins.json`
|
||||||
|
- Store manager (`src/plugin_system/store_manager.py`) handles install/update/uninstall
|
||||||
|
- Monorepo plugins are installed via ZIP extraction (no `.git` directory)
|
||||||
|
- Update detection for monorepo plugins uses version comparison (manifest version vs registry latest_version)
|
||||||
|
- Plugin configs stored in `config/config.json`, NOT in plugin directories — safe across reinstalls
|
||||||
|
- Third-party plugins can use their own repo URL with empty `plugin_path`
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
- paho-mqtt 2.x needs `callback_api_version=mqtt.CallbackAPIVersion.VERSION1` for v1 compat
|
||||||
|
- BasePlugin uses `get_logger()` from `src.logging_config`, not standard `logging.getLogger()`
|
||||||
|
- When modifying a plugin in the monorepo, you MUST bump `version` in its `manifest.json` and run `python update_registry.py` — otherwise users won't receive the update
|
||||||
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
@@ -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.
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
# LED Matrix Installation Guide
|
|
||||||
|
|
||||||
## Quick Start (Recommended for First-Time Installation)
|
|
||||||
|
|
||||||
# System Setup & Installation
|
|
||||||
|
|
||||||
1. Open PowerShell and ssh into your Raspberry Pi with ledpi@ledpi (or Username@Hostname)
|
|
||||||
```bash
|
|
||||||
ssh ledpi@ledpi
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update repositories, upgrade raspberry pi OS, install git
|
|
||||||
```bash
|
|
||||||
sudo apt update && sudo apt upgrade -y
|
|
||||||
sudo apt install -y git python3-pip cython3 build-essential python3-dev python3-pillow scons
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Clone this repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/ChuckBuilds/LEDMatrix.git
|
|
||||||
cd LEDMatrix
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Install dependencies:
|
|
||||||
```bash
|
|
||||||
sudo pip3 install --break-system-packages -r requirements.txt
|
|
||||||
```
|
|
||||||
--break-system-packages allows us to install without a virtual environment
|
|
||||||
|
|
||||||
|
|
||||||
5. Install rpi-rgb-led-matrix dependencies:
|
|
||||||
```bash
|
|
||||||
cd rpi-rgb-led-matrix-master
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
sudo make build-python PYTHON=$(which python3)
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
cd bindings/python
|
|
||||||
sudo python3 setup.py install
|
|
||||||
```
|
|
||||||
Test it with:
|
|
||||||
```bash
|
|
||||||
python3 -c 'from rgbmatrix import RGBMatrix, RGBMatrixOptions; print("Success!")'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Important: Sound Module Configuration
|
|
||||||
|
|
||||||
1. Remove unnecessary services that might interfere with the LED matrix:
|
|
||||||
```bash
|
|
||||||
sudo apt-get remove bluez bluez-firmware pi-bluetooth triggerhappy pigpio
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Blacklist the sound module:
|
|
||||||
```bash
|
|
||||||
cat <<EOF | sudo tee /etc/modprobe.d/blacklist-rgb-matrix.conf
|
|
||||||
blacklist snd_bcm2835
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
then execute
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo update-initramfs -u
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Reboot:
|
|
||||||
```bash
|
|
||||||
sudo reboot
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
To reduce flickering and improve display quality:
|
|
||||||
|
|
||||||
1. Edit `/boot/firmware/cmdline.txt`:
|
|
||||||
```bash
|
|
||||||
sudo nano /boot/firmware/cmdline.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add `isolcpus=3` at the end of the line
|
|
||||||
|
|
||||||
3. Ctrl + X to exit, Y to save, Enter to Confirm
|
|
||||||
|
|
||||||
4. Edit /boot/firmware/config.txt with
|
|
||||||
```bash
|
|
||||||
sudo nano /boot/firmware/config.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Edit the `dtparam=audio=on` section to `dtparam=audio=off`
|
|
||||||
|
|
||||||
7. Ctrl + X to exit, Y to save, Enter to Confirm
|
|
||||||
|
|
||||||
8. Save and reboot:
|
|
||||||
```bash
|
|
||||||
sudo reboot
|
|
||||||
```
|
|
||||||
|
|
||||||
9. Run the first_time_install.sh with
|
|
||||||
```
|
|
||||||
sudo ./first_time_install.sh
|
|
||||||
```
|
|
||||||
to ensure all the permissions are correct.
|
|
||||||
|
|
||||||
10. Then run
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo python start_web_conditionally.py
|
|
||||||
```
|
|
||||||
to start the web ui and download the r
|
|
||||||
-----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
1.Edit `config/config.json` with your preferences via `sudo nano config/config.json`
|
|
||||||
|
|
||||||
###API Keys
|
|
||||||
|
|
||||||
For sensitive settings like API keys:
|
|
||||||
Copy the template: `cp config/config_secrets.template.json config/config_secrets.json`
|
|
||||||
Edit `config/config_secrets.json` with your API keys via `sudo nano config/config_secrets.json`
|
|
||||||
Ctrl + X to exit, Y to overwrite, Enter to Confirm
|
|
||||||
|
|
||||||
Everything is configured via `config/config.json` and `config/config_secrets.json`.
|
|
||||||
|
|
||||||
|
|
||||||
For a complete first-time installation, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
chmod +x first_time_install.sh
|
|
||||||
```
|
|
||||||
then
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ./first_time_install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This single script handles everything you need for a new installation.
|
|
||||||
|
|
||||||
## Individual Scripts Explained
|
|
||||||
|
|
||||||
### **First-Time Installation Scripts**
|
|
||||||
|
|
||||||
#### `first_time_install.sh` ⭐ **RECOMMENDED**
|
|
||||||
- **When to use**: New installations only
|
|
||||||
- **What it does**: Complete setup including all steps below
|
|
||||||
- **Usage**: `sudo ./first_time_install.sh`
|
|
||||||
|
|
||||||
### **Service Installation Scripts**
|
|
||||||
|
|
||||||
#### `install_service.sh`
|
|
||||||
- **When to use**: Install main LED Matrix display service
|
|
||||||
- **What it does**:
|
|
||||||
- Creates systemd service for main display
|
|
||||||
- Creates systemd service for web interface
|
|
||||||
- Enables services to start on boot
|
|
||||||
- **Usage**: `sudo ./install_service.sh`
|
|
||||||
|
|
||||||
#### `install_web_service.sh`
|
|
||||||
- **When to use**: Install only the web interface service (legacy)
|
|
||||||
- **What it does**: Installs the web interface systemd service
|
|
||||||
- **Usage**: `sudo ./install_web_service.sh`
|
|
||||||
- **Note**: `install_service.sh` now handles this automatically
|
|
||||||
|
|
||||||
### **Permission Fix Scripts**
|
|
||||||
|
|
||||||
#### `fix_cache_permissions.sh`
|
|
||||||
- **When to use**: When you see cache permission errors
|
|
||||||
- **What it does**:
|
|
||||||
- Creates cache directories (`/var/cache/ledmatrix`)
|
|
||||||
- Sets proper permissions for cache access
|
|
||||||
- Creates placeholder logo directories
|
|
||||||
- **Usage**: `sudo ./fix_cache_permissions.sh`
|
|
||||||
|
|
||||||
#### `fix_web_permissions.sh`
|
|
||||||
- **When to use**: When web interface can't access logs or system commands
|
|
||||||
- **What it does**:
|
|
||||||
- Adds user to `systemd-journal` group (for log access)
|
|
||||||
- Adds user to `adm` group (for system access)
|
|
||||||
- Sets proper file ownership
|
|
||||||
- **Usage**: `./fix_web_permissions.sh` (run as regular user)
|
|
||||||
|
|
||||||
#### `configure_web_sudo.sh`
|
|
||||||
- **When to use**: When web interface buttons don't work (sudo password errors)
|
|
||||||
- **What it does**:
|
|
||||||
- Configures passwordless sudo access for web interface
|
|
||||||
- Allows web interface to start/stop services without password
|
|
||||||
- **Usage**: `./configure_web_sudo.sh` (run as regular user)
|
|
||||||
|
|
||||||
### **Dependency Installation Scripts**
|
|
||||||
|
|
||||||
#### `install_dependencies_apt.py`
|
|
||||||
- **When to use**: When you want to install packages via apt first, then pip
|
|
||||||
- **What it does**:
|
|
||||||
- Tries to install packages via apt (system packages)
|
|
||||||
- Falls back to pip with `--break-system-packages`
|
|
||||||
- Handles externally managed Python environments
|
|
||||||
- **Usage**: `sudo python3 install_dependencies_apt.py`
|
|
||||||
|
|
||||||
#### `start_web_v2.py`
|
|
||||||
- **When to use**: Manual web interface startup
|
|
||||||
- **What it does**:
|
|
||||||
- Installs dependencies
|
|
||||||
- Starts web interface directly
|
|
||||||
- Includes comprehensive logging
|
|
||||||
- **Usage**: `python3 start_web_v2.py`
|
|
||||||
|
|
||||||
#### `run_web_v2.sh`
|
|
||||||
- **When to use**: Manual web interface startup (shell script version)
|
|
||||||
- **What it does**: Same as `start_web_v2.py` but as a shell script
|
|
||||||
- **Usage**: `./run_web_v2.sh`
|
|
||||||
|
|
||||||
### **Utility Scripts**
|
|
||||||
|
|
||||||
#### `cleanup_venv.sh`
|
|
||||||
- **When to use**: Remove virtual environment if you don't want to use it
|
|
||||||
- **What it does**: Removes `venv_web_v2` directory
|
|
||||||
- **Usage**: `./cleanup_venv.sh`
|
|
||||||
|
|
||||||
#### `start_web_conditionally.py`
|
|
||||||
- **When to use**: Called by systemd service (don't run manually)
|
|
||||||
- **What it does**:
|
|
||||||
- Checks config for `web_display_autostart` setting
|
|
||||||
- Starts web interface only if enabled
|
|
||||||
- Used by the systemd service
|
|
||||||
|
|
||||||
## Installation Scenarios
|
|
||||||
|
|
||||||
### **Scenario 1: Brand New Installation**
|
|
||||||
```bash
|
|
||||||
# One command does everything
|
|
||||||
sudo ./first_time_install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Scenario 2: Adding Web Interface to Existing Installation**
|
|
||||||
```bash
|
|
||||||
# Install web interface dependencies
|
|
||||||
sudo python3 install_dependencies_apt.py
|
|
||||||
|
|
||||||
# Fix permissions
|
|
||||||
./fix_web_permissions.sh
|
|
||||||
|
|
||||||
# Configure sudo access
|
|
||||||
./configure_web_sudo.sh
|
|
||||||
|
|
||||||
# Install services
|
|
||||||
sudo ./install_service.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Scenario 3: Fixing Permission Issues**
|
|
||||||
```bash
|
|
||||||
# Fix cache permissions
|
|
||||||
sudo ./fix_cache_permissions.sh
|
|
||||||
|
|
||||||
# Fix web interface permissions
|
|
||||||
./fix_web_permissions.sh
|
|
||||||
|
|
||||||
# Configure sudo access
|
|
||||||
./configure_web_sudo.sh
|
|
||||||
|
|
||||||
# Log out and back in for group changes to take effect
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Scenario 4: Manual Web Interface Startup**
|
|
||||||
```bash
|
|
||||||
# Start web interface manually (for testing)
|
|
||||||
python3 start_web_v2.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Post-Installation Steps
|
|
||||||
|
|
||||||
### **1. Log Out and Log Back In**
|
|
||||||
After running permission scripts, you need to log out and back in for group changes to take effect:
|
|
||||||
```bash
|
|
||||||
# Or use this command to apply group changes immediately
|
|
||||||
newgrp systemd-journal
|
|
||||||
```
|
|
||||||
|
|
||||||
### **2. Configure the Web Interface**
|
|
||||||
Edit `config/config.json` and set:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"web_display_autostart": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **3. Access the Web Interface**
|
|
||||||
Open your browser and go to:
|
|
||||||
```
|
|
||||||
http://your-pi-ip:5001
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4. Test Everything**
|
|
||||||
- Check if services are running: `sudo systemctl status ledmatrix.service`
|
|
||||||
- Check web interface: `sudo systemctl status ledmatrix-web.service`
|
|
||||||
- View logs: `journalctl -u ledmatrix.service -f`
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### **Web Interface Not Accessible**
|
|
||||||
1. Check if service is running: `sudo systemctl status ledmatrix-web.service`
|
|
||||||
2. Check logs: `journalctl -u ledmatrix-web.service -f`
|
|
||||||
3. Ensure `web_display_autostart` is `true` in config
|
|
||||||
|
|
||||||
### **Permission Errors**
|
|
||||||
1. Run: `./fix_web_permissions.sh`
|
|
||||||
2. Run: `./configure_web_sudo.sh`
|
|
||||||
3. Log out and back in
|
|
||||||
|
|
||||||
### **Cache Permission Errors**
|
|
||||||
1. Run: `sudo ./fix_cache_permissions.sh`
|
|
||||||
|
|
||||||
### **Sudo Password Prompts**
|
|
||||||
1. Run: `./configure_web_sudo.sh`
|
|
||||||
2. Log out and back in
|
|
||||||
|
|
||||||
### **Dependency Installation Errors**
|
|
||||||
1. Run: `sudo python3 install_dependencies_apt.py`
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
For **first-time installations**: Use `first_time_install.sh`
|
|
||||||
|
|
||||||
For **existing installations with issues**: Use the individual permission and configuration scripts as needed.
|
|
||||||
|
|
||||||
The `first_time_install.sh` script is designed to handle everything automatically, so you typically only need to run individual scripts if you're troubleshooting specific issues.
|
|
||||||
19
LEDMatrix.code-workspace
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": ".",
|
||||||
|
"name": "LEDMatrix (Main)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-plugins",
|
||||||
|
"name": "Plugins (Monorepo)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/__pycache__": true,
|
||||||
|
"**/*.pyc": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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>.
|
||||||
@@ -1 +0,0 @@
|
|||||||
This thing was created by Thingiverse user randomwire, and is licensed under cc.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
P4 Matrix Stand by randomwire on Thingiverse: https://www.thingiverse.com/thing:5169867
|
|
||||||
|
Before Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 230 KiB |
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.
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def add_custom_feed(feed_name, feed_url):
|
|
||||||
"""Add a custom RSS feed to the news manager configuration"""
|
|
||||||
config_path = "config/config.json"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Load current config
|
|
||||||
with open(config_path, 'r') as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
# Ensure news_manager section exists
|
|
||||||
if 'news_manager' not in config:
|
|
||||||
print("ERROR: News manager configuration not found!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Add custom feed
|
|
||||||
if 'custom_feeds' not in config['news_manager']:
|
|
||||||
config['news_manager']['custom_feeds'] = {}
|
|
||||||
|
|
||||||
config['news_manager']['custom_feeds'][feed_name] = feed_url
|
|
||||||
|
|
||||||
# Add to enabled feeds if not already there
|
|
||||||
if feed_name not in config['news_manager']['enabled_feeds']:
|
|
||||||
config['news_manager']['enabled_feeds'].append(feed_name)
|
|
||||||
|
|
||||||
# Save updated config
|
|
||||||
with open(config_path, 'w') as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
print(f"SUCCESS: Successfully added custom feed: {feed_name}")
|
|
||||||
print(f" URL: {feed_url}")
|
|
||||||
print(f" Feed is now enabled and will appear in rotation")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: Error adding custom feed: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def list_all_feeds():
|
|
||||||
"""List all available feeds (default + custom)"""
|
|
||||||
config_path = "config/config.json"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(config_path, 'r') as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
news_config = config.get('news_manager', {})
|
|
||||||
custom_feeds = news_config.get('custom_feeds', {})
|
|
||||||
enabled_feeds = news_config.get('enabled_feeds', [])
|
|
||||||
|
|
||||||
print("\nAvailable News Feeds:")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# Default feeds (hardcoded in news_manager.py)
|
|
||||||
default_feeds = {
|
|
||||||
'MLB': 'http://espn.com/espn/rss/mlb/news',
|
|
||||||
'NFL': 'http://espn.go.com/espn/rss/nfl/news',
|
|
||||||
'NCAA FB': 'https://www.espn.com/espn/rss/ncf/news',
|
|
||||||
'NHL': 'https://www.espn.com/espn/rss/nhl/news',
|
|
||||||
'NBA': 'https://www.espn.com/espn/rss/nba/news',
|
|
||||||
'TOP SPORTS': 'https://www.espn.com/espn/rss/news',
|
|
||||||
'BIG10': 'https://www.espn.com/blog/feed?blog=bigten',
|
|
||||||
'NCAA': 'https://www.espn.com/espn/rss/ncaa/news',
|
|
||||||
'Other': 'https://www.coveringthecorner.com/rss/current.xml'
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nDefault Sports Feeds:")
|
|
||||||
for name, url in default_feeds.items():
|
|
||||||
status = "ENABLED" if name in enabled_feeds else "DISABLED"
|
|
||||||
print(f" {name}: {status}")
|
|
||||||
print(f" {url}")
|
|
||||||
|
|
||||||
if custom_feeds:
|
|
||||||
print("\nCustom Feeds:")
|
|
||||||
for name, url in custom_feeds.items():
|
|
||||||
status = "ENABLED" if name in enabled_feeds else "DISABLED"
|
|
||||||
print(f" {name}: {status}")
|
|
||||||
print(f" {url}")
|
|
||||||
else:
|
|
||||||
print("\nCustom Feeds: None added yet")
|
|
||||||
|
|
||||||
print(f"\nCurrently Enabled Feeds: {len(enabled_feeds)}")
|
|
||||||
print(f" {', '.join(enabled_feeds)}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: Error listing feeds: {e}")
|
|
||||||
|
|
||||||
def remove_custom_feed(feed_name):
|
|
||||||
"""Remove a custom RSS feed"""
|
|
||||||
config_path = "config/config.json"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(config_path, 'r') as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
news_config = config.get('news_manager', {})
|
|
||||||
custom_feeds = news_config.get('custom_feeds', {})
|
|
||||||
|
|
||||||
if feed_name not in custom_feeds:
|
|
||||||
print(f"ERROR: Custom feed '{feed_name}' not found!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Remove from custom feeds
|
|
||||||
del config['news_manager']['custom_feeds'][feed_name]
|
|
||||||
|
|
||||||
# Remove from enabled feeds if present
|
|
||||||
if feed_name in config['news_manager']['enabled_feeds']:
|
|
||||||
config['news_manager']['enabled_feeds'].remove(feed_name)
|
|
||||||
|
|
||||||
# Save updated config
|
|
||||||
with open(config_path, 'w') as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
print(f"SUCCESS: Successfully removed custom feed: {feed_name}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: Error removing custom feed: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage:")
|
|
||||||
print(" python3 add_custom_feed_example.py list")
|
|
||||||
print(" python3 add_custom_feed_example.py add <feed_name> <feed_url>")
|
|
||||||
print(" python3 add_custom_feed_example.py remove <feed_name>")
|
|
||||||
print("\nExamples:")
|
|
||||||
print(" # Add F1 news feed")
|
|
||||||
print(" python3 add_custom_feed_example.py add 'F1' 'https://www.espn.com/espn/rss/rpm/news'")
|
|
||||||
print(" # Add BBC F1 feed")
|
|
||||||
print(" python3 add_custom_feed_example.py add 'BBC F1' 'http://feeds.bbci.co.uk/sport/formula1/rss.xml'")
|
|
||||||
print(" # Add personal blog feed")
|
|
||||||
print(" python3 add_custom_feed_example.py add 'My Blog' 'https://myblog.com/rss.xml'")
|
|
||||||
return
|
|
||||||
|
|
||||||
command = sys.argv[1].lower()
|
|
||||||
|
|
||||||
if command == 'list':
|
|
||||||
list_all_feeds()
|
|
||||||
elif command == 'add':
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print("ERROR: Usage: python3 add_custom_feed_example.py add <feed_name> <feed_url>")
|
|
||||||
return
|
|
||||||
feed_name = sys.argv[2]
|
|
||||||
feed_url = sys.argv[3]
|
|
||||||
add_custom_feed(feed_name, feed_url)
|
|
||||||
elif command == 'remove':
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print("ERROR: Usage: python3 add_custom_feed_example.py remove <feed_name>")
|
|
||||||
return
|
|
||||||
feed_name = sys.argv[2]
|
|
||||||
remove_custom_feed(feed_name)
|
|
||||||
else:
|
|
||||||
print(f"ERROR: Unknown command: {command}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
BIN
assets/broadcast_logos/paramount-plus.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/broadcast_logos/prime.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -9,20 +9,20 @@ from the utils/ directory.
|
|||||||
|
|
||||||
Tom-Thumb.bdf is included in this directory under [MIT license](http://vt100.tarunz.org/LICENSE). Tom-thumb.bdf was created by [@robey](http://twitter.com/robey) and originally published at https://robey.lag.net/2010/01/23/tiny-monospace-font.html
|
Tom-Thumb.bdf is included in this directory under [MIT license](http://vt100.tarunz.org/LICENSE). Tom-thumb.bdf was created by [@robey](http://twitter.com/robey) and originally published at https://robey.lag.net/2010/01/23/tiny-monospace-font.html
|
||||||
|
|
||||||
The texguire-27.bdf font was created using the [otf2bdf] tool from the TeX Gyre font.
|
The texgyre-27.bdf font was created using the [otf2bdf] tool from the TeX Gyre font.
|
||||||
```
|
```bash
|
||||||
otf2bdf -v -o texgyre-27.bdf -r 72 -p 27 texgyreadventor-regular.otf
|
otf2bdf -v -o texgyre-27.bdf -r 72 -p 27 texgyreadventor-regular.otf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Create your own
|
## Create your own
|
||||||
|
|
||||||
Fonts are in a human readable and editbable `*.bdf` format, but unless you
|
Fonts are in a human-readable and editable `*.bdf` format, but unless you
|
||||||
like reading and writing pixels in hex, generating them is probably easier :)
|
like reading and writing pixels in hex, generating them is probably easier :)
|
||||||
|
|
||||||
You can use any font-editor to generate a BDF font or use the conversion
|
You can use any font-editor to generate a BDF font or use the conversion
|
||||||
tool [otf2bdf] to create one from some other font format.
|
tool [otf2bdf] to create one from some other font format.
|
||||||
|
|
||||||
Here is an example how you could create a 30pixel high BDF font from some
|
Here is an example how you could create a 30-pixel high BDF font from some
|
||||||
TrueType font:
|
TrueType font:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -31,9 +31,9 @@ otf2bdf -v -o myfont.bdf -r 72 -p 30 /path/to/font-Bold.ttf
|
|||||||
|
|
||||||
## Getting otf2bdf
|
## Getting otf2bdf
|
||||||
|
|
||||||
Installing the tool should be fairly straight-foward
|
Installing the tool should be fairly straightforward.
|
||||||
|
|
||||||
```
|
```bash
|
||||||
sudo apt-get install otf2bdf
|
sudo apt-get install otf2bdf
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ If you like to compile otf2bdf, you might notice that the configure script
|
|||||||
uses some old way of getting the freetype configuration. There does not seem
|
uses some old way of getting the freetype configuration. There does not seem
|
||||||
to be much activity on the mature code, so let's patch that first:
|
to be much activity on the mature code, so let's patch that first:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
sudo apt-get install -y libfreetype6-dev pkg-config autoconf
|
sudo apt-get install -y libfreetype6-dev pkg-config autoconf
|
||||||
git clone https://github.com/jirutka/otf2bdf.git # check it out
|
git clone https://github.com/jirutka/otf2bdf.git # check it out
|
||||||
cd otf2bdf
|
cd otf2bdf
|
||||||
BIN
assets/news_logos/cbc.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/news_logos/cnn.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
@@ -1,404 +1,53 @@
|
|||||||
NCAAF
|
NCAAF
|
||||||
AAMU => Alabama A&M Bulldogs
|
AMH => Amherst Mammoths
|
||||||
ACU => Abilene Christian Wildcats
|
|
||||||
ADA => Adams State Grizzlies
|
|
||||||
ADR => Adrian Bulldogs
|
|
||||||
AFA => Air Force Falcons
|
|
||||||
AIC => American International Yellow Jackets
|
|
||||||
AKR => Akron Zips
|
|
||||||
ALA => Alabama Crimson Tide
|
|
||||||
ALB => Albright Lions
|
|
||||||
ALBS => Albany State (GA) Golden Rams
|
|
||||||
ALCN => Alcorn State Braves
|
|
||||||
ALD => Alderson Broaddus Battlers
|
|
||||||
ALF => Alfred Saxons
|
|
||||||
ALL => Allegheny Gators
|
|
||||||
ALST => Alabama State Hornets
|
|
||||||
AMH => Amherst College Mammoths
|
|
||||||
AND => Anderson (IN) Ravens
|
|
||||||
ANG => Angelo State Rams
|
|
||||||
ANN => Anna Maria College Amcats
|
ANN => Anna Maria College Amcats
|
||||||
APP => Appalachian State Mountaineers
|
|
||||||
APSU => Austin Peay Governors
|
|
||||||
ARIZ => Arizona Wildcats
|
ARIZ => Arizona Wildcats
|
||||||
ARK => Arkansas-Monticello Boll Weevils
|
ARK => Arkansas Razorbacks
|
||||||
ARMY => Army Black Knights
|
|
||||||
ARST => Arkansas State Red Wolves
|
|
||||||
ASH => Ashland Eagles
|
|
||||||
ASP => Assumption Greyhounds
|
|
||||||
ASU => Arizona State Sun Devils
|
ASU => Arizona State Sun Devils
|
||||||
AUB => Auburn Tigers
|
AUB => Auburn Tigers
|
||||||
AUG => St. Augustine's Falcons
|
BOIS => Boise State Broncos
|
||||||
AUR => Aurora Spartans
|
BRST => Bridgewater State Bears
|
||||||
AUS => Austin College 'Roos
|
BUENA => Buena Vista Beavers
|
||||||
AVE => Averett Cougars
|
CAL => California Golden Bears
|
||||||
AVI => Avila College Eagles
|
CAR => Carroll University (WI) Pioneers
|
||||||
AZU => Azusa Pacific Cougars
|
CLA => Claremont-Mudd-Scripps College Stags
|
||||||
BAK => Baker University Wildcats
|
COLBY => Colby College White Mules
|
||||||
BAL => Baldwin Wallace Yellow Jackets
|
|
||||||
BALL => Ball State Cardinals
|
|
||||||
BAT => Bates College Bobcats
|
|
||||||
BAY => Baylor Bears
|
|
||||||
BC => Boston College Eagles
|
|
||||||
BEC => Becker College Hawks
|
|
||||||
BEL => Beloit College Buccaneers
|
|
||||||
BEN => Benedictine University (IL) Eagles
|
|
||||||
BENT => Bentley Falcons
|
|
||||||
BET => Bethel (TN) Wildcats
|
|
||||||
BGSU => Bowling Green Falcons
|
|
||||||
BHS => Black Hills State Yellow Jackets
|
|
||||||
BIR => Birmingham-Southern Panthers
|
|
||||||
BKN => Bacone College Warriors
|
|
||||||
BLA => Blackburn Beavers
|
|
||||||
BLOM => Bloomsburg Huskies
|
|
||||||
BLU => Bluffton Beavers
|
|
||||||
BOW => Bowdoin Polar Bears
|
|
||||||
BRI => British Columbia Thunderbirds
|
|
||||||
BRWN => Brown Bears
|
|
||||||
BST => Bemidji State Beavers
|
|
||||||
BSU => Bowie State Bulldogs
|
|
||||||
BUCK => Bucknell Bison
|
|
||||||
BUE => Buena Vista Beavers
|
|
||||||
BUF => Buffalo State Bengals
|
|
||||||
BUFF => Buffalo Bulls
|
|
||||||
BUT => Butler Bulldogs
|
|
||||||
BYU => BYU Cougars
|
|
||||||
CAL => California Lutheran Kingsmen
|
|
||||||
CAM => Campbell Fighting Camels
|
|
||||||
CAP => Capital University Crusaders
|
|
||||||
CAR => Carthage College Red Men
|
|
||||||
CARK => Central Arkansas Bears
|
|
||||||
CAS => Castleton Spartans
|
|
||||||
CAT => Catholic University Cardinals
|
|
||||||
CCSU => Central Connecticut Blue Devils
|
|
||||||
CCU => Coastal Carolina Chanticleers
|
|
||||||
CEN => Centre College Colonels
|
|
||||||
CHA => Chapman University Panthers
|
|
||||||
CHI => Chicago Maroons
|
|
||||||
CHSO => Charleston Southern Buccaneers
|
|
||||||
CIN => Cincinnati Bearcats
|
|
||||||
CLA => Clarion Golden Eagles
|
|
||||||
CLEM => Clemson Tigers
|
|
||||||
CLMB => Columbia Lions
|
|
||||||
CLT => Charlotte 49ers
|
|
||||||
CMU => Central Michigan Chippewas
|
|
||||||
COE => Coe College Kohawks
|
|
||||||
COL => Colorado School of Mines Orediggers
|
|
||||||
COLC => Colorado College Tigers
|
|
||||||
COLG => Colgate Raiders
|
|
||||||
COLO => Colorado Buffaloes
|
COLO => Colorado Buffaloes
|
||||||
CON => Concordia-Minnesota Cobbers
|
CONN => UConn Huskies
|
||||||
COR => Cornell College (IA) Rams
|
|
||||||
CP => Cal Poly Mustangs
|
CP => Cal Poly Mustangs
|
||||||
CRO => Crown Storm
|
|
||||||
CSU => Colorado State Rams
|
CSU => Colorado State Rams
|
||||||
CUL => Culver-Stockton Wildcats
|
|
||||||
CUM => Cumberland College Indians
|
|
||||||
CUR => Curry College Colonels
|
CUR => Curry College Colonels
|
||||||
DAK => Dakota Wesleyan Tigers
|
DEL => Delaware Blue Hens
|
||||||
DART => Dartmouth Big Green
|
|
||||||
DAV => Davidson Wildcats
|
|
||||||
DAY => Dayton Flyers
|
|
||||||
DEF => Defiance Yellow Jackets
|
|
||||||
DEL => Delta State Statesmen
|
|
||||||
DEN => Denison Big Red
|
|
||||||
DEP => DePauw Tigers
|
|
||||||
DIC => Dickinson State Blue Hawks
|
|
||||||
DRKE => Drake Bulldogs
|
|
||||||
DSU => Delaware State Hornets
|
|
||||||
DUB => Dubuque Spartans
|
DUB => Dubuque Spartans
|
||||||
DUKE => Duke Blue Devils
|
ELM => Elmhurst Bluejays
|
||||||
DUQ => Duquesne Dukes
|
FAMU => Florida A&M Rattlers
|
||||||
EAS => Eastern New Mexico Greyhounds
|
|
||||||
ECU => East Carolina Pirates
|
|
||||||
EDI => Edinboro Fighting Scots
|
|
||||||
EIU => Eastern Illinois Panthers
|
|
||||||
EKU => Eastern Kentucky Colonels
|
|
||||||
ELI => Elizabeth City State Vikings
|
|
||||||
ELM => Elmhurst Blue Jays
|
|
||||||
ELON => Elon Phoenix
|
|
||||||
EMO => Emory & Henry Wasps
|
|
||||||
EMP => Emporia State Hornets
|
|
||||||
EMU => Eastern Michigan Eagles
|
|
||||||
END => Endicott College Gulls
|
|
||||||
EOR => Eastern Oregon Mountaineers
|
|
||||||
ETSU => East Tennessee State Buccaneers
|
|
||||||
EUR => Eureka College Red Devils
|
|
||||||
EWU => Eastern Washington Eagles
|
|
||||||
FAU => Florida Atlantic Owls
|
|
||||||
FAY => Fayetteville State Broncos
|
|
||||||
FDU => FDU-Florham Devils
|
|
||||||
FER => Ferrum Panthers
|
|
||||||
FIN => Findlay Oilers
|
|
||||||
FIT => Fitchburg State Falcons
|
|
||||||
FIU => Florida International Panthers
|
|
||||||
FLA => Florida Gators
|
FLA => Florida Gators
|
||||||
FOR => Fort Valley State Wildcats
|
|
||||||
FRA => Franklin Grizzlies
|
|
||||||
FRES => Fresno State Bulldogs
|
|
||||||
FRO => Frostburg State Bobcats
|
|
||||||
FRST => Ferris State Bulldogs
|
|
||||||
FSU => Florida State Seminoles
|
FSU => Florida State Seminoles
|
||||||
FTLW => Fort Lewis Skyhawks
|
|
||||||
FUR => Furman Paladins
|
|
||||||
GAL => Gallaudet Bison
|
|
||||||
GAN => Gannon Golden Knights
|
|
||||||
GASO => Georgia Southern Eagles
|
|
||||||
GAST => Georgia State Panthers
|
|
||||||
GEN => Geneva College Golden Tornadoes
|
|
||||||
GEO => George Fox University Bruins
|
|
||||||
GET => Gettysburg Bullets
|
|
||||||
GLE => Glenville State Pioneers
|
|
||||||
GMU => George Mason Patriots
|
|
||||||
GRA => Grand Valley State Lakers
|
|
||||||
GRE => Greenville Panthers
|
|
||||||
GRI => Grinnell Pioneers
|
GRI => Grinnell Pioneers
|
||||||
GRO => Grove City College Wolverines
|
|
||||||
GT => Georgia Tech Yellow Jackets
|
GT => Georgia Tech Yellow Jackets
|
||||||
GUI => Guilford Quakers
|
GTWN => Georgetown Hoyas
|
||||||
GWEB => Gardner-Webb Bulldogs
|
|
||||||
HAM => Hampden-Sydney Tigers
|
|
||||||
HAMP => Hampton Pirates
|
|
||||||
HAN => Hanover Panthers
|
|
||||||
HAR => Hartwick Hawks
|
|
||||||
HARV => Harvard Crimson
|
|
||||||
HAS => Haskell Indian Nations Jayhawks
|
|
||||||
HAW => Hawai'i Rainbow Warriors
|
HAW => Hawai'i Rainbow Warriors
|
||||||
HBU => Houston Baptist Huskies
|
|
||||||
HC => Holy Cross Crusaders
|
|
||||||
HEI => Heidelberg Student Princes
|
|
||||||
HEN => Hendrix College Warriors
|
|
||||||
HIL => Hillsdale Chargers
|
|
||||||
HIR => Hiram College Terriers
|
|
||||||
HOB => Hobart Statesmen
|
|
||||||
HOU => Houston Cougars
|
|
||||||
HOW => Howard Bison
|
HOW => Howard Bison
|
||||||
HUS => Husson Eagles
|
|
||||||
IDHO => Idaho Vandals
|
IDHO => Idaho Vandals
|
||||||
IDST => Idaho State Bengals
|
|
||||||
ILL => Illinois Fighting Illini
|
|
||||||
ILST => Illinois State Redbirds
|
|
||||||
ILW => Illinois Wesleyan Titans
|
|
||||||
IND => Indianapolis
|
|
||||||
INST => Indiana State Sycamores
|
|
||||||
IOW => Iowa Wesleyan Tigers
|
|
||||||
IOWA => Iowa Hawkeyes
|
|
||||||
ISU => Iowa State Cyclones
|
ISU => Iowa State Cyclones
|
||||||
ITH => Ithaca Bombers
|
JXST => Jacksonville State Gamecocks
|
||||||
IU => Indiana Hoosiers
|
|
||||||
JKST => Jackson State Tigers
|
|
||||||
JMU => James Madison Dukes
|
|
||||||
JOH => Johnson C Smith Golden Bulls
|
|
||||||
JUN => Juniata Eagles
|
|
||||||
JVST => Jacksonville State Gamecocks
|
|
||||||
KAL => Kalamazoo Hornets
|
|
||||||
KAN => Kansas Wesleyan University Coyotes
|
|
||||||
KEN => Kenyon Lords
|
|
||||||
KENN => Kennesaw State Owls
|
|
||||||
KENT => Kent State Golden Flashes
|
|
||||||
KIN => King's College (PA) Monarchs
|
|
||||||
KNO => Knox College Prairie Fire
|
|
||||||
KSU => Kansas State Wildcats
|
|
||||||
KU => Kansas Jayhawks
|
|
||||||
KUT => Kutztown Golden Bears
|
|
||||||
KYST => Kentucky State Thorobreds
|
|
||||||
KYW => Kentucky Wesleyan Panthers
|
|
||||||
LA => La Verne Leopards
|
|
||||||
LAC => Lane Dragons
|
|
||||||
LAF => Lafayette Leopards
|
|
||||||
LAG => LaGrange College Panthers
|
|
||||||
LAK => Lake Forest Foresters
|
|
||||||
LAM => Lambuth Eagles
|
|
||||||
LAN => Langston Lions
|
|
||||||
LAW => Lawrence Vikings
|
|
||||||
LEB => Lebanon Valley Flying Dutchmen
|
|
||||||
LEH => Lehigh Mountain Hawks
|
|
||||||
LEN => Lenoir-Rhyne Bears
|
|
||||||
LEW => Lewis & Clark Pioneers
|
|
||||||
LIB => Liberty Flames
|
|
||||||
LIM => Limestone Saints
|
|
||||||
LIN => Linfield Wildcats
|
|
||||||
LOC => Lock Haven Bald Eagles
|
|
||||||
LOR => Loras College Duhawks
|
|
||||||
LOU => Louisville Cardinals
|
|
||||||
LSU => LSU Tigers
|
|
||||||
LT => Louisiana Tech Bulldogs
|
|
||||||
LUT => Luther Norse
|
LUT => Luther Norse
|
||||||
LYC => Lycoming Warriors
|
MESA => Colorado Mesa Mavericks
|
||||||
M-OH => Miami (OH) RedHawks
|
MIL => Millikin Big Blue
|
||||||
MAC => Macalester Scots
|
MOR => Morehouse College Maroon Tigers
|
||||||
MAI => Maine Maritime Mariners
|
NOR => North Park Vikings
|
||||||
MAN => Mansfield Mountaineers
|
|
||||||
MAR => Maryville College Fighting Scots
|
|
||||||
MAS => Mass Maritime Buccaneers
|
|
||||||
MASS => UMass Minutemen
|
|
||||||
MAY => Mayville State Comets
|
|
||||||
MCM => McMurry War Hawks
|
|
||||||
MCN => McNeese Cowboys
|
|
||||||
MD => Maryland Terrapins
|
|
||||||
MEM => Memphis Tigers
|
|
||||||
MEN => Menlo College Oaks
|
|
||||||
MER => Merchant Marine Mariners
|
|
||||||
MERC => Mercyhurst Lakers
|
|
||||||
MES => Colorado Mesa Mavericks
|
|
||||||
MET => Methodist Monarchs
|
|
||||||
MH => Mars Hill Mountain Lions
|
|
||||||
MIAMI => Miami Hurricanes
|
|
||||||
MICH => Michigan Wolverines
|
|
||||||
MID => Midwestern State Mustangs
|
|
||||||
MIL => Millsaps Majors
|
|
||||||
MIN => Minot State Beavers
|
|
||||||
MINN => Minnesota Golden Gophers
|
|
||||||
MIS => Missouri Western Griffons
|
|
||||||
MISS => Ole Miss Rebels
|
|
||||||
MIZ => Missouri Tigers
|
|
||||||
MNST => Minnesota State Mavericks
|
|
||||||
MONM => Monmouth Hawks
|
|
||||||
MONT => Montana Grizzlies
|
|
||||||
MOR => Morningside Chiefs
|
|
||||||
MORE => Morehead State Eagles
|
|
||||||
MORG => Morgan State Bears
|
|
||||||
MOU => Mount Union Raiders
|
|
||||||
MRSH => Marshall Thundering Herd
|
|
||||||
MRST => Marist Red Foxes
|
|
||||||
MSST => Mississippi State Bulldogs
|
|
||||||
MSU => Michigan State Spartans
|
|
||||||
MTST => Montana State Bobcats
|
|
||||||
MTSU => Middle Tennessee Blue Raiders
|
|
||||||
MTU => Michigan Tech Huskies
|
|
||||||
MUH => Muhlenberg Mules
|
|
||||||
MUR => Murray State Racers
|
|
||||||
MUS => Muskingum Fighting Muskies
|
|
||||||
MVSU => Mississippi Valley State Delta Devils
|
|
||||||
NAU => Northern Arizona Lumberjacks
|
|
||||||
NAVY => Navy Midshipmen
|
|
||||||
NBY => Newberry Wolves
|
|
||||||
NCAT => North Carolina A&T Aggies
|
|
||||||
NCCU => North Carolina Central Eagles
|
|
||||||
NCST => NC State Wolfpack
|
|
||||||
ND => Notre Dame Fighting Irish
|
|
||||||
NDOH => Notre Dame College Falcons
|
|
||||||
NDSU => North Dakota State Bison
|
|
||||||
NEB => Nebraska-Kearney Lopers
|
|
||||||
NEV => Nevada Wolf Pack
|
|
||||||
NH => New Haven Chargers
|
|
||||||
NICH => Nicholls Colonels
|
|
||||||
NIU => Northern Illinois Huskies
|
|
||||||
NMH => New Mexico Highlands Cowboys
|
|
||||||
NMI => Northern Michigan Wildcats
|
|
||||||
NMSU => New Mexico State Aggies
|
|
||||||
NOR => Univ. of Northwestern-St. Paul Eagles
|
|
||||||
NORF => Norfolk State Spartans
|
|
||||||
NW => Northwestern Wildcats
|
|
||||||
OBE => Oberlin Yeomen
|
|
||||||
ODU => Old Dominion Monarchs
|
|
||||||
OHI => Ohio Northern Polar Bears
|
|
||||||
OHIO => Ohio Bobcats
|
|
||||||
OKL => Oklahoma Baptist Bison
|
|
||||||
OKST => Oklahoma State Cowboys
|
|
||||||
OLI => Olivet College Comets
|
|
||||||
OMA => Omaha Mavericks
|
|
||||||
ORST => Oregon State Beavers
|
|
||||||
OSU => Ohio State Buckeyes
|
|
||||||
OTT => Otterbein Cardinals
|
|
||||||
OU => Oklahoma Sooners
|
|
||||||
PAC => Pacific (OR) Boxers
|
|
||||||
PENN => Pennsylvania Quakers
|
|
||||||
PIKE => Pikeville Bears
|
|
||||||
PITT => Pittsburgh Panthers
|
|
||||||
PRE => Presentation College Saints
|
|
||||||
PRI => Principia College Panthers
|
|
||||||
PRIN => Princeton Tigers
|
|
||||||
PST => Pittsburg State Gorillas
|
|
||||||
PSU => Penn State Nittany Lions
|
|
||||||
RED => Redlands Bulldogs
|
RED => Redlands Bulldogs
|
||||||
RICE => Rice Owls
|
|
||||||
RICH => Richmond Spiders
|
|
||||||
RIT => Rochester Yellow Jackets
|
|
||||||
ROB => Robert Morris (IL) Eagles
|
|
||||||
ROS => Rose-Hulman Engineers
|
|
||||||
RUTG => Rutgers Scarlet Knights
|
|
||||||
SAC => Sacramento State Hornets
|
SAC => Sacramento State Hornets
|
||||||
SAG => Saginaw Valley Cardinals
|
|
||||||
SDAK => South Dakota Coyotes
|
|
||||||
SDSU => San Diego State Aztecs
|
SDSU => San Diego State Aztecs
|
||||||
SET => Seton Hill Griffins
|
|
||||||
SIU => Southern Illinois Salukis
|
|
||||||
SJSU => San José State Spartans
|
SJSU => San José State Spartans
|
||||||
SLI => Slippery Rock The Rock
|
|
||||||
SOU => Southwestern College Moundbuilders
|
|
||||||
SPR => Springfield College Pride
|
|
||||||
ST => St. Scholastica Saints
|
|
||||||
STAN => Stanford Cardinal
|
STAN => Stanford Cardinal
|
||||||
STE => Stevenson University Mustangs
|
|
||||||
STET => Stetson Hatters
|
STET => Stetson Hatters
|
||||||
STO => Stonehill College Skyhawks
|
|
||||||
SUS => Susquehanna University River Hawks
|
|
||||||
SUU => Southern Utah Thunderbirds
|
|
||||||
SYR => Syracuse Orange
|
|
||||||
TA&M => Texas A&M Aggies
|
|
||||||
TAY => Taylor Trojans
|
|
||||||
TEM => Temple Owls
|
|
||||||
TEX => Texas Longhorns
|
|
||||||
TIF => Tiffin University Dragons
|
|
||||||
TLSA => Tulsa Golden Hurricane
|
|
||||||
TRI => Trinity University (TX) Tigers
|
|
||||||
TUF => Tufts University Jumbos
|
|
||||||
TXST => Texas State Bobcats
|
|
||||||
UAB => UAB Blazers
|
UAB => UAB Blazers
|
||||||
UAPB => Arkansas-Pine Bluff Golden Lions
|
|
||||||
UCD => UC Davis Aggies
|
|
||||||
UCF => UCF Knights
|
|
||||||
UCLA => UCLA Bruins
|
UCLA => UCLA Bruins
|
||||||
UCONN => UConn Huskies
|
|
||||||
UGA => Georgia Bulldogs
|
UGA => Georgia Bulldogs
|
||||||
UK => Kentucky Wildcats
|
|
||||||
UL => Louisiana Ragin' Cajuns
|
|
||||||
ULM => UL Monroe Warhawks
|
|
||||||
UMD => Minnesota-Duluth Bulldogs
|
|
||||||
UMDA => UMASS Dartmouth Corsairs
|
|
||||||
UML => UMass Lowell River Hawks
|
|
||||||
UNA => North Alabama Lions
|
|
||||||
UNC => North Carolina Tar Heels
|
|
||||||
UNCO => Northern Colorado Bears
|
|
||||||
UND => North Dakota Fighting Hawks
|
|
||||||
UNH => New Hampshire Wildcats
|
|
||||||
UNI => University of Mary Marauders
|
|
||||||
UNLV => UNLV Rebels
|
|
||||||
UNM => New Mexico Lobos
|
|
||||||
UNNY => Union Dutchmen
|
|
||||||
UNT => North Texas Mean Green
|
|
||||||
UPP => Upper Iowa Peacocks
|
|
||||||
URI => Rhode Island Rams
|
|
||||||
USA => South Alabama Jaguars
|
USA => South Alabama Jaguars
|
||||||
USC => USC Trojans
|
USC => USC Trojans
|
||||||
USD => San Diego Toreros
|
|
||||||
USF => South Florida Bulls
|
USF => South Florida Bulls
|
||||||
USU => Utah State Aggies
|
|
||||||
UTAH => Utah Utes
|
|
||||||
UTC => Chattanooga Mocs
|
|
||||||
UTI => Utica College Pioneers
|
|
||||||
UVA => Virginia Cavaliers
|
|
||||||
VAL => Valley City State Vikings
|
|
||||||
VAN => Vanderbilt Commodores
|
|
||||||
VILL => Villanova Wildcats
|
|
||||||
VIR => Virginia State Trojans
|
|
||||||
VT => Virginia Tech Hokies
|
|
||||||
WAB => Wabash College Little Giants
|
|
||||||
WAKE => Wake Forest Demon Deacons
|
|
||||||
WAS => Washington-Missouri Bears
|
|
||||||
WASH => Washington Huskies
|
|
||||||
WAY => Wayne State (MI) Warriors
|
|
||||||
WES => Westminster College (MO) Blue Jays
|
|
||||||
WHE => Wheaton College Illinois Thunder
|
|
||||||
WIL => Wilkes University Colonels
|
|
||||||
WIN => Wingate Bulldogs
|
|
||||||
WIS => Wisconsin-Platteville Pioneers
|
|
||||||
WISC => Wisconsin Badgers
|
|
||||||
WKU => Western Kentucky Hilltoppers
|
|
||||||
WOR => Worcester State College Lancers
|
|
||||||
WSU => Washington State Cougars
|
|
||||||
WVU => West Virginia Mountaineers
|
|
||||||
YALE => Yale Bulldogs
|
YALE => Yale Bulldogs
|
||||||
|
|
||||||
NBA
|
NBA
|
||||||
@@ -1106,6 +755,181 @@ MLB Conferences/Divisions
|
|||||||
OAK => Oakland Athletics
|
OAK => Oakland Athletics
|
||||||
SEA => Seattle Mariners
|
SEA => Seattle Mariners
|
||||||
TEX => Texas Rangers
|
TEX => Texas Rangers
|
||||||
|
|
||||||
|
Soccer Leagues:
|
||||||
|
LEAGUE_SLUGS = {
|
||||||
|
"eng.1": "Premier League",
|
||||||
|
"esp.1": "La Liga",
|
||||||
|
"ger.1": "Bundesliga",
|
||||||
|
"ita.1": "Serie A",
|
||||||
|
"fra.1": "Ligue 1",
|
||||||
|
"uefa.champions": "Champions League",
|
||||||
|
"uefa.europa": "Europa League",
|
||||||
|
"usa.1": "MLS",
|
||||||
|
"por.1": "Liga Portugal", # Add this line
|
||||||
|
}
|
||||||
|
|
||||||
|
Soccer - Premier League (England)
|
||||||
|
ARS => Arsenal
|
||||||
|
AVL => Aston Villa
|
||||||
|
BHA => Brighton & Hove Albion
|
||||||
|
BOU => AFC Bournemouth
|
||||||
|
BRE => Brentford
|
||||||
|
BUR => Burnley
|
||||||
|
CHE => Chelsea
|
||||||
|
CRY => Crystal Palace
|
||||||
|
EVE => Everton
|
||||||
|
FUL => Fulham
|
||||||
|
LIV => Liverpool
|
||||||
|
LUT => Luton Town
|
||||||
|
MCI => Manchester City
|
||||||
|
MUN => Manchester United
|
||||||
|
NEW => Newcastle United
|
||||||
|
NFO => Nottingham Forest
|
||||||
|
SHU => Sheffield United
|
||||||
|
TOT => Tottenham Hotspur
|
||||||
|
WHU => West Ham United
|
||||||
|
WOL => Wolverhampton Wanderers
|
||||||
|
|
||||||
|
Soccer - La Liga (Spain)
|
||||||
|
ALA => Alavés
|
||||||
|
ATH => Athletic Bilbao
|
||||||
|
ATM => Atlético Madrid
|
||||||
|
BAR => Barcelona
|
||||||
|
BET => Real Betis
|
||||||
|
CAG => Cagliari
|
||||||
|
CEL => Celta Vigo
|
||||||
|
ESP => Espanyol
|
||||||
|
GET => Getafe
|
||||||
|
GIR => Girona
|
||||||
|
LAZ => Lazio
|
||||||
|
LEG => Leganés
|
||||||
|
RAY => Rayo Vallecano
|
||||||
|
RMA => Real Madrid
|
||||||
|
SEV => Sevilla
|
||||||
|
VAL => Valencia
|
||||||
|
VLD => Valladolid
|
||||||
|
|
||||||
|
Soccer - Bundesliga (Germany)
|
||||||
|
BOC => VfL Bochum
|
||||||
|
BOL => VfL Bochum
|
||||||
|
DOR => Borussia Dortmund
|
||||||
|
FCA => FC Augsburg
|
||||||
|
FCB => Bayern Munich
|
||||||
|
FCU => FC Union Berlin
|
||||||
|
HAC => Hannover 96
|
||||||
|
HDH => Hertha BSC
|
||||||
|
KOL => 1. FC Köln
|
||||||
|
LEV => Bayer Leverkusen
|
||||||
|
M05 => Mainz 05
|
||||||
|
RBL => RB Leipzig
|
||||||
|
SCF => SC Freiburg
|
||||||
|
SGE => Eintracht Frankfurt
|
||||||
|
STU => VfB Stuttgart
|
||||||
|
SVW => Werder Bremen
|
||||||
|
TSG => TSG Hoffenheim
|
||||||
|
WOB => VfL Wolfsburg
|
||||||
|
|
||||||
|
Soccer - Serie A (Italy)
|
||||||
|
ATA => Atalanta
|
||||||
|
CAG => Cagliari
|
||||||
|
EMP => Empoli
|
||||||
|
FIO => Fiorentina
|
||||||
|
INT => Inter Milan
|
||||||
|
JUV => Juventus
|
||||||
|
LAZ => Lazio
|
||||||
|
MIL => AC Milan
|
||||||
|
MON => Monza
|
||||||
|
NAP => Napoli
|
||||||
|
ROM => Roma
|
||||||
|
TOR => Torino
|
||||||
|
UDI => Udinese
|
||||||
|
VER => Hellas Verona
|
||||||
|
|
||||||
|
Soccer - Ligue 1 (France)
|
||||||
|
LIL => Lille
|
||||||
|
LPM => Lille
|
||||||
|
LYON => Lyon
|
||||||
|
MAR => Marseille
|
||||||
|
MON => Monaco
|
||||||
|
NAN => Nantes
|
||||||
|
NICE => Nice
|
||||||
|
OL => Olympique Lyonnais
|
||||||
|
OM => Olympique de Marseille
|
||||||
|
PAR => Paris Saint-Germain
|
||||||
|
PSG => Paris Saint-Germain
|
||||||
|
REN => Rennes
|
||||||
|
STR => Strasbourg
|
||||||
|
|
||||||
|
Soccer - Champions League
|
||||||
|
AJA => Ajax
|
||||||
|
ASM => AS Monaco
|
||||||
|
ASS => AS Saint-Étienne
|
||||||
|
BOC => VfL Bochum
|
||||||
|
CEL => Celtic
|
||||||
|
COM => Club Brugge
|
||||||
|
FCA => FC Augsburg
|
||||||
|
FCB => Bayern Munich
|
||||||
|
FCU => FC Union Berlin
|
||||||
|
FIO => Fiorentina
|
||||||
|
GEN => Genoa
|
||||||
|
HAC => Hannover 96
|
||||||
|
IPS => Ipswich Town
|
||||||
|
KSV => Kaiserslautern
|
||||||
|
LEC => Lecce
|
||||||
|
LIL => Lille
|
||||||
|
LIV => Liverpool
|
||||||
|
M05 => Mainz 05
|
||||||
|
MCI => Manchester City
|
||||||
|
MUN => Manchester United
|
||||||
|
NAN => Nantes
|
||||||
|
OSA => Osasuna
|
||||||
|
RBL => RB Leipzig
|
||||||
|
RCL => RC Lens
|
||||||
|
RMA => Real Madrid
|
||||||
|
SCF => SC Freiburg
|
||||||
|
SGE => Eintracht Frankfurt
|
||||||
|
SR => Sporting CP
|
||||||
|
STP => St. Pauli
|
||||||
|
SVW => Werder Bremen
|
||||||
|
TFC => Toulouse FC
|
||||||
|
TOT => Tottenham Hotspur
|
||||||
|
TSG => TSG Hoffenheim
|
||||||
|
UDI => Udinese
|
||||||
|
VEN => Venezia
|
||||||
|
VFB => VfB Stuttgart
|
||||||
|
VIL => Villarreal
|
||||||
|
|
||||||
|
Soccer - Liga Portugal (Portugal)
|
||||||
|
ARO => Arouca
|
||||||
|
BEN => SL Benfica
|
||||||
|
BRA => SC Braga
|
||||||
|
CHA => Chaves
|
||||||
|
EST => Estoril Praia
|
||||||
|
FAM => Famalicão
|
||||||
|
GIL => Gil Vicente
|
||||||
|
MOR => Moreirense
|
||||||
|
POR => FC Porto
|
||||||
|
PTM => Portimonense
|
||||||
|
RIO => Rio Ave
|
||||||
|
SR => Sporting CP
|
||||||
|
VGU => Vitória de Guimarães
|
||||||
|
VSC => Vitória de Setúbal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Soccer - Other Teams
|
||||||
|
austin => Austin FC
|
||||||
|
cf_montral => CF Montréal
|
||||||
|
charlotte => Charlotte FC
|
||||||
|
dortmund => Borussia Dortmund
|
||||||
|
gladbach => Borussia Mönchengladbach
|
||||||
|
lafc => Los Angeles FC
|
||||||
|
leverkusen => Bayer Leverkusen
|
||||||
|
nycfc => New York City FC
|
||||||
|
paris_sg => Paris Saint-Germain
|
||||||
|
st_louis => St. Louis City SC
|
||||||
|
|
||||||
MLS Conferences/Divisions
|
MLS Conferences/Divisions
|
||||||
Conferences currently unsupported
|
Conferences currently unsupported
|
||||||
|
|
||||||
|
|||||||
BIN
assets/sports/mlb_logos/mlb.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 94 KiB |