Skip to content

Commit f546580

Browse files
authored
Set up new endpoint for discovery (#136)
Since the /api/discovery_info endpoint has been deprecated, we lost a way to retrieve a unique identifier from the remote instance. This change introduces /api/remote_homeassistant/discovery containing the information we are missing. The downside is that the component now must be installed on the remote instance as well. The upside however is that we now can add any new endpoint we need for additional features in the future, which is good. Fixes #133
1 parent 4bf7702 commit f546580

File tree

8 files changed

+94
-100
lines changed

8 files changed

+94
-100
lines changed

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ Platform | Description
1515
-- | --
1616
`remote_homeassistant` | Link multiple Home-Assistant instances together .
1717

18-
The master instance connects to the Websocket APIs of the secondary instances (already enabled out of box), the connection options are specified via the `host`, `port`, and `secure` configuration parameters. If the secondary instance requires an access token to connect (created on the Profile page), it can be set via the `access_token` parameter. To ignore SSL warnings in secure mode, set the `verify_ssl` parameter to false.
18+
The main instance connects to the Websocket APIs of the remote instances (already enabled out of box), the connection options are specified via the `host`, `port`, and `secure` configuration parameters. If the remote instance requires an access token to connect (created on the Profile page), it can be set via the `access_token` parameter. To ignore SSL warnings in secure mode, set the `verify_ssl` parameter to false.
1919

2020
After the connection is completed, the remote states get populated into the master instance.
2121
The entity ids can optionally be prefixed via the `entity_prefix` parameter.
2222

23-
The component keeps track which objects originate from which instance. Whenever a service is called on an object, the call gets forwarded to the particular secondary instance.
23+
The component keeps track which objects originate from which instance. Whenever a service is called on an object, the call gets forwarded to the particular remote instance.
2424

2525
When the connection to the remote instance is lost, all previously published states are removed again from the local state registry.
2626

@@ -29,7 +29,7 @@ A possible use case for this is to be able to use different Z-Wave networks, on
2929

3030
## Installation
3131

32-
This component should be installed on the main instance of Home Assistant
32+
This component *must* be installed on both the main and remote instance of Home Assistant
3333

3434
If you use HACS:
3535

@@ -38,7 +38,18 @@ If you use HACS:
3838
Otherwise:
3939

4040
1. To use this plugin, copy the `remote_homeassistant` folder into your [custom_components folder](https://developers.home-assistant.io/docs/creating_integration_file_structure/#where-home-assistant-looks-for-integrations).
41-
2. Add `remote_homeassistant:` to your HA configuration.
41+
42+
43+
**Remote instance**
44+
45+
On the remote instance you also need to add this to `configuration.yaml`:
46+
47+
```yaml
48+
remote_homeassistant:
49+
instances:
50+
```
51+
52+
This is not needed on the main instance.
4253
4354
## Configuration
4455
@@ -229,7 +240,7 @@ services:
229240
230241
### Missing Components
231242
232-
If you have remote domains (e.g. `switch`), that are not loaded on the master instance you need to list them under `load_components`, otherwise you'll get a `Call service failed` error.
243+
If you have remote domains (e.g. `switch`), that are not loaded on the main instance you need to list them under `load_components`, otherwise you'll get a `Call service failed` error.
233244
234245
E.g. on the master:
235246

custom_components/remote_homeassistant/__init__.py

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,44 @@
44
For more details about this component, please refer to the documentation at
55
https://home-assistant.io/components/remote_homeassistant/
66
"""
7-
import fnmatch
8-
import logging
9-
import copy
107
import asyncio
8+
import copy
9+
import fnmatch
1110
import inspect
11+
import logging
12+
import re
1213
from contextlib import suppress
1314

1415
import aiohttp
15-
import re
16-
17-
import voluptuous as vol
18-
19-
from homeassistant.core import HomeAssistant, callback, Context
2016
import homeassistant.components.websocket_api.auth as api
21-
from homeassistant.core import EventOrigin, split_entity_id
22-
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
23-
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
24-
from homeassistant.const import (
25-
CONF_HOST,
26-
CONF_PORT,
27-
EVENT_CALL_SERVICE,
28-
EVENT_HOMEASSISTANT_STOP,
29-
EVENT_STATE_CHANGED,
30-
CONF_EXCLUDE,
31-
CONF_ENTITIES,
32-
CONF_ENTITY_ID,
33-
CONF_DOMAINS,
34-
CONF_INCLUDE,
35-
CONF_UNIT_OF_MEASUREMENT,
36-
CONF_ABOVE,
37-
CONF_BELOW,
38-
CONF_VERIFY_SSL,
39-
CONF_ACCESS_TOKEN,
40-
SERVICE_RELOAD,
41-
)
17+
import homeassistant.helpers.config_validation as cv
18+
import voluptuous as vol
19+
from homeassistant.components.http import HomeAssistantView
4220
from homeassistant.config import DATA_CUSTOMIZE
21+
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
22+
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
23+
CONF_DOMAINS, CONF_ENTITIES, CONF_ENTITY_ID,
24+
CONF_EXCLUDE, CONF_HOST, CONF_INCLUDE,
25+
CONF_PORT, CONF_UNIT_OF_MEASUREMENT,
26+
CONF_VERIFY_SSL, EVENT_CALL_SERVICE,
27+
EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED,
28+
SERVICE_RELOAD)
29+
from homeassistant.core import (Context, EventOrigin, HomeAssistant, callback,
30+
split_entity_id)
4331
from homeassistant.helpers import device_registry as dr
4432
from homeassistant.helpers.aiohttp_client import async_get_clientsession
45-
from homeassistant.helpers.reload import async_integration_yaml_config
46-
import homeassistant.helpers.config_validation as cv
4733
from homeassistant.helpers.dispatcher import async_dispatcher_send
34+
from homeassistant.helpers.reload import async_integration_yaml_config
35+
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
4836
from homeassistant.setup import async_setup_component
4937

50-
from .const import (
51-
CONF_REMOTE_CONNECTION,
52-
CONF_UNSUB_LISTENER,
53-
CONF_INCLUDE_DOMAINS,
54-
CONF_INCLUDE_ENTITIES,
55-
CONF_EXCLUDE_DOMAINS,
56-
CONF_EXCLUDE_ENTITIES,
57-
CONF_OPTIONS,
58-
CONF_LOAD_COMPONENTS,
59-
CONF_SERVICE_PREFIX,
60-
CONF_SERVICES,
61-
DOMAIN,
62-
)
63-
from .rest_api import UnsupportedVersion, async_get_discovery_info
38+
from .const import (CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES,
39+
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
40+
CONF_LOAD_COMPONENTS, CONF_OPTIONS, CONF_REMOTE_CONNECTION,
41+
CONF_SERVICE_PREFIX, CONF_SERVICES, CONF_UNSUB_LISTENER,
42+
DOMAIN)
6443
from .proxy_services import ProxyServices
44+
from .rest_api import UnsupportedVersion, async_get_discovery_info
6545

6646
_LOGGER = logging.getLogger(__name__)
6747

@@ -221,6 +201,8 @@ async def _handle_reload(service):
221201

222202
await asyncio.gather(*update_tasks)
223203

204+
hass.http.register_view(DiscoveryInfoView())
205+
224206
hass.helpers.service.async_register_admin_service(
225207
DOMAIN,
226208
SERVICE_RELOAD,
@@ -234,6 +216,7 @@ async def _handle_reload(service):
234216
DOMAIN, context={"source": SOURCE_IMPORT}, data=instance
235217
)
236218
)
219+
237220
return True
238221

239222

@@ -746,3 +729,20 @@ def got_states(message):
746729
await self.call(got_states, "get_states")
747730

748731
await self.proxy_services.load()
732+
733+
734+
class DiscoveryInfoView(HomeAssistantView):
735+
"""Get all logged errors and warnings."""
736+
737+
url = "/api/remote_homeassistant/discovery"
738+
name = "api:remote_homeassistant:discovery"
739+
740+
async def get(self, request):
741+
"""Get discovery information."""
742+
hass = request.app["hass"]
743+
return self.json(
744+
{
745+
"uuid": await hass.helpers.instance_id.async_get(),
746+
"location_name": hass.config.location_name,
747+
}
748+
)

custom_components/remote_homeassistant/config_flow.py

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,25 @@
22
import logging
33
from urllib.parse import urlparse
44

5+
import homeassistant.helpers.config_validation as cv
56
import voluptuous as vol
6-
77
from homeassistant import config_entries, core
8-
from homeassistant.helpers.instance_id import async_get
9-
import homeassistant.helpers.config_validation as cv
10-
from homeassistant.const import (
11-
CONF_HOST,
12-
CONF_PORT,
13-
CONF_VERIFY_SSL,
14-
CONF_ACCESS_TOKEN,
15-
CONF_ENTITY_ID,
16-
CONF_UNIT_OF_MEASUREMENT,
17-
CONF_ABOVE,
18-
CONF_BELOW,
19-
)
8+
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
9+
CONF_ENTITY_ID, CONF_HOST, CONF_PORT,
10+
CONF_UNIT_OF_MEASUREMENT, CONF_VERIFY_SSL)
2011
from homeassistant.core import callback
12+
from homeassistant.helpers.instance_id import async_get
2113
from homeassistant.util import slugify
2214

2315
from . import async_yaml_to_config_entry
24-
from .rest_api import (
25-
ApiProblem,
26-
CannotConnect,
27-
InvalidAuth,
28-
UnsupportedVersion,
29-
async_get_discovery_info,
30-
)
31-
from .const import (
32-
CONF_REMOTE_CONNECTION,
33-
CONF_SECURE,
34-
CONF_LOAD_COMPONENTS,
35-
CONF_SERVICE_PREFIX,
36-
CONF_SERVICES,
37-
CONF_FILTER,
38-
CONF_SUBSCRIBE_EVENTS,
39-
CONF_ENTITY_PREFIX,
40-
CONF_INCLUDE_DOMAINS,
41-
CONF_INCLUDE_ENTITIES,
42-
CONF_EXCLUDE_DOMAINS,
43-
CONF_EXCLUDE_ENTITIES,
44-
CONF_OPTIONS,
45-
DOMAIN,
46-
) # pylint:disable=unused-import
16+
from .const import (CONF_ENTITY_PREFIX, # pylint:disable=unused-import
17+
CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES, CONF_FILTER,
18+
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
19+
CONF_LOAD_COMPONENTS, CONF_OPTIONS, CONF_REMOTE_CONNECTION,
20+
CONF_SECURE, CONF_SERVICE_PREFIX, CONF_SERVICES,
21+
CONF_SUBSCRIBE_EVENTS, DOMAIN)
22+
from .rest_api import (ApiProblem, CannotConnect, EndpointMissing, InvalidAuth,
23+
UnsupportedVersion, async_get_discovery_info)
4724

4825
_LOGGER = logging.getLogger(__name__)
4926

@@ -100,13 +77,16 @@ async def async_step_user(self, user_input=None):
10077
try:
10178
info = await validate_input(self.hass, user_input)
10279
except ApiProblem:
80+
_LOGGER.exception("test")
10381
errors["base"] = "api_problem"
10482
except CannotConnect:
10583
errors["base"] = "cannot_connect"
10684
except InvalidAuth:
10785
errors["base"] = "invalid_auth"
10886
except UnsupportedVersion:
10987
errors["base"] = "unsupported_version"
88+
except EndpointMissing:
89+
errors["base"] = "missing_endpoint"
11090
except Exception: # pylint: disable=broad-except
11191
_LOGGER.exception("Unexpected exception")
11292
errors["base"] = "unknown"
@@ -135,6 +115,7 @@ async def async_step_user(self, user_input=None):
135115
async def async_step_zeroconf(self, info):
136116
"""Handle instance discovered via zeroconf."""
137117
properties = info["properties"]
118+
port = info["port"]
138119
uuid = properties["uuid"]
139120

140121
await self.async_set_unique_id(uuid)
@@ -150,7 +131,7 @@ async def async_step_zeroconf(self, info):
150131

151132
self.prefill = {
152133
CONF_HOST: url.hostname,
153-
CONF_PORT: url.port,
134+
CONF_PORT: port,
154135
CONF_SECURE: url.scheme == "https",
155136
}
156137

custom_components/remote_homeassistant/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "Remote Home-Assistant",
44
"issue_tracker": "https://github.com/custom-components/remote_homeassistant/issues",
55
"documentation": "https://github.com/custom-components/remote_homeassistant",
6-
"dependencies": [],
6+
"dependencies": ["http"],
77
"config_flow": true,
88
"codeowners": [
99
"@lukas-hetzenecker",
@@ -13,6 +13,6 @@
1313
"zeroconf": [
1414
"_home-assistant._tcp.local."
1515
],
16-
"version": "3.4",
16+
"version": "3.5",
1717
"iot_class": "local_push"
1818
}

custom_components/remote_homeassistant/proxy_services.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Support for proxy services."""
22
import asyncio
3-
import voluptuous as vol
43

4+
import voluptuous as vol
55
from homeassistant.core import SERVICE_CALL_LIMIT
66
from homeassistant.exceptions import HomeAssistantError
77
from homeassistant.helpers.service import SERVICE_DESCRIPTION_CACHE

custom_components/remote_homeassistant/rest_api.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from homeassistant import exceptions
44
from homeassistant.helpers.aiohttp_client import async_get_clientsession
55

6-
API_URL = "{proto}://{host}:{port}/api/"
6+
API_URL = "{proto}://{host}:{port}/api/remote_homeassistant/discovery"
77

88

99
class ApiProblem(exceptions.HomeAssistantError):
@@ -26,6 +26,10 @@ class UnsupportedVersion(exceptions.HomeAssistantError):
2626
"""Error to indicate an unsupported version of Home Assistant."""
2727

2828

29+
class EndpointMissing(exceptions.HomeAssistantError):
30+
"""Error to indicate there is invalid auth."""
31+
32+
2933
async def async_get_discovery_info(hass, host, port, secure, access_token, verify_ssl):
3034
"""Get discovery information from server."""
3135
url = API_URL.format(
@@ -39,17 +43,14 @@ async def async_get_discovery_info(hass, host, port, secure, access_token, verif
3943
}
4044
session = async_get_clientsession(hass, verify_ssl)
4145

42-
# Try to reach api endpoint solely for verifying auth
46+
# Fetch discovery info location for name and unique UUID
4347
async with session.get(url, headers=headers) as resp:
48+
if resp.status == 404:
49+
raise EndpointMissing()
4450
if 400 <= resp.status < 500:
4551
raise InvalidAuth()
4652
if resp.status != 200:
4753
raise ApiProblem()
48-
49-
# Fetch discovery info location for name and unique UUID
50-
async with session.get(url + "discovery_info") as resp:
51-
if resp.status != 200:
52-
raise ApiProblem()
5354
json = await resp.json()
5455
if not isinstance(json, dict):
5556
raise BadResponse(f"Bad response data: {json}")

custom_components/remote_homeassistant/sensor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Sensor platform for connection status.."""
22
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_VERIFY_SSL
3-
from homeassistant.helpers.entity import Entity
43
from homeassistant.helpers.dispatcher import async_dispatcher_connect
4+
from homeassistant.helpers.entity import Entity
55

6-
from .const import CONF_SECURE, CONF_ENTITY_PREFIX
6+
from .const import CONF_ENTITY_PREFIX, CONF_SECURE
77

88

99
async def async_setup_entry(hass, config_entry, async_add_entities):

custom_components/remote_homeassistant/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"cannot_connect": "Failed to connect to server",
1818
"invalid_auth": "Invalid credentials",
1919
"unsupported_version": "Unsupported version. At least version 0.111 is required.",
20-
"unknown": "An unknown error occurred"
20+
"unknown": "An unknown error occurred",
21+
"missing_endpoint": "You need to install Remote Home Assistant on this host and add remote_homeassistant: to its configuration."
2122
},
2223
"abort": {
2324
"already_configured": "Already configured"

0 commit comments

Comments
 (0)