Files
LEDMatrix/config/config.template.json
T
ChuckBuildsandClaude Sonnet 5 809d676d3b Give plugins access to device-wide config, and a global scroll frame rate
The sports scoreboards read `getattr(self, 'global_config', {})` to find a
shared scroll frame rate, but nothing ever set that attribute: the loader
constructs plugins with only plugin_id/config/display_manager/cache_manager/
plugin_manager (plugin_loader.py:671), `global_config` appears nowhere in
src/, no plugin manager assigns it, and BasePlugin has no __getattr__ to
synthesize it. The lookup always returned {}, so the ten scroll_display.py
copies that thread target_fps through to ScrollHelper could never fire on any
core. There was also no global target_fps to find -- the only one in the
template is display.vegas_scroll.target_fps, which is Vegas-scoped.

Adds the missing half:

- `BasePlugin.global_config` resolves the full config via
  plugin_manager.config_manager, then cache_manager.config_manager, then {}.
  Same order the sports timezone helpers already use. Exceptions are swallowed
  to debug so an unreadable config can never stop a plugin loading, and a
  non-dict result is rejected rather than handed to callers that will .get()
  it and feed the result to numeric code.
- A top-level `target_fps` (default 100), exposed on the General tab and
  validated 30-200 on save to match ScrollHelper.set_target_fps -- which
  clamps silently, so a rejected save reports a value that would otherwise
  appear to save and then behave differently.

The property has a setter deliberately. news, stock-news, ledmatrix-stocks,
ledmatrix-elections, ledmatrix-leaderboard and nfl-draft all assign
`self.global_config = config.get('global', {})`; without a setter that raises
"property has no setter" and those six plugins stop loading. Reproduced, then
pinned with a test.

target_fps is also kept out of the `is_general_update` key list: that branch
treats a missing web_display_autostart as an unchecked box, so counting a
target_fps-only POST as a General save would silently switch autostart off.

Verified end to end: config.json -> BasePlugin.global_config ->
scroll_display's existing block -> ScrollHelper.target_fps 120 -> 100, with no
plugin-side change needed. Suite 1441 passed; the 4 failures
(test_display_dirty_tracking, test_web_api::test_get_system_status, two in
test_state_reconciliation) are pre-existing and reproduce identically on a
clean tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
2026-08-01 09:59:59 -04:00

170 lines
4.7 KiB
JSON

{
"web_display_autostart": true,
"schedule": {
"enabled": false,
"mode": "per-day",
"start_time": "07:00",
"end_time": "23:00",
"days": {
"monday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"tuesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"wednesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"thursday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"friday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"saturday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"sunday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
}
}
},
"dim_schedule": {
"enabled": false,
"dim_brightness": 30,
"mode": "global",
"start_time": "20:00",
"end_time": "07:00",
"days": {
"monday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"tuesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"wednesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"thursday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"friday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"saturday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"sunday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
}
}
},
"timezone": "America/New_York",
"target_fps": 100,
"location": {
"city": "Tampa",
"state": "Florida",
"country": "US"
},
"display": {
"hardware": {
"rows": 32,
"cols": 64,
"chain_length": 2,
"parallel": 1,
"brightness": 90,
"hardware_mapping": "adafruit-hat",
"scan_mode": 0,
"pwm_bits": 9,
"pwm_dither_bits": 1,
"pwm_lsb_nanoseconds": 130,
"disable_hardware_pulsing": false,
"inverse_colors": false,
"show_refresh_rate": false,
"led_rgb_sequence": "RGB",
"limit_refresh_rate_hz": 100
},
"runtime": {
"gpio_slowdown": 3,
"rp1_rio": 0
},
"double_sided": {
"enabled": false,
"copies": 2,
"axis": "horizontal"
},
"display_durations": {},
"plugin_rotation_order": [],
"use_short_date_format": true,
"vegas_scroll": {
"enabled": false,
"scroll_speed": 50,
"separator_width": 32,
"plugin_order": [],
"excluded_plugins": [],
"target_fps": 125,
"buffer_ahead": 2,
"intra_plugin_gap": 8,
"render_width_pct": 100,
"min_content_separation": 24,
"min_cut_gap": 6,
"continuous_scroll": true,
"smooth_scroll": true,
"extend_threshold_screens": 2.0,
"auto_trim": true,
"trim_threshold": 10,
"content_padding": 8,
"min_plugin_width": 8,
"lead_in_width": 0,
"plugins_per_cycle": 6,
"max_plugin_width_ratio": 3.0,
"overflow_mode": "rotate",
"dynamic_duration_enabled": true,
"min_cycle_duration": 60,
"max_cycle_duration": 240
}
},
"sync": {
"role": "standalone",
"port": 5765,
"follower_position": "left"
},
"plugin_system": {
"plugins_directory": "plugin-repos",
"auto_discover": true,
"auto_load_enabled": true
},
"web-ui-info": {
"enabled": true,
"display_duration": 10
}
}