Skip to content

Commit aac178b

Browse files
Implement #141 - add remote instance configuration to config flow (#144)
* Implement #141 - add remote instance configuration to config flow * Include Home Assistant Version and Installation Type into discovery API (#145)
1 parent f546580 commit aac178b

File tree

6 files changed

+128
-69
lines changed

6 files changed

+128
-69
lines changed

custom_components/remote_homeassistant/__init__.py

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import homeassistant.components.websocket_api.auth as api
1717
import homeassistant.helpers.config_validation as cv
1818
import voluptuous as vol
19-
from homeassistant.components.http import HomeAssistantView
2019
from homeassistant.config import DATA_CUSTOMIZE
2120
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
2221
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
@@ -35,11 +34,13 @@
3534
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
3635
from homeassistant.setup import async_setup_component
3736

37+
from custom_components.remote_homeassistant.views import DiscoveryInfoView
38+
3839
from .const import (CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES,
3940
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
4041
CONF_LOAD_COMPONENTS, CONF_OPTIONS, CONF_REMOTE_CONNECTION,
4142
CONF_SERVICE_PREFIX, CONF_SERVICES, CONF_UNSUB_LISTENER,
42-
DOMAIN)
43+
DOMAIN, REMOTE_ID)
4344
from .proxy_services import ProxyServices
4445
from .rest_api import UnsupportedVersion, async_get_discovery_info
4546

@@ -179,6 +180,10 @@ async def _async_update_config_entry_if_from_yaml(hass, entries_by_id, conf):
179180
hass.config_entries.async_update_entry(entry, data=data, options=options)
180181

181182

183+
async def setup_remote_instance(hass: HomeAssistantType):
184+
hass.http.register_view(DiscoveryInfoView())
185+
186+
182187
async def async_setup(hass: HomeAssistantType, config: ConfigType):
183188
"""Set up the remote_homeassistant component."""
184189
hass.data.setdefault(DOMAIN, {})
@@ -201,7 +206,7 @@ async def _handle_reload(service):
201206

202207
await asyncio.gather(*update_tasks)
203208

204-
hass.http.register_view(DiscoveryInfoView())
209+
hass.async_create_task(setup_remote_instance(hass))
205210

206211
hass.helpers.service.async_register_admin_service(
207212
DOMAIN,
@@ -223,29 +228,33 @@ async def _handle_reload(service):
223228
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
224229
"""Set up Remote Home-Assistant from a config entry."""
225230
_async_import_options_from_yaml(hass, entry)
226-
remote = RemoteConnection(hass, entry)
227-
228-
hass.data[DOMAIN][entry.entry_id] = {
229-
CONF_REMOTE_CONNECTION: remote,
230-
CONF_UNSUB_LISTENER: entry.add_update_listener(_update_listener),
231-
}
232-
233-
async def setup_components_and_platforms():
234-
"""Set up platforms and initiate connection."""
235-
for domain in entry.options.get(CONF_LOAD_COMPONENTS, []):
236-
hass.async_create_task(async_setup_component(hass, domain, {}))
237-
238-
await asyncio.gather(
239-
*[
240-
hass.config_entries.async_forward_entry_setup(entry, platform)
241-
for platform in PLATFORMS
242-
]
243-
)
244-
await remote.async_connect()
231+
if entry.unique_id == REMOTE_ID:
232+
hass.async_create_task(setup_remote_instance(hass))
233+
return True
234+
else:
235+
remote = RemoteConnection(hass, entry)
236+
237+
hass.data[DOMAIN][entry.entry_id] = {
238+
CONF_REMOTE_CONNECTION: remote,
239+
CONF_UNSUB_LISTENER: entry.add_update_listener(_update_listener),
240+
}
241+
242+
async def setup_components_and_platforms():
243+
"""Set up platforms and initiate connection."""
244+
for domain in entry.options.get(CONF_LOAD_COMPONENTS, []):
245+
hass.async_create_task(async_setup_component(hass, domain, {}))
246+
247+
await asyncio.gather(
248+
*[
249+
hass.config_entries.async_forward_entry_setup(entry, platform)
250+
for platform in PLATFORMS
251+
]
252+
)
253+
await remote.async_connect()
245254

246-
hass.async_create_task(setup_components_and_platforms())
255+
hass.async_create_task(setup_components_and_platforms())
247256

248-
return True
257+
return True
249258

250259

251260
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
@@ -425,7 +434,7 @@ def _async_instance_id_match(info):
425434
name=info.get("location_name"),
426435
manufacturer="Home Assistant",
427436
model=info.get("installation_type"),
428-
sw_version=info.get("version"),
437+
sw_version=info.get("ha_version"),
429438
)
430439

431440
asyncio.ensure_future(self._recv())
@@ -729,20 +738,3 @@ def got_states(message):
729738
await self.call(got_states, "get_states")
730739

731740
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: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"""Config flow for Remote Home-Assistant integration."""
22
import logging
3+
import enum
4+
35
from urllib.parse import urlparse
46

57
import homeassistant.helpers.config_validation as cv
68
import voluptuous as vol
79
from homeassistant import config_entries, core
810
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
911
CONF_ENTITY_ID, CONF_HOST, CONF_PORT,
10-
CONF_UNIT_OF_MEASUREMENT, CONF_VERIFY_SSL)
12+
CONF_UNIT_OF_MEASUREMENT, CONF_VERIFY_SSL, CONF_TYPE)
1113
from homeassistant.core import callback
1214
from homeassistant.helpers.instance_id import async_get
1315
from homeassistant.util import slugify
@@ -16,9 +18,9 @@
1618
from .const import (CONF_ENTITY_PREFIX, # pylint:disable=unused-import
1719
CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES, CONF_FILTER,
1820
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
19-
CONF_LOAD_COMPONENTS, CONF_OPTIONS, CONF_REMOTE_CONNECTION,
21+
CONF_LOAD_COMPONENTS, CONF_MAIN, CONF_OPTIONS, CONF_REMOTE, CONF_REMOTE_CONNECTION,
2022
CONF_SECURE, CONF_SERVICE_PREFIX, CONF_SERVICES,
21-
CONF_SUBSCRIBE_EVENTS, DOMAIN)
23+
CONF_SUBSCRIBE_EVENTS, DOMAIN, REMOTE_ID)
2224
from .rest_api import (ApiProblem, CannotConnect, EndpointMissing, InvalidAuth,
2325
UnsupportedVersion, async_get_discovery_info)
2426

@@ -54,6 +56,13 @@ async def validate_input(hass: core.HomeAssistant, conf):
5456
return {"title": info["location_name"], "uuid": info["uuid"]}
5557

5658

59+
class InstanceType(enum.Enum):
60+
"""Possible options for instance type."""
61+
62+
remote = "Setup as remote node"
63+
main = "Add a remote"
64+
65+
5766
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
5867
"""Handle a config flow for Remote Home-Assistant."""
5968

@@ -73,11 +82,36 @@ def async_get_options_flow(config_entry):
7382
async def async_step_user(self, user_input=None):
7483
"""Handle the initial step."""
7584
errors = {}
85+
86+
if user_input is not None:
87+
if user_input[CONF_TYPE] == CONF_REMOTE:
88+
await self.async_set_unique_id(REMOTE_ID)
89+
self._abort_if_unique_id_configured()
90+
return self.async_create_entry(title="Remote instance", data=user_input)
91+
92+
elif user_input[CONF_TYPE] == CONF_MAIN:
93+
return await self.async_step_connection_details()
94+
95+
errors["base"] = "unknown"
96+
97+
return self.async_show_form(
98+
step_id="user",
99+
data_schema=vol.Schema(
100+
{
101+
vol.Required(CONF_TYPE): vol.In([CONF_REMOTE, CONF_MAIN])
102+
}
103+
),
104+
errors=errors,
105+
)
106+
107+
108+
async def async_step_connection_details(self, user_input=None):
109+
"""Handle the connection details step."""
110+
errors = {}
76111
if user_input is not None:
77112
try:
78113
info = await validate_input(self.hass, user_input)
79114
except ApiProblem:
80-
_LOGGER.exception("test")
81115
errors["base"] = "api_problem"
82116
except CannotConnect:
83117
errors["base"] = "cannot_connect"
@@ -99,7 +133,7 @@ async def async_step_user(self, user_input=None):
99133
port = self.prefill.get(CONF_PORT) or vol.UNDEFINED
100134
secure = self.prefill.get(CONF_SECURE) or vol.UNDEFINED
101135
return self.async_show_form(
102-
step_id="user",
136+
step_id="connection_details",
103137
data_schema=vol.Schema(
104138
{
105139
vol.Required(CONF_HOST, default=host): str,
@@ -138,7 +172,7 @@ async def async_step_zeroconf(self, info):
138172
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
139173
self.context["identifier"] = self.unique_id
140174
self.context["title_placeholders"] = {"name": properties["location_name"]}
141-
return await self.async_step_user()
175+
return await self.async_step_connection_details()
142176

143177
async def async_step_import(self, user_input):
144178
"""Handle import from YAML."""
@@ -164,14 +198,17 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
164198
"""Handle options flow for the Home Assistant remote integration."""
165199

166200
def __init__(self, config_entry):
167-
"""Initialize localtuya options flow."""
201+
"""Initialize remote_homeassistant options flow."""
168202
self.config_entry = config_entry
169203
self.filters = None
170204
self.events = None
171205
self.options = None
172206

173207
async def async_step_init(self, user_input=None):
174208
"""Manage basic options."""
209+
if self.config_entry.unique_id == REMOTE_ID:
210+
return
211+
175212
if user_input is not None:
176213
self.options = user_input.copy()
177214
return await self.async_step_domain_entity_filters()

custom_components/remote_homeassistant/const.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@
1919
CONF_EXCLUDE_DOMAINS = "exclude_domains"
2020
CONF_EXCLUDE_ENTITIES = "exclude_entities"
2121

22+
# FIXME: There seems to be ne way to make these strings translateable
23+
CONF_MAIN = "Add a remote node"
24+
CONF_REMOTE = "Setup as remote node"
25+
2226
DOMAIN = "remote_homeassistant"
27+
28+
REMOTE_ID = "remote"

custom_components/remote_homeassistant/sensor.py

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

66
from .const import CONF_ENTITY_PREFIX, CONF_SECURE
77

@@ -12,37 +12,31 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
1212

1313

1414
class ConnectionStatusSensor(Entity):
15-
"""Representation of a Tuya sensor."""
15+
"""Representation of a remote_homeassistant sensor."""
1616

1717
def __init__(self, config_entry):
18-
"""Initialize the Tuya sensor."""
18+
"""Initialize the remote_homeassistant sensor."""
1919
self._state = None
2020
self._entry = config_entry
2121

22-
@property
23-
def name(self):
24-
"""Return name of sensor."""
25-
host = self._entry.data[CONF_HOST]
26-
port = self._entry.data[CONF_PORT]
27-
return f"Remote connection to {host}:{port}"
22+
proto = 'http' if config_entry.data.get(CONF_SECURE) else 'https'
23+
host = config_entry.data[CONF_HOST]
24+
port = config_entry.data[CONF_PORT]
25+
self._attr_name = f"Remote connection to {host}:{port}"
26+
self._attr_unique_id = config_entry.unique_id
27+
self._attr_should_poll = False
28+
self._attr_device_info = DeviceInfo(
29+
name="Home Assistant",
30+
configuration_url=f"{proto}://{host}:{port}",
31+
)
2832

2933
@property
3034
def state(self):
3135
"""Return sensor state."""
3236
return self._state
3337

3438
@property
35-
def unique_id(self):
36-
"""Return unique device identifier."""
37-
return self._entry.unique_id
38-
39-
@property
40-
def should_poll(self):
41-
"""Return if entity should be polled."""
42-
return False
43-
44-
@property
45-
def device_state_attributes(self):
39+
def extra_state_attributes(self):
4640
"""Return device state attributes."""
4741
return {
4842
"host": self._entry.data[CONF_HOST],

custom_components/remote_homeassistant/translations/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
"flow_title": "Remote: {name}",
44
"step": {
55
"user": {
6+
"title": "Select installation type",
7+
"description": "The remote node is the instance on which the states are gathered from"
8+
},
9+
"connection_details": {
10+
"title": "Connection details",
611
"data": {
712
"host": "Host",
813
"port": "Port",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import homeassistant
2+
from homeassistant.components.http import HomeAssistantView
3+
from homeassistant.helpers.system_info import async_get_system_info
4+
5+
ATTR_INSTALLATION_TYPE = "installation_type"
6+
7+
8+
class DiscoveryInfoView(HomeAssistantView):
9+
"""Get all logged errors and warnings."""
10+
11+
url = "/api/remote_homeassistant/discovery"
12+
name = "api:remote_homeassistant:discovery"
13+
14+
async def get(self, request):
15+
"""Get discovery information."""
16+
hass = request.app["hass"]
17+
system_info = await async_get_system_info(hass)
18+
return self.json(
19+
{
20+
"uuid": await hass.helpers.instance_id.async_get(),
21+
"location_name": hass.config.location_name,
22+
"ha_version": homeassistant.const.__version__,
23+
"installation_type": system_info[ATTR_INSTALLATION_TYPE],
24+
}
25+
)

0 commit comments

Comments
 (0)