mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-06 23:35:08 +00:00
chore: remove dead code, deprecate unused plugin APIs (over-engineering audit)
Whole-tree audit. Every symbol was checked against core, the plugin monorepo and all eight third-party plugins in plugins.json first. - Deprecate (removal 3.10.0) plugin-facing methods nothing calls: LogoDownloader bulk download, ConfigManager backup/secret wrappers, APIHelper extras, BackgroundDataService poll API, PluginManager / PluginStateManager info readers, and a few CacheManager, FontManager, BaseOddsManager, DynamicTeamResolver methods and PluginTestCase. plugin_api_usage.py learns their receiver names; DEPRECATIONS doc regenerated. - Remove core-internal dead code: CacheMetrics, Vegas status/stats plumbing, sync "new cycle" message (followers ignore unknown types), unused operation types, test-only PluginCatalog readers, IPC to_dict and ping, _parse_form_value, CacheStrategyProtocol, ErrorAggregator callbacks, duplicate web response helpers. - Web UI: drop never-mounted json-file-manager.js, the example widget, utils/error_handler.js, four uncalled PluginAPI methods, and 29 escapeHtml shims (call window.LEDEscape directly). Public globals, BaseWidget and widget names unchanged. - Remove six one-off scripts (owner decision) and the unused markupsafe and pytest-mock pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-8
@@ -15,7 +15,7 @@ display; **diagnostic** — run by hand on a Pi when something is wrong.
|
||||
| [`install/`](install/README.md) | keep | The installers: one-shot, services, sudoers/WiFi permissions, cache setup, and the shared `lib_*.sh` helpers `first_time_install.sh` sources |
|
||||
| [`fix_perms/`](fix_perms/README.md) | keep | Permission repair scripts, plus the two root helpers the web interface runs through sudo (`safe_plugin_rm.sh`, `safe_pip_install.sh`) |
|
||||
| [`utils/`](utils/README.md) | keep | Scripts run by systemd units or the web interface (conditional web start, WiFi monitor, update verify, DNS fix, Pixlet config editor, cache clearing) |
|
||||
| [`dev/`](dev/README.md) | dev-only | Plugin linking, emulator runner, Vegas density audit, Pillow smoke test |
|
||||
| [`dev/`](dev/README.md) | dev-only | Plugin linking, Vegas density audit, Pillow smoke test |
|
||||
| `templates/` | dev-only | `dev_preview.html`, the page `dev_server.py` serves |
|
||||
|
||||
## Top-level scripts
|
||||
@@ -43,22 +43,18 @@ display; **diagnostic** — run by hand on a Pi when something is wrong.
|
||||
| `scroll_speeds.py` | keep | Shows and tries the scroll speeds your panel can display cleanly |
|
||||
| `sports_drift_report.py` | keep | Counts the different bodies of each method across the nine scoreboards in a `ledmatrix-plugins` checkout (report-only CI job; docs/SPORTS_UNIFICATION.md) |
|
||||
| `troubleshoot_captive_portal.sh` | diagnostic | Troubleshoots captive-portal WiFi setup after you can SSH back in |
|
||||
| `update_plugin_repos.py` | dev-only | Pulls the latest `ledmatrix-plugins` monorepo |
|
||||
| `verify_installation.sh` | diagnostic | Checks that an installation completed correctly |
|
||||
| `verify_wifi_setup.sh` | diagnostic | Health check of the WiFi management setup |
|
||||
|
||||
## Candidates for removal
|
||||
## Hand-run tools nothing else references
|
||||
|
||||
Nothing in the repo (docs, CI, tests, other scripts or code) refers to these.
|
||||
They are kept for now; each one needs an owner decision before it goes.
|
||||
They are run by hand and were kept by owner decision (October 2026); the
|
||||
old one-off schema fixers and WiFi test scripts listed here were removed.
|
||||
|
||||
| Script | What it does |
|
||||
|---|---|
|
||||
| `add_defaults_to_schemas.py` | One-off: adds missing `default` values to plugin config schemas |
|
||||
| `analyze_plugin_schemas.py` | One-off: reports duplicate/inconsistent fields across plugin schemas |
|
||||
| `audit_plugins.py` | AST security audit of plugin code; says it is "designed to run in CI" but no workflow runs it |
|
||||
| `audit_render_path.py` | Finds blocking calls reachable from a plugin's `display()` |
|
||||
| `sports_scroll_check.py` | Drives a sports scoreboard scroll on the panel and reports its pacing |
|
||||
| `test_captive_portal.sh` | Tests the captive portal from a device connected to the AP |
|
||||
| `verify_wifi_before_testing.sh` | Pre-flight check before unplugging Ethernet to test WiFi |
|
||||
| `dev/test_pillow_compat.py` | Pillow API smoke test to run after upgrading Pillow |
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to add default values to plugin config schemas where missing.
|
||||
|
||||
This ensures that configs never start with None values, improving user experience
|
||||
and preventing validation errors.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
def get_default_for_field(prop: Dict[str, Any]) -> Any:
|
||||
"""
|
||||
Determine a sensible default value for a field based on its type and constraints.
|
||||
|
||||
Args:
|
||||
prop: Field property schema
|
||||
|
||||
Returns:
|
||||
Default value or None if no default should be added
|
||||
"""
|
||||
prop_type = prop.get('type')
|
||||
|
||||
# Handle union types (array with multiple types)
|
||||
if isinstance(prop_type, list):
|
||||
# Use the first non-null type
|
||||
prop_type = next((t for t in prop_type if t != 'null'), prop_type[0] if prop_type else 'string')
|
||||
|
||||
if prop_type == 'boolean':
|
||||
return False
|
||||
|
||||
elif prop_type == 'number':
|
||||
# For numbers, use minimum if available, or a sensible default
|
||||
minimum = prop.get('minimum')
|
||||
maximum = prop.get('maximum')
|
||||
|
||||
if minimum is not None:
|
||||
return minimum
|
||||
elif maximum is not None:
|
||||
# Use a reasonable fraction of max (like 30% or minimum 1)
|
||||
return max(1, int(maximum * 0.3))
|
||||
else:
|
||||
# No constraints, use 0
|
||||
return 0
|
||||
|
||||
elif prop_type == 'integer':
|
||||
# Similar to number
|
||||
minimum = prop.get('minimum')
|
||||
maximum = prop.get('maximum')
|
||||
|
||||
if minimum is not None:
|
||||
return minimum
|
||||
elif maximum is not None:
|
||||
return max(1, int(maximum * 0.3))
|
||||
else:
|
||||
return 0
|
||||
|
||||
elif prop_type == 'string':
|
||||
# Only add default for strings if it makes sense
|
||||
# Check if there's an enum - use first value
|
||||
enum_values = prop.get('enum')
|
||||
if enum_values:
|
||||
return enum_values[0]
|
||||
|
||||
# For optional string fields, empty string might be okay, but be cautious
|
||||
# We'll skip adding defaults for strings unless explicitly needed
|
||||
return None
|
||||
|
||||
elif prop_type == 'array':
|
||||
# Empty array as default
|
||||
return []
|
||||
|
||||
elif prop_type == 'object':
|
||||
# Empty object - but we'll handle nested objects separately
|
||||
return {}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def should_add_default(prop: Dict[str, Any], field_path: str) -> bool:
|
||||
"""
|
||||
Determine if we should add a default value to this field.
|
||||
|
||||
Args:
|
||||
prop: Field property schema
|
||||
field_path: Dot-separated path to the field
|
||||
|
||||
Returns:
|
||||
True if default should be added
|
||||
"""
|
||||
# Skip if already has a default
|
||||
if 'default' in prop:
|
||||
return False
|
||||
|
||||
# Skip secret fields (they should be user-provided)
|
||||
if prop.get('x-secret', False):
|
||||
return False
|
||||
|
||||
# Skip API keys and similar sensitive fields
|
||||
field_name = field_path.split('.')[-1].lower()
|
||||
sensitive_keywords = ['key', 'password', 'secret', 'token', 'auth', 'credential']
|
||||
if any(keyword in field_name for keyword in sensitive_keywords):
|
||||
return False
|
||||
|
||||
prop_type = prop.get('type')
|
||||
if isinstance(prop_type, list):
|
||||
prop_type = next((t for t in prop_type if t != 'null'), prop_type[0] if prop_type else None)
|
||||
|
||||
# Only add defaults for certain types
|
||||
if prop_type in ('boolean', 'number', 'integer', 'array'):
|
||||
return True
|
||||
|
||||
# For strings, only if there's an enum
|
||||
if prop_type == 'string' and 'enum' in prop:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def add_defaults_recursive(schema: Dict[str, Any], path: str = "", modified: List[str] = None) -> bool:
|
||||
"""
|
||||
Recursively add default values to schema fields.
|
||||
|
||||
Args:
|
||||
schema: Schema dictionary to modify
|
||||
path: Current path in the schema (for logging)
|
||||
modified: List to track which fields were modified
|
||||
|
||||
Returns:
|
||||
True if any modifications were made
|
||||
"""
|
||||
if modified is None:
|
||||
modified = []
|
||||
|
||||
if not isinstance(schema, dict) or 'properties' not in schema:
|
||||
return False
|
||||
|
||||
changes_made = False
|
||||
|
||||
for key, prop in schema['properties'].items():
|
||||
if not isinstance(prop, dict):
|
||||
continue
|
||||
|
||||
current_path = f"{path}.{key}" if path else key
|
||||
|
||||
# Check nested objects
|
||||
if prop.get('type') == 'object' and 'properties' in prop:
|
||||
if add_defaults_recursive(prop, current_path, modified):
|
||||
changes_made = True
|
||||
|
||||
# Add default if appropriate
|
||||
if should_add_default(prop, current_path):
|
||||
default_value = get_default_for_field(prop)
|
||||
if default_value is not None:
|
||||
prop['default'] = default_value
|
||||
modified.append(current_path)
|
||||
changes_made = True
|
||||
print(f" Added default to {current_path}: {default_value} (type: {prop.get('type')})")
|
||||
|
||||
return changes_made
|
||||
|
||||
|
||||
def process_schema_file(schema_path: Path) -> bool:
|
||||
"""
|
||||
Process a single schema file to add defaults.
|
||||
|
||||
Args:
|
||||
schema_path: Path to the schema file
|
||||
|
||||
Returns:
|
||||
True if file was modified
|
||||
"""
|
||||
print(f"\nProcessing: {schema_path}")
|
||||
|
||||
try:
|
||||
with open(schema_path, 'r', encoding='utf-8') as f:
|
||||
schema = json.load(f)
|
||||
except Exception as e:
|
||||
print(f" Error reading schema: {e}")
|
||||
return False
|
||||
|
||||
modified_fields = []
|
||||
changes_made = add_defaults_recursive(schema, modified=modified_fields)
|
||||
|
||||
if changes_made:
|
||||
# Write back with pretty formatting
|
||||
with open(schema_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(schema, f, indent=2, ensure_ascii=False)
|
||||
f.write('\n') # Add trailing newline
|
||||
|
||||
print(f" ✓ Modified {len(modified_fields)} fields")
|
||||
return True
|
||||
else:
|
||||
print(" ✓ No changes needed")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
project_root = Path(__file__).parent.parent
|
||||
plugins_dir = project_root / 'plugin-repos'
|
||||
|
||||
if not plugins_dir.exists():
|
||||
print(f"Error: Plugins directory not found: {plugins_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Find all config_schema.json files
|
||||
schema_files = list(plugins_dir.rglob('config_schema.json'))
|
||||
|
||||
if not schema_files:
|
||||
print("No config_schema.json files found")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"Found {len(schema_files)} schema files")
|
||||
|
||||
modified_count = 0
|
||||
for schema_file in sorted(schema_files):
|
||||
if process_schema_file(schema_file):
|
||||
modified_count += 1
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Summary: Modified {modified_count} out of {len(schema_files)} schema files")
|
||||
print(f"{'='*60}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze all plugin config schemas to identify issues:
|
||||
- Duplicate fields
|
||||
- Inconsistencies
|
||||
- Missing common fields
|
||||
- Naming variations
|
||||
- Formatting issues
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
import jsonschema
|
||||
from jsonschema import Draft7Validator
|
||||
|
||||
# Standard common fields that should be in all plugins
|
||||
STANDARD_COMMON_FIELDS = {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Enable or disable this plugin",
|
||||
"required": True,
|
||||
"order": 1
|
||||
},
|
||||
"display_duration": {
|
||||
"type": "number",
|
||||
"default": 15,
|
||||
"minimum": 1,
|
||||
"maximum": 300,
|
||||
"description": "How long to display this plugin in seconds",
|
||||
"order": 2
|
||||
},
|
||||
"live_priority": {
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Enable live priority takeover when plugin has live content",
|
||||
"order": 3
|
||||
},
|
||||
"high_performance_transitions": {
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Use high-performance transitions (120 FPS) instead of standard (30 FPS)",
|
||||
"order": 4
|
||||
},
|
||||
"update_interval": {
|
||||
"type": "integer",
|
||||
"default": 60,
|
||||
"minimum": 1,
|
||||
"description": "How often to refresh data in seconds",
|
||||
"order": 5
|
||||
},
|
||||
"transition": {
|
||||
"type": "object",
|
||||
"order": 6
|
||||
}
|
||||
}
|
||||
|
||||
def find_duplicate_fields(schema: Dict[str, Any], path: str = "") -> List[str]:
|
||||
"""Find duplicate field definitions within a schema."""
|
||||
duplicates = []
|
||||
seen_fields = {}
|
||||
|
||||
def check_properties(props: Dict[str, Any], current_path: str):
|
||||
if not isinstance(props, dict):
|
||||
return
|
||||
|
||||
for key, value in props.items():
|
||||
full_path = f"{current_path}.{key}" if current_path else key
|
||||
if key in seen_fields:
|
||||
duplicates.append(f"Duplicate field '{key}' at {full_path} (also at {seen_fields[key]})")
|
||||
else:
|
||||
seen_fields[key] = full_path
|
||||
|
||||
# Recursively check nested objects
|
||||
if isinstance(value, dict):
|
||||
if "properties" in value:
|
||||
check_properties(value["properties"], full_path)
|
||||
elif "items" in value and isinstance(value["items"], dict):
|
||||
if "properties" in value["items"]:
|
||||
check_properties(value["items"]["properties"], f"{full_path}[items]")
|
||||
|
||||
if "properties" in schema:
|
||||
check_properties(schema["properties"], "")
|
||||
|
||||
return duplicates
|
||||
|
||||
def validate_schema_syntax(schema_path: Path) -> tuple[bool, List[str]]:
|
||||
"""Validate JSON Schema syntax."""
|
||||
try:
|
||||
with open(schema_path, 'r', encoding='utf-8') as f:
|
||||
schema = json.load(f)
|
||||
|
||||
# Validate schema structure
|
||||
Draft7Validator.check_schema(schema)
|
||||
return True, []
|
||||
except json.JSONDecodeError as e:
|
||||
return False, [f"JSON syntax error: {str(e)}"]
|
||||
except jsonschema.SchemaError as e:
|
||||
return False, [f"Schema validation error: {str(e)}"]
|
||||
except Exception as e:
|
||||
return False, [f"Error: {str(e)}"]
|
||||
|
||||
def analyze_schema(schema_path: Path) -> Dict[str, Any]:
|
||||
"""Analyze a single schema file."""
|
||||
plugin_id = schema_path.parent.name
|
||||
analysis = {
|
||||
"plugin_id": plugin_id,
|
||||
"path": str(schema_path),
|
||||
"valid": False,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"has_title": False,
|
||||
"has_description": False,
|
||||
"common_fields": {},
|
||||
"missing_common_fields": [],
|
||||
"naming_issues": [],
|
||||
"duplicates": [],
|
||||
"property_order": [],
|
||||
"update_interval_variant": None
|
||||
}
|
||||
|
||||
try:
|
||||
with open(schema_path, 'r', encoding='utf-8') as f:
|
||||
schema = json.load(f)
|
||||
|
||||
# Check for title and description
|
||||
analysis["has_title"] = "title" in schema
|
||||
analysis["has_description"] = "description" in schema
|
||||
|
||||
if not analysis["has_title"]:
|
||||
analysis["warnings"].append("Missing 'title' field at root level")
|
||||
if not analysis["has_description"]:
|
||||
analysis["warnings"].append("Missing 'description' field at root level")
|
||||
|
||||
# Validate schema syntax
|
||||
is_valid, errors = validate_schema_syntax(schema_path)
|
||||
analysis["valid"] = is_valid
|
||||
analysis["errors"].extend(errors)
|
||||
|
||||
if not is_valid:
|
||||
return analysis
|
||||
|
||||
# Check for duplicate fields
|
||||
duplicates = find_duplicate_fields(schema)
|
||||
analysis["duplicates"] = duplicates
|
||||
|
||||
# Check properties
|
||||
if "properties" not in schema:
|
||||
analysis["errors"].append("Missing 'properties' field")
|
||||
return analysis
|
||||
|
||||
properties = schema["properties"]
|
||||
|
||||
# Check common fields
|
||||
for field_name, field_spec in STANDARD_COMMON_FIELDS.items():
|
||||
if field_name in properties:
|
||||
analysis["common_fields"][field_name] = properties[field_name]
|
||||
else:
|
||||
# Check for variants
|
||||
if field_name == "update_interval":
|
||||
# Check for update_interval_seconds variant
|
||||
if "update_interval_seconds" in properties:
|
||||
analysis["update_interval_variant"] = "update_interval_seconds"
|
||||
analysis["naming_issues"].append(
|
||||
"Uses 'update_interval_seconds' instead of 'update_interval'"
|
||||
)
|
||||
else:
|
||||
analysis["missing_common_fields"].append(field_name)
|
||||
else:
|
||||
analysis["missing_common_fields"].append(field_name)
|
||||
|
||||
# Check property order (enabled should be first)
|
||||
prop_keys = list(properties.keys())
|
||||
analysis["property_order"] = prop_keys
|
||||
|
||||
if prop_keys and prop_keys[0] != "enabled":
|
||||
analysis["warnings"].append(
|
||||
f"'enabled' is not first property. First property is '{prop_keys[0]}'"
|
||||
)
|
||||
|
||||
# Check for required fields
|
||||
required = schema.get("required", [])
|
||||
if "enabled" not in required:
|
||||
analysis["warnings"].append("'enabled' is not in required fields")
|
||||
|
||||
except Exception as e:
|
||||
analysis["errors"].append(f"Failed to analyze schema: {str(e)}")
|
||||
|
||||
return analysis
|
||||
|
||||
def main():
|
||||
"""Main analysis function."""
|
||||
project_root = Path(__file__).parent.parent
|
||||
plugins_dir = project_root / "plugin-repos"
|
||||
|
||||
if not plugins_dir.exists():
|
||||
print(f"Plugins directory not found: {plugins_dir}")
|
||||
return
|
||||
|
||||
results = []
|
||||
|
||||
# Find all config_schema.json files
|
||||
schema_files = list(plugins_dir.glob("*/config_schema.json"))
|
||||
|
||||
print(f"Found {len(schema_files)} plugin schemas to analyze\n")
|
||||
|
||||
for schema_path in sorted(schema_files):
|
||||
print(f"Analyzing {schema_path.parent.name}...")
|
||||
analysis = analyze_schema(schema_path)
|
||||
results.append(analysis)
|
||||
|
||||
# Print summary
|
||||
print("\n" + "="*80)
|
||||
print("ANALYSIS SUMMARY")
|
||||
print("="*80)
|
||||
|
||||
for result in results:
|
||||
print(f"\n{result['plugin_id']}:")
|
||||
print(f" Valid: {result['valid']}")
|
||||
|
||||
if result['errors']:
|
||||
print(f" Errors ({len(result['errors'])}):")
|
||||
for error in result['errors']:
|
||||
print(f" - {error}")
|
||||
|
||||
if result['warnings']:
|
||||
print(f" Warnings ({len(result['warnings'])}):")
|
||||
for warning in result['warnings']:
|
||||
print(f" - {warning}")
|
||||
|
||||
if result['duplicates']:
|
||||
print(f" Duplicates ({len(result['duplicates'])}):")
|
||||
for dup in result['duplicates']:
|
||||
print(f" - {dup}")
|
||||
|
||||
if result['missing_common_fields']:
|
||||
print(f" Missing common fields: {', '.join(result['missing_common_fields'])}")
|
||||
|
||||
if result['naming_issues']:
|
||||
print(" Naming issues:")
|
||||
for issue in result['naming_issues']:
|
||||
print(f" - {issue}")
|
||||
|
||||
if result['property_order'] and result['property_order'][0] != 'enabled':
|
||||
print(f" Property order: First is '{result['property_order'][0]}' (should be 'enabled')")
|
||||
|
||||
# Overall statistics
|
||||
print("\n" + "="*80)
|
||||
print("OVERALL STATISTICS")
|
||||
print("="*80)
|
||||
|
||||
valid_count = sum(1 for r in results if r['valid'])
|
||||
has_title_count = sum(1 for r in results if r['has_title'])
|
||||
has_description_count = sum(1 for r in results if r['has_description'])
|
||||
enabled_first_count = sum(1 for r in results if r['property_order'] and r['property_order'][0] == 'enabled')
|
||||
total_errors = sum(len(r['errors']) for r in results)
|
||||
total_warnings = sum(len(r['warnings']) for r in results)
|
||||
total_duplicates = sum(len(r['duplicates']) for r in results)
|
||||
|
||||
print(f"Total plugins: {len(results)}")
|
||||
print(f"Valid schemas: {valid_count}/{len(results)}")
|
||||
print(f"Has title: {has_title_count}/{len(results)}")
|
||||
print(f"Has description: {has_description_count}/{len(results)}")
|
||||
print(f"'enabled' first: {enabled_first_count}/{len(results)}")
|
||||
print(f"Total errors: {total_errors}")
|
||||
print(f"Total warnings: {total_warnings}")
|
||||
print(f"Total duplicates: {total_duplicates}")
|
||||
|
||||
# Save detailed report
|
||||
report_path = project_root / "plugin_schema_analysis.json"
|
||||
with open(report_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
print(f"\nDetailed report saved to: {report_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -5,7 +5,6 @@ This directory contains scripts and utilities for development and testing.
|
||||
## Scripts
|
||||
|
||||
- **`dev_plugin_setup.sh`** - Sets up plugin development environment by linking plugin repositories
|
||||
- **`run_emulator.sh`** - Runs the LED Matrix display in emulator mode (for development without hardware)
|
||||
- **`vegas_audit.py`** - Measures how much of the Vegas ticker strip actually shows content (dead-frame ratio)
|
||||
- **`test_pillow_compat.py`** - Pillow API smoke test to run after upgrading Pillow (`python3 scripts/dev/test_pillow_compat.py`)
|
||||
|
||||
@@ -28,6 +27,6 @@ links. To use a fork or another clone location, copy
|
||||
|
||||
### Running Emulator
|
||||
```bash
|
||||
./scripts/dev/run_emulator.sh
|
||||
python3 run.py -e
|
||||
```
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
# LEDMatrix Emulator Runner
|
||||
# This script runs the LEDMatrix system in emulator mode for development and testing
|
||||
|
||||
echo "Starting LEDMatrix Emulator..."
|
||||
echo "Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
# Set emulator mode
|
||||
export EMULATOR=true
|
||||
|
||||
# Run the main application
|
||||
python3 run.py
|
||||
@@ -75,6 +75,14 @@ OWNER_RECEIVERS: Dict[str, Tuple[str, ...]] = {
|
||||
"DisplayManager": ("display_manager", "display_mgr", "displaymanager", "display", "dm"),
|
||||
"FontManager": ("font_manager", "font_mgr", "fontmanager", "fonts", "fm"),
|
||||
"PluginManager": ("plugin_manager", "plugin_mgr", "pluginmanager", "pm"),
|
||||
"PluginStateManager": ("state_manager", "plugin_state", "state_mgr"),
|
||||
"ConfigManager": ("config_manager", "config_mgr", "configmanager"),
|
||||
"LogoDownloader": ("logo_downloader", "downloader", "logodownloader"),
|
||||
"APIHelper": ("api_helper", "apihelper", "api"),
|
||||
"BackgroundDataService": ("background_service", "background_data_service", "bg_service",
|
||||
"data_service"),
|
||||
"BaseOddsManager": ("odds_manager", "oddsmanager", "odds"),
|
||||
"DynamicTeamResolver": ("dynamic_resolver", "team_resolver", "resolver"),
|
||||
}
|
||||
|
||||
#: Directories never scanned (vendored environments, VCS metadata, caches).
|
||||
@@ -330,8 +338,7 @@ class _Scanner(ast.NodeVisitor):
|
||||
return "call"
|
||||
definers = self.local_definers.get(m.method, ())
|
||||
if name in definers or self.built.get(name or "") in definers:
|
||||
# e.g. the weather plugin's WeatherIcons.draw_sun, or
|
||||
# self._strategy_component = CacheStrategy(); ...get_sport_live_interval()
|
||||
# e.g. the weather plugin's WeatherIcons.draw_sun
|
||||
return "unrelated"
|
||||
return "review"
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for captive portal functionality
|
||||
# This script tests the captive portal from a device connected to the AP network
|
||||
|
||||
set -e
|
||||
|
||||
PI_IP="192.168.4.1"
|
||||
PI_PORT="5000"
|
||||
BASE_URL="http://${PI_IP}:${PI_PORT}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Captive Portal Functionality Test"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Make sure you're connected to 'LEDMatrix-Setup' network"
|
||||
echo "Pi IP: ${PI_IP}"
|
||||
echo "Web Interface Port: ${PI_PORT}"
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Test counter
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
test_result() {
|
||||
if [ $1 -eq 0 ]; then
|
||||
echo -e "${GREEN}✓${NC} $2"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $2"
|
||||
((FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 1: Check if Pi is reachable
|
||||
echo "1. Testing Pi connectivity..."
|
||||
if ping -c 1 -W 2 ${PI_IP} > /dev/null 2>&1; then
|
||||
test_result 0 "Pi is reachable at ${PI_IP}"
|
||||
else
|
||||
test_result 1 "Pi is NOT reachable at ${PI_IP}"
|
||||
echo " Make sure you're connected to LEDMatrix-Setup network"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: DNS Redirection
|
||||
echo ""
|
||||
echo "2. Testing DNS redirection..."
|
||||
DNS_RESULT=$(nslookup google.com 2>/dev/null | grep -i "address" | tail -1 | awk '{print $2}')
|
||||
if [ "$DNS_RESULT" = "${PI_IP}" ]; then
|
||||
test_result 0 "DNS redirection works (google.com resolves to ${PI_IP})"
|
||||
else
|
||||
test_result 1 "DNS redirection failed (got ${DNS_RESULT}, expected ${PI_IP})"
|
||||
fi
|
||||
|
||||
# Test 3: HTTP Redirect
|
||||
echo ""
|
||||
echo "3. Testing HTTP redirect..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -L --max-time 5 "${BASE_URL}/google.com" 2>/dev/null || echo "000")
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
test_result 0 "HTTP redirect works (got 200, redirected to setup page)"
|
||||
else
|
||||
test_result 1 "HTTP redirect failed (got ${HTTP_CODE})"
|
||||
fi
|
||||
|
||||
# Test 4: Captive Portal Detection Endpoints
|
||||
echo ""
|
||||
echo "4. Testing captive portal detection endpoints..."
|
||||
|
||||
# iOS/macOS
|
||||
IOS_RESPONSE=$(curl -s --max-time 5 "${BASE_URL}/hotspot-detect.html" 2>/dev/null || echo "")
|
||||
if echo "$IOS_RESPONSE" | grep -qi "success"; then
|
||||
test_result 0 "iOS/macOS endpoint works"
|
||||
else
|
||||
test_result 1 "iOS/macOS endpoint failed"
|
||||
fi
|
||||
|
||||
# Android
|
||||
ANDROID_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${BASE_URL}/generate_204" 2>/dev/null || echo "000")
|
||||
if [ "$ANDROID_CODE" = "204" ]; then
|
||||
test_result 0 "Android endpoint works"
|
||||
else
|
||||
test_result 1 "Android endpoint failed (got ${ANDROID_CODE})"
|
||||
fi
|
||||
|
||||
# Windows
|
||||
WIN_RESPONSE=$(curl -s --max-time 5 "${BASE_URL}/connecttest.txt" 2>/dev/null || echo "")
|
||||
if echo "$WIN_RESPONSE" | grep -qi "microsoft"; then
|
||||
test_result 0 "Windows endpoint works"
|
||||
else
|
||||
test_result 1 "Windows endpoint failed"
|
||||
fi
|
||||
|
||||
# Firefox
|
||||
FF_RESPONSE=$(curl -s --max-time 5 "${BASE_URL}/success.txt" 2>/dev/null || echo "")
|
||||
if echo "$FF_RESPONSE" | grep -qi "success"; then
|
||||
test_result 0 "Firefox endpoint works"
|
||||
else
|
||||
test_result 1 "Firefox endpoint failed"
|
||||
fi
|
||||
|
||||
# Test 5: API Endpoints (should NOT redirect)
|
||||
echo ""
|
||||
echo "5. Testing API endpoints (should work normally)..."
|
||||
API_RESPONSE=$(curl -s --max-time 5 "${BASE_URL}/api/v3/wifi/status" 2>/dev/null || echo "")
|
||||
if echo "$API_RESPONSE" | grep -qi "status"; then
|
||||
test_result 0 "API endpoints work (not redirected)"
|
||||
else
|
||||
test_result 1 "API endpoints failed or were redirected"
|
||||
fi
|
||||
|
||||
# Test 6: Main Interface (should be accessible)
|
||||
echo ""
|
||||
echo "6. Testing main interface accessibility..."
|
||||
MAIN_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${BASE_URL}/v3" 2>/dev/null || echo "000")
|
||||
if [ "$MAIN_CODE" = "200" ]; then
|
||||
test_result 0 "Main interface is accessible"
|
||||
else
|
||||
test_result 1 "Main interface failed (got ${MAIN_CODE})"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test Summary"
|
||||
echo "=========================================="
|
||||
echo -e "${GREEN}Passed: ${PASSED}${NC}"
|
||||
echo -e "${RED}Failed: ${FAILED}${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}All tests passed! Captive portal is working correctly.${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${YELLOW}Some tests failed. Check the output above for details.${NC}"
|
||||
echo ""
|
||||
echo "Troubleshooting tips:"
|
||||
echo "1. Verify AP mode is active: sudo systemctl status hostapd"
|
||||
echo "2. Check dnsmasq config: sudo cat /etc/dnsmasq.conf"
|
||||
echo "3. Check web interface logs: sudo journalctl -u ledmatrix-web -n 50"
|
||||
echo "4. Verify you're connected to LEDMatrix-Setup network"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Update the ledmatrix-plugins monorepo by pulling latest changes.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
MONOREPO_DIR = Path(__file__).parent.parent.parent / "ledmatrix-plugins"
|
||||
|
||||
|
||||
def main():
|
||||
if not MONOREPO_DIR.exists():
|
||||
print(f"Error: Monorepo not found: {MONOREPO_DIR}")
|
||||
return 1
|
||||
|
||||
if not (MONOREPO_DIR / ".git").exists():
|
||||
print(f"Error: {MONOREPO_DIR} is not a git repository")
|
||||
return 1
|
||||
|
||||
print(f"Updating {MONOREPO_DIR}...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(MONOREPO_DIR), "pull"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"Error: git pull timed out after 120 seconds for {MONOREPO_DIR}")
|
||||
return 1
|
||||
|
||||
if result.returncode == 0:
|
||||
print(result.stdout.strip())
|
||||
return 0
|
||||
else:
|
||||
print(f"Error: {result.stderr.strip()}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,225 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Pre-Testing WiFi Verification Script
|
||||
# Run this BEFORE disconnecting Ethernet to ensure WiFi is ready
|
||||
|
||||
# Don't use set -e as it can cause premature exits with arithmetic operations
|
||||
# Instead, we'll check return codes explicitly where needed
|
||||
set -u # Fail on undefined variables
|
||||
|
||||
echo "=========================================="
|
||||
echo "WiFi Pre-Testing Verification"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "This script verifies WiFi is enabled and working"
|
||||
echo "before you disconnect Ethernet for captive portal testing."
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check counter
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
WARNINGS=0
|
||||
|
||||
check_result() {
|
||||
local result=$1
|
||||
local message=$2
|
||||
if [ $result -eq 0 ]; then
|
||||
echo -e "${GREEN}✓${NC} $message"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $message"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
warn_result() {
|
||||
local message=$2
|
||||
echo -e "${YELLOW}⚠${NC} $message"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
}
|
||||
|
||||
# Check 1: WiFi interface exists
|
||||
echo "1. Checking WiFi interface..."
|
||||
if ip link show wlan0 > /dev/null 2>&1; then
|
||||
check_result 0 "WiFi interface wlan0 exists"
|
||||
else
|
||||
check_result 1 "WiFi interface wlan0 NOT found"
|
||||
echo " → Check if WiFi adapter is connected"
|
||||
echo " → Run: lsusb (for USB WiFi) or check built-in WiFi"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check 2: WiFi radio is enabled
|
||||
echo ""
|
||||
echo "2. Checking WiFi radio status..."
|
||||
WIFI_STATUS=$(nmcli radio wifi 2>/dev/null || echo "unknown")
|
||||
if echo "$WIFI_STATUS" | grep -qi "enabled"; then
|
||||
check_result 0 "WiFi radio is enabled"
|
||||
elif echo "$WIFI_STATUS" | grep -qi "disabled"; then
|
||||
check_result 1 "WiFi radio is DISABLED"
|
||||
echo " → Enabling WiFi..."
|
||||
sudo nmcli radio wifi on
|
||||
sleep 2
|
||||
if nmcli radio wifi | grep -qi "enabled"; then
|
||||
check_result 0 "WiFi radio enabled successfully"
|
||||
else
|
||||
check_result 1 "Failed to enable WiFi radio"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
warn_result 1 "Could not determine WiFi radio status"
|
||||
fi
|
||||
|
||||
# Check 3: WiFi can scan for networks
|
||||
echo ""
|
||||
echo "3. Testing WiFi scanning capability..."
|
||||
SCAN_RESULT=$(timeout 10 nmcli device wifi list 2>&1 | head -5)
|
||||
if [ $? -eq 0 ] && [ -n "$SCAN_RESULT" ]; then
|
||||
NETWORK_COUNT=$(echo "$SCAN_RESULT" | wc -l)
|
||||
if [ "$NETWORK_COUNT" -gt 1 ]; then
|
||||
check_result 0 "WiFi scanning works (found networks)"
|
||||
echo " Sample networks found:"
|
||||
echo "$SCAN_RESULT" | head -3 | sed 's/^/ /'
|
||||
else
|
||||
warn_result 1 "WiFi scanning works but no networks found"
|
||||
echo " → This might be okay if you're in a remote location"
|
||||
echo " → Make sure you can see networks when you need to connect"
|
||||
fi
|
||||
else
|
||||
check_result 1 "WiFi scanning FAILED"
|
||||
echo " → WiFi adapter may not be working properly"
|
||||
echo " → Check: dmesg | grep -i wifi"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check 4: Current network connections
|
||||
echo ""
|
||||
echo "4. Checking current network status..."
|
||||
ETH_STATUS=$(nmcli device status | grep "ethernet" | grep -v "unavailable" | head -1 || echo "")
|
||||
WIFI_STATUS=$(nmcli device status | grep "wifi" | head -1 || echo "")
|
||||
|
||||
if echo "$ETH_STATUS" | grep -q "connected"; then
|
||||
ETH_NAME=$(echo "$ETH_STATUS" | awk '{print $1}')
|
||||
ETH_IP=$(ip addr show $ETH_NAME 2>/dev/null | grep "inet " | awk '{print $2}' | cut -d/ -f1 | head -1)
|
||||
check_result 0 "Ethernet is connected ($ETH_NAME)"
|
||||
if [ -n "$ETH_IP" ]; then
|
||||
echo " Ethernet IP: $ETH_IP"
|
||||
fi
|
||||
else
|
||||
warn_result 1 "Ethernet is NOT connected"
|
||||
echo " → You may already be on WiFi only"
|
||||
fi
|
||||
|
||||
if echo "$WIFI_STATUS" | grep -q "connected"; then
|
||||
WIFI_NAME=$(echo "$WIFI_STATUS" | awk '{print $1}')
|
||||
WIFI_IP=$(ip addr show $WIFI_NAME 2>/dev/null | grep "inet " | awk '{print $2}' | cut -d/ -f1 | head -1)
|
||||
WIFI_SSID=$(nmcli -t -f active,ssid dev wifi | grep "^yes:" | cut -d: -f2 | head -1)
|
||||
check_result 0 "WiFi is connected ($WIFI_NAME)"
|
||||
if [ -n "$WIFI_SSID" ]; then
|
||||
echo " Connected to: $WIFI_SSID"
|
||||
fi
|
||||
if [ -n "$WIFI_IP" ]; then
|
||||
echo " WiFi IP: $WIFI_IP"
|
||||
fi
|
||||
echo ""
|
||||
echo " ⚠ You are already connected via WiFi!"
|
||||
echo " → You may want to disconnect WiFi first to test captive portal"
|
||||
echo " → Or test from a different device"
|
||||
else
|
||||
if echo "$WIFI_STATUS" | grep -q "disconnected"; then
|
||||
check_result 0 "WiFi is disconnected (ready for AP mode)"
|
||||
else
|
||||
warn_result 1 "WiFi status unclear"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check 5: Internet connectivity test
|
||||
echo ""
|
||||
echo "5. Testing internet connectivity..."
|
||||
if ping -c 2 -W 3 8.8.8.8 > /dev/null 2>&1; then
|
||||
check_result 0 "Internet connectivity working"
|
||||
echo " → You have internet access via current connection"
|
||||
else
|
||||
warn_result 1 "No internet connectivity detected"
|
||||
echo " → This might be okay if you're testing in isolation"
|
||||
echo " → But you won't be able to download packages if needed"
|
||||
fi
|
||||
|
||||
# Check 6: Saved WiFi connections
|
||||
echo ""
|
||||
echo "6. Checking saved WiFi connections..."
|
||||
SAVED_CONNECTIONS=$(nmcli connection show | grep -i wifi | wc -l)
|
||||
if [ "$SAVED_CONNECTIONS" -gt 0 ]; then
|
||||
check_result 0 "Found $SAVED_CONNECTIONS saved WiFi connection(s)"
|
||||
echo " Saved connections:"
|
||||
nmcli connection show | grep -i wifi | awk '{print " - " $1}' | head -5
|
||||
echo ""
|
||||
echo " → You can reconnect using: sudo nmcli connection up <name>"
|
||||
else
|
||||
warn_result 1 "No saved WiFi connections found"
|
||||
echo " → Make sure you know your WiFi SSID and password"
|
||||
echo " → You'll need them to reconnect after testing"
|
||||
fi
|
||||
|
||||
# Check 7: Required services
|
||||
echo ""
|
||||
echo "7. Checking required services..."
|
||||
if systemctl is-active --quiet hostapd 2>/dev/null; then
|
||||
warn_result 1 "hostapd is already running (AP mode may be active)"
|
||||
else
|
||||
check_result 0 "hostapd service is stopped (normal)"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet dnsmasq 2>/dev/null; then
|
||||
warn_result 1 "dnsmasq is already running (AP mode may be active)"
|
||||
else
|
||||
check_result 0 "dnsmasq service is stopped (normal)"
|
||||
fi
|
||||
|
||||
# Check 8: WiFi monitor service
|
||||
echo ""
|
||||
echo "8. Checking WiFi monitor service..."
|
||||
if systemctl is-active --quiet ledmatrix-wifi-monitor 2>/dev/null; then
|
||||
check_result 0 "WiFi monitor service is running"
|
||||
else
|
||||
warn_result 1 "WiFi monitor service is NOT running"
|
||||
echo " → Start with: sudo systemctl start ledmatrix-wifi-monitor"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Verification Summary"
|
||||
echo "=========================================="
|
||||
echo -e "${GREEN}Passed: ${PASSED}${NC}"
|
||||
echo -e "${YELLOW}Warnings: ${WARNINGS}${NC}"
|
||||
echo -e "${RED}Failed: ${FAILED}${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
if [ $WARNINGS -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All checks passed! WiFi is ready for testing.${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. You can safely disconnect Ethernet"
|
||||
echo "2. Enable AP mode to test captive portal"
|
||||
echo "3. Use emergency_reconnect.sh if you need to reconnect"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Checks passed with warnings.${NC}"
|
||||
echo ""
|
||||
echo "WiFi appears ready, but review warnings above."
|
||||
echo "You can proceed with testing, but be aware of the warnings."
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ Some checks failed. Please fix issues before testing.${NC}"
|
||||
echo ""
|
||||
echo "Do NOT disconnect Ethernet until all issues are resolved!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user