Add full state view for emulated_hue (apps using emulated_hue, 'sleep cycle' and 'sleep as android') (#26650)
* Add full state view for emulated_hue * clean and support updated sleep cycle * emulated hue add reuasable logic and cleanup code * emulated hue correct typos * Update hue_api.py * correct error message and update test_hue_api.py * cleanup test_hue_api.py
This commit is contained in:
parent
173966f459
commit
9ba9b3339b
4 changed files with 155 additions and 9 deletions
|
@ -14,11 +14,13 @@ from homeassistant.components.http import real_ip
|
|||
|
||||
from .hue_api import (
|
||||
HueUsernameView,
|
||||
HueUnauthorizedUser,
|
||||
HueAllLightsStateView,
|
||||
HueOneLightStateView,
|
||||
HueOneLightChangeView,
|
||||
HueGroupView,
|
||||
HueAllGroupsStateView,
|
||||
HueFullStateView,
|
||||
)
|
||||
from .upnp import DescriptionXmlView, UPNPResponderThread
|
||||
|
||||
|
@ -113,11 +115,13 @@ async def async_setup(hass, yaml_config):
|
|||
|
||||
DescriptionXmlView(config).register(app, app.router)
|
||||
HueUsernameView().register(app, app.router)
|
||||
HueUnauthorizedUser().register(app, app.router)
|
||||
HueAllLightsStateView(config).register(app, app.router)
|
||||
HueOneLightStateView(config).register(app, app.router)
|
||||
HueOneLightChangeView(config).register(app, app.router)
|
||||
HueAllGroupsStateView(config).register(app, app.router)
|
||||
HueGroupView(config).register(app, app.router)
|
||||
HueFullStateView(config).register(app, app.router)
|
||||
|
||||
upnp_listener = UPNPResponderThread(
|
||||
config.host_ip_addr,
|
||||
|
|
|
@ -89,6 +89,24 @@ HUE_API_STATE_SAT_MAX = 254
|
|||
HUE_API_STATE_CT_MIN = 153 # Color temp
|
||||
HUE_API_STATE_CT_MAX = 500
|
||||
|
||||
HUE_API_USERNAME = "12345678901234567890"
|
||||
UNAUTHORIZED_USER = [
|
||||
{"error": {"address": "/", "description": "unauthorized user", "type": "1"}}
|
||||
]
|
||||
|
||||
|
||||
class HueUnauthorizedUser(HomeAssistantView):
|
||||
"""Handle requests to find the emulated hue bridge."""
|
||||
|
||||
url = "/api"
|
||||
name = "emulated_hue:api:unauthorized_user"
|
||||
extra_urls = ["/api/"]
|
||||
requires_auth = False
|
||||
|
||||
async def get(self, request):
|
||||
"""Handle a GET request."""
|
||||
return self.json(UNAUTHORIZED_USER)
|
||||
|
||||
|
||||
class HueUsernameView(HomeAssistantView):
|
||||
"""Handle requests to create a username for the emulated hue bridge."""
|
||||
|
@ -111,7 +129,7 @@ class HueUsernameView(HomeAssistantView):
|
|||
if "devicetype" not in data:
|
||||
return self.json_message("devicetype not specified", HTTP_BAD_REQUEST)
|
||||
|
||||
return self.json([{"success": {"username": "12345678901234567890"}}])
|
||||
return self.json([{"success": {"username": HUE_API_USERNAME}}])
|
||||
|
||||
|
||||
class HueAllGroupsStateView(HomeAssistantView):
|
||||
|
@ -181,13 +199,37 @@ class HueAllLightsStateView(HomeAssistantView):
|
|||
if not is_local(request[KEY_REAL_IP]):
|
||||
return self.json_message("Only local IPs allowed", HTTP_UNAUTHORIZED)
|
||||
|
||||
hass = request.app["hass"]
|
||||
json_response = {}
|
||||
return self.json(create_list_of_entities(self.config, request))
|
||||
|
||||
for entity in hass.states.async_all():
|
||||
if self.config.is_entity_exposed(entity):
|
||||
number = self.config.entity_id_to_number(entity.entity_id)
|
||||
json_response[number] = entity_to_json(self.config, entity)
|
||||
|
||||
class HueFullStateView(HomeAssistantView):
|
||||
"""Return full state view of emulated hue."""
|
||||
|
||||
url = "/api/{username}"
|
||||
name = "emulated_hue:username:state"
|
||||
requires_auth = False
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the instance of the view."""
|
||||
self.config = config
|
||||
|
||||
@core.callback
|
||||
def get(self, request, username):
|
||||
"""Process a request to get the list of available lights."""
|
||||
if not is_local(request[KEY_REAL_IP]):
|
||||
return self.json_message("only local IPs allowed", HTTP_UNAUTHORIZED)
|
||||
if username != HUE_API_USERNAME:
|
||||
return self.json(UNAUTHORIZED_USER)
|
||||
|
||||
json_response = {
|
||||
"lights": create_list_of_entities(self.config, request),
|
||||
"config": {
|
||||
"mac": "00:00:00:00:00:00",
|
||||
"swversion": "01003542",
|
||||
"whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}},
|
||||
"ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}",
|
||||
},
|
||||
}
|
||||
|
||||
return self.json(json_response)
|
||||
|
||||
|
@ -673,3 +715,16 @@ def create_hue_success_response(entity_id, attr, value):
|
|||
"""Create a success response for an attribute set on a light."""
|
||||
success_key = f"/lights/{entity_id}/state/{attr}"
|
||||
return {"success": {success_key: value}}
|
||||
|
||||
|
||||
def create_list_of_entities(config, request):
|
||||
"""Create a list of all entites."""
|
||||
hass = request.app["hass"]
|
||||
json_response = {}
|
||||
|
||||
for entity in hass.states.async_all():
|
||||
if config.is_entity_exposed(entity):
|
||||
number = config.entity_id_to_number(entity.entity_id)
|
||||
json_response[number] = entity_to_json(config, entity)
|
||||
|
||||
return json_response
|
||||
|
|
|
@ -25,11 +25,13 @@ from homeassistant.components.emulated_hue.hue_api import (
|
|||
HUE_API_STATE_BRI,
|
||||
HUE_API_STATE_HUE,
|
||||
HUE_API_STATE_SAT,
|
||||
HUE_API_USERNAME,
|
||||
HueUsernameView,
|
||||
HueOneLightStateView,
|
||||
HueAllLightsStateView,
|
||||
HueOneLightChangeView,
|
||||
HueAllGroupsStateView,
|
||||
HueFullStateView,
|
||||
)
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
|
@ -188,6 +190,7 @@ def hue_client(loop, hass_hue, aiohttp_client):
|
|||
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)
|
||||
|
||||
return loop.run_until_complete(aiohttp_client(web_app))
|
||||
|
||||
|
@ -252,6 +255,49 @@ def test_reachable_for_state(hass_hue, hue_client, state, is_reachable):
|
|||
assert state_json["state"]["reachable"] == is_reachable, state_json
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_discover_full_state(hue_client):
|
||||
"""Test the discovery of full state."""
|
||||
result = yield from hue_client.get("/api/" + HUE_API_USERNAME)
|
||||
|
||||
assert result.status == 200
|
||||
assert "application/json" in result.headers["content-type"]
|
||||
|
||||
result_json = yield from result.json()
|
||||
|
||||
# Make sure array has correct content
|
||||
assert "lights" in result_json
|
||||
assert "lights" not in result_json["config"]
|
||||
assert "config" in result_json
|
||||
assert "config" not in result_json["lights"]
|
||||
|
||||
lights_json = result_json["lights"]
|
||||
config_json = result_json["config"]
|
||||
|
||||
# Make sure array is correct size
|
||||
assert len(result_json) == 2
|
||||
assert len(config_json) == 4
|
||||
assert len(lights_json) >= 1
|
||||
|
||||
# Make sure the config wrapper added to the config is there
|
||||
assert "mac" in config_json
|
||||
assert "00:00:00:00:00:00" in config_json["mac"]
|
||||
|
||||
# Make sure the correct version in config
|
||||
assert "swversion" in config_json
|
||||
assert "01003542" in config_json["swversion"]
|
||||
|
||||
# Make sure the correct username in config
|
||||
assert "whitelist" in config_json
|
||||
assert HUE_API_USERNAME in config_json["whitelist"]
|
||||
assert "name" in config_json["whitelist"][HUE_API_USERNAME]
|
||||
assert "HASS BRIDGE" in config_json["whitelist"][HUE_API_USERNAME]["name"]
|
||||
|
||||
# Make sure the correct ip in config
|
||||
assert "ipaddress" in config_json
|
||||
assert "127.0.0.1:8300" in config_json["ipaddress"]
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_get_light_state(hass_hue, hue_client):
|
||||
"""Test the getting of light state."""
|
||||
|
@ -763,10 +809,26 @@ def perform_put_light_state(
|
|||
|
||||
async def test_external_ip_blocked(hue_client):
|
||||
"""Test external IP blocked."""
|
||||
getUrls = [
|
||||
"/api/username/groups",
|
||||
"/api/username",
|
||||
"/api/username/lights",
|
||||
"/api/username/lights/light.ceiling_lights",
|
||||
]
|
||||
postUrls = ["/api"]
|
||||
putUrls = ["/api/username/lights/light.ceiling_lights/state"]
|
||||
with patch(
|
||||
"homeassistant.components.http.real_ip.ip_address",
|
||||
return_value=ip_address("45.45.45.45"),
|
||||
):
|
||||
result = await hue_client.get("/api/username/lights")
|
||||
for getUrl in getUrls:
|
||||
result = await hue_client.get(getUrl)
|
||||
assert result.status == 401
|
||||
|
||||
assert result.status == 401
|
||||
for postUrl in postUrls:
|
||||
result = await hue_client.post(postUrl)
|
||||
assert result.status == 401
|
||||
|
||||
for putUrl in putUrls:
|
||||
result = await hue_client.put(putUrl)
|
||||
assert result.status == 401
|
||||
|
|
|
@ -82,6 +82,31 @@ class TestEmulatedHue(unittest.TestCase):
|
|||
assert "success" in success_json
|
||||
assert "username" in success_json["success"]
|
||||
|
||||
def test_unauthorized_view(self):
|
||||
"""Test unauthorized view."""
|
||||
request_json = {"devicetype": "my_device"}
|
||||
|
||||
result = requests.get(
|
||||
BRIDGE_URL_BASE.format("/api/unauthorized"),
|
||||
data=json.dumps(request_json),
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert result.status_code == 200
|
||||
assert "application/json" in result.headers["content-type"]
|
||||
|
||||
resp_json = result.json()
|
||||
assert len(resp_json) == 1
|
||||
success_json = resp_json[0]
|
||||
assert len(success_json) == 1
|
||||
|
||||
assert "error" in success_json
|
||||
error_json = success_json["error"]
|
||||
assert len(error_json) == 3
|
||||
assert "/" in error_json["address"]
|
||||
assert "unauthorized user" in error_json["description"]
|
||||
assert "1" in error_json["type"]
|
||||
|
||||
def test_valid_username_request(self):
|
||||
"""Test request with a valid username."""
|
||||
request_json = {"invalid_key": "my_device"}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue