mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Cache which entities are exposed in emulated_hue (#73093)
This commit is contained in:
parent
7a5fa8eb58
commit
f4d339119f
@ -1,14 +1,40 @@
|
|||||||
"""Support for local control of entities by emulating a Philips Hue bridge."""
|
"""Support for local control of entities by emulating a Philips Hue bridge."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from functools import cache
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components import (
|
||||||
|
climate,
|
||||||
|
cover,
|
||||||
|
fan,
|
||||||
|
humidifier,
|
||||||
|
light,
|
||||||
|
media_player,
|
||||||
|
scene,
|
||||||
|
script,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
|
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
|
||||||
from homeassistant.core import HomeAssistant, State
|
from homeassistant.core import Event, HomeAssistant, State, callback, split_entity_id
|
||||||
from homeassistant.helpers import storage
|
from homeassistant.helpers import storage
|
||||||
|
from homeassistant.helpers.event import (
|
||||||
|
async_track_state_added_domain,
|
||||||
|
async_track_state_removed_domain,
|
||||||
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
SUPPORTED_DOMAINS = {
|
||||||
|
climate.DOMAIN,
|
||||||
|
cover.DOMAIN,
|
||||||
|
fan.DOMAIN,
|
||||||
|
humidifier.DOMAIN,
|
||||||
|
light.DOMAIN,
|
||||||
|
media_player.DOMAIN,
|
||||||
|
scene.DOMAIN,
|
||||||
|
script.DOMAIN,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TYPE_ALEXA = "alexa"
|
TYPE_ALEXA = "alexa"
|
||||||
TYPE_GOOGLE = "google_home"
|
TYPE_GOOGLE = "google_home"
|
||||||
|
|
||||||
@ -78,7 +104,7 @@ class Config:
|
|||||||
|
|
||||||
# Get whether or not UPNP binds to multicast address (239.255.255.250)
|
# Get whether or not UPNP binds to multicast address (239.255.255.250)
|
||||||
# or to the unicast address (host_ip_addr)
|
# or to the unicast address (host_ip_addr)
|
||||||
self.upnp_bind_multicast = conf.get(
|
self.upnp_bind_multicast: bool = conf.get(
|
||||||
CONF_UPNP_BIND_MULTICAST, DEFAULT_UPNP_BIND_MULTICAST
|
CONF_UPNP_BIND_MULTICAST, DEFAULT_UPNP_BIND_MULTICAST
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,7 +119,7 @@ class Config:
|
|||||||
|
|
||||||
# Get whether or not entities should be exposed by default, or if only
|
# Get whether or not entities should be exposed by default, or if only
|
||||||
# explicitly marked ones will be exposed
|
# explicitly marked ones will be exposed
|
||||||
self.expose_by_default = conf.get(
|
self.expose_by_default: bool = conf.get(
|
||||||
CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT
|
CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,18 +144,31 @@ class Config:
|
|||||||
|
|
||||||
# Get whether all non-dimmable lights should be reported as dimmable
|
# Get whether all non-dimmable lights should be reported as dimmable
|
||||||
# for compatibility with older installations.
|
# for compatibility with older installations.
|
||||||
self.lights_all_dimmable = conf.get(CONF_LIGHTS_ALL_DIMMABLE)
|
self.lights_all_dimmable: bool = conf.get(CONF_LIGHTS_ALL_DIMMABLE) or False
|
||||||
|
|
||||||
|
if self.expose_by_default:
|
||||||
|
self.track_domains = set(self.exposed_domains) or SUPPORTED_DOMAINS
|
||||||
|
else:
|
||||||
|
self.track_domains = {
|
||||||
|
split_entity_id(entity_id)[0] for entity_id in self.entities
|
||||||
|
}
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Set up and migrate to storage."""
|
"""Set up tracking and migrate to storage."""
|
||||||
self.store = storage.Store(self.hass, DATA_VERSION, DATA_KEY) # type: ignore[arg-type]
|
hass = self.hass
|
||||||
|
self.store = storage.Store(hass, DATA_VERSION, DATA_KEY) # type: ignore[arg-type]
|
||||||
|
numbers_path = hass.config.path(NUMBERS_FILE)
|
||||||
self.numbers = (
|
self.numbers = (
|
||||||
await storage.async_migrator(
|
await storage.async_migrator(hass, numbers_path, self.store) or {}
|
||||||
self.hass, self.hass.config.path(NUMBERS_FILE), self.store
|
|
||||||
)
|
)
|
||||||
or {}
|
async_track_state_added_domain(
|
||||||
|
hass, self.track_domains, self._clear_exposed_cache
|
||||||
|
)
|
||||||
|
async_track_state_removed_domain(
|
||||||
|
hass, self.track_domains, self._clear_exposed_cache
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cache # pylint: disable=method-cache-max-size-none
|
||||||
def entity_id_to_number(self, entity_id: str) -> str:
|
def entity_id_to_number(self, entity_id: str) -> str:
|
||||||
"""Get a unique number for the entity id."""
|
"""Get a unique number for the entity id."""
|
||||||
if self.type == TYPE_ALEXA:
|
if self.type == TYPE_ALEXA:
|
||||||
@ -166,6 +205,27 @@ class Config:
|
|||||||
|
|
||||||
return state.attributes.get(ATTR_EMULATED_HUE_NAME, state.name)
|
return state.attributes.get(ATTR_EMULATED_HUE_NAME, state.name)
|
||||||
|
|
||||||
|
@cache # pylint: disable=method-cache-max-size-none
|
||||||
|
def get_exposed_states(self) -> list[State]:
|
||||||
|
"""Return a list of exposed states."""
|
||||||
|
state_machine = self.hass.states
|
||||||
|
if self.expose_by_default:
|
||||||
|
return [
|
||||||
|
state
|
||||||
|
for state in state_machine.async_all()
|
||||||
|
if self.is_state_exposed(state)
|
||||||
|
]
|
||||||
|
states: list[State] = []
|
||||||
|
for entity_id in self.entities:
|
||||||
|
if (state := state_machine.get(entity_id)) and self.is_state_exposed(state):
|
||||||
|
states.append(state)
|
||||||
|
return states
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _clear_exposed_cache(self, event: Event) -> None:
|
||||||
|
"""Clear the cache of exposed states."""
|
||||||
|
self.get_exposed_states.cache_clear() # pylint: disable=no-member
|
||||||
|
|
||||||
def is_state_exposed(self, state: State) -> bool:
|
def is_state_exposed(self, state: State) -> bool:
|
||||||
"""Cache determine if an entity should be exposed on the emulated bridge."""
|
"""Cache determine if an entity should be exposed on the emulated bridge."""
|
||||||
if (exposed := self._exposed_cache.get(state.entity_id)) is not None:
|
if (exposed := self._exposed_cache.get(state.entity_id)) is not None:
|
||||||
@ -174,13 +234,6 @@ class Config:
|
|||||||
self._exposed_cache[state.entity_id] = exposed
|
self._exposed_cache[state.entity_id] = exposed
|
||||||
return exposed
|
return exposed
|
||||||
|
|
||||||
def filter_exposed_states(self, states: Iterable[State]) -> list[State]:
|
|
||||||
"""Filter a list of all states down to exposed entities."""
|
|
||||||
exposed: list[State] = [
|
|
||||||
state for state in states if self.is_state_exposed(state)
|
|
||||||
]
|
|
||||||
return exposed
|
|
||||||
|
|
||||||
def _is_state_exposed(self, state: State) -> bool:
|
def _is_state_exposed(self, state: State) -> bool:
|
||||||
"""Determine if an entity state should be exposed on the emulated bridge.
|
"""Determine if an entity state should be exposed on the emulated bridge.
|
||||||
|
|
||||||
|
@ -844,10 +844,9 @@ def create_config_model(config: Config, request: web.Request) -> dict[str, Any]:
|
|||||||
|
|
||||||
def create_list_of_entities(config: Config, request: web.Request) -> dict[str, Any]:
|
def create_list_of_entities(config: Config, request: web.Request) -> dict[str, Any]:
|
||||||
"""Create a list of all entities."""
|
"""Create a list of all entities."""
|
||||||
hass: core.HomeAssistant = request.app["hass"]
|
|
||||||
json_response: dict[str, Any] = {
|
json_response: dict[str, Any] = {
|
||||||
config.entity_id_to_number(entity.entity_id): state_to_json(config, entity)
|
config.entity_id_to_number(state.entity_id): state_to_json(config, state)
|
||||||
for entity in config.filter_exposed_states(hass.states.async_all())
|
for state in config.get_exposed_states()
|
||||||
}
|
}
|
||||||
return json_response
|
return json_response
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@ from homeassistant.const import (
|
|||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
@ -96,41 +97,58 @@ ENTITY_IDS_BY_NUMBER = {
|
|||||||
ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()}
|
ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
def patch_upnp():
|
||||||
def hass_hue(loop, hass):
|
"""Patch async_create_upnp_datagram_endpoint."""
|
||||||
"""Set up a Home Assistant instance for these tests."""
|
return patch(
|
||||||
# We need to do this to get access to homeassistant/turn_(on,off)
|
|
||||||
loop.run_until_complete(setup.async_setup_component(hass, "homeassistant", {}))
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
hass, http.DOMAIN, {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint"
|
"homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint"
|
||||||
):
|
)
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
|
async def async_get_lights(client):
|
||||||
|
"""Get lights with the hue client."""
|
||||||
|
result = await client.get("/api/username/lights")
|
||||||
|
assert result.status == HTTPStatus.OK
|
||||||
|
assert CONTENT_TYPE_JSON in result.headers["content-type"]
|
||||||
|
return await result.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_setup_emulated_hue(hass: HomeAssistant, conf: ConfigType) -> None:
|
||||||
|
"""Set up emulated_hue with a specific config."""
|
||||||
|
with patch_upnp():
|
||||||
|
await setup.async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
emulated_hue.DOMAIN,
|
emulated_hue.DOMAIN,
|
||||||
{
|
{emulated_hue.DOMAIN: conf},
|
||||||
emulated_hue.DOMAIN: {
|
),
|
||||||
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
|
await hass.async_block_till_done()
|
||||||
emulated_hue.CONF_EXPOSE_BY_DEFAULT: True,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def base_setup(hass):
|
||||||
|
"""Set up homeassistant and http."""
|
||||||
|
await asyncio.gather(
|
||||||
|
setup.async_setup_component(hass, "homeassistant", {}),
|
||||||
setup.async_setup_component(
|
setup.async_setup_component(
|
||||||
hass, light.DOMAIN, {"light": [{"platform": "demo"}]}
|
hass, http.DOMAIN, {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def demo_setup(hass):
|
||||||
|
"""Fixture to setup demo platforms."""
|
||||||
|
# We need to do this to get access to homeassistant/turn_(on,off)
|
||||||
|
setups = [
|
||||||
|
setup.async_setup_component(hass, "homeassistant", {}),
|
||||||
|
setup.async_setup_component(
|
||||||
|
hass, http.DOMAIN, {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}
|
||||||
|
),
|
||||||
|
*[
|
||||||
|
setup.async_setup_component(
|
||||||
|
hass, comp.DOMAIN, {comp.DOMAIN: [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
for comp in (light, climate, humidifier, media_player, fan, cover)
|
||||||
|
],
|
||||||
setup.async_setup_component(
|
setup.async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
script.DOMAIN,
|
script.DOMAIN,
|
||||||
@ -149,39 +167,7 @@ def hass_hue(loop, hass):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
hass, media_player.DOMAIN, {"media_player": [{"platform": "demo"}]}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(hass, fan.DOMAIN, {"fan": [{"platform": "demo"}]})
|
|
||||||
)
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
|
||||||
hass, cover.DOMAIN, {"cover": [{"platform": "demo"}]}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# setup a dummy scene
|
|
||||||
loop.run_until_complete(
|
|
||||||
setup.async_setup_component(
|
setup.async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
"scene",
|
"scene",
|
||||||
@ -199,21 +185,49 @@ def hass_hue(loop, hass):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
)
|
]
|
||||||
|
|
||||||
# create a lamp without brightness support
|
await asyncio.gather(*setups)
|
||||||
hass.states.async_set("light.no_brightness", "on", {})
|
|
||||||
|
|
||||||
return hass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def hue_client(loop, hass_hue, hass_client_no_auth):
|
async def hass_hue(hass, base_setup, demo_setup):
|
||||||
|
"""Set up a Home Assistant instance for these tests."""
|
||||||
|
await _async_setup_emulated_hue(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
|
||||||
|
emulated_hue.CONF_EXPOSE_BY_DEFAULT: True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# create a lamp without brightness support
|
||||||
|
hass.states.async_set("light.no_brightness", "on", {})
|
||||||
|
return hass
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _mock_hue_endpoints(
|
||||||
|
hass: HomeAssistant, conf: ConfigType, entity_numbers: dict[str, str]
|
||||||
|
) -> None:
|
||||||
|
"""Override the hue config with specific entity numbers."""
|
||||||
|
web_app = hass.http.app
|
||||||
|
config = Config(hass, conf, "127.0.0.1")
|
||||||
|
config.numbers = entity_numbers
|
||||||
|
HueUsernameView().register(web_app, web_app.router)
|
||||||
|
HueAllLightsStateView(config).register(web_app, web_app.router)
|
||||||
|
HueOneLightStateView(config).register(web_app, web_app.router)
|
||||||
|
HueOneLightChangeView(config).register(web_app, web_app.router)
|
||||||
|
HueAllGroupsStateView(config).register(web_app, web_app.router)
|
||||||
|
HueFullStateView(config).register(web_app, web_app.router)
|
||||||
|
HueConfigView(config).register(web_app, web_app.router)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def hue_client(hass_hue, hass_client_no_auth):
|
||||||
"""Create web client for emulated hue api."""
|
"""Create web client for emulated hue api."""
|
||||||
web_app = hass_hue.http.app
|
_mock_hue_endpoints(
|
||||||
config = Config(
|
hass_hue,
|
||||||
None,
|
|
||||||
{
|
{
|
||||||
emulated_hue.CONF_ENTITIES: {
|
emulated_hue.CONF_ENTITIES: {
|
||||||
"light.bed_light": {emulated_hue.CONF_ENTITY_HIDDEN: True},
|
"light.bed_light": {emulated_hue.CONF_ENTITY_HIDDEN: True},
|
||||||
@ -244,22 +258,12 @@ def hue_client(loop, hass_hue, hass_client_no_auth):
|
|||||||
"scene.light_off": {emulated_hue.CONF_ENTITY_HIDDEN: False},
|
"scene.light_off": {emulated_hue.CONF_ENTITY_HIDDEN: False},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"127.0.0.1",
|
ENTITY_IDS_BY_NUMBER,
|
||||||
)
|
)
|
||||||
config.numbers = ENTITY_IDS_BY_NUMBER
|
return await hass_client_no_auth()
|
||||||
|
|
||||||
HueUsernameView().register(web_app, web_app.router)
|
|
||||||
HueAllLightsStateView(config).register(web_app, web_app.router)
|
|
||||||
HueOneLightStateView(config).register(web_app, web_app.router)
|
|
||||||
HueOneLightChangeView(config).register(web_app, web_app.router)
|
|
||||||
HueAllGroupsStateView(config).register(web_app, web_app.router)
|
|
||||||
HueFullStateView(config).register(web_app, web_app.router)
|
|
||||||
HueConfigView(config).register(web_app, web_app.router)
|
|
||||||
|
|
||||||
return loop.run_until_complete(hass_client_no_auth())
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discover_lights(hue_client):
|
async def test_discover_lights(hass, hue_client):
|
||||||
"""Test the discovery of lights."""
|
"""Test the discovery of lights."""
|
||||||
result = await hue_client.get("/api/username/lights")
|
result = await hue_client.get("/api/username/lights")
|
||||||
|
|
||||||
@ -292,6 +296,21 @@ async def test_discover_lights(hue_client):
|
|||||||
assert "00:62:5c:3e:df:58:40:01-43" in devices # scene.light_on
|
assert "00:62:5c:3e:df:58:40:01-43" in devices # scene.light_on
|
||||||
assert "00:1c:72:08:ed:09:e7:89-77" in devices # scene.light_off
|
assert "00:1c:72:08:ed:09:e7:89-77" in devices # scene.light_off
|
||||||
|
|
||||||
|
# Remove the state and ensure it disappears from devices
|
||||||
|
hass.states.async_remove("light.ceiling_lights")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result_json = await async_get_lights(hue_client)
|
||||||
|
devices = {val["uniqueid"] for val in result_json.values()}
|
||||||
|
assert "00:2f:d2:31:ce:c5:55:cc-ee" not in devices # light.ceiling_lights
|
||||||
|
|
||||||
|
# Restore the state and ensure it reappears in devices
|
||||||
|
hass.states.async_set("light.ceiling_lights", STATE_ON)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
result_json = await async_get_lights(hue_client)
|
||||||
|
devices = {val["uniqueid"] for val in result_json.values()}
|
||||||
|
assert "00:2f:d2:31:ce:c5:55:cc-ee" in devices # light.ceiling_lights
|
||||||
|
|
||||||
|
|
||||||
async def test_light_without_brightness_supported(hass_hue, hue_client):
|
async def test_light_without_brightness_supported(hass_hue, hue_client):
|
||||||
"""Test that light without brightness is supported."""
|
"""Test that light without brightness is supported."""
|
||||||
@ -316,19 +335,8 @@ async def test_lights_all_dimmable(hass, hass_client_no_auth):
|
|||||||
emulated_hue.CONF_EXPOSE_BY_DEFAULT: True,
|
emulated_hue.CONF_EXPOSE_BY_DEFAULT: True,
|
||||||
emulated_hue.CONF_LIGHTS_ALL_DIMMABLE: True,
|
emulated_hue.CONF_LIGHTS_ALL_DIMMABLE: True,
|
||||||
}
|
}
|
||||||
with patch(
|
await _async_setup_emulated_hue(hass, hue_config)
|
||||||
"homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint"
|
_mock_hue_endpoints(hass, hue_config, ENTITY_IDS_BY_NUMBER)
|
||||||
):
|
|
||||||
await setup.async_setup_component(
|
|
||||||
hass,
|
|
||||||
emulated_hue.DOMAIN,
|
|
||||||
{emulated_hue.DOMAIN: hue_config},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
config = Config(None, hue_config, "127.0.0.1")
|
|
||||||
config.numbers = ENTITY_IDS_BY_NUMBER
|
|
||||||
web_app = hass.http.app
|
|
||||||
HueOneLightStateView(config).register(web_app, web_app.router)
|
|
||||||
client = await hass_client_no_auth()
|
client = await hass_client_no_auth()
|
||||||
light_without_brightness_json = await perform_get_light_state(
|
light_without_brightness_json = await perform_get_light_state(
|
||||||
client, "light.no_brightness", HTTPStatus.OK
|
client, "light.no_brightness", HTTPStatus.OK
|
||||||
@ -568,13 +576,7 @@ async def test_get_light_state(hass_hue, hue_client):
|
|||||||
assert office_json["state"][HUE_API_STATE_SAT] == 217
|
assert office_json["state"][HUE_API_STATE_SAT] == 217
|
||||||
|
|
||||||
# Check all lights view
|
# Check all lights view
|
||||||
result = await hue_client.get("/api/username/lights")
|
result_json = await async_get_lights(hue_client)
|
||||||
|
|
||||||
assert result.status == HTTPStatus.OK
|
|
||||||
assert CONTENT_TYPE_JSON in result.headers["content-type"]
|
|
||||||
|
|
||||||
result_json = await result.json()
|
|
||||||
|
|
||||||
assert ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] in result_json
|
assert ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] in result_json
|
||||||
assert (
|
assert (
|
||||||
result_json[ENTITY_NUMBERS_BY_ID["light.ceiling_lights"]]["state"][
|
result_json[ENTITY_NUMBERS_BY_ID["light.ceiling_lights"]]["state"][
|
||||||
@ -1616,3 +1618,32 @@ async def test_only_change_hue_or_saturation(hass, hass_hue, hue_client):
|
|||||||
assert hass_hue.states.get("light.ceiling_lights").attributes[
|
assert hass_hue.states.get("light.ceiling_lights").attributes[
|
||||||
light.ATTR_HS_COLOR
|
light.ATTR_HS_COLOR
|
||||||
] == (0, 3)
|
] == (0, 3)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_specificly_exposed_entities(hass, base_setup, hass_client_no_auth):
|
||||||
|
"""Test specific entities with expose by default off."""
|
||||||
|
conf = {
|
||||||
|
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
|
||||||
|
emulated_hue.CONF_EXPOSE_BY_DEFAULT: False,
|
||||||
|
emulated_hue.CONF_ENTITIES: {
|
||||||
|
"light.exposed": {emulated_hue.CONF_ENTITY_HIDDEN: False},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await _async_setup_emulated_hue(hass, conf)
|
||||||
|
_mock_hue_endpoints(hass, conf, {"1": "light.exposed"})
|
||||||
|
hass.states.async_set("light.exposed", STATE_ON)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
client = await hass_client_no_auth()
|
||||||
|
result_json = await async_get_lights(client)
|
||||||
|
assert "1" in result_json
|
||||||
|
|
||||||
|
hass.states.async_remove("light.exposed")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
result_json = await async_get_lights(client)
|
||||||
|
assert "1" not in result_json
|
||||||
|
|
||||||
|
hass.states.async_set("light.exposed", STATE_ON)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
result_json = await async_get_lights(client)
|
||||||
|
|
||||||
|
assert "1" in result_json
|
||||||
|
Loading…
x
Reference in New Issue
Block a user