mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
WLED WebSocket support - local push updates (#51683)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
5cc31a98e2
commit
b83b82ca7d
@ -38,5 +38,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Unload WLED config entry."""
|
"""Unload WLED config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
|
coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
# Ensure disconnected and cleanup stop sub
|
||||||
|
await coordinator.wled.disconnect()
|
||||||
|
if coordinator.unsub:
|
||||||
|
coordinator.unsub()
|
||||||
|
|
||||||
del hass.data[DOMAIN][entry.entry_id]
|
del hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -6,7 +6,7 @@ import logging
|
|||||||
DOMAIN = "wled"
|
DOMAIN = "wled"
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
SCAN_INTERVAL = timedelta(seconds=5)
|
SCAN_INTERVAL = timedelta(seconds=10)
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
ATTR_COLOR_PRIMARY = "color_primary"
|
ATTR_COLOR_PRIMARY = "color_primary"
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
"""DataUpdateCoordinator for WLED."""
|
"""DataUpdateCoordinator for WLED."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from wled import WLED, Device as WLEDDevice, WLEDError
|
import asyncio
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from wled import WLED, Device as WLEDDevice, WLEDConnectionClosed, WLEDError
|
||||||
|
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
@ -20,6 +25,7 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize global WLED data updater."""
|
"""Initialize global WLED data updater."""
|
||||||
self.wled = WLED(host, session=async_get_clientsession(hass))
|
self.wled = WLED(host, session=async_get_clientsession(hass))
|
||||||
|
self.unsub: Callable | None = None
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
@ -33,9 +39,62 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
|
|||||||
for update_callback in self._listeners:
|
for update_callback in self._listeners:
|
||||||
update_callback()
|
update_callback()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _use_websocket(self) -> None:
|
||||||
|
"""Use WebSocket for updates, instead of polling."""
|
||||||
|
|
||||||
|
async def listen() -> None:
|
||||||
|
"""Listen for state changes via WebSocket."""
|
||||||
|
try:
|
||||||
|
await self.wled.connect()
|
||||||
|
except WLEDError as err:
|
||||||
|
self.logger.info(err)
|
||||||
|
if self.unsub:
|
||||||
|
self.unsub()
|
||||||
|
self.unsub = None
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.wled.listen(callback=self.async_set_updated_data)
|
||||||
|
except WLEDConnectionClosed as err:
|
||||||
|
self.last_update_success = False
|
||||||
|
self.logger.info(err)
|
||||||
|
except WLEDError as err:
|
||||||
|
self.last_update_success = False
|
||||||
|
self.update_listeners()
|
||||||
|
self.logger.error(err)
|
||||||
|
|
||||||
|
# Ensure we are disconnected
|
||||||
|
await self.wled.disconnect()
|
||||||
|
if self.unsub:
|
||||||
|
self.unsub()
|
||||||
|
self.unsub = None
|
||||||
|
|
||||||
|
async def close_websocket(_) -> None:
|
||||||
|
"""Close WebSocket connection."""
|
||||||
|
await self.wled.disconnect()
|
||||||
|
|
||||||
|
# Clean disconnect WebSocket on Home Assistant shutdown
|
||||||
|
self.unsub = self.hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STOP, close_websocket
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start listening
|
||||||
|
asyncio.create_task(listen())
|
||||||
|
|
||||||
async def _async_update_data(self) -> WLEDDevice:
|
async def _async_update_data(self) -> WLEDDevice:
|
||||||
"""Fetch data from WLED."""
|
"""Fetch data from WLED."""
|
||||||
try:
|
try:
|
||||||
return await self.wled.update(full_update=not self.last_update_success)
|
device = await self.wled.update(full_update=not self.last_update_success)
|
||||||
except WLEDError as error:
|
except WLEDError as error:
|
||||||
raise UpdateFailed(f"Invalid response from API: {error}") from error
|
raise UpdateFailed(f"Invalid response from API: {error}") from error
|
||||||
|
|
||||||
|
# If the device supports a WebSocket, try activating it.
|
||||||
|
if (
|
||||||
|
device.info.websocket is not None
|
||||||
|
and not self.wled.connected
|
||||||
|
and not self.unsub
|
||||||
|
):
|
||||||
|
self._use_websocket()
|
||||||
|
|
||||||
|
return device
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"zeroconf": ["_wled._tcp.local."],
|
"zeroconf": ["_wled._tcp.local."],
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ async def async_setup_entry(
|
|||||||
WLEDWifiSignalSensor(coordinator),
|
WLEDWifiSignalSensor(coordinator),
|
||||||
]
|
]
|
||||||
|
|
||||||
async_add_entities(sensors, True)
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class WLEDEstimatedCurrentSensor(WLEDEntity, SensorEntity):
|
class WLEDEstimatedCurrentSensor(WLEDEntity, SensorEntity):
|
||||||
|
@ -35,7 +35,7 @@ async def async_setup_entry(
|
|||||||
WLEDSyncSendSwitch(coordinator),
|
WLEDSyncSendSwitch(coordinator),
|
||||||
WLEDSyncReceiveSwitch(coordinator),
|
WLEDSyncReceiveSwitch(coordinator),
|
||||||
]
|
]
|
||||||
async_add_entities(switches, True)
|
async_add_entities(switches)
|
||||||
|
|
||||||
|
|
||||||
class WLEDNightlightSwitch(WLEDEntity, SwitchEntity):
|
class WLEDNightlightSwitch(WLEDEntity, SwitchEntity):
|
||||||
|
@ -63,6 +63,7 @@ def mock_wled(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None
|
|||||||
) as wled_mock:
|
) as wled_mock:
|
||||||
wled = wled_mock.return_value
|
wled = wled_mock.return_value
|
||||||
wled.update.return_value = device
|
wled.update.return_value = device
|
||||||
|
wled.connected = False
|
||||||
yield wled
|
yield wled
|
||||||
|
|
||||||
|
|
||||||
|
196
tests/components/wled/test_coordinator.py
Normal file
196
tests/components/wled/test_coordinator.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
"""Tests for the coordinator of the WLED integration."""
|
||||||
|
import asyncio
|
||||||
|
from copy import deepcopy
|
||||||
|
from typing import Callable
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from wled import (
|
||||||
|
Device as WLEDDevice,
|
||||||
|
WLEDConnectionClosed,
|
||||||
|
WLEDConnectionError,
|
||||||
|
WLEDError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.wled.const import SCAN_INTERVAL
|
||||||
|
from homeassistant.const import (
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_not_supporting_websocket(
|
||||||
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Ensure no WebSocket attempt is made if non-WebSocket device."""
|
||||||
|
assert mock_wled.connect.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
|
async def test_websocket_already_connected(
|
||||||
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Ensure no a second WebSocket connection is made, if already connected."""
|
||||||
|
assert mock_wled.connect.call_count == 1
|
||||||
|
|
||||||
|
mock_wled.connected = True
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_wled.connect.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
|
async def test_websocket_connect_error_no_listen(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_wled: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure we don't start listening if WebSocket connection failed."""
|
||||||
|
assert mock_wled.connect.call_count == 1
|
||||||
|
assert mock_wled.listen.call_count == 1
|
||||||
|
|
||||||
|
mock_wled.connect.side_effect = WLEDConnectionError
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_wled.connect.call_count == 2
|
||||||
|
assert mock_wled.listen.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
|
async def test_websocket(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_wled: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test WebSocket connection."""
|
||||||
|
state = hass.states.get("light.wled_websocket")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
# There is no Future in place yet...
|
||||||
|
assert mock_wled.connect.call_count == 1
|
||||||
|
assert mock_wled.listen.call_count == 1
|
||||||
|
assert mock_wled.disconnect.call_count == 1
|
||||||
|
|
||||||
|
connection_connected = asyncio.Future()
|
||||||
|
connection_finished = asyncio.Future()
|
||||||
|
|
||||||
|
async def connect(callback: Callable[[WLEDDevice], None]):
|
||||||
|
connection_connected.set_result(callback)
|
||||||
|
await connection_finished
|
||||||
|
|
||||||
|
# Mock out wled.listen with a Future
|
||||||
|
mock_wled.listen.side_effect = connect
|
||||||
|
|
||||||
|
# Mock out the event bus
|
||||||
|
mock_bus = MagicMock()
|
||||||
|
hass.bus = mock_bus
|
||||||
|
|
||||||
|
# Next refresh it should connect
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
callback = await connection_connected
|
||||||
|
|
||||||
|
# Connected to WebSocket, disconnect not called
|
||||||
|
# listening for Home Assistant to stop
|
||||||
|
assert mock_wled.connect.call_count == 2
|
||||||
|
assert mock_wled.listen.call_count == 2
|
||||||
|
assert mock_wled.disconnect.call_count == 1
|
||||||
|
assert mock_bus.async_listen_once.call_count == 1
|
||||||
|
assert (
|
||||||
|
mock_bus.async_listen_once.call_args_list[0][0][0] == EVENT_HOMEASSISTANT_STOP
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
mock_bus.async_listen_once.call_args_list[0][0][1].__name__ == "close_websocket"
|
||||||
|
)
|
||||||
|
assert mock_bus.async_listen_once.return_value.call_count == 0
|
||||||
|
|
||||||
|
# Send update from WebSocket
|
||||||
|
updated_device = deepcopy(mock_wled.update.return_value)
|
||||||
|
updated_device.state.on = False
|
||||||
|
callback(updated_device)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check if entity updated
|
||||||
|
state = hass.states.get("light.wled_websocket")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
# Resolve Future with a connection losed.
|
||||||
|
connection_finished.set_exception(WLEDConnectionClosed)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Disconnect called, unsubbed Home Assistant stop listener
|
||||||
|
assert mock_wled.disconnect.call_count == 2
|
||||||
|
assert mock_bus.async_listen_once.return_value.call_count == 1
|
||||||
|
|
||||||
|
# Light still available, as polling takes over
|
||||||
|
state = hass.states.get("light.wled_websocket")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
|
async def test_websocket_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_wled: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test WebSocket connection erroring out, marking lights unavailable."""
|
||||||
|
state = hass.states.get("light.wled_websocket")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
connection_connected = asyncio.Future()
|
||||||
|
connection_finished = asyncio.Future()
|
||||||
|
|
||||||
|
async def connect(callback: Callable[[WLEDDevice], None]):
|
||||||
|
connection_connected.set_result(None)
|
||||||
|
await connection_finished
|
||||||
|
|
||||||
|
mock_wled.listen.side_effect = connect
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await connection_connected
|
||||||
|
|
||||||
|
# Resolve Future with an error.
|
||||||
|
connection_finished.set_exception(WLEDError)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Light no longer available as an error occurred
|
||||||
|
state = hass.states.get("light.wled_websocket")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
|
async def test_websocket_disconnect_on_home_assistant_stop(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_wled: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure WebSocket is disconnected when Home Assistant stops."""
|
||||||
|
assert mock_wled.disconnect.call_count == 1
|
||||||
|
connection_connected = asyncio.Future()
|
||||||
|
connection_finished = asyncio.Future()
|
||||||
|
|
||||||
|
async def connect(callback: Callable[[WLEDDevice], None]):
|
||||||
|
connection_connected.set_result(None)
|
||||||
|
await connection_finished
|
||||||
|
|
||||||
|
mock_wled.listen.side_effect = connect
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await connection_connected
|
||||||
|
|
||||||
|
assert mock_wled.disconnect.call_count == 1
|
||||||
|
|
||||||
|
hass.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_wled.disconnect.call_count == 2
|
@ -1,6 +1,9 @@
|
|||||||
"""Tests for the WLED integration."""
|
"""Tests for the WLED integration."""
|
||||||
|
import asyncio
|
||||||
|
from typing import Callable
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
from wled import WLEDConnectionError
|
from wled import WLEDConnectionError
|
||||||
|
|
||||||
from homeassistant.components.wled.const import DOMAIN
|
from homeassistant.components.wled.const import DOMAIN
|
||||||
@ -10,19 +13,36 @@ from homeassistant.core import HomeAssistant
|
|||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True)
|
||||||
async def test_load_unload_config_entry(
|
async def test_load_unload_config_entry(
|
||||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_wled: AsyncMock
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_wled: AsyncMock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the WLED configuration entry unloading."""
|
"""Test the WLED configuration entry unloading."""
|
||||||
|
connection_connected = asyncio.Future()
|
||||||
|
connection_finished = asyncio.Future()
|
||||||
|
|
||||||
|
async def connect(callback: Callable):
|
||||||
|
connection_connected.set_result(None)
|
||||||
|
await connection_finished
|
||||||
|
|
||||||
|
# Mock out wled.listen with a Future
|
||||||
|
mock_wled.listen.side_effect = connect
|
||||||
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
await connection_connected
|
||||||
|
|
||||||
|
# Ensure config entry is loaded and are connected
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
assert mock_wled.connect.call_count == 1
|
||||||
|
assert mock_wled.disconnect.call_count == 0
|
||||||
|
|
||||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Ensure everything is cleaned up nicely and are disconnected
|
||||||
|
assert mock_wled.disconnect.call_count == 1
|
||||||
assert not hass.data.get(DOMAIN)
|
assert not hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
289
tests/fixtures/wled/rgb_websocket.json
vendored
Normal file
289
tests/fixtures/wled/rgb_websocket.json
vendored
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"on": true,
|
||||||
|
"bri": 255,
|
||||||
|
"transition": 7,
|
||||||
|
"ps": -1,
|
||||||
|
"pl": -1,
|
||||||
|
"ccnf": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 5,
|
||||||
|
"time": 12
|
||||||
|
},
|
||||||
|
"nl": {
|
||||||
|
"on": false,
|
||||||
|
"dur": 60,
|
||||||
|
"fade": true,
|
||||||
|
"mode": 1,
|
||||||
|
"tbri": 0,
|
||||||
|
"rem": -1
|
||||||
|
},
|
||||||
|
"udpn": {
|
||||||
|
"send": false,
|
||||||
|
"recv": true
|
||||||
|
},
|
||||||
|
"lor": 0,
|
||||||
|
"mainseg": 0,
|
||||||
|
"seg": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"start": 0,
|
||||||
|
"stop": 13,
|
||||||
|
"len": 13,
|
||||||
|
"grp": 1,
|
||||||
|
"spc": 0,
|
||||||
|
"on": true,
|
||||||
|
"bri": 255,
|
||||||
|
"col": [
|
||||||
|
[
|
||||||
|
255,
|
||||||
|
181,
|
||||||
|
218
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"fx": 0,
|
||||||
|
"sx": 43,
|
||||||
|
"ix": 128,
|
||||||
|
"pal": 2,
|
||||||
|
"sel": true,
|
||||||
|
"rev": false,
|
||||||
|
"mi": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"ver": "0.12.0-b2",
|
||||||
|
"vid": 2103220,
|
||||||
|
"leds": {
|
||||||
|
"count": 13,
|
||||||
|
"rgbw": false,
|
||||||
|
"wv": false,
|
||||||
|
"pin": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"pwr": 266,
|
||||||
|
"fps": 2,
|
||||||
|
"maxpwr": 1000,
|
||||||
|
"maxseg": 12,
|
||||||
|
"seglock": false
|
||||||
|
},
|
||||||
|
"str": false,
|
||||||
|
"name": "WLED WebSocket",
|
||||||
|
"udpport": 21324,
|
||||||
|
"live": false,
|
||||||
|
"lm": "",
|
||||||
|
"lip": "",
|
||||||
|
"ws": 0,
|
||||||
|
"fxcount": 118,
|
||||||
|
"palcount": 56,
|
||||||
|
"wifi": {
|
||||||
|
"bssid": "AA:AA:AA:AA:AA:BB",
|
||||||
|
"rssi": -68,
|
||||||
|
"signal": 64,
|
||||||
|
"channel": 6
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"u": 40,
|
||||||
|
"t": 1024,
|
||||||
|
"pmt": 1623156685
|
||||||
|
},
|
||||||
|
"ndc": 1,
|
||||||
|
"arch": "esp8266",
|
||||||
|
"core": "2_7_4_7",
|
||||||
|
"lwip": 1,
|
||||||
|
"freeheap": 22752,
|
||||||
|
"uptime": 258411,
|
||||||
|
"opt": 127,
|
||||||
|
"brand": "WLED",
|
||||||
|
"product": "FOSS",
|
||||||
|
"mac": "aabbccddeeff"
|
||||||
|
},
|
||||||
|
"effects": [
|
||||||
|
"Solid",
|
||||||
|
"Blink",
|
||||||
|
"Breathe",
|
||||||
|
"Wipe",
|
||||||
|
"Wipe Random",
|
||||||
|
"Random Colors",
|
||||||
|
"Sweep",
|
||||||
|
"Dynamic",
|
||||||
|
"Colorloop",
|
||||||
|
"Rainbow",
|
||||||
|
"Scan",
|
||||||
|
"Scan Dual",
|
||||||
|
"Fade",
|
||||||
|
"Theater",
|
||||||
|
"Theater Rainbow",
|
||||||
|
"Running",
|
||||||
|
"Saw",
|
||||||
|
"Twinkle",
|
||||||
|
"Dissolve",
|
||||||
|
"Dissolve Rnd",
|
||||||
|
"Sparkle",
|
||||||
|
"Sparkle Dark",
|
||||||
|
"Sparkle+",
|
||||||
|
"Strobe",
|
||||||
|
"Strobe Rainbow",
|
||||||
|
"Strobe Mega",
|
||||||
|
"Blink Rainbow",
|
||||||
|
"Android",
|
||||||
|
"Chase",
|
||||||
|
"Chase Random",
|
||||||
|
"Chase Rainbow",
|
||||||
|
"Chase Flash",
|
||||||
|
"Chase Flash Rnd",
|
||||||
|
"Rainbow Runner",
|
||||||
|
"Colorful",
|
||||||
|
"Traffic Light",
|
||||||
|
"Sweep Random",
|
||||||
|
"Running 2",
|
||||||
|
"Aurora",
|
||||||
|
"Stream",
|
||||||
|
"Scanner",
|
||||||
|
"Lighthouse",
|
||||||
|
"Fireworks",
|
||||||
|
"Rain",
|
||||||
|
"Tetrix",
|
||||||
|
"Fire Flicker",
|
||||||
|
"Gradient",
|
||||||
|
"Loading",
|
||||||
|
"Police",
|
||||||
|
"Police All",
|
||||||
|
"Two Dots",
|
||||||
|
"Two Areas",
|
||||||
|
"Circus",
|
||||||
|
"Halloween",
|
||||||
|
"Tri Chase",
|
||||||
|
"Tri Wipe",
|
||||||
|
"Tri Fade",
|
||||||
|
"Lightning",
|
||||||
|
"ICU",
|
||||||
|
"Multi Comet",
|
||||||
|
"Scanner Dual",
|
||||||
|
"Stream 2",
|
||||||
|
"Oscillate",
|
||||||
|
"Pride 2015",
|
||||||
|
"Juggle",
|
||||||
|
"Palette",
|
||||||
|
"Fire 2012",
|
||||||
|
"Colorwaves",
|
||||||
|
"Bpm",
|
||||||
|
"Fill Noise",
|
||||||
|
"Noise 1",
|
||||||
|
"Noise 2",
|
||||||
|
"Noise 3",
|
||||||
|
"Noise 4",
|
||||||
|
"Colortwinkles",
|
||||||
|
"Lake",
|
||||||
|
"Meteor",
|
||||||
|
"Meteor Smooth",
|
||||||
|
"Railway",
|
||||||
|
"Ripple",
|
||||||
|
"Twinklefox",
|
||||||
|
"Twinklecat",
|
||||||
|
"Halloween Eyes",
|
||||||
|
"Solid Pattern",
|
||||||
|
"Solid Pattern Tri",
|
||||||
|
"Spots",
|
||||||
|
"Spots Fade",
|
||||||
|
"Glitter",
|
||||||
|
"Candle",
|
||||||
|
"Fireworks Starburst",
|
||||||
|
"Fireworks 1D",
|
||||||
|
"Bouncing Balls",
|
||||||
|
"Sinelon",
|
||||||
|
"Sinelon Dual",
|
||||||
|
"Sinelon Rainbow",
|
||||||
|
"Popcorn",
|
||||||
|
"Drip",
|
||||||
|
"Plasma",
|
||||||
|
"Percent",
|
||||||
|
"Ripple Rainbow",
|
||||||
|
"Heartbeat",
|
||||||
|
"Pacifica",
|
||||||
|
"Candle Multi",
|
||||||
|
"Solid Glitter",
|
||||||
|
"Sunrise",
|
||||||
|
"Phased",
|
||||||
|
"Twinkleup",
|
||||||
|
"Noise Pal",
|
||||||
|
"Sine",
|
||||||
|
"Phased Noise",
|
||||||
|
"Flow",
|
||||||
|
"Chunchun",
|
||||||
|
"Dancing Shadows",
|
||||||
|
"Washing Machine",
|
||||||
|
"Candy Cane",
|
||||||
|
"Blends",
|
||||||
|
"TV Simulator",
|
||||||
|
"Dynamic Smooth"
|
||||||
|
],
|
||||||
|
"palettes": [
|
||||||
|
"Default",
|
||||||
|
"* Random Cycle",
|
||||||
|
"* Color 1",
|
||||||
|
"* Colors 1&2",
|
||||||
|
"* Color Gradient",
|
||||||
|
"* Colors Only",
|
||||||
|
"Party",
|
||||||
|
"Cloud",
|
||||||
|
"Lava",
|
||||||
|
"Ocean",
|
||||||
|
"Forest",
|
||||||
|
"Rainbow",
|
||||||
|
"Rainbow Bands",
|
||||||
|
"Sunset",
|
||||||
|
"Rivendell",
|
||||||
|
"Breeze",
|
||||||
|
"Red & Blue",
|
||||||
|
"Yellowout",
|
||||||
|
"Analogous",
|
||||||
|
"Splash",
|
||||||
|
"Pastel",
|
||||||
|
"Sunset 2",
|
||||||
|
"Beech",
|
||||||
|
"Vintage",
|
||||||
|
"Departure",
|
||||||
|
"Landscape",
|
||||||
|
"Beach",
|
||||||
|
"Sherbet",
|
||||||
|
"Hult",
|
||||||
|
"Hult 64",
|
||||||
|
"Drywet",
|
||||||
|
"Jul",
|
||||||
|
"Grintage",
|
||||||
|
"Rewhi",
|
||||||
|
"Tertiary",
|
||||||
|
"Fire",
|
||||||
|
"Icefire",
|
||||||
|
"Cyane",
|
||||||
|
"Light Pink",
|
||||||
|
"Autumn",
|
||||||
|
"Magenta",
|
||||||
|
"Magred",
|
||||||
|
"Yelmag",
|
||||||
|
"Yelblu",
|
||||||
|
"Orange & Teal",
|
||||||
|
"Tiamat",
|
||||||
|
"April Night",
|
||||||
|
"Orangery",
|
||||||
|
"C9",
|
||||||
|
"Sakura",
|
||||||
|
"Aurora",
|
||||||
|
"Atlantica",
|
||||||
|
"C9 2",
|
||||||
|
"C9 New",
|
||||||
|
"Temperature",
|
||||||
|
"Aurora 2"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user