Files
LEDMatrix/docs/CONFIG_REFERENCE.md
T
ChuckandClaude Opus 5.5 a231d4dbc7 chore: delete unreferenced scripts and archived docs; fix stale doc claims (#607)
* chore(scripts): delete unreferenced helper scripts

None of these is referenced by an installer, systemd unit, CI workflow,
test, the web UI or src/:

- utils/cleanup_venv.sh removes venv_web_v2, which nothing creates
- utils/clear_python_cache.sh hardcodes ~/LEDMatrix and a .webassets-cache
  nothing uses
- install/migrate_config.sh only copies the template, which the installer
  and ConfigManager already do
- install/debug_install.sh, debug/debug_web_manual.py
- diagnose_web_ui.sh and verify_web_ui.sh overlap diagnose_web_interface.sh,
  which the docs point to
- fix_internet_connectivity.sh is iptables-only (stale on nftables)
- diagnose_plugin_permissions.sh, dev/validate_python.py
- download_nba_logos.py + README_NBA_LOGOS.md: logo_downloader fetches
  logos on demand
- setup_plugin_repos.py linked into the production plugin-repos/ dir; the
  dev workflow is scripts/dev/dev_plugin_setup.sh, and
  MULTI_ROOT_WORKSPACE_SETUP.md now uses it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(config): drop unused plugin_system flags and a dead unit comment

- config.template.json: remove plugin_system.auto_discover,
  auto_load_enabled and development_mode. Nothing reads them; the web UI
  only stores them when a client sends them. ConfigManager's migration
  only adds template keys, so existing configs keep theirs unchanged.
- config.template.json: re-indent vegas_scroll's live_* keys.
- systemd/ledmatrix.service: remove the comment documenting
  LEDMATRIX_ON_DEMAND_PLUGIN / on_demand_env.conf; nothing reads either.
- CONFIG_REFERENCE.md: say the legacy keys are no longer in the template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: delete docs/archive and PLUGIN_IMPLEMENTATION_SUMMARY.md

- docs/archive/: superseded guides; the repository history keeps them
  and no live doc links into the directory. The one open document in it,
  WEB_UI_AUDIT_2026-09.md, moves to docs/audits/ and is linked from the
  docs index.
- PLUGIN_IMPLEMENTATION_SUMMARY.md invented usage statistics, called
  v2.0.0 current, listed shipped auto-updates as future work and
  documented a BasePlugin.get_config() that does not exist.
- docs/README.md: drop both, and stop telling contributors to archive
  obsolete pages instead of deleting them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-api): fix extra_small_font size, cache metric key and scroll pacing example

- PLUGIN_API_REFERENCE: extra_small_font loads at 7, not 6 (crisp_size
  snaps it, src/display_manager.py); get_cache_metrics() returns
  cache_hit_rate, not hit_rate (src/cache/cache_metrics.py).
- ADVANCED_PLUGIN_DEVELOPMENT: the basic scrolling example slept in a loop
  and never passed frame_hold; use ScrollHelper + scroll_config.configure()
  and set_scrolling_state(True, frame_hold=...) as PLUGIN_API_REFERENCE does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-config): match the config tab, icon and web-action docs to the code

- PLUGIN_CONFIG_QUICK_START / PLUGIN_CONFIGURATION_TABS /
  PLUGIN_CONFIGURATION_GUIDE: there is no "Reset to Defaults" button (the
  tab has Refresh, Update, Uninstall, Save Configuration); plugin config
  hot-reloads (ConfigService + on_config_change), so no restart; the
  schema is found by the fixed name config_schema.json, not a manifest
  config_schema field; the tab row is "Plugin Manager", not "Plugins";
  forms are server-rendered from /v3/partials/plugin-config/<id>; the
  duration hook is get_display_duration()/display_duration; a class_name
  mismatch raises PluginError; the store requires id, name, class_name and
  display_modes (not version); plugin_system.debug/log_level do not exist
  (use run.py -d / LEDMATRIX_DEBUG). Drop "future" features that shipped.
- PLUGIN_CONFIG_CORE_PROPERTIES: list all of CORE_PLUGIN_PROPERTIES,
  including skin, skin_options and the vegas_* tuning keys.
- PLUGIN_CUSTOM_ICONS: icon is only a Font Awesome class (fallback
  fa-puzzle-piece); emoji/URL icons and getPluginIcon() never existed in
  v3. Note that /api/v3/plugins/installed currently omits icon.
- PLUGIN_WEB_UI_ACTIONS (+ example JSON): success_message, error_message
  and step1_message are never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(store): describe the monorepo registry and the store UI as they are

- PLUGIN_STORE_GUIDE: the Plugin Store is a section of the Plugin
  Manager tab; URL installs are "Install from GitHub" -> "Install Single
  Plugin"; bulk update exists (Check & Update All) plus opt-in weekly
  auto-update; PluginStoreManager() defaults to plugins/, so the Python
  examples pass plugin-repos; registry plugins are downloaded (GitHub API,
  ZIP fallback), not cloned; updates compare version with latest_version.
- PLUGIN_REGISTRY_SETUP_GUIDE: replace the per-plugin-repo + tag
  walkthrough with a short page on the monorepo registry (plugin_path,
  latest_version, update_registry.py) that points at the monorepo's own
  SUBMISSION.md. Drops the reference to the deleted
  PLUGIN_IMPLEMENTATION_SUMMARY.md and setup_plugin_repos.py.
- plugin_registry_template.json: use the real entry shape.
- PLUGIN_QUICK_REFERENCE: automatic background updates exist (opt-in);
  registry example and publishing steps use the monorepo, not tags.
- PLUGIN_DEVELOPMENT_GUIDE: tags/releases are not read by the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(readme): fix the Triple Bonnet mapping, install prerequisites and backup names

- README: the Adafruit Triple Bonnet uses `regular` (3 outputs), not
  `regular-pi1` (1 output) -- src/matrix_support.py MAPPING_OUTPUTS, and
  the README's own hardware_mapping section; the template default mapping
  is adafruit-hat, the PWM mod switches it to adafruit-hat-pwm; manual
  install only needs git up front (first_time_install.sh installs
  python-dev-is-python3, cmake, ninja-build etc.; cython3/scons are not
  used); the Pi Zero 2 W is a supported low-memory board, consistent with
  PRODUCT.md, LOW_MEMORY_BOARDS.md and the installer's low-memory build;
  fix the "First_time_install.sh" spelling, an orphan "2." list item and
  the hello-world starter link (it lives in the plugins monorepo).
- CONFIG_DEBUGGING: automatic backups are
  config/backups/config.json.backup.<YYYYMMDD_HHMMSS_ffffff> (five kept),
  not config_YYYYMMDD_HHMMSS.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(dev): correct the test-running and rgbmatrix build instructions

- HOW_TO_RUN_TESTS: coverage is not collected by a plain pytest run and
  pytest.ini has no threshold; the only one is --cov-fail-under=52 in the
  core unit-test job of .github/workflows/test.yml, which runs the whole
  test/ tree (not an allowlist). Almost no tests carry markers, so
  -m integration / -m slow select nothing; drop them and -m unit as the
  quick check. Replace the hardcoded /home/chuck path.
- DEVELOPMENT: the rgbmatrix package is built with pip install . from
  the submodule root (scikit-build-core + CMake + Ninja), as
  first_time_install.sh does; there is no make build-python /
  bindings/python step, and the build deps are python-dev-is-python3,
  cmake and ninja-build, not cython3/scons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(wifi): the setup AP is open; auto-enable can be turned off without code changes

- WIFI_NETWORK_SETUP / SSH_UNAVAILABLE_AFTER_INSTALL: both AP paths in
  src/wifi_manager.py create an open network and nothing reads
  ap_password, so drop the "ledmatrix123" password and the ap_password
  key/advice.
- SSH_UNAVAILABLE_AFTER_INSTALL: disabling automatic AP mode does not
  need code changes -- auto_enable_ap_mode is a WiFi-tab toggle and
  POST /api/v3/wifi/ap/auto-enable; note the monitor daemon reads
  wifi_config.json at start, so restart it after changing the setting.
  Use the ledpi username and a relative install path like the other docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(reference): add auto_update, drop drifted line numbers, fix UI and service details

- CONFIG_REFERENCE: document the top-level auto_update.enabled key (read
  by web_interface/auto_update.py and src/auto_update_setup.py); replace
  drifted file:line references with function names; the template's
  dim_schedule mode is "global".
- ADVANCED_FEATURES: core does not read a per-plugin background_service
  block (the sports plugins read their own), and priority is "higher
  number = higher priority" on FetchRequest but not used for ordering.
- WEB_INTERFACE_GUIDE: the General tab toggle is "Web Display Autostart"
  (web interface service), brightness is 1-100, and config paths are
  relative to the LEDMatrix folder, not /config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop references to code removed in #608

get_installed_plugin_info, WiFiManager's saved_networks and the six
always-skipping plugin test files are deleted there. NetworkManager already
remembers joined networks; LEDMatrix no longer stores WiFi passwords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: don't link SKIN_SYSTEM.md from the core-properties page

#615 deletes SKIN_SYSTEM.md; with this link, whichever of the two merged
second would break test_doc_links. The skin/skin_options entries go when
#615 removes the keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:36:38 -04:00

12 KiB
Raw Blame History

Configuration Reference

Every key in config/config.json, what it does, its default, and where the code reads it. The file is created from config/config.template.json on first run, and ConfigManager._migrate_config() merges any template keys added by later releases into your existing config (your values are never overwritten). Secrets live in config/config_secrets.json and are merged into the config at load time.

Most settings are editable from the web interface; this page documents the underlying keys for people editing config.json directly or writing tooling against it.

Top level

Key Type / default Meaning Read by
web_display_autostart bool, true Whether the web interface service starts with the system scripts/utils/start_web_conditionally.py
auto_update.enabled bool, false Weekly automatic updates: LEDMatrix code first (health-checked, rolled back on failure), then installed plugins. Toggle in the General tab or install with first_time_install.sh --enable-auto-update web_interface/auto_update.py, src/auto_update_setup.py (is_enabled())
timezone string, "America/New_York" IANA timezone for schedules and displays ConfigManager.get_timezone()
target_fps int, 100 Legacy "Scroll Frame Rate". Core scrolling no longer reads it: scroll frames are presented at display.hardware.limit_refresh_rate_hz divided by each scroll's frame hold, and speed comes from each plugin's scroll settings. Still exposed to plugins via BasePlugin.global_config src/plugin_system/base_plugin.py
location object city / state / country. Supplies the default for a plugin's own location_city / location_state / location_country setting, so weather, radar and friends follow this device without being configured twice. A value saved on the plugin itself still overrides it. SchemaManager.apply_device_location(), then plugins via merged config

schedule — display on/off hours

Key Type / default Meaning
enabled bool, false Master switch for scheduled display on/off
mode "global" or "per-day", template uses "per-day" Whether one time range applies to all days or each day has its own
start_time / end_time "HH:MM", 07:00–23:00 Global-mode on/off times
days.<weekday>.{enabled,start_time,end_time} per-day objects Per-day-mode overrides

Read by DisplayController._check_schedule() (src/display_controller.py). Managed in the web UI under Schedule.

dim_schedule — scheduled brightness dimming

Same shape as schedule (the template sets its mode to "global"), plus:

Key Type / default Meaning
dim_brightness int, 30 Brightness percentage applied while the dim window is active

Read by DisplayController._check_dim_schedule() (src/display_controller.py; saved via POST /api/v3/config/dim-schedule). The display returns to display.hardware.brightness outside the window.

display.hardware — matrix panel hardware

All keys map to the corresponding rpi-rgb-led-matrix options and are read in DisplayManager._setup_matrix (src/display_manager.py). Defaults are the config/config.template.json values: ConfigManager adds any key missing from config.json from the template on load, so DisplayManager's own fallbacks don't apply on a normal install.

The ranges are what the pinned rgbmatrix library and its Python binding accept (src/matrix_support.py). The config API refuses anything else; a value hand-edited into config.json makes the display log the setting and run in fallback mode instead of starting the matrix.

Key Type / default
rows / cols int, 32 / 64 — rows: even, 8–64; cols: at least 16
chain_length int, 2 — 1–255 (the Python binding stores it in one byte)
parallel int, 1 — 1–3, and no more than hardware_mapping has outputs (regular, classic: 3; the others: 1)
brightness int, 90 — 1–100
hardware_mapping string, "adafruit-hat" — "adafruit-hat-pwm", "adafruit-hat", "regular", "regular-pi1", "classic" or "classic-pi1" (case-insensitive; compute-module isn't in the installed build). A Pi 5 doesn't support "classic-pi1"
scan_mode int, 0 — 0 progressive, 1 interlaced
pwm_bits int, 9 — 1–11
pwm_dither_bits int, 1 — 0–2
pwm_lsb_nanoseconds int, 130 — 50–3000
disable_hardware_pulsing bool, false — true times brightness pulses in software (less exact); hardware pulsing needs the OE line on GPIO 18 and the Pi's onboard sound driver off
inverse_colors bool, false
show_refresh_rate bool, false — prints the refresh rate to stdout; draws nothing on the panel
led_rgb_sequence string, "RGB" — "RGB", "RBG", "GRB", "GBR", "BRG" or "BGR"
limit_refresh_rate_hz int, 100 — 0 = no cap; scroll timing assumes 100 Hz when 0
pixel_mapper_config string, "" — e.g. "U-mapper" / "Rotate:90"; mappers that rotate or fold the chain change the display size plugins and the web preview see
orientation string, "normal" — "180" rotates the rendered image 180° for panels physically mounted upside down (e.g. to move the Pi/wiring to a more convenient side); "90" / "270" for a panel on its side, swapping width and height; composed onto pixel_mapper_config as a trailing Rotate:<degrees> mapper, so it stays independent of any custom pixel_mapper_config value
row_address_type int, 0 — non-standard panel row addressing: 1 AB, 2 direct row select, 3 ABC, 4 ABC shift + DE direct, 5 SM5368 / B707 row shift register (e.g. Waveshare 96x48 V2, with led_rgb_sequence "BGR"). On a Pi 5 the library supports only 0 and 2, and LEDMatrix enforces that (src/pi5_matrix_support.py)
multiplexing int, 0 — 0–22, pixel wiring scheme for outdoor/specialty panels (names listed in the README)
panel_type string, "" — set to "FM6126A" or "FM6127" for panels needing init; FM6124 / FM6124D / FM6124DJ panels need none, so leave it ""

display.runtime

Key Type / default Meaning
gpio_slowdown int, 3 GPIO timing slowdown for faster Pis (0–10). On a Pi 5 in PIO mode start at 1 (0 acts as 1) and raise it if the image flickers or shows garbage. Panels on row_address_type 5 (SM5368 row drivers) can need 6–8 on a Pi 4 — lower values make rows jump
rp1_rio int, 0 Pi 5 only: 0 = PIO (less CPU), 1 = RIO (higher refresh; gpio_slowdown effect inverted). Applied only if the installed matrix library supports it

display.double_sided

Drives _LogicalMatrix in src/display_manager.py — renders the same logical image to multiple chained physical panels.

Key Type / default Meaning
enabled bool, false Mirror output across panel copies
copies int, 2 Number of physical copies in the chain
axis "horizontal", default Axis along which panels are chained

display — other keys

Key Type / default Meaning Read by
display_durations object, {} Per-plugin display duration in seconds, keyed by plugin id (e.g. "clock": 15) DisplayController._get_display_duration() (src/display_controller.py)
plugin_rotation_order array, [] Explicit rotation order of plugin ids; empty = all enabled plugins in discovery order DisplayController._apply_plugin_rotation_order() (src/display_controller.py)
use_short_date_format bool, true Compact date rendering in sports scoreboards src/base_classes/sports/core.py
dynamic_duration.max_duration_seconds int, optional Cap for plugins that request dynamic display time DisplayController._get_global_dynamic_cap() (src/display_controller.py)

display.vegas_scroll — continuous scroll mode

Read by src/vegas_mode/config.py (VegasScrollConfig.from_config). See ADVANCED_FEATURES.md for behavior details, including live content in the ticker.

Key Type / default
enabled bool, false
scroll_speed int, 50 (px/s)
separator_width int, 32
plugin_order array, []
excluded_plugins array, []
target_fps int, 125
buffer_ahead int, 2
intra_plugin_gap int, 8
render_width_pct int, 100
min_content_separation int, 24
min_cut_gap int, 6
continuous_scroll bool, true
smooth_scroll bool, true
extend_threshold_screens float, 2.0
auto_trim bool, true
trim_threshold int, 10
content_padding int, 8
min_plugin_width int, 8
lead_in_width int, 0
plugins_per_cycle int, 6
max_plugin_width_ratio float, 0.0
overflow_mode string, "rotate"
dynamic_duration_enabled bool, true
min_cycle_duration int, 60
max_cycle_duration int, 240
frame_based_scrolling bool, true — does not step or set a frame rate; motion is by elapsed time either way. When true, scroll_speed passes through a clamp of 0.1–5 px per scroll_delay (see next row)
scroll_delay float, 0.02 — not a frame period. Only used with frame_based_scrolling: the applied speed is clamp(scroll_speed × scroll_delay, 0.1, 5) / scroll_delay px/s, so at 0.02 speeds under 5 px/s run at 5, and at 0.001 nothing runs slower than 100 px/s
live_in_ticker bool, false — keep scrolling during live games instead of handing the display to a full-screen scoreboard
live_weight int, 3 (1–10) — slots per cycle for a plugin with live content
favorite_live_weight int, 5 (1–10) — slots per cycle when a plugin reports a favorite team is live

sync — multi-display synchronization

Read by src/common/sync_manager.py and src/display_controller.py.

Key Type / default Meaning
role "standalone" (default), "leader", or "follower" This device's role in a synced pair
port int, 5765 TCP port used for sync traffic
follower_position "left" (default) or "right" Which half of the combined image this follower renders (src/display_controller.py)

plugin_system

Key Type / default Meaning
plugins_directory string, "plugin-repos" Where the Plugin Store installs plugins and the only directory the plugin loader scans. Read by PluginManager and PluginStoreManager (src/plugin_system/); editable under General settings
auto_discover, auto_load_enabled, development_mode bool Unused. Legacy keys, read by nothing and no longer in the template; older configs may still carry them. Plugins are always discovered, and every plugin with enabled: true is loaded — to keep a plugin installed but dormant, set its own enabled to false. Not shown in the web UI; may be left in or removed from config.json

Plugin config blocks

Every installed plugin stores its settings under a top-level key equal to its plugin id (the template ships one for the bundled web-ui-info plugin). The shape of each block is defined by that plugin's config_schema.json; common keys are enabled and display_duration. See PLUGIN_CONFIG_CORE_PROPERTIES.md.

config/config_secrets.json

Key Meaning
github.api_token Optional GitHub token the Plugin Store uses to avoid API rate limits (src/plugin_system/store_manager.py)
<plugin-id>.* Secrets a plugin declares with "x-secret": true in its config schema; merged into that plugin's config at load time