diff --git a/CHANGELOG.md b/CHANGELOG.md index 37e9cc15..ab4c6b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,15 +47,22 @@ Web interface: JSON API saves are unaffected. Lets plugins keep deprecated or internal keys declared, e.g. countdown's row `id` and weather's `api_key` / `radar_zoom`. See `docs/widget-guide.md`. -- Display settings accept every panel size and timing value the rgbmatrix - library does. Saving no longer silently cuts columns to 128, chain length to - 24 or PWM LSB nanoseconds to 500; rows are held to the library's even 8–64, - parallel to 1–3 and PWM dither bits to 0–2. A stored GPIO slowdown or PWM - dither bits of 0 no longer shows (and re-saves) as 3 or 1. The config API - rejects out-of-range `rows`, `cols`, `chain_length`, `parallel`, +- Display settings no longer silently cut values on save: columns were capped + at 128, chain length at 24 and PWM LSB nanoseconds at 500. Rows, columns and + chain length now have no upper limit (the current rgbmatrix library still + rejects more than 64 rows per panel); rows must be even and at least 8, + parallel is 1–3 and PWM dither bits 0–2, matching the library. A stored GPIO + slowdown, PWM dither bits or refresh-rate cap of 0 no longer shows (and + re-saves) as 3, 1 or 120, and the refresh cap accepts 0 (no cap). The config + API 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 instead of saving a - config the matrix refuses to start with. + `pwm_lsb_nanoseconds`, `limit_refresh_rate_hz` and `gpio_slowdown` with a + 400 instead of saving a config the matrix refuses to start with. +- Display setting help tips and README / config-reference entries corrected + and completed: `panel_type` and `rp1_rio` are documented, + `show_refresh_rate` prints to the console rather than drawing on the panel, + PWM dither bits raise the refresh rate rather than lowering it, and every + numeric setting states its range. - Row Address Type offers 5, the SM5368 / B707 row shift register. The Waveshare 96x48 V2 panel (back silkscreen `24S-A1`) needs it with RGB sequence BGR and, on a Pi 4, a GPIO slowdown of 6–8. Panels with FM6124 diff --git a/README.md b/README.md index c6171490..9245fc56 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,9 @@ These settings control the physical hardware configuration and how the matrix is - **`rows`** (integer, default: 32) - Number of LED rows (vertical pixels) in each panel - Common values: 16, 32, 48, 64 - - Must be an even number from 8 to 64 (the rgbmatrix library's limit) + - Must be an even number, at least 8. LEDMatrix sets no upper limit, but the + current rgbmatrix library rejects more than 64 rows per panel — the display + then won't start (see Troubleshooting Display Settings below) - Must match your physical panel configuration - **`cols`** (integer, default: 64) @@ -531,52 +533,52 @@ These settings control the physical hardware configuration and how the matrix is - Specifies which GPIO pin mapping to use for your hardware - **`"adafruit-hat-pwm"`**: Use this for Adafruit RGB Matrix Bonnet/HAT WITH the jumper mod (PWM enabled). This is the recommended setting for Adafruit hardware with the PWM jumper soldered. - **`"adafruit-hat"`**: Use this for Adafruit RGB Matrix Bonnet/HAT WITHOUT the jumper mod (no PWM). Remove `-pwm` from the value if you did not solder the jumper. - - **`"regular"`**: Standard GPIO pin mapping for direct GPIO connections (Generic) + - **`"regular"`**: Standard GPIO pin mapping for direct GPIO connections (Generic). Also the right choice for the Adafruit Triple LED Matrix Bonnet - **`"regular-pi1"`**: Standard GPIO pin mapping for Raspberry Pi 1 (older hardware or non-standard hat mapping) - Choose the option that matches your specific hardware setup, if aren't sure try them all. + - Hardware pulsing (see `disable_hardware_pulsing`) needs the panel's OE line on GPIO 18, which `adafruit-hat-pwm` and `regular` provide and `adafruit-hat` does not #### PWM (Pulse Width Modulation) Settings These settings affect color fidelity and smoothness of color transitions: -- **`pwm_bits`** (integer, default: 9) - - Number of bits used for PWM (affects color depth) - - Higher values (9-11) = more color levels, smoother gradients - - Lower values (7-8) = fewer color levels, but may improve stability on some hardware - - Range: 1-11, recommended: 9-10 +- **`pwm_bits`** (integer, 1-11, default: 9) + - Color depth per channel: how many brightness levels each LED gets + - Higher values (9-11) = more color levels, smoother gradients, lower refresh rate + - Lower values (7-8) = the subtlest shades are dropped for a higher refresh rate; `1` gives 8 colors + - Recommended: 9-10 -- **`pwm_dither_bits`** (integer, default: 1) - - Additional dithering bits for smoother color transitions - - Helps reduce color banding in gradients - - Higher values (1-2) = smoother gradients but may impact performance - - Range: 0-2, recommended: 1 +- **`pwm_dither_bits`** (integer, 0-2, default: 1) + - Time-dithers the lowest color bits: their brightness comes from showing them on only some frames + - Raises the refresh rate; the cost is that dark shades can shimmer slightly + - `0` = steadiest dim colors, `2` = fastest + - The rgbmatrix library accepts only 0-2; a higher value stops the display starting -- **`pwm_lsb_nanoseconds`** (integer, default: 130) - - Least significant bit timing in nanoseconds - - Controls the base timing for PWM signals - - Lower values = faster PWM, higher values = slower PWM +- **`pwm_lsb_nanoseconds`** (integer, 50-3000, default: 130) + - On-time of the least significant color bit; each higher bit doubles it + - Lower values = higher refresh rate, but can cost color accuracy or add ghosting on some panels + - Higher values = less ghosting (faint trails behind bright text on black), lower refresh rate - Typical range: 100-300 nanoseconds - - May need adjustment if you see flickering or color issues #### Advanced Hardware Settings -- **`scan_mode`** (integer, default: 0) - - Panel scan mode (how rows are addressed) - - Common values: 0 (progressive), 1 (interlaced) - - Most panels use 0, but some require 1 - - Check your panel datasheet if colors appear incorrect +- **`scan_mode`** (integer, 0-1, default: 0) + - Order the rows are refreshed in: `0` = progressive, `1` = interlaced + - Interlaced can look a little smoother when the refresh rate is very low, but usually shows a comb effect on anything moving + - Leave at `0` unless you are tuning a slow setup - **`limit_refresh_rate_hz`** (integer, default: 100) - - Maximum refresh rate in Hz (frames per second) - - Caps the refresh rate for better stability - - Lower values (60-80) = more stable, less CPU usage - - Higher values (100-120) = smoother animations, more CPU usage - - Recommended: 80-100 for most setups + - Caps the panel refresh rate in Hz; `0` = no cap + - A steady cap reduces flicker caused by other activity on the Pi, and in camera recordings + - Scroll speeds are worked out against this value (against 100 Hz when it is `0`), so a cap the panel can actually hold keeps scrolling even + - If the key is missing from the config, `DisplayManager` uses 90 + - Recommended: 80-120. `sudo python3 scripts/scroll_speeds.py --measure` reports the rate your panel really achieves - **`disable_hardware_pulsing`** (boolean, default: false) - - Disables hardware pulsing (usually leave as false) - - Set to `true` only if you experience timing issues - - Most users should leave this as `false` + - `false` = the Pi's hardware PWM times each brightness pulse; `true` = software timing + - Leave `false` where possible. Software timing is less exact, so a row, or the whole panel, can briefly flash brighter + - Hardware pulsing needs the panel's OE line on GPIO 18 (`adafruit-hat-pwm`, `regular`, the Adafruit Triple LED Matrix Bonnet). With `adafruit-hat` the library uses software timing anyway + - It also needs the Pi's onboard sound driver (`snd_bcm2835`) disabled, which `first_time_install.sh` does. Set `true` only if you need the Pi's own audio - **`inverse_colors`** (boolean, default: false) - Inverts all colors (red becomes cyan, etc.) @@ -584,9 +586,9 @@ These settings affect color fidelity and smoothness of color transitions: - Set to `true` only if colors appear inverted - **`show_refresh_rate`** (boolean, default: false) - - Displays the current refresh rate on the matrix (for debugging) - - Set to `true` to see FPS on the display - - Useful for troubleshooting performance issues + - Prints the live refresh rate to the console; nothing is drawn on the panel + - Readable when you stop the service and run `sudo python3 run.py` in a terminal; under the service the output is buffered + - `sudo python3 scripts/scroll_speeds.py --measure` is an easier way to see the real refresh rate #### Advanced Panel Configuration (Advanced Users Only) @@ -596,6 +598,7 @@ These settings are typically only needed for non-standard panels or custom confi - Color channel order for your LED panel - Common values: "RGB", "RBG", "GRB", "GBR", "BRG", "BGR" - Most panels use "RGB", but some use "GRB" or other orders + - If red shows as blue, try "BGR" (the Waveshare 96x48 V2 needs it) - Check your panel datasheet if colors appear wrong - **`pixel_mapper_config`** (string, default: "") @@ -630,28 +633,42 @@ These settings are typically only needed for non-standard panels or custom confi - On a Raspberry Pi 5 the rgbmatrix library currently supports only 0 and 2 - Check your panel datasheet if display appears corrupted -- **`multiplexing`** (integer, default: 0) - - Panel multiplexing type - - 0 = no multiplexing (standard panels) - - Higher values for panels with different multiplexing schemes - - Check your panel datasheet for the correct value +- **`multiplexing`** (integer, 0-22, default: 0) + - How pixels are wired on outdoor/specialty panels (P10, P8, P4 and P3 outdoor modules and similar) whose LEDs aren't laid out in straight rows + - `0` = direct (standard indoor panels) + - `1` Stripe, `2` Checkered, `3` Spiral, `4` ZStripe, `5` ZnMirrorZStripe, + `6` Coreman, `7` Kaler2Scan, `8` ZStripeUneven, `9` P10-128x4-Z, + `10` QiangLiQ8, `11` InversedZStripe, `12`–`14` P10Outdoor1R1G1B v1–v3, + `15` P10CoremanMapper, `16` P8Outdoor1R1G1B, `17` FlippedStripe, + `18` P10-32x16-HalfScan, `19` P10-32x16-QuarterScan, `20` P3Outdoor-64x64, + `21` DoubleZMultiplex, `22` P4Outdoor-80x40 + - If the image is scrambled in a repeating pattern, try the value named after your panel first + +- **`panel_type`** (string, default: `""`) + - Sends a start-up initialization sequence to driver chips that need one + - `""` = Standard (no initialization) — right for most panels, including FM6124 / FM6124D / FM6124DJ + - `"FM6126A"` or `"FM6127"` for panels with those chips; try `"FM6126A"` if the panel stays dark or lights only the first pixel on Standard ### Runtime Configuration (`display.runtime`) These settings control runtime behavior and GPIO timing: - **`gpio_slowdown`** (integer, default: 3) - - GPIO timing slowdown factor - - **Critical setting**: Must match your Raspberry Pi model for stability - - **Raspberry Pi 3**: Use 3 - - **Raspberry Pi 4**: Use 4 - - **Raspberry Pi 5**: Use 1–2 in PIO mode (`rp1_rio: 0`, the default); start with `1` and increase if you see flickering - - **Raspberry Pi Zero/1**: Use 1-2 - - Range: 0-10. Panels on `row_address_type` 5 (SM5368 row drivers) can need - 6-8 on a Pi 4 - - Incorrect values can cause display corruption, flickering, or system instability + - GPIO timing slowdown factor (0-10): slows GPIO writes so the panel electronics keep up. Higher is more reliable but lowers the refresh rate + - **Critical setting**: depends on your Raspberry Pi model and your panel + - **Raspberry Pi Zero/1**: 0-1 + - **Raspberry Pi 2/3**: 1-3 + - **Raspberry Pi 4**: 2-4 (the config template ships 3) + - **Raspberry Pi 5**: 1–3 in PIO mode (`rp1_rio: 0`, the default); start with `1` and increase if you see flickering + - Panels on `row_address_type` 5 (SM5368 row drivers) can need 6-8 on a Pi 4 + - Too low: garbage, flicker or rows jumping. Too high: a lower refresh rate - If you experience issues, try adjusting this value up or down by 1 +- **`rp1_rio`** (integer, 0 or 1, default: 0) — Raspberry Pi 5 only + - Which driver the Pi 5's RP1 chip uses: `0` = PIO (default, less CPU), `1` = RIO (registered I/O, can reach a higher refresh rate) + - In RIO mode the effect of `gpio_slowdown` is inverted: higher values may be faster + - Ignored on a Pi 0-4, and applied only if the installed rgbmatrix library supports it + ### Display Durations (`display.display_durations`) Controls how long each installed plugin stays visible in seconds before switching to the next one, keyed by plugin id. @@ -731,6 +748,14 @@ Controls how long each installed plugin stays visible in seconds before switchin - Verify `hardware_mapping` matches your HAT/connection type - Try adjusting `gpio_slowdown` - Ensure your display doesn't need the E-Addressable line +- If it went blank right after a settings change, check `sudo journalctl -u ledmatrix` for `Failed to initialize RGB Matrix`: the rgbmatrix library refused a value (for example more than 64 `rows`, or `pwm_dither_bits` above 2) and the display fell back to no output. The library's own message nearby names the setting +- A repeating scramble points at `row_address_type` or `multiplexing`; a panel that stays dark, at `panel_type` + +**Rows jump up and down, or the bottom row repeats other rows:** +- Raise `gpio_slowdown` a step at a time (SM5368 panels on `row_address_type` 5 can need 6-8 on a Pi 4) + +**A row or the whole panel briefly flashes brighter:** +- Set `disable_hardware_pulsing` to `false` (needs the OE line on GPIO 18; see `hardware_mapping`) **Colors are wrong or inverted:** - Check `led_rgb_sequence` (try "GRB" if "RGB" doesn't work) diff --git a/docs/CONFIG_REFERENCE.md b/docs/CONFIG_REFERENCE.md index 3f3c182f..b9d76b9a 100644 --- a/docs/CONFIG_REFERENCE.md +++ b/docs/CONFIG_REFERENCE.md @@ -51,7 +51,7 @@ in `DisplayManager` (`src/display_manager.py`, ~lines 270–295). | Key | Type / default | |---|---| -| `rows` / `cols` | int, `32` / `64` — rows: even, 8–64; cols: at least 16, no upper limit | +| `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 | @@ -60,15 +60,15 @@ in `DisplayManager` (`src/display_manager.py`, ~lines 270–295). | `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` | +| `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` | -| `led_rgb_sequence` | string, `"RGB"` | -| `limit_refresh_rate_hz` | int, `100` (code default 90) | +| `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` | -| `multiplexing` | int, `0` — panel multiplexing scheme | +| `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 @@ -79,7 +79,7 @@ applies if the key is missing entirely from your config. | 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` | RP1 RIO mode on Pi 5 (applied only if the installed matrix library supports it) | +| `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` diff --git a/docs/WEB_INTERFACE_GUIDE.md b/docs/WEB_INTERFACE_GUIDE.md index 77db0b10..10ef082b 100644 --- a/docs/WEB_INTERFACE_GUIDE.md +++ b/docs/WEB_INTERFACE_GUIDE.md @@ -132,18 +132,26 @@ require a display service restart from **Overview**. Configure your LED matrix hardware: **Matrix configuration:** -- `rows` — LED rows (typically 32 or 64) -- `cols` — LED columns (typically 64 or 96) +- `rows` — LED rows per panel (typically 32 or 64; even, at least 8 — the + current rgbmatrix library rejects more than 64) +- `cols` — LED columns per panel (typically 64 or 96; at least 16) - `chain_length` — number of horizontally chained panels -- `parallel` — number of parallel chains +- `parallel` — number of parallel chains (1–3) - `hardware_mapping` — `adafruit-hat-pwm` (with PWM jumper mod), - `adafruit-hat` (without), `regular`, or `regular-pi1` -- `gpio_slowdown` — must match your Pi model (3 for Pi 3, 4 for Pi 4, etc.) -- `brightness` — 0–100% + `adafruit-hat` (without), `regular` (direct wiring, and the Adafruit Triple + LED Matrix Bonnet), or `regular-pi1` +- `gpio_slowdown` — depends on your Pi and panel (roughly 1–3 on a Pi 3, + 2–4 on a Pi 4); raise it if rows jump or the image is garbage +- `brightness` — 1–100% - `pwm_bits`, `pwm_lsb_nanoseconds`, `pwm_dither_bits` — PWM tuning - Dynamic Duration — global cap for plugins that extend their display time based on content +The collapsed **Advanced Hardware & Display Options** section holds +multiplexing, panel type, row address type, scan mode, PWM tuning, the +refresh-rate cap and hardware pulsing. Every field has a help tip, and the +README's Display Settings section describes each one with its allowed range. + **Vegas Scroll Mode:** the Display tab also has a full Vegas Scroll Mode section — enable toggle, scroll speed, separator width, dynamic duration, and related settings — so you can configure Vegas mode diff --git a/test/test_api_v3_display_hardware.py b/test/test_api_v3_display_hardware.py index 67b6636a..c7e78008 100644 --- a/test/test_api_v3_display_hardware.py +++ b/test/test_api_v3_display_hardware.py @@ -1,8 +1,9 @@ """Display hardware settings accept what the rgbmatrix library accepts. Held to the ranges in the pinned library (RGBMatrix::Options::Validate in -lib/options-initialize.cc, the gpio_slowdown check in lib/led-matrix.cc). Two -ways this used to go wrong: +lib/options-initialize.cc, the gpio_slowdown check in lib/led-matrix.cc), with +one deliberate exception: rows has no upper bound here, although the library +currently rejects more than 64 per panel. Two ways this used to go wrong: - The Display form capped cols at 128, chain_length at 24 and pwm_lsb_nanoseconds at 500, and its submit handler (fixInvalidNumberInputs) @@ -82,7 +83,7 @@ def test_waveshare_96x48_v2_settings_all_save(api_v3_client, saved, as_strings): @pytest.mark.parametrize('field,value', [ - ('rows', 8), ('rows', 64), + ('rows', 8), ('rows', 64), ('rows', 96), ('rows', 128), ('cols', 16), ('cols', 192), ('cols', 512), ('chain_length', 1), ('chain_length', 32), ('parallel', 3), @@ -93,15 +94,16 @@ def test_waveshare_96x48_v2_settings_all_save(api_v3_client, saved, as_strings): ('pwm_lsb_nanoseconds', 50), ('pwm_lsb_nanoseconds', 3000), ('scan_mode', 1), ('brightness', 1), ('brightness', 100), + ('limit_refresh_rate_hz', 0), ('limit_refresh_rate_hz', 1000), ]) -def test_values_the_library_accepts_are_saved(api_v3_client, saved, field, value): +def test_values_in_range_are_saved(api_v3_client, saved, field, value): response = _post(api_v3_client, {field: value}) assert response.status_code == 200, response.get_data(as_text=True)[:200] assert _stored(saved['config'], field) == value @pytest.mark.parametrize('field,value', [ - ('rows', 6), ('rows', 47), ('rows', 66), ('rows', '48.5'), + ('rows', 6), ('rows', 47), ('rows', 97), ('rows', '48.5'), ('cols', 15), ('cols', 96.5), ('cols', True), ('cols', 'wide'), ('chain_length', 0), ('parallel', 0), ('parallel', 4), @@ -112,8 +114,9 @@ def test_values_the_library_accepts_are_saved(api_v3_client, saved, field, value ('pwm_lsb_nanoseconds', 49), ('pwm_lsb_nanoseconds', 3001), ('scan_mode', 2), ('brightness', 0), ('brightness', 101), + ('limit_refresh_rate_hz', -1), ]) -def test_values_the_library_rejects_are_refused(api_v3_client, saved, field, value): +def test_values_out_of_range_are_refused(api_v3_client, saved, field, value): """Refused with a message naming the field, and nothing written.""" response = _post(api_v3_client, {field: value}) assert response.status_code == 400 @@ -171,7 +174,7 @@ def _selected_option(body, select_id): @pytest.mark.parametrize('input_id,expected', [ - ('rows', {'min': '8', 'max': '64', 'step': '2'}), + ('rows', {'min': '8', 'max': None, 'step': '2'}), ('cols', {'min': '16', 'max': None}), ('chain_length', {'min': '1', 'max': None}), ('parallel', {'min': '1', 'max': '3'}), @@ -179,6 +182,7 @@ def _selected_option(body, select_id): ('pwm_bits', {'min': '1', 'max': '11'}), ('pwm_dither_bits', {'min': '0', 'max': '2'}), ('pwm_lsb_nanoseconds', {'min': '50', 'max': '3000'}), + ('limit_refresh_rate_hz', {'min': '0', 'max': '1000'}), ]) def test_form_limits_match_the_library(display_page, input_id, expected): """fixInvalidNumberInputs rewrites a value past min/max on submit, so these @@ -207,6 +211,7 @@ def test_waveshare_96x48_v2_config_renders_back_unchanged(display_page): @pytest.mark.parametrize('field,section', [ ('gpio_slowdown', 'runtime'), ('pwm_dither_bits', 'hardware'), + ('limit_refresh_rate_hz', 'hardware'), ]) def test_a_stored_zero_renders_as_zero(display_page, field, section): """`value or default` showed a stored 0 as the default, and the next save wrote it back.""" diff --git a/web_interface/blueprints/api_v3/config.py b/web_interface/blueprints/api_v3/config.py index b0a93474..bd1ae53a 100644 --- a/web_interface/blueprints/api_v3/config.py +++ b/web_interface/blueprints/api_v3/config.py @@ -586,17 +586,19 @@ def save_main_config(): # accepts (RGBMatrix::Options::Validate in lib/options-initialize.cc, # the gpio_slowdown check in lib/led-matrix.cc). Outside those ranges # the config used to save, then the matrix refused to start and the - # display dropped to fallback mode. cols and chain_length have no - # upper bound in the library, so none here. + # display dropped to fallback mode. cols, chain_length and + # limit_refresh_rate_hz (0 = no cap) have no upper bound in the + # library. rows has none here by choice: the library currently + # rejects more than 64 per panel, and that limit is left to it so a + # library that lifts it needs no change here. def _hardware_int_error(field, low, high=None, even=False): """A 400 response if data[field] is not an allowed integer, else None.""" raw = data[field] - if even: - allowed = f"an even integer from {low} to {high}" - elif high is None: - allowed = f"an integer of at least {low}" + kind = "an even integer" if even else "an integer" + if high is None: + allowed = f"{kind} of at least {low}" else: - allowed = f"an integer from {low} to {high}" + allowed = f"{kind} from {low} to {high}" rejection = (jsonify({'status': 'error', 'message': f"Invalid {field} '{raw}'. Must be {allowed}."}), 400) # int() would quietly turn true into 1 and 48.5 into 48. if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()): @@ -609,11 +611,12 @@ def save_main_config(): return rejection return None - for field, low, high, even in (('rows', 8, 64, True), ('cols', 16, None, False), + for field, low, high, even in (('rows', 8, None, True), ('cols', 16, None, False), ('chain_length', 1, None, False), ('parallel', 1, 3, False), ('brightness', 1, 100, False), ('scan_mode', 0, 1, False), ('pwm_bits', 1, 11, False), ('pwm_dither_bits', 0, 2, False), ('pwm_lsb_nanoseconds', 50, 3000, False), + ('limit_refresh_rate_hz', 0, None, False), ('gpio_slowdown', 0, 10, False)): if field in data: error = _hardware_int_error(field, low, high, even) diff --git a/web_interface/templates/v3/partials/display.html b/web_interface/templates/v3/partials/display.html index 2e43a44d..12908028 100644 --- a/web_interface/templates/v3/partials/display.html +++ b/web_interface/templates/v3/partials/display.html @@ -41,13 +41,12 @@