-
-
Notifications
You must be signed in to change notification settings - Fork 43
Fetch effects from /json/effects to handle ESP8266 buffer truncation #2083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
63b94c1
f7ce075
3498651
ecca50a
0af8fd1
355aa3b
c880cd2
95c40fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,14 @@ class _PresetsVersion: | |
| boot_time: int | ||
|
|
||
|
|
||
| @dataclass | ||
| class _EffectsVersion: | ||
| """Tracks effects state to avoid unnecessary fetches.""" | ||
|
|
||
| effect_count: int | ||
| boot_time: int | ||
|
|
||
|
|
||
| @dataclass | ||
| class WLED: | ||
| """Main class for handling connections with WLED.""" | ||
|
|
@@ -54,6 +62,7 @@ class WLED: | |
| _close_session: bool = False | ||
| _device: Device | None = None | ||
| _presets_version: _PresetsVersion | None = None | ||
| _effects_version: _EffectsVersion | None = None | ||
|
|
||
| @property | ||
| def connected(self) -> bool: | ||
|
|
@@ -138,7 +147,7 @@ async def listen(self, callback: Callable[[Device], None]) -> None: | |
| if not (presets := await self.request("/presets.json")): | ||
| msg = ( | ||
| f"WLED device at {self.host} returned an empty API" | ||
| " response on presets update", | ||
| " response on presets update" | ||
| ) | ||
| raise WLEDEmptyResponseError(msg) | ||
| message_data["presets"] = presets | ||
|
|
@@ -302,7 +311,7 @@ async def update(self) -> Device: | |
| if not (data := await self.request("/json")): | ||
| msg = ( | ||
| f"WLED device at {self.host} returned an empty API" | ||
| " response on full update", | ||
| " response on full update" | ||
| ) | ||
| raise WLEDEmptyResponseError(msg) | ||
|
|
||
|
|
@@ -311,17 +320,32 @@ async def update(self) -> Device: | |
| if not (presets := await self.request("/presets.json")): | ||
| msg = ( | ||
| f"WLED device at {self.host} returned an empty API" | ||
| " response on presets update", | ||
| " response on presets update" | ||
| ) | ||
| raise WLEDEmptyResponseError(msg) | ||
| data["presets"] = presets | ||
|
|
||
| changed_effects, new_effects_version = self._check_effects_changed(data) | ||
| if changed_effects: | ||
| if not (effects := await self.request("/json/effects")): | ||
| msg = ( | ||
| f"WLED device at {self.host} returned an empty API" | ||
| " response on effects update" | ||
| ) | ||
| raise WLEDEmptyResponseError(msg) | ||
| data["effects"] = effects | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle empty effects lists without treating them as an error. At Line 330, 💡 Suggested fix- if changed_effects:
- if not (effects := await self.request("/json/effects")):
+ if changed_effects:
+ effects = await self.request("/json/effects")
+ if effects is None:
msg = (
f"WLED device at {self.host} returned an empty API"
" response on effects update"
)
raise WLEDEmptyResponseError(msg)
data["effects"] = effects🤖 Prompt for AI Agents |
||
| else: | ||
| # Drop the possibly-truncated effects list from /json so that | ||
| # update_from_dict() keeps the cached full list from /json/effects. | ||
| data.pop("effects", None) | ||
|
|
||
|
mik-laj marked this conversation as resolved.
|
||
| if not self._device: | ||
| self._device = Device.from_dict(data) | ||
| else: | ||
| self._device.update_from_dict(data) | ||
|
|
||
| self._presets_version = new_version | ||
| self._effects_version = new_effects_version | ||
| return self._device | ||
|
|
||
| async def master( | ||
|
|
@@ -823,6 +847,59 @@ def _check_presets_changed( | |
| ) | ||
| return (changed, new_version) | ||
|
|
||
| def _check_effects_changed( | ||
| self, data: dict[str, Any] | ||
| ) -> tuple[bool, _EffectsVersion | None]: | ||
| """Check if effects have changed since the last check. | ||
|
|
||
| Compares the effect count and approximate boot time to detect changes. | ||
| A significant shift in boot_time (> 2 s) signals a device restart. | ||
|
|
||
| On ESP8266 devices the /json response may return a truncated effects | ||
| list due to a limited output buffer (WLED issue #5674). The initial | ||
| load therefore always fetches the complete list from /json/effects, | ||
| which is unaffected by that limitation. | ||
|
|
||
| Returns | ||
| ------- | ||
| A tuple of (changed, new_version). If the version cannot be | ||
| determined from the data, returns (True, None) to trigger a | ||
| safe refetch. | ||
|
|
||
| """ | ||
| if not isinstance(data, dict) or "info" not in data: | ||
| # No info in message (e.g. state-only WebSocket update), | ||
| # effects can't have changed. | ||
| return (False, self._effects_version) | ||
|
|
||
| info = data["info"] | ||
| if (uptime := info.get("uptime")) is None or ( | ||
| fxcount := info.get("fxcount") | ||
| ) is None: | ||
| return (True, None) | ||
|
mik-laj marked this conversation as resolved.
mik-laj marked this conversation as resolved.
|
||
|
|
||
| try: | ||
| new_version = _EffectsVersion( | ||
| effect_count=int(fxcount), | ||
| boot_time=int(time.time()) - int(uptime), | ||
| ) | ||
| except (ValueError, TypeError): | ||
| return (True, None) | ||
|
|
||
| # For initial load, always fetch effects as /json may not include | ||
| # all effect information. | ||
| if self._device is None or self._device.effects is None: | ||
| return (True, new_version) | ||
|
mik-laj marked this conversation as resolved.
|
||
|
|
||
| if self._effects_version is None: | ||
| return (True, new_version) | ||
|
|
||
| changed = ( | ||
| self._effects_version.effect_count != new_version.effect_count | ||
| or abs(self._effects_version.boot_time - new_version.boot_time) > 2 | ||
| ) | ||
| return (changed, new_version) | ||
|
|
||
|
|
||
| @dataclass | ||
| class WLEDReleases: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.