mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5b45f35888
commit
809d676d3b
@@ -167,6 +167,63 @@ class TestConfigAPI:
|
||||
'enabled': True, 'copies': 2, 'axis': 'vertical',
|
||||
}
|
||||
|
||||
def test_save_target_fps(self, client, mock_config_manager):
|
||||
"""The device-wide scroll frame rate persists as a top-level int."""
|
||||
response = client.post(
|
||||
'/api/v3/config/main',
|
||||
data={'target_fps': '90'},
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
saved = mock_config_manager.save_config_atomic.call_args[0][0]
|
||||
# Must be the coerced int, not the raw form string -- the generic
|
||||
# remaining-keys loop would otherwise write '90' back over it.
|
||||
assert saved['target_fps'] == 90
|
||||
|
||||
def test_save_target_fps_alone_does_not_reset_other_general_settings(
|
||||
self, client, mock_config_manager):
|
||||
"""A target_fps-only POST must not be treated as a full General-tab save.
|
||||
|
||||
The general branch reads web_display_autostart as an unchecked-checkbox
|
||||
(absent means False), so counting target_fps as a general update would
|
||||
silently switch autostart off for anyone setting only the frame rate.
|
||||
"""
|
||||
mock_config_manager.load_config.return_value['web_display_autostart'] = True
|
||||
|
||||
response = client.post(
|
||||
'/api/v3/config/main',
|
||||
data={'target_fps': '90'},
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
saved = mock_config_manager.save_config_atomic.call_args[0][0]
|
||||
assert saved['web_display_autostart'] is True
|
||||
|
||||
@pytest.mark.parametrize('value', ['20', '250', 'fast'])
|
||||
def test_save_target_fps_rejects_out_of_range(self, client, mock_config_manager, value):
|
||||
"""Values ScrollHelper would silently clamp are reported instead."""
|
||||
response = client.post(
|
||||
'/api/v3/config/main',
|
||||
data={'target_fps': value},
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_save_target_fps_accepts_bounds(self, client, mock_config_manager):
|
||||
"""Both endpoints of the documented range are valid."""
|
||||
for value in ('30', '200'):
|
||||
response = client.post(
|
||||
'/api/v3/config/main',
|
||||
data={'target_fps': value},
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
)
|
||||
assert response.status_code == 200, f"{value} should be accepted"
|
||||
saved = mock_config_manager.save_config_atomic.call_args[0][0]
|
||||
assert saved['target_fps'] == int(value)
|
||||
|
||||
def test_save_double_sided_unchecked_disables(self, client, mock_config_manager):
|
||||
"""An omitted 'enabled' checkbox is saved as disabled, not left stale."""
|
||||
response = client.post(
|
||||
|
||||
Reference in New Issue
Block a user