Files
LEDMatrix/docs/CONFIG_REFERENCE.md
T
ChuckandClaude Opus 5 f9b3d6ae52 fix(web): accept every panel size and row address type the rgbmatrix library does (#586)
* fix(web): accept every panel size and row address type the rgbmatrix library does

The Display form capped columns at 128 and chain length at 24, and its
submit handler (fixInvalidNumberInputs) rewrote anything larger to the cap,
so wide panels and long chains silently saved as the wrong size. The config
API checked none of the hardware numbers, so values the library rejects (odd
rows, parallel 4, PWM dither bits 3) saved and the matrix then refused to
start.

- Form limits now match the pinned library: rows even 8-64, cols >= 16 and
  chain_length >= 1 with no upper bound, parallel 1-3, PWM dither bits 0-2,
  PWM LSB nanoseconds 50-3000.
- save_main_config rejects out-of-range rows, cols, chain_length, parallel,
  brightness, scan_mode, pwm_bits, pwm_dither_bits, pwm_lsb_nanoseconds and
  gpio_slowdown with a 400.
- A stored gpio_slowdown or pwm_dither_bits of 0 renders as 0 instead of the
  default, so saving the tab no longer overwrites it.
- Row Address Type offers 5 (SM5368 / B707 row shift register). Verified on a
  Waveshare 96x48 V2 (24S-A1) on a Pi 4 with the Adafruit Triple LED Matrix
  Bonnet: rows 48, cols 96, row address type 5, BGR, GPIO slowdown 8.
- Help text and docs: FM6124-family panels use Panel Type Standard; on a Pi 5
  the library supports only row address types 0 and 2.

No change to the rpi-rgb-led-matrix submodule.

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

* fix(web): drop the rows cap and document every display setting accurately

Rows: no upper limit in the form or the API. Still even and at least 8. The
current rgbmatrix library rejects more than 64 per panel, so a larger value
saves but the matrix won't start; the help tip, README, config reference and
troubleshooting section all say so, and nothing here needs changing if the
library lifts the limit.

limit_refresh_rate_hz: the form accepts 0 (the library's "no cap"), a stored
0 no longer renders and re-saves as 120, and the API rejects negatives.

pwm_dither_bits stays 0-2: the library rejects 3 and 4, so the old form's
0-4 only ever let users save a config the display couldn't start with.

Docs and help tips, checked against the pinned library and its README:
- panel_type and rp1_rio get README entries
- show_refresh_rate prints to stdout; it never drew on the panel
- dither bits raise the refresh rate; the tip said they lowered it
- scan_mode is about interlacing at low refresh, not wrong colours
- disable_hardware_pulsing: hardware pulsing needs OE on GPIO 18 and the
  onboard sound driver off; software timing makes rows flash brighter
- gpio_slowdown guidance agrees between the README and the UI
- all 22 multiplexing values listed; every numeric setting states its range
- troubleshooting for a blank panel after a settings change, jumping rows
  and brightness flashes

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

* fix(web): reject true and 5.5 for row_address_type and multiplexing

Both still went straight through int(), so a JSON true saved as 1 and 5.5
as 5. They now use the shared hardware range check like the other panel
fields. Review feedback on #586.

Also: the RP1 Backend tooltip said it is ignored on Pi 3/4 (it is ignored
on every model but the Pi 5), and the README gave the dynamic-duration
default cap as 90s; the code default is 180s.

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

* feat: refuse matrix settings a Raspberry Pi 5 can't drive

On a Pi 5 the pinned rgbmatrix library drives the panel through the RP1
chip, and that path supports only row address types 0 and 2, parallel 1-3
and the regular / regular-pi1 / classic / adafruit-hat(-pwm) mappings
(Rp1PioConfigSupported in lib/rp1/rp1_pio_backend.cc). For anything else
CreateFromOptions returns NULL; the Python binding doesn't check, so the
display process crashed on its first call into the matrix and systemd
restarted it into the same crash every 10 seconds.

- src/pi5_matrix_support.py: the rule and Pi 5 detection, matching the
  library's /proc/device-tree/model check
- DisplayManager raises before creating the matrix, so it is a logged init
  failure (reported by /api/v3/hardware/status) and fallback mode
- the config API rejects those settings on a Pi 5 when a request sets
  row_address_type, parallel or hardware_mapping
- the Display form offers only row address types 0 and 2 on a Pi 5, and
  warns when a stored value can't be used
- CLAUDE.md: re-check the rule whenever the submodule is bumped

Review feedback on #586.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 19:17:48 -04:00

9.8 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
timezone string, "America/New_York" IANA timezone for schedules and displays ConfigManager.get_timezone()
target_fps int, 100 Frame-rate ceiling for plugin rendering src/plugin_system/base_plugin.py, src/common/sports_scroll.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 (src/display_controller.py, _check_schedule around line 603). Managed in the web UI under Schedule.

dim_schedule — scheduled brightness dimming

Same shape as schedule, plus:

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

Read by DisplayController (src/display_controller.py around line 770; 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 (src/display_manager.py, ~lines 270–295).

Key Type / default
rows / cols int, 32 / 64 — rows: even, at least 8, no upper limit here (the current rgbmatrix library rejects more than 64); cols: at least 16, no upper limit
chain_length int, 2 — at least 1, no upper limit
parallel int, 1 — 1–3
brightness int, 90 — 1–100
hardware_mapping string, "adafruit-hat" (code default "adafruit-hat-pwm")
scan_mode int, 0 — 0 progressive, 1 interlaced
pwm_bits int, 9 (code default 10) — 1–11
pwm_dither_bits int, 1 — 0–2
pwm_lsb_nanoseconds int, 130 (code default 150) — 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 (code default 90) — 0 = no cap; scroll timing assumes 100 Hz when 0
pixel_mapper_config string, "" — e.g. "U-mapper" / "Rotate:90"
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); composed onto pixel_mapper_config as a trailing Rotate:180 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 ""

Where "code default" differs from the template value, the code default only applies if the key is missing entirely from your config.

display.runtime

Key Type / default Meaning
gpio_slowdown int, 3 GPIO timing slowdown for faster Pis (0–10). 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) src/display_controller.py:1030
plugin_rotation_order array, [] Explicit rotation order of plugin ids; empty = all enabled plugins in discovery order src/display_controller.py:2894
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 src/display_controller.py:405

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 — frame-count-based scroll stepping
scroll_delay float, 0.02 — seconds between scroll updates (~50 FPS)
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:522)

plugin_system

Read by the plugin loader/manager (src/plugin_system/).

Key Type / default Meaning
plugins_directory string, "plugin-repos" Where the Plugin Store installs plugins
auto_discover bool, true Scan the plugins directory at startup
auto_load_enabled bool, true Load discovered plugins automatically
development_mode bool, false Development conveniences in the web UI (editable under General settings)

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:348)
<plugin-id>.* Secrets a plugin declares with "x-secret": true in its config schema; merged into that plugin's config at load time