Skip to content

Commit

Permalink
feat: respect assist entity exposure
Browse files Browse the repository at this point in the history
Home Assistant now allows you to explicitly expose entities to specific voice assistants.

This feature will respect that setting by default.
  • Loading branch information
mikejgray committed Jul 24, 2023
1 parent 7257efa commit 82dca59
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The config also takes some optional properties:

`brightness_increment` - the amount to increment/decrement the brightness of a light when the brightness up/down commands are sent. The default value is 10 and represents a percentage, e.g. 10%.
`search_confidence_threshold` - the confidence threshold for the search skill to use when searching for devices. The default value is 0.5, or 50%. Must be a value between 0 and 1.
`assist_only` - whether to pull down only entities exposed to Assist. Default True.

Sample config:

Expand Down
30 changes: 20 additions & 10 deletions ovos_PHAL_plugin_homeassistant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,22 @@ def search_confidence_threshold(self) -> int:
return self.config.get("search_confidence_threshold", 0.5)

# SETUP INSTANCE SUPPORT
def validate_instance_connection(self, host, api_key):
def validate_instance_connection(self, host, api_key, assist_only):
""" Validate the connection to the Home Assistant instance
Args:
host (str): The Home Assistant instance URL
api_key (str): The Home Assistant API key
assist_only (bool): Whether to only pull entities exposed to Assist. Default True
Returns:
bool: True if the connection is valid, False otherwise
"""
try:
if self.use_ws:
validator = HomeAssistantWSConnector(host, api_key)
validator = HomeAssistantWSConnector(host, api_key, assist_only)
else:
validator = HomeAssistantRESTConnector(host, api_key)
validator = HomeAssistantRESTConnector(host, api_key, assist_only)

validator.get_all_devices()

Expand All @@ -167,12 +168,13 @@ def setup_configuration(self, message):
"""
host = message.data.get("url", "")
key = message.data.get("api_key", "")
assist_only = message.data.get("assist_only", True)

if host and key:
if host.startswith("ws") or host.startswith("wss"):
self.use_ws = True

if self.validate_instance_connection(host, key):
if self.validate_instance_connection(host, key, assist_only):
self.config["host"] = host
self.config["api_key"] = key
self.instance_available = True
Expand All @@ -193,6 +195,7 @@ def init_configuration(self, message=None):
""" Initialize instance configuration """
configuration_host = self.config.get("host", "")
configuration_api_key = self.config.get("api_key", "")
configuration_assist_only = self.config.get("assist_only", True)
if configuration_host.startswith("ws") or configuration_host.startswith("wss"):
self.use_ws = True

Expand All @@ -202,9 +205,17 @@ def init_configuration(self, message=None):
if configuration_host != "" and configuration_api_key != "":
self.instance_available = True
if self.use_ws:
self.connector = HomeAssistantWSConnector(configuration_host, configuration_api_key)
self.connector = HomeAssistantWSConnector(
configuration_host,
configuration_api_key,
configuration_assist_only
)
else:
self.connector = HomeAssistantRESTConnector(configuration_host, configuration_api_key)
self.connector = HomeAssistantRESTConnector(
configuration_host,
configuration_api_key,
configuration_assist_only
)
self.devices = self.connector.get_all_devices()
self.registered_devices = []
self.build_devices()
Expand Down Expand Up @@ -364,7 +375,6 @@ def handle_get_device(self, message: Message):

# Device ID not provided, usually VUI
device = message.data.get("device")
device_result = match_one(device, self.registered_device_names)
device_result = self.fuzzy_match_name(
self.registered_devices,
device,
Expand Down Expand Up @@ -606,7 +616,7 @@ def handle_get_device_display_list_model(self, message):
display_list_model = []
for device in self.registered_devices:
display_list_model.append(device.get_device_display_model())
self.bus.emit(message.response(data=display_list_model))
self.bus.emit(message.response(data=display_list_model)) # TODO: Fix type, this may be causing GUI problems

def handle_assist_message(self, message):
"""Handle a passthrough message to Home Assistant's Assist API.
Expand Down Expand Up @@ -720,7 +730,7 @@ def handle_set_group_display_settings(self, message):
"""
group_settings = message.data.get("use_group_display", None)
if group_settings is not None:
if group_settings == True:
if group_settings is True:
use_group_display = True
self.config["use_group_display"] = use_group_display
else:
Expand Down Expand Up @@ -822,7 +832,7 @@ def device_updated(self, device_id):
It can request a new display model once it receives this signal.
Args:
device (dict): The device that was updated.
device_id (dict): The device that was updated.
"""
# GUI only event as we don't want to flood the GUI bus
self.gui.send_event("ovos.phal.plugin.homeassistant.device.updated", {"device_id": device_id})
Expand Down
12 changes: 7 additions & 5 deletions ovos_PHAL_plugin_homeassistant/logic/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@


class HomeAssistantConnector:
def __init__(self, host, api_key):
def __init__(self, host, api_key, assist_only=True):
"""Constructor
Args:
host (str): The host of the home assistant instance.
api_key (str): The api key
assist_only (bool): Whether to only pull entities exposed to Assist. Default True.
"""
self.host = host
self.api_key = api_key
self.assist_only = assist_only
self.event_listeners = {}

@abstractmethod
Expand Down Expand Up @@ -131,8 +133,8 @@ def register_callback(self, device_id, callback):


class HomeAssistantRESTConnector(HomeAssistantConnector):
def __init__(self, host, api_key):
super().__init__(host, api_key)
def __init__(self, host, api_key, assist_only):
super().__init__(host, api_key, assist_only)
self.headers = {
"Authorization": "Bearer " + self.api_key,
"content-type": "application/json",
Expand Down Expand Up @@ -306,8 +308,8 @@ def send_assist_command(self, command, arguments={}):


class HomeAssistantWSConnector(HomeAssistantConnector):
def __init__(self, host, api_key):
super().__init__(host, api_key)
def __init__(self, host, api_key, assist_only=True):
super().__init__(host, api_key, assist_only)
if self.host.startswith("http"):
self.host.replace("http", "ws", 1)
self._connection = HomeAssistantClient(self.host, self.api_key)
Expand Down
20 changes: 12 additions & 8 deletions ovos_PHAL_plugin_homeassistant/logic/socketclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@


class HomeAssistantClient:
def __init__(self, url, token):
def __init__(self, url, token, assist_only=True):
self.url = url
self.token = token
self.assist_only = assist_only
self.websocket = None
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
Expand Down Expand Up @@ -149,7 +150,7 @@ async def get_instance(self):
await asyncio.sleep(0.1)
return self

async def build_registries(self):
async def build_registries(self, assist_only: bool):
# First clean the registries
self._device_registry = {}
self._entity_registry = {}
Expand All @@ -168,8 +169,11 @@ async def build_registries(self):
message = await self.response_queue.get()
self.response_queue.task_done()
for item in message["result"]:
item_id = item["entity_id"]
self._entity_registry[item_id] = item
if assist_only and item.get("options", {}).get("conversation", {}).get("should_expose") is False:
LOG.debug(f"{item['entity_id']} is not exposed to Assist, skipping")
else:
item_id = item["entity_id"]
self._entity_registry[item_id] = item

# area registry
await self.send_command("config/area_registry/list")
Expand All @@ -186,7 +190,7 @@ def device_registry(self) -> dict:
"""Return device registry."""
if not self._device_registry:
asyncio.run_coroutine_threadsafe(
self.build_registries(), self.loop)
self.build_registries(assist_only=self.assist_only), self.loop)
LOG.debug("Registry is empty, building registry first.")
return self._device_registry

Expand All @@ -195,7 +199,7 @@ def entity_registry(self) -> dict:
"""Return device registry."""
if not self._entity_registry:
asyncio.run_coroutine_threadsafe(
self.build_registries(), self.loop)
self.build_registries(assist_only=self.assist_only), self.loop)
LOG.debug("Registry is empty, building registry first.")
return self._entity_registry

Expand All @@ -204,7 +208,7 @@ def area_registry(self) -> dict:
"""Return device registry."""
if not self._area_registry:
asyncio.run_coroutine_threadsafe(
self.build_registries(), self.loop)
self.build_registries(assist_only=self.assist_only), self.loop)
LOG.debug("Registry is empty, building registry first.")
return self._area_registry

Expand Down Expand Up @@ -272,7 +276,7 @@ def get_event_sync(self):

def build_registries_sync(self):
task = asyncio.run_coroutine_threadsafe(
self.build_registries(), self.loop)
self.build_registries(assist_only=self.assist_only), self.loop)
return task.result()

def register_event_listener(self, listener):
Expand Down

0 comments on commit 82dca59

Please sign in to comment.