Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import asyncio

from wled import WLED, Preset
from wled import WLED, Playlist, Preset


async def main():
Expand All @@ -15,6 +15,9 @@ async def main():
if isinstance(device.state.preset, Preset):
print(f"Preset active! Name: {device.state.preset.name}")

if isinstance(device.state.playlist, Playlist):
print(f"Playlist active! Name: {device.state.playlist.name}")

# Turn strip on, full brightness
await led.master(on=True, brightness=255)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ max-line-length=88
max-attributes=20

[tool.pytest.ini_options]
addopts = "--cov"
addopts = " --cov"

[tool.vulture]
min_confidence = 80
Expand Down
4 changes: 4 additions & 0 deletions src/wled/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
Live,
Nightlight,
Palette,
Playlist,
PlaylistEntry,
Preset,
Segment,
State,
Expand All @@ -29,6 +31,8 @@
"Live",
"Nightlight",
"Palette",
"Playlist",
"PlaylistEntry",
"Preset",
"Segment",
"State",
Expand Down
103 changes: 99 additions & 4 deletions src/wled/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ class State:
brightness: int
nightlight: Nightlight
on: bool
playlist: int
playlist: Playlist | int | None
preset: Preset | int | None
segments: list[Segment]
sync: Sync
Expand Down Expand Up @@ -356,14 +356,16 @@ def from_dict(
effects: list[Effect],
palettes: list[Palette],
presets: list[Preset],
playlists: list[Playlist],
) -> State:
"""Return State object from WLED API response.

Args:
data: The state response received from the WLED device API.
effects: A list of effect objects.
palettes: A list of palette objects.
presets: A list of presets objects.
presets: A list of preset objects.
playlists: A list of playlist objects.

Returns:
A State object.
Expand All @@ -384,18 +386,24 @@ def from_dict(
for segment_id, segment in enumerate(data.get("seg", []))
]

playlist = data.get("pl", -1)
preset = data.get("ps", -1)
if presets:
preset = next(
(item for item in presets if item.preset_id == data.get("ps")),
None,
)

playlist = next(
(item for item in playlists if item.playlist_id == data.get("pl")),
None,
)

return State(
brightness=brightness,
nightlight=Nightlight.from_dict(data),
on=on,
playlist=data.get("pl", -1),
playlist=playlist,
preset=preset,
segments=segments,
sync=Sync.from_dict(data),
Expand Down Expand Up @@ -469,12 +477,82 @@ def from_dict(
)


@dataclass
class PlaylistEntry:
"""Object representing a entry in a WLED playlist."""

duration: int
entry_id: int
preset: Preset | None
transition: int


@dataclass
class Playlist:
"""Object representing a WLED playlist."""

end: Preset | None
entries: list[PlaylistEntry]
name: str
playlist_id: int
repeat: int
shuffle: bool

@staticmethod
def from_dict(
playlist_id: int,
data: dict[str, Any],
presets: list[Preset],
) -> Playlist:
"""Return Playlist object from WLED API response.

Args:
playlist_id: The ID of the playlist.
data: The data from the WLED device API.
presets: A list of preset objects.

Returns:
A Playlist object.
"""
playlist = data.get("playlist", {})
entries_durations = playlist.get("dur", [])
entries_presets = playlist.get("ps", [])
entries_transitions = playlist.get("transition", [])

entries = [
PlaylistEntry(
entry_id=entry_id,
duration=entries_durations[entry_id],
transition=entries_transitions[entry_id],
preset=next(
(item for item in presets if item.preset_id == preset_id), None
),
)
for entry_id, preset_id in enumerate(entries_presets)
]

end = next(
(item for item in presets if item.preset_id == playlist.get("end")),
None,
)

return Playlist(
playlist_id=playlist_id,
shuffle=playlist.get("r", False),
name=data.get("n", str(playlist_id)),
repeat=playlist.get("repeat", 0),
end=end,
entries=entries,
)


class Device:
"""Object holding all information of WLED."""

effects: list[Effect] = []
info: Info
palettes: list[Palette] = []
playlists: list[Playlist] = []
presets: list[Preset] = []
state: State

Expand Down Expand Up @@ -523,20 +601,37 @@ def update_from_dict(self, data: dict) -> Device:
self.palettes = palettes

if _presets := data.get("presets"):
# The preset data contains both presets and playlists,
# we split those out, so we can handle those correctly.

# Nobody cares about 0.
_presets.pop("0")

presets = [
Preset.from_dict(int(preset_id), preset, self.effects, self.palettes)
for preset_id, preset in _presets.items()
if "playlist" not in preset
or not ("ps" in preset["playlist"] and preset["playlist"]["ps"])
]
presets.sort(key=lambda x: x.name)
self.presets = presets

playlists = [
Playlist.from_dict(int(playlist_id), playlist, self.presets)
for playlist_id, playlist in _presets.items()
if "playlist" in playlist
and "ps" in playlist["playlist"]
and playlist["playlist"]["ps"]
]
playlists.sort(key=lambda x: x.name)
self.playlists = playlists

if _info := data.get("info"):
self.info = Info.from_dict(_info)

if _state := data.get("state"):
self.state = State.from_dict(
_state, self.effects, self.palettes, self.presets
_state, self.effects, self.palettes, self.presets, self.playlists
)

return self
Expand Down
41 changes: 29 additions & 12 deletions src/wled/wled.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
WLEDEmptyResponseError,
WLEDError,
)
from .models import Device, Live
from .models import Device, Live, Playlist, Preset


@dataclass
Expand Down Expand Up @@ -474,11 +474,11 @@ async def transition(self, transition: int) -> None:
"/json/state", method="POST", data={"transition": transition}
)

async def preset(self, preset: int | str) -> None:
async def preset(self, preset: int | str | Preset) -> None:
"""Set a preset on a WLED device.

Args:
preset: The preset number to activate on this WLED device.
preset: The preset to activate on this WLED device.
"""
# Find preset if it was based on a name
if self._device and self._device.presets and isinstance(preset, str):
Expand All @@ -491,24 +491,41 @@ async def preset(self, preset: int | str) -> None:
preset,
)

if isinstance(preset, Preset):
preset = preset.preset_id

await self.request("/json/state", method="POST", data={"ps": preset})

async def live(self, live: Live) -> None:
"""Set the live override mode on a WLED device.
async def playlist(self, playlist: int | str | Playlist) -> None:
"""Set a playlist on a WLED device.

Args:
live: The live override mode to set on this WLED device.
playlist: The playlist to activate on this WLED device.
"""
await self.request("/json/state", method="POST", data={"lor": live.value})

async def playlist(self, playlist: int) -> None:
"""Set a running playlist on a WLED device.
# Find playlist if it was based on a name
if self._device and self._device.playlists and isinstance(playlist, str):
playlist = next(
(
item.playlist_id
for item in self._device.playlists
if item.name.lower() == playlist.lower()
),
playlist,
)

if isinstance(playlist, Playlist):
playlist = playlist.playlist_id

await self.request("/json/state", method="POST", data={"ps": playlist})

async def live(self, live: Live) -> None:
"""Set the live override mode on a WLED device.

Args:
playlist: ID of playlist to run. For now, this sets the preset
cycle feature, -1 is off and 0 is on.
live: The live override mode to set on this WLED device.
"""
await self.request("/json/state", method="POST", data={"pl": playlist})
await self.request("/json/state", method="POST", data={"lor": live.value})

async def sync(
self, *, send: bool | None = None, receive: bool | None = None
Expand Down