updated lovelace dashboards and migrate tcp sensors to use serial component with SOCAT
This commit is contained in:
@@ -4,7 +4,9 @@ Connect two Home Assistant instances via the Websocket API.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/remote_homeassistant/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
import copy
|
||||
import fnmatch
|
||||
import inspect
|
||||
@@ -13,10 +15,15 @@ import re
|
||||
from contextlib import suppress
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import ClientWebSocketResponse
|
||||
import homeassistant.components.websocket_api.auth as api
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.config import DATA_CUSTOMIZE
|
||||
try:
|
||||
from homeassistant.core_config import DATA_CUSTOMIZE
|
||||
except (ModuleNotFoundError, ImportError):
|
||||
# hass 2024.10 or older
|
||||
from homeassistant.config import DATA_CUSTOMIZE
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
|
||||
CONF_DOMAINS, CONF_ENTITIES, CONF_ENTITY_ID,
|
||||
@@ -28,10 +35,12 @@ from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
|
||||
from homeassistant.core import (Context, EventOrigin, HomeAssistant, callback,
|
||||
split_entity_id)
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.reload import async_integration_yaml_config
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.helpers.service import async_register_admin_service
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from custom_components.remote_homeassistant.views import DiscoveryInfoView
|
||||
@@ -52,6 +61,7 @@ CONF_INSTANCES = "instances"
|
||||
CONF_SECURE = "secure"
|
||||
CONF_SUBSCRIBE_EVENTS = "subscribe_events"
|
||||
CONF_ENTITY_PREFIX = "entity_prefix"
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX = "entity_friendly_name_prefix"
|
||||
CONF_FILTER = "filter"
|
||||
CONF_MAX_MSG_SIZE = "max_message_size"
|
||||
|
||||
@@ -64,6 +74,7 @@ STATE_RECONNECTING = "reconnecting"
|
||||
STATE_DISCONNECTED = "disconnected"
|
||||
|
||||
DEFAULT_ENTITY_PREFIX = ""
|
||||
DEFAULT_ENTITY_FRIENDLY_NAME_PREFIX = ""
|
||||
|
||||
INSTANCES_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -103,7 +114,10 @@ INSTANCES_SCHEMA = vol.Schema(
|
||||
],
|
||||
),
|
||||
vol.Optional(CONF_SUBSCRIBE_EVENTS): cv.ensure_list,
|
||||
vol.Optional(CONF_ENTITY_PREFIX, default=DEFAULT_ENTITY_PREFIX): cv.string,
|
||||
vol.Optional(CONF_ENTITY_PREFIX,
|
||||
default=DEFAULT_ENTITY_PREFIX): cv.string,
|
||||
vol.Optional(CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
default=DEFAULT_ENTITY_FRIENDLY_NAME_PREFIX): cv.string,
|
||||
vol.Optional(CONF_LOAD_COMPONENTS): cv.ensure_list,
|
||||
vol.Required(CONF_SERVICE_PREFIX, default="remote_"): cv.string,
|
||||
vol.Optional(CONF_SERVICES): cv.ensure_list,
|
||||
@@ -152,6 +166,7 @@ def async_yaml_to_config_entry(instance_conf):
|
||||
CONF_FILTER,
|
||||
CONF_SUBSCRIBE_EVENTS,
|
||||
CONF_ENTITY_PREFIX,
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
CONF_LOAD_COMPONENTS,
|
||||
CONF_SERVICE_PREFIX,
|
||||
CONF_SERVICES,
|
||||
@@ -182,11 +197,11 @@ async def _async_update_config_entry_if_from_yaml(hass, entries_by_id, conf):
|
||||
hass.config_entries.async_update_entry(entry, data=data, options=options)
|
||||
|
||||
|
||||
async def setup_remote_instance(hass: HomeAssistantType):
|
||||
async def setup_remote_instance(hass: HomeAssistant.core.HomeAssistant):
|
||||
hass.http.register_view(DiscoveryInfoView())
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
async def async_setup(hass: HomeAssistant.core.HomeAssistant, config: ConfigType):
|
||||
"""Set up the remote_homeassistant component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
@@ -210,7 +225,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
|
||||
hass.async_create_task(setup_remote_instance(hass))
|
||||
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
async_register_admin_service(hass,
|
||||
DOMAIN,
|
||||
SERVICE_RELOAD,
|
||||
_handle_reload,
|
||||
@@ -246,12 +261,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
for domain in entry.options.get(CONF_LOAD_COMPONENTS, []):
|
||||
hass.async_create_task(async_setup_component(hass, domain, {}))
|
||||
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
for platform in PLATFORMS
|
||||
]
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
await remote.async_connect()
|
||||
|
||||
hass.async_create_task(setup_components_and_platforms())
|
||||
@@ -292,7 +302,7 @@ async def _update_listener(hass, config_entry):
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
|
||||
class RemoteConnection(object):
|
||||
class RemoteConnection:
|
||||
"""A Websocket connection to a remote home-assistant instance."""
|
||||
|
||||
def __init__(self, hass, config_entry):
|
||||
@@ -302,7 +312,7 @@ class RemoteConnection(object):
|
||||
self._secure = config_entry.data.get(CONF_SECURE, False)
|
||||
self._verify_ssl = config_entry.data.get(CONF_VERIFY_SSL, False)
|
||||
self._access_token = config_entry.data.get(CONF_ACCESS_TOKEN)
|
||||
self._max_msg_size = config_entry.data.get(CONF_MAX_MSG_SIZE)
|
||||
self._max_msg_size = config_entry.data.get(CONF_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE)
|
||||
|
||||
# see homeassistant/components/influxdb/__init__.py
|
||||
# for include/exclude logic
|
||||
@@ -326,9 +336,12 @@ class RemoteConnection(object):
|
||||
self._subscribe_events = set(
|
||||
config_entry.options.get(CONF_SUBSCRIBE_EVENTS, []) + INTERNALLY_USED_EVENTS
|
||||
)
|
||||
self._entity_prefix = config_entry.options.get(CONF_ENTITY_PREFIX, "")
|
||||
self._entity_prefix = config_entry.options.get(
|
||||
CONF_ENTITY_PREFIX, "")
|
||||
self._entity_friendly_name_prefix = config_entry.options.get(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX, "")
|
||||
|
||||
self._connection = None
|
||||
self._connection : Optional[ClientWebSocketResponse] = None
|
||||
self._heartbeat_task = None
|
||||
self._is_stopping = False
|
||||
self._entities = set()
|
||||
@@ -349,6 +362,26 @@ class RemoteConnection(object):
|
||||
return entity_id
|
||||
return entity_id
|
||||
|
||||
def _prefixed_entity_friendly_name(self, entity_friendly_name):
|
||||
if (self._entity_friendly_name_prefix
|
||||
and entity_friendly_name.startswith(self._entity_friendly_name_prefix)
|
||||
== False):
|
||||
entity_friendly_name = (self._entity_friendly_name_prefix +
|
||||
entity_friendly_name)
|
||||
return entity_friendly_name
|
||||
return entity_friendly_name
|
||||
|
||||
def _full_picture_url(self, url):
|
||||
baseURL = "%s://%s:%s" % (
|
||||
"https" if self._secure else "http",
|
||||
self._entry.data[CONF_HOST],
|
||||
self._entry.data[CONF_PORT],
|
||||
)
|
||||
if url.startswith(baseURL) == False:
|
||||
url = baseURL + url
|
||||
return url
|
||||
return url
|
||||
|
||||
def set_connection_state(self, state):
|
||||
"""Change current connection state."""
|
||||
signal = f"remote_homeassistant_{self._entry.unique_id}"
|
||||
@@ -445,7 +478,7 @@ class RemoteConnection(object):
|
||||
|
||||
async def _heartbeat_loop(self):
|
||||
"""Send periodic heartbeats to remote instance."""
|
||||
while not self._connection.closed:
|
||||
while self._connection is not None and not self._connection.closed:
|
||||
await asyncio.sleep(HEARTBEAT_INTERVAL)
|
||||
|
||||
_LOGGER.debug("Sending ping")
|
||||
@@ -460,7 +493,7 @@ class RemoteConnection(object):
|
||||
try:
|
||||
await asyncio.wait_for(event.wait(), HEARTBEAT_TIMEOUT)
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("heartbeat failed")
|
||||
_LOGGER.warning("heartbeat failed")
|
||||
|
||||
# Schedule closing on event loop to avoid deadlock
|
||||
asyncio.ensure_future(self._connection.close())
|
||||
@@ -478,9 +511,13 @@ class RemoteConnection(object):
|
||||
self.__id += 1
|
||||
return _id
|
||||
|
||||
async def call(self, callback, message_type, **extra_args):
|
||||
async def call(self, handler, message_type, **extra_args) -> None:
|
||||
if self._connection is None:
|
||||
_LOGGER.error("No remote websocket connection")
|
||||
return
|
||||
|
||||
_id = self._next_id()
|
||||
self._handlers[_id] = callback
|
||||
self._handlers[_id] = handler
|
||||
try:
|
||||
await self._connection.send_json(
|
||||
{"id": _id, "type": message_type, **extra_args}
|
||||
@@ -511,7 +548,7 @@ class RemoteConnection(object):
|
||||
asyncio.ensure_future(self.async_connect())
|
||||
|
||||
async def _recv(self):
|
||||
while not self._connection.closed:
|
||||
while self._connection is not None and not self._connection.closed:
|
||||
try:
|
||||
data = await self._connection.receive()
|
||||
except aiohttp.client_exceptions.ClientError as err:
|
||||
@@ -552,13 +589,13 @@ class RemoteConnection(object):
|
||||
|
||||
elif message["type"] == api.TYPE_AUTH_REQUIRED:
|
||||
if self._access_token:
|
||||
data = {"type": api.TYPE_AUTH, "access_token": self._access_token}
|
||||
json_data = {"type": api.TYPE_AUTH, "access_token": self._access_token}
|
||||
else:
|
||||
_LOGGER.error("Access token required, but not provided")
|
||||
self.set_connection_state(STATE_AUTH_REQUIRED)
|
||||
return
|
||||
try:
|
||||
await self._connection.send_json(data)
|
||||
await self._connection.send_json(json_data)
|
||||
except Exception as err:
|
||||
_LOGGER.error("could not send data to remote connection: %s", err)
|
||||
break
|
||||
@@ -570,12 +607,12 @@ class RemoteConnection(object):
|
||||
return
|
||||
|
||||
else:
|
||||
callback = self._handlers.get(message["id"])
|
||||
if callback is not None:
|
||||
if inspect.iscoroutinefunction(callback):
|
||||
await callback(message)
|
||||
handler = self._handlers.get(message["id"])
|
||||
if handler is not None:
|
||||
if inspect.iscoroutinefunction(handler):
|
||||
await handler(message)
|
||||
else:
|
||||
callback(message)
|
||||
handler(message)
|
||||
|
||||
await self._disconnected()
|
||||
|
||||
@@ -583,8 +620,8 @@ class RemoteConnection(object):
|
||||
async def forward_event(event):
|
||||
"""Send local event to remote instance.
|
||||
|
||||
The affected entity_id has to origin from that remote instance,
|
||||
otherwise the event is dicarded.
|
||||
The affected entity_id has to originate from that remote instance,
|
||||
otherwise the event is discarded.
|
||||
"""
|
||||
event_data = event.data
|
||||
service_data = event_data["service_data"]
|
||||
@@ -627,7 +664,10 @@ class RemoteConnection(object):
|
||||
data = {"id": _id, "type": event.event_type, **event_data}
|
||||
|
||||
_LOGGER.debug("forward event: %s", data)
|
||||
|
||||
|
||||
if self._connection is None:
|
||||
_LOGGER.error("There is no remote connecion to send send data to")
|
||||
return
|
||||
try:
|
||||
await self._connection.send_json(data)
|
||||
except Exception as err:
|
||||
@@ -636,7 +676,7 @@ class RemoteConnection(object):
|
||||
|
||||
def state_changed(entity_id, state, attr):
|
||||
"""Publish remote state change on local instance."""
|
||||
domain, object_id = split_entity_id(entity_id)
|
||||
domain, _object_id = split_entity_id(entity_id)
|
||||
|
||||
self._all_entity_names.add(entity_id)
|
||||
|
||||
@@ -661,7 +701,7 @@ class RemoteConnection(object):
|
||||
try:
|
||||
if f[CONF_BELOW] and float(state) < f[CONF_BELOW]:
|
||||
_LOGGER.info(
|
||||
"%s: ignoring state '%s', because " "below '%s'",
|
||||
"%s: ignoring state '%s', because below '%s'",
|
||||
entity_id,
|
||||
state,
|
||||
f[CONF_BELOW],
|
||||
@@ -669,7 +709,7 @@ class RemoteConnection(object):
|
||||
return
|
||||
if f[CONF_ABOVE] and float(state) > f[CONF_ABOVE]:
|
||||
_LOGGER.info(
|
||||
"%s: ignoring state '%s', because " "above '%s'",
|
||||
"%s: ignoring state '%s', because above '%s'",
|
||||
entity_id,
|
||||
state,
|
||||
f[CONF_ABOVE],
|
||||
@@ -680,15 +720,32 @@ class RemoteConnection(object):
|
||||
|
||||
entity_id = self._prefixed_entity_id(entity_id)
|
||||
|
||||
# Add local unique id
|
||||
domain, object_id = split_entity_id(entity_id)
|
||||
attr['unique_id'] = f"{self._entry.unique_id[:16]}_{entity_id}"
|
||||
entity_registry = er.async_get(self._hass)
|
||||
entity_registry.async_get_or_create(
|
||||
domain=domain,
|
||||
platform='remote_homeassistant',
|
||||
unique_id=attr['unique_id'],
|
||||
suggested_object_id=object_id,
|
||||
)
|
||||
|
||||
# Add local customization data
|
||||
if DATA_CUSTOMIZE in self._hass.data:
|
||||
attr.update(self._hass.data[DATA_CUSTOMIZE].get(entity_id))
|
||||
|
||||
for attrId, value in attr.items():
|
||||
if attrId == "friendly_name":
|
||||
attr[attrId] = self._prefixed_entity_friendly_name(value)
|
||||
if attrId == "entity_picture":
|
||||
attr[attrId] = self._full_picture_url(value)
|
||||
|
||||
self._entities.add(entity_id)
|
||||
self._hass.states.async_set(entity_id, state, attr)
|
||||
|
||||
def fire_event(message):
|
||||
"""Publish remove event on local instance."""
|
||||
"""Publish remote event on local instance."""
|
||||
if message["type"] == "result":
|
||||
return
|
||||
|
||||
@@ -730,6 +787,11 @@ class RemoteConnection(object):
|
||||
entity_id = entity["entity_id"]
|
||||
state = entity["state"]
|
||||
attributes = entity["attributes"]
|
||||
for attr, value in attributes.items():
|
||||
if attr == "friendly_name":
|
||||
attributes[attr] = self._prefixed_entity_friendly_name(value)
|
||||
if attr == "entity_picture":
|
||||
attributes[attr] = self._full_picture_url(value)
|
||||
|
||||
state_changed(entity_id, state, attributes)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Config flow for Remote Home-Assistant integration."""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import enum
|
||||
from typing import Any, Mapping
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -16,6 +18,7 @@ from homeassistant.util import slugify
|
||||
|
||||
from . import async_yaml_to_config_entry
|
||||
from .const import (CONF_ENTITY_PREFIX, # pylint:disable=unused-import
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES, CONF_FILTER,
|
||||
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
|
||||
CONF_LOAD_COMPONENTS, CONF_MAIN, CONF_OPTIONS, CONF_REMOTE, CONF_REMOTE_CONNECTION,
|
||||
@@ -31,11 +34,11 @@ ADD_NEW_EVENT = "add_new_event"
|
||||
FILTER_OPTIONS = [CONF_ENTITY_ID, CONF_UNIT_OF_MEASUREMENT, CONF_ABOVE, CONF_BELOW]
|
||||
|
||||
|
||||
def _filter_str(index, filter):
|
||||
entity_id = filter[CONF_ENTITY_ID]
|
||||
unit = filter[CONF_UNIT_OF_MEASUREMENT]
|
||||
above = filter[CONF_ABOVE]
|
||||
below = filter[CONF_BELOW]
|
||||
def _filter_str(index, filter_conf: Mapping[str, str|float]):
|
||||
entity_id = filter_conf[CONF_ENTITY_ID]
|
||||
unit = filter_conf[CONF_UNIT_OF_MEASUREMENT]
|
||||
above = filter_conf[CONF_ABOVE]
|
||||
below = filter_conf[CONF_BELOW]
|
||||
return f"{index+1}. {entity_id}, unit: {unit}, above: {above}, below: {below}"
|
||||
|
||||
|
||||
@@ -50,8 +53,8 @@ async def validate_input(hass: core.HomeAssistant, conf):
|
||||
conf[CONF_ACCESS_TOKEN],
|
||||
conf.get(CONF_VERIFY_SSL, False),
|
||||
)
|
||||
except OSError:
|
||||
raise CannotConnect()
|
||||
except OSError as exc:
|
||||
raise CannotConnect() from exc
|
||||
|
||||
return {"title": info["location_name"], "uuid": info["uuid"]}
|
||||
|
||||
@@ -91,9 +94,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
elif user_input[CONF_TYPE] == CONF_MAIN:
|
||||
return await self.async_step_connection_details()
|
||||
|
||||
|
||||
errors["base"] = "unknown"
|
||||
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
@@ -129,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
user_input = user_input or dict()
|
||||
user_input = user_input or {}
|
||||
host = user_input.get(CONF_HOST, self.prefill.get(CONF_HOST) or vol.UNDEFINED)
|
||||
port = user_input.get(CONF_PORT, self.prefill.get(CONF_PORT) or vol.UNDEFINED)
|
||||
secure = user_input.get(CONF_SECURE, self.prefill.get(CONF_SECURE) or vol.UNDEFINED)
|
||||
@@ -149,10 +152,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(self, info):
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""Handle instance discovered via zeroconf."""
|
||||
properties = info.properties
|
||||
port = info.port
|
||||
properties = discovery_info.properties
|
||||
port = discovery_info.port
|
||||
uuid = properties["uuid"]
|
||||
|
||||
await self.async_set_unique_id(uuid)
|
||||
@@ -203,11 +206,11 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize remote_homeassistant options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.filters = None
|
||||
self.events = None
|
||||
self.options = None
|
||||
self.filters : list[Any] | None = None
|
||||
self.events : set[Any] | None = None
|
||||
self.options : dict[str, Any] | None = None
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(self, user_input : dict[str, str] | None = None):
|
||||
"""Manage basic options."""
|
||||
if self.config_entry.unique_id == REMOTE_ID:
|
||||
return self.async_abort(reason="not_supported")
|
||||
@@ -235,6 +238,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
)
|
||||
},
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
description={
|
||||
"suggested_value": self.config_entry.options.get(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX
|
||||
)
|
||||
},
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_LOAD_COMPONENTS,
|
||||
default=self._default(CONF_LOAD_COMPONENTS),
|
||||
@@ -252,7 +263,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
|
||||
async def async_step_domain_entity_filters(self, user_input=None):
|
||||
"""Manage domain and entity filters."""
|
||||
if user_input is not None:
|
||||
if self.options is not None and user_input is not None:
|
||||
self.options.update(user_input)
|
||||
return await self.async_step_general_filters()
|
||||
|
||||
@@ -289,21 +300,25 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
# Each filter string is prefixed with a number (index in self.filter+1).
|
||||
# Extract all of them and build the final filter list.
|
||||
selected_indices = [
|
||||
int(filter.split(".")[0]) - 1
|
||||
for filter in user_input.get(CONF_FILTER, [])
|
||||
int(filterItem.split(".")[0]) - 1
|
||||
for filterItem in user_input.get(CONF_FILTER, [])
|
||||
]
|
||||
self.options[CONF_FILTER] = [self.filters[i] for i in selected_indices]
|
||||
if self.options is not None:
|
||||
self.options[CONF_FILTER] = [self.filters[i] for i in selected_indices] # type: ignore
|
||||
return await self.async_step_events()
|
||||
|
||||
selected = user_input.get(CONF_FILTER, [])
|
||||
new_filter = {conf: user_input.get(conf) for conf in FILTER_OPTIONS}
|
||||
selected.append(_filter_str(len(self.filters), new_filter))
|
||||
self.filters.append(new_filter)
|
||||
|
||||
selected.append(_filter_str(len(self.filters), new_filter)) # type: ignore
|
||||
self.filters.append(new_filter) # type: ignore
|
||||
else:
|
||||
self.filters = self.config_entry.options.get(CONF_FILTER, [])
|
||||
selected = [_filter_str(i, filter) for i, filter in enumerate(self.filters)]
|
||||
selected = [_filter_str(i, filterItem) for i, filterItem in enumerate(self.filters)] # type: ignore
|
||||
|
||||
strings = [_filter_str(i, filter) for i, filter in enumerate(self.filters)]
|
||||
if self.filters is None:
|
||||
self.filters = []
|
||||
strings = [_filter_str(i, filterItem) for i, filterItem in enumerate(self.filters)]
|
||||
return self.async_show_form(
|
||||
step_id="general_filters",
|
||||
data_schema=vol.Schema(
|
||||
@@ -322,13 +337,15 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
async def async_step_events(self, user_input=None):
|
||||
"""Manage event options."""
|
||||
if user_input is not None:
|
||||
if ADD_NEW_EVENT not in user_input:
|
||||
if ADD_NEW_EVENT not in user_input and self.options is not None:
|
||||
self.options[CONF_SUBSCRIBE_EVENTS] = user_input.get(
|
||||
CONF_SUBSCRIBE_EVENTS, []
|
||||
)
|
||||
return self.async_create_entry(title="", data=self.options)
|
||||
|
||||
selected = user_input.get(CONF_SUBSCRIBE_EVENTS, [])
|
||||
if self.events is None:
|
||||
self.events = set()
|
||||
self.events.add(user_input[ADD_NEW_EVENT])
|
||||
selected.append(user_input[ADD_NEW_EVENT])
|
||||
else:
|
||||
|
||||
@@ -13,6 +13,7 @@ CONF_SECURE = "secure"
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
CONF_SUBSCRIBE_EVENTS = "subscribe_events"
|
||||
CONF_ENTITY_PREFIX = "entity_prefix"
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX = "entity_friendly_name_prefix"
|
||||
CONF_MAX_MSG_SIZE = "max_message_size"
|
||||
|
||||
CONF_INCLUDE_DOMAINS = "include_domains"
|
||||
@@ -20,7 +21,7 @@ CONF_INCLUDE_ENTITIES = "include_entities"
|
||||
CONF_EXCLUDE_DOMAINS = "exclude_domains"
|
||||
CONF_EXCLUDE_ENTITIES = "exclude_entities"
|
||||
|
||||
# FIXME: There seems to be ne way to make these strings translateable
|
||||
# FIXME: There seems to be no way to make these strings translateable
|
||||
CONF_MAIN = "Add a remote node"
|
||||
CONF_REMOTE = "Setup as remote node"
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"domain": "remote_homeassistant",
|
||||
"name": "Remote Home-Assistant",
|
||||
"issue_tracker": "https://github.com/custom-components/remote_homeassistant/issues",
|
||||
"documentation": "https://github.com/custom-components/remote_homeassistant",
|
||||
"dependencies": ["http"],
|
||||
"config_flow": true,
|
||||
"codeowners": [
|
||||
"@jaym25",
|
||||
"@lukas-hetzenecker",
|
||||
"@postlund"
|
||||
],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://github.com/custom-components/remote_homeassistant",
|
||||
"iot_class": "local_push",
|
||||
"issue_tracker": "https://github.com/custom-components/remote_homeassistant/issues",
|
||||
"requirements": [],
|
||||
"version": "4.5",
|
||||
"zeroconf": [
|
||||
"_home-assistant._tcp.local."
|
||||
],
|
||||
"version": "3.11",
|
||||
"iot_class": "local_push"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Support for proxy services."""
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
@@ -78,14 +80,14 @@ class ProxyServices:
|
||||
|
||||
self.registered_services.append((domain, service))
|
||||
|
||||
async def _async_handle_service_call(self, event):
|
||||
async def _async_handle_service_call(self, event) -> None:
|
||||
"""Handle service call to proxy service."""
|
||||
# An eception must be raised from the service call handler (thus method) in
|
||||
# An exception must be raised from the service call handler (thus method) in
|
||||
# order to end up in the frontend. The code below synchronizes reception of
|
||||
# the service call result, so potential error message can be used as exception
|
||||
# message. Not very pretty...
|
||||
ev = asyncio.Event()
|
||||
res = None
|
||||
res : dict[str,Any] | None = None
|
||||
|
||||
def _resp(message):
|
||||
nonlocal res
|
||||
@@ -103,5 +105,5 @@ class ProxyServices:
|
||||
)
|
||||
|
||||
await asyncio.wait_for(ev.wait(), SERVICE_CALL_LIMIT)
|
||||
if not res["success"]:
|
||||
if isinstance(res, dict) and not res["success"]:
|
||||
raise HomeAssistantError(res["error"]["message"])
|
||||
|
||||
@@ -3,8 +3,10 @@ from homeassistant.const import CONF_HOST, CONF_PORT, CONF_VERIFY_SSL
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DOMAIN, CONF_ENTITY_PREFIX, CONF_SECURE, CONF_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE
|
||||
|
||||
from .const import (DOMAIN, CONF_ENTITY_PREFIX,
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
CONF_SECURE, CONF_MAX_MSG_SIZE,
|
||||
DEFAULT_MAX_MSG_SIZE)
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up sensor based ok config entry."""
|
||||
@@ -46,6 +48,7 @@ class ConnectionStatusSensor(Entity):
|
||||
"verify_ssl": self._entry.data.get(CONF_VERIFY_SSL, False),
|
||||
"max_msg_size": self._entry.data.get(CONF_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE),
|
||||
"entity_prefix": self._entry.options.get(CONF_ENTITY_PREFIX, ""),
|
||||
"entity_friendly_name_prefix": self._entry.options.get(CONF_ENTITY_FRIENDLY_NAME_PREFIX, ""),
|
||||
"uuid": self.unique_id,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
reload:
|
||||
name: Reload Remote Home-Assistant
|
||||
description: Reload remote_homeassistant and re-process yaml configuration.
|
||||
|
||||
@@ -30,22 +30,13 @@
|
||||
"already_configured": "Bereits konfiguriert"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"_": {
|
||||
"disconnected": "Getrennt",
|
||||
"connecting": "Verbindet",
|
||||
"connected": "Verbunden",
|
||||
"reconnecting": "Wiederverbinden",
|
||||
"auth_invalid": "Ungültiger Zugangstoken",
|
||||
"auth_required": "Authentifizierung erforderlich"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Basis-Einstellungen (Schritt 1/4)",
|
||||
"data": {
|
||||
"entity_prefix": "Entitätspräfix (optional)",
|
||||
"entity_friendly_name_prefix": "Entitätsname präfix (optional)",
|
||||
"load_components": "Komponente laden (wenn nicht geladen)",
|
||||
"service_prefix": "Servicepräfix",
|
||||
"services": "Remote Services"
|
||||
|
||||
@@ -30,22 +30,13 @@
|
||||
"already_configured": "Already configured"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"_": {
|
||||
"disconnected": "Disconnected",
|
||||
"connecting": "Connecting",
|
||||
"connected": "Connected",
|
||||
"reconnecting": "Re-connecting",
|
||||
"auth_invalid": "Invalid access token",
|
||||
"auth_required": "Authentication Required"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Basic Options (step 1/4)",
|
||||
"data": {
|
||||
"entity_prefix": "Entity prefix (optional)",
|
||||
"entity_friendly_name_prefix": "Entity name prefix (optional)",
|
||||
"load_components": "Load component (if not loaded)",
|
||||
"service_prefix": "Service prefix",
|
||||
"services": "Remote Services"
|
||||
|
||||
@@ -30,22 +30,13 @@
|
||||
"already_configured": "Já configurado"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"_": {
|
||||
"disconnected": "Desconectado",
|
||||
"connecting": "Conectando",
|
||||
"connected": "Conectado",
|
||||
"reconnecting": "Reconectando",
|
||||
"auth_invalid": "Token de acesso inválido",
|
||||
"auth_required": "Autentificação requerida"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Opções básicas (passo 1/4)",
|
||||
"data": {
|
||||
"entity_prefix": "Prefixo da entidade (opcional)",
|
||||
"entity_friendly_name_prefix": "Prefixo da entidade nombre (opcional)",
|
||||
"load_components": "Carregar componente (se não estiver carregado)",
|
||||
"service_prefix": "Prefixo do serviço",
|
||||
"services": "Serviços remotos"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import homeassistant
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.helpers.instance_id import async_get as async_get_instance_id
|
||||
|
||||
ATTR_INSTALLATION_TYPE = "installation_type"
|
||||
|
||||
@@ -17,7 +18,7 @@ class DiscoveryInfoView(HomeAssistantView):
|
||||
system_info = await async_get_system_info(hass)
|
||||
return self.json(
|
||||
{
|
||||
"uuid": await hass.helpers.instance_id.async_get(),
|
||||
"uuid": await async_get_instance_id(hass),
|
||||
"location_name": hass.config.location_name,
|
||||
"ha_version": homeassistant.const.__version__,
|
||||
"installation_type": system_info[ATTR_INSTALLATION_TYPE],
|
||||
|
||||
Reference in New Issue
Block a user