Add WiZ occupancy sensor support (#66231)

This commit is contained in:
J. Nick Koston 2022-02-14 07:25:15 -06:00 committed by GitHub
parent b2ee7cebc9
commit 00d7fdd274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 242 additions and 28 deletions

View File

@ -4,13 +4,15 @@ from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
from pywizlight import wizlight from pywizlight import PilotParser, wizlight
from pywizlight.bulb import PIR_SOURCE
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -19,6 +21,7 @@ from .const import (
DISCOVER_SCAN_TIMEOUT, DISCOVER_SCAN_TIMEOUT,
DISCOVERY_INTERVAL, DISCOVERY_INTERVAL,
DOMAIN, DOMAIN,
SIGNAL_WIZ_PIR,
WIZ_CONNECT_EXCEPTIONS, WIZ_CONNECT_EXCEPTIONS,
WIZ_EXCEPTIONS, WIZ_EXCEPTIONS,
) )
@ -27,7 +30,7 @@ from .models import WizData
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.LIGHT, Platform.SWITCH] PLATFORMS = [Platform.BINARY_SENSOR, Platform.LIGHT, Platform.SWITCH]
REQUEST_REFRESH_DELAY = 0.35 REQUEST_REFRESH_DELAY = 0.35
@ -76,7 +79,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
), ),
) )
await bulb.start_push(lambda _: coordinator.async_set_updated_data(None)) @callback
def _async_push_update(state: PilotParser) -> None:
"""Receive a push update."""
_LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult)
coordinator.async_set_updated_data(None)
if state.get_source() == PIR_SOURCE:
async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac))
await bulb.start_push(_async_push_update)
bulb.set_discovery_callback(lambda bulb: async_trigger_discovery(hass, [bulb])) bulb.set_discovery_callback(lambda bulb: async_trigger_discovery(hass, [bulb]))
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()

View File

@ -0,0 +1,81 @@
"""WiZ integration binary sensor platform."""
from __future__ import annotations
from collections.abc import Callable
from pywizlight.bulb import PIR_SOURCE
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, SIGNAL_WIZ_PIR
from .entity import WizEntity
from .models import WizData
OCCUPANCY_UNIQUE_ID = "{}_occupancy"
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the WiZ binary sensor platform."""
wiz_data: WizData = hass.data[DOMAIN][entry.entry_id]
mac = wiz_data.bulb.mac
if er.async_get(hass).async_get_entity_id(
Platform.BINARY_SENSOR, DOMAIN, OCCUPANCY_UNIQUE_ID.format(mac)
):
async_add_entities([WizOccupancyEntity(wiz_data, entry.title)])
return
cancel_dispatcher: Callable[[], None] | None = None
@callback
def _async_add_occupancy_sensor() -> None:
nonlocal cancel_dispatcher
assert cancel_dispatcher is not None
cancel_dispatcher()
cancel_dispatcher = None
async_add_entities([WizOccupancyEntity(wiz_data, entry.title)])
cancel_dispatcher = async_dispatcher_connect(
hass, SIGNAL_WIZ_PIR.format(mac), _async_add_occupancy_sensor
)
@callback
def _async_cancel_dispatcher() -> None:
nonlocal cancel_dispatcher
if cancel_dispatcher is not None:
cancel_dispatcher()
cancel_dispatcher = None
entry.async_on_unload(_async_cancel_dispatcher)
class WizOccupancyEntity(WizEntity, BinarySensorEntity):
"""Representation of WiZ Occupancy sensor."""
_attr_device_class = BinarySensorDeviceClass.OCCUPANCY
def __init__(self, wiz_data: WizData, name: str) -> None:
"""Initialize an WiZ device."""
super().__init__(wiz_data, name)
self._attr_unique_id = OCCUPANCY_UNIQUE_ID.format(self._device.mac)
self._attr_name = f"{name} Occupancy"
self._async_update_attrs()
@callback
def _async_update_attrs(self) -> None:
"""Handle updating _attr values."""
if self._device.state.get_source() == PIR_SOURCE:
self._attr_is_on = self._device.status

View File

@ -21,3 +21,5 @@ WIZ_EXCEPTIONS = (
ConnectionRefusedError, ConnectionRefusedError,
) )
WIZ_CONNECT_EXCEPTIONS = (WizLightNotKnownBulb, *WIZ_EXCEPTIONS) WIZ_CONNECT_EXCEPTIONS = (WizLightNotKnownBulb, *WIZ_EXCEPTIONS)
SIGNAL_WIZ_PIR = "wiz_pir_{}"

View File

@ -1,23 +1,24 @@
"""WiZ integration entities.""" """WiZ integration entities."""
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
from typing import Any from typing import Any
from pywizlight.bulblibrary import BulbType from pywizlight.bulblibrary import BulbType
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo, ToggleEntity from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .models import WizData from .models import WizData
class WizToggleEntity(CoordinatorEntity, ToggleEntity): class WizEntity(CoordinatorEntity, Entity):
"""Representation of WiZ toggle entity.""" """Representation of WiZ entity."""
def __init__(self, wiz_data: WizData, name: str) -> None: def __init__(self, wiz_data: WizData, name: str) -> None:
"""Initialize an WiZ device.""" """Initialize a WiZ entity."""
super().__init__(wiz_data.coordinator) super().__init__(wiz_data.coordinator)
self._device = wiz_data.bulb self._device = wiz_data.bulb
bulb_type: BulbType = self._device.bulbtype bulb_type: BulbType = self._device.bulbtype
@ -41,6 +42,15 @@ class WizToggleEntity(CoordinatorEntity, ToggleEntity):
self._async_update_attrs() self._async_update_attrs()
super()._handle_coordinator_update() super()._handle_coordinator_update()
@callback
@abstractmethod
def _async_update_attrs(self) -> None:
"""Handle updating _attr values."""
class WizToggleEntity(WizEntity, ToggleEntity):
"""Representation of WiZ toggle entity."""
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Handle updating _attr values.""" """Handle updating _attr values."""

View File

@ -13,6 +13,7 @@ from pywizlight.discovery import DiscoveredBulb
from homeassistant.components.wiz.const import DOMAIN from homeassistant.components.wiz.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -110,6 +111,15 @@ FAKE_DIMMABLE_BULB = BulbType(
white_channels=1, white_channels=1,
white_to_color_ratio=80, white_to_color_ratio=80,
) )
FAKE_TURNABLE_BULB = BulbType(
bulb_type=BulbClass.TW,
name="ESP01_TW_03",
features=FEATURE_MAP[BulbClass.TW],
kelvin_range=KelvinRange(2700, 6500),
fw_version="1.0.0",
white_channels=1,
white_to_color_ratio=80,
)
FAKE_SOCKET = BulbType( FAKE_SOCKET = BulbType(
bulb_type=BulbClass.SOCKET, bulb_type=BulbClass.SOCKET,
name="ESP01_SOCKET_03", name="ESP01_SOCKET_03",
@ -144,16 +154,18 @@ async def setup_integration(
def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight:
bulb = MagicMock(auto_spec=wizlight) bulb = MagicMock(auto_spec=wizlight, name="Mocked wizlight")
async def _save_setup_callback(callback: Callable) -> None: async def _save_setup_callback(callback: Callable) -> None:
bulb.data_receive_callback = callback bulb.push_callback = callback
bulb.getBulbConfig = AsyncMock(return_value=device or FAKE_BULB_CONFIG) bulb.getBulbConfig = AsyncMock(return_value=device or FAKE_BULB_CONFIG)
bulb.getExtendedWhiteRange = AsyncMock( bulb.getExtendedWhiteRange = AsyncMock(
return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE
) )
bulb.getMac = AsyncMock(return_value=FAKE_MAC) bulb.getMac = AsyncMock(return_value=FAKE_MAC)
bulb.turn_on = AsyncMock()
bulb.turn_off = AsyncMock()
bulb.updateState = AsyncMock(return_value=FAKE_STATE) bulb.updateState = AsyncMock(return_value=FAKE_STATE)
bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES)) bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES))
bulb.start_push = AsyncMock(side_effect=_save_setup_callback) bulb.start_push = AsyncMock(side_effect=_save_setup_callback)
@ -169,8 +181,8 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight:
def _patch_wizlight(device=None, extended_white_range=None, bulb_type=None): def _patch_wizlight(device=None, extended_white_range=None, bulb_type=None):
@contextmanager @contextmanager
def _patcher(): def _patcher():
bulb = _mocked_wizlight(device, extended_white_range, bulb_type) bulb = device or _mocked_wizlight(device, extended_white_range, bulb_type)
with patch("homeassistant.components.wiz.wizlight", return_value=bulb,), patch( with patch("homeassistant.components.wiz.wizlight", return_value=bulb), patch(
"homeassistant.components.wiz.config_flow.wizlight", "homeassistant.components.wiz.config_flow.wizlight",
return_value=bulb, return_value=bulb,
): ):
@ -189,3 +201,28 @@ def _patch_discovery():
yield yield
return _patcher() return _patcher()
async def async_setup_integration(
hass, device=None, extended_white_range=None, bulb_type=None
):
"""Set up the integration with a mock device."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FAKE_MAC,
data={CONF_HOST: FAKE_IP},
)
entry.add_to_hass(hass)
bulb = _mocked_wizlight(device, extended_white_range, bulb_type)
with _patch_discovery(), _patch_wizlight(device=bulb):
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done()
return bulb, entry
async def async_push_update(hass, device, params):
"""Push an update to the device."""
device.state = PilotParser(params)
device.status = params["state"]
device.push_callback(device.state)
await hass.async_block_till_done()

View File

@ -0,0 +1,83 @@
"""Tests for WiZ binary_sensor platform."""
from homeassistant import config_entries
from homeassistant.components import wiz
from homeassistant.components.wiz.binary_sensor import OCCUPANCY_UNIQUE_ID
from homeassistant.const import CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from . import (
FAKE_IP,
FAKE_MAC,
_mocked_wizlight,
_patch_discovery,
_patch_wizlight,
async_push_update,
async_setup_integration,
)
from tests.common import MockConfigEntry
async def test_binary_sensor_created_from_push_updates(hass: HomeAssistant) -> None:
"""Test a binary sensor created from push updates."""
bulb, _ = await async_setup_integration(hass)
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": True})
entity_id = "binary_sensor.mock_title_occupancy"
entity_registry = er.async_get(hass)
assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_occupancy"
state = hass.states.get(entity_id)
assert state.state == STATE_ON
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": False})
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
async def test_binary_sensor_restored_from_registry(hass: HomeAssistant) -> None:
"""Test a binary sensor restored from registry with state unknown."""
entry = MockConfigEntry(
domain=wiz.DOMAIN,
unique_id=FAKE_MAC,
data={CONF_HOST: FAKE_IP},
)
entry.add_to_hass(hass)
bulb = _mocked_wizlight(None, None, None)
entity_registry = er.async_get(hass)
reg_ent = entity_registry.async_get_or_create(
Platform.BINARY_SENSOR, wiz.DOMAIN, OCCUPANCY_UNIQUE_ID.format(bulb.mac)
)
entity_id = reg_ent.entity_id
with _patch_discovery(), _patch_wizlight(device=bulb):
await async_setup_component(hass, wiz.DOMAIN, {wiz.DOMAIN: {}})
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": True})
assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_occupancy"
state = hass.states.get(entity_id)
assert state.state == STATE_ON
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == config_entries.ConfigEntryState.NOT_LOADED
async def test_binary_sensor_never_created_no_error_on_unload(
hass: HomeAssistant,
) -> None:
"""Test a binary sensor does not error on unload."""
_, entry = await async_setup_integration(hass)
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == config_entries.ConfigEntryState.NOT_LOADED

View File

@ -12,7 +12,6 @@ from homeassistant.const import CONF_HOST
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM
from . import ( from . import (
FAKE_BULB_CONFIG,
FAKE_DIMMABLE_BULB, FAKE_DIMMABLE_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
FAKE_IP, FAKE_IP,
@ -20,7 +19,6 @@ from . import (
FAKE_RGBW_BULB, FAKE_RGBW_BULB,
FAKE_RGBWW_BULB, FAKE_RGBWW_BULB,
FAKE_SOCKET, FAKE_SOCKET,
FAKE_SOCKET_CONFIG,
TEST_CONNECTION, TEST_CONNECTION,
TEST_SYSTEM_INFO, TEST_SYSTEM_INFO,
_patch_discovery, _patch_discovery,
@ -184,12 +182,11 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"source, data, device, bulb_type, extended_white_range, name", "source, data, bulb_type, extended_white_range, name",
[ [
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
DHCP_DISCOVERY, DHCP_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_DIMMABLE_BULB, FAKE_DIMMABLE_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ Dimmable White ABCABC", "WiZ Dimmable White ABCABC",
@ -197,7 +194,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_DIMMABLE_BULB, FAKE_DIMMABLE_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ Dimmable White ABCABC", "WiZ Dimmable White ABCABC",
@ -205,7 +201,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
DHCP_DISCOVERY, DHCP_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_RGBW_BULB, FAKE_RGBW_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ RGBW Tunable ABCABC", "WiZ RGBW Tunable ABCABC",
@ -213,7 +208,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_RGBW_BULB, FAKE_RGBW_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ RGBW Tunable ABCABC", "WiZ RGBW Tunable ABCABC",
@ -221,7 +215,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
DHCP_DISCOVERY, DHCP_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_RGBWW_BULB, FAKE_RGBWW_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ RGBWW Tunable ABCABC", "WiZ RGBWW Tunable ABCABC",
@ -229,7 +222,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY,
FAKE_BULB_CONFIG,
FAKE_RGBWW_BULB, FAKE_RGBWW_BULB,
FAKE_EXTENDED_WHITE_RANGE, FAKE_EXTENDED_WHITE_RANGE,
"WiZ RGBWW Tunable ABCABC", "WiZ RGBWW Tunable ABCABC",
@ -237,7 +229,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
DHCP_DISCOVERY, DHCP_DISCOVERY,
FAKE_SOCKET_CONFIG,
FAKE_SOCKET, FAKE_SOCKET,
None, None,
"WiZ Socket ABCABC", "WiZ Socket ABCABC",
@ -245,7 +236,6 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY,
FAKE_SOCKET_CONFIG,
FAKE_SOCKET, FAKE_SOCKET,
None, None,
"WiZ Socket ABCABC", "WiZ Socket ABCABC",
@ -253,11 +243,11 @@ async def test_discovered_by_dhcp_connection_fails(hass, source, data):
], ],
) )
async def test_discovered_by_dhcp_or_integration_discovery( async def test_discovered_by_dhcp_or_integration_discovery(
hass, source, data, device, bulb_type, extended_white_range, name hass, source, data, bulb_type, extended_white_range, name
): ):
"""Test we can configure when discovered from dhcp or discovery.""" """Test we can configure when discovered from dhcp or discovery."""
with _patch_wizlight( with _patch_wizlight(
device=device, extended_white_range=extended_white_range, bulb_type=bulb_type device=None, extended_white_range=extended_white_range, bulb_type=bulb_type
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": source}, data=data DOMAIN, context={"source": source}, data=data
@ -268,7 +258,7 @@ async def test_discovered_by_dhcp_or_integration_discovery(
assert result["step_id"] == "discovery_confirm" assert result["step_id"] == "discovery_confirm"
with _patch_wizlight( with _patch_wizlight(
device=device, extended_white_range=extended_white_range, bulb_type=bulb_type device=None, extended_white_range=extended_white_range, bulb_type=bulb_type
), patch( ), patch(
"homeassistant.components.wiz.async_setup_entry", "homeassistant.components.wiz.async_setup_entry",
return_value=True, return_value=True,
@ -423,7 +413,7 @@ async def test_setup_via_discovery_cannot_connect(hass):
async def test_discovery_with_firmware_update(hass): async def test_discovery_with_firmware_update(hass):
"""Test we check the device again between first discovery and config entry creation.""" """Test we check the device again between first discovery and config entry creation."""
with _patch_wizlight( with _patch_wizlight(
device=FAKE_BULB_CONFIG, device=None,
extended_white_range=FAKE_EXTENDED_WHITE_RANGE, extended_white_range=FAKE_EXTENDED_WHITE_RANGE,
bulb_type=FAKE_RGBW_BULB, bulb_type=FAKE_RGBW_BULB,
): ):
@ -447,7 +437,7 @@ async def test_discovery_with_firmware_update(hass):
) as mock_setup_entry, patch( ) as mock_setup_entry, patch(
"homeassistant.components.wiz.async_setup", return_value=True "homeassistant.components.wiz.async_setup", return_value=True
) as mock_setup, _patch_wizlight( ) as mock_setup, _patch_wizlight(
device=FAKE_BULB_CONFIG, device=None,
extended_white_range=FAKE_EXTENDED_WHITE_RANGE, extended_white_range=FAKE_EXTENDED_WHITE_RANGE,
bulb_type=FAKE_RGBWW_BULB, bulb_type=FAKE_RGBWW_BULB,
): ):