mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
* 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>
178 lines
9.8 KiB
Markdown
178 lines
9.8 KiB
Markdown
# 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](ADVANCED_FEATURES.md) for behavior details, including
|
||
[live content in the ticker](ADVANCED_FEATURES.md#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](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 |
|