mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Add ozw add-on discovery and mqtt client (#43838)
This commit is contained in:
parent
8efa9c5097
commit
9043b7b214
@ -2,6 +2,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ from homeassistant.util.dt import utcnow
|
|||||||
|
|
||||||
from .addon_panel import async_setup_addon_panel
|
from .addon_panel import async_setup_addon_panel
|
||||||
from .auth import async_setup_auth_view
|
from .auth import async_setup_auth_view
|
||||||
|
from .const import ATTR_DISCOVERY
|
||||||
from .discovery import async_setup_discovery_view
|
from .discovery import async_setup_discovery_view
|
||||||
from .handler import HassIO, HassioAPIError, api_data
|
from .handler import HassIO, HassioAPIError, api_data
|
||||||
from .http import HassIOView
|
from .http import HassIOView
|
||||||
@ -200,6 +202,17 @@ async def async_set_addon_options(
|
|||||||
return await hassio.send_command(command, payload=options)
|
return await hassio.send_command(command, payload=options)
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
async def async_get_addon_discovery_info(
|
||||||
|
hass: HomeAssistantType, slug: str
|
||||||
|
) -> Optional[dict]:
|
||||||
|
"""Return discovery data for an add-on."""
|
||||||
|
hassio = hass.data[DOMAIN]
|
||||||
|
data = await hassio.retrieve_discovery_messages()
|
||||||
|
discovered_addons = data[ATTR_DISCOVERY]
|
||||||
|
return next((addon for addon in discovered_addons if addon["addon"] == slug), None)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def get_info(hass):
|
def get_info(hass):
|
||||||
|
@ -17,22 +17,25 @@ from openzwavemqtt.const import (
|
|||||||
)
|
)
|
||||||
from openzwavemqtt.models.node import OZWNode
|
from openzwavemqtt.models.node import OZWNode
|
||||||
from openzwavemqtt.models.value import OZWValue
|
from openzwavemqtt.models.value import OZWValue
|
||||||
|
from openzwavemqtt.util.mqtt_client import MQTTClient
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import mqtt
|
from homeassistant.components import mqtt
|
||||||
from homeassistant.components.hassio.handler import HassioAPIError
|
from homeassistant.components.hassio.handler import HassioAPIError
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg
|
from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from . import const
|
from . import const
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_INTEGRATION_CREATED_ADDON,
|
CONF_INTEGRATION_CREATED_ADDON,
|
||||||
|
CONF_USE_ADDON,
|
||||||
DATA_UNSUBSCRIBE,
|
DATA_UNSUBSCRIBE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MANAGER,
|
MANAGER,
|
||||||
OPTIONS,
|
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
TOPIC_OPENZWAVE,
|
TOPIC_OPENZWAVE,
|
||||||
)
|
)
|
||||||
@ -50,13 +53,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||||
DATA_DEVICES = "zwave-mqtt-devices"
|
DATA_DEVICES = "zwave-mqtt-devices"
|
||||||
|
DATA_STOP_MQTT_CLIENT = "ozw_stop_mqtt_client"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict):
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
"""Initialize basic config of ozw component."""
|
"""Initialize basic config of ozw component."""
|
||||||
if "mqtt" not in hass.config.components:
|
|
||||||
_LOGGER.error("MQTT integration is not set up")
|
|
||||||
return False
|
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -69,16 +70,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
data_nodes = {}
|
data_nodes = {}
|
||||||
data_values = {}
|
data_values = {}
|
||||||
removed_nodes = []
|
removed_nodes = []
|
||||||
|
manager_options = {"topic_prefix": f"{TOPIC_OPENZWAVE}/"}
|
||||||
|
|
||||||
|
if entry.unique_id is None:
|
||||||
|
hass.config_entries.async_update_entry(entry, unique_id=DOMAIN)
|
||||||
|
|
||||||
|
if entry.data.get(CONF_USE_ADDON):
|
||||||
|
# Do not use MQTT integration. Use own MQTT client.
|
||||||
|
# Retrieve discovery info from the OpenZWave add-on.
|
||||||
|
discovery_info = await hass.components.hassio.async_get_addon_discovery_info(
|
||||||
|
"core_zwave"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not discovery_info:
|
||||||
|
_LOGGER.error("Failed to get add-on discovery info")
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
discovery_info_config = discovery_info["config"]
|
||||||
|
|
||||||
|
host = discovery_info_config["host"]
|
||||||
|
port = discovery_info_config["port"]
|
||||||
|
username = discovery_info_config["username"]
|
||||||
|
password = discovery_info_config["password"]
|
||||||
|
mqtt_client = MQTTClient(host, port, username=username, password=password)
|
||||||
|
manager_options["send_message"] = mqtt_client.send_message
|
||||||
|
|
||||||
|
else:
|
||||||
|
if "mqtt" not in hass.config.components:
|
||||||
|
_LOGGER.error("MQTT integration is not set up")
|
||||||
|
return False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def send_message(topic, payload):
|
def send_message(topic, payload):
|
||||||
mqtt.async_publish(hass, topic, json.dumps(payload))
|
mqtt.async_publish(hass, topic, json.dumps(payload))
|
||||||
|
|
||||||
options = OZWOptions(send_message=send_message, topic_prefix=f"{TOPIC_OPENZWAVE}/")
|
manager_options["send_message"] = send_message
|
||||||
|
|
||||||
|
options = OZWOptions(**manager_options)
|
||||||
manager = OZWManager(options)
|
manager = OZWManager(options)
|
||||||
|
|
||||||
hass.data[DOMAIN][MANAGER] = manager
|
hass.data[DOMAIN][MANAGER] = manager
|
||||||
hass.data[DOMAIN][OPTIONS] = options
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_node_added(node):
|
def async_node_added(node):
|
||||||
@ -234,9 +265,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
for component in PLATFORMS
|
for component in PLATFORMS
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
if entry.data.get(CONF_USE_ADDON):
|
||||||
|
mqtt_client_task = asyncio.create_task(mqtt_client.start_client(manager))
|
||||||
|
|
||||||
|
async def async_stop_mqtt_client(event=None):
|
||||||
|
"""Stop the mqtt client.
|
||||||
|
|
||||||
|
Do not unsubscribe the manager topic.
|
||||||
|
"""
|
||||||
|
mqtt_client_task.cancel()
|
||||||
|
try:
|
||||||
|
await mqtt_client_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_mqtt_client)
|
||||||
|
ozw_data[DATA_STOP_MQTT_CLIENT] = async_stop_mqtt_client
|
||||||
|
|
||||||
|
else:
|
||||||
ozw_data[DATA_UNSUBSCRIBE].append(
|
ozw_data[DATA_UNSUBSCRIBE].append(
|
||||||
await mqtt.async_subscribe(
|
await mqtt.async_subscribe(
|
||||||
hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message
|
hass, f"{manager.options.topic_prefix}#", async_receive_message
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -262,6 +311,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
# unsubscribe all listeners
|
# unsubscribe all listeners
|
||||||
for unsubscribe_listener in hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE]:
|
for unsubscribe_listener in hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE]:
|
||||||
unsubscribe_listener()
|
unsubscribe_listener()
|
||||||
|
|
||||||
|
if entry.data.get(CONF_USE_ADDON):
|
||||||
|
async_stop_mqtt_client = hass.data[DOMAIN][entry.entry_id][
|
||||||
|
DATA_STOP_MQTT_CLIENT
|
||||||
|
]
|
||||||
|
await async_stop_mqtt_client()
|
||||||
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant import config_entries
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import AbortFlow
|
from homeassistant.data_entry_flow import AbortFlow
|
||||||
|
|
||||||
from .const import CONF_INTEGRATION_CREATED_ADDON
|
from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON
|
||||||
from .const import DOMAIN # pylint:disable=unused-import
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -16,7 +16,6 @@ CONF_ADDON_DEVICE = "device"
|
|||||||
CONF_ADDON_NETWORK_KEY = "network_key"
|
CONF_ADDON_NETWORK_KEY = "network_key"
|
||||||
CONF_NETWORK_KEY = "network_key"
|
CONF_NETWORK_KEY = "network_key"
|
||||||
CONF_USB_PATH = "usb_path"
|
CONF_USB_PATH = "usb_path"
|
||||||
CONF_USE_ADDON = "use_addon"
|
|
||||||
TITLE = "OpenZWave"
|
TITLE = "OpenZWave"
|
||||||
|
|
||||||
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=False): bool})
|
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=False): bool})
|
||||||
@ -43,17 +42,36 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if self._async_current_entries():
|
if self._async_current_entries():
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
# Currently all flow results need the MQTT integration.
|
# Set a unique_id to make sure discovery flow is aborted on progress.
|
||||||
# This will change when we have the direct MQTT client connection.
|
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
|
||||||
# When that is implemented, move this check to _async_use_mqtt_integration.
|
|
||||||
if "mqtt" not in self.hass.config.components:
|
|
||||||
return self.async_abort(reason="mqtt_required")
|
|
||||||
|
|
||||||
if not self.hass.components.hassio.is_hassio():
|
if not self.hass.components.hassio.is_hassio():
|
||||||
return self._async_use_mqtt_integration()
|
return self._async_use_mqtt_integration()
|
||||||
|
|
||||||
return await self.async_step_on_supervisor()
|
return await self.async_step_on_supervisor()
|
||||||
|
|
||||||
|
async def async_step_hassio(self, discovery_info):
|
||||||
|
"""Receive configuration from add-on discovery info.
|
||||||
|
|
||||||
|
This flow is triggered by the OpenZWave add-on.
|
||||||
|
"""
|
||||||
|
await self.async_set_unique_id(DOMAIN)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
addon_config = await self._async_get_addon_config()
|
||||||
|
self.usb_path = addon_config[CONF_ADDON_DEVICE]
|
||||||
|
self.network_key = addon_config.get(CONF_ADDON_NETWORK_KEY, "")
|
||||||
|
|
||||||
|
return await self.async_step_hassio_confirm()
|
||||||
|
|
||||||
|
async def async_step_hassio_confirm(self, user_input=None):
|
||||||
|
"""Confirm the add-on discovery."""
|
||||||
|
if user_input is not None:
|
||||||
|
self.use_addon = True
|
||||||
|
return self._async_create_entry_from_vars()
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="hassio_confirm")
|
||||||
|
|
||||||
def _async_create_entry_from_vars(self):
|
def _async_create_entry_from_vars(self):
|
||||||
"""Return a config entry for the flow."""
|
"""Return a config entry for the flow."""
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
@ -73,6 +91,8 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
This is the entry point for the logic that is needed
|
This is the entry point for the logic that is needed
|
||||||
when this integration will depend on the MQTT integration.
|
when this integration will depend on the MQTT integration.
|
||||||
"""
|
"""
|
||||||
|
if "mqtt" not in self.hass.config.components:
|
||||||
|
return self.async_abort(reason="mqtt_required")
|
||||||
return self._async_create_entry_from_vars()
|
return self._async_create_entry_from_vars()
|
||||||
|
|
||||||
async def async_step_on_supervisor(self, user_input=None):
|
async def async_step_on_supervisor(self, user_input=None):
|
||||||
|
@ -12,6 +12,7 @@ DOMAIN = "ozw"
|
|||||||
DATA_UNSUBSCRIBE = "unsubscribe"
|
DATA_UNSUBSCRIBE = "unsubscribe"
|
||||||
|
|
||||||
CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon"
|
CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon"
|
||||||
|
CONF_USE_ADDON = "use_addon"
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
BINARY_SENSOR_DOMAIN,
|
BINARY_SENSOR_DOMAIN,
|
||||||
@ -24,7 +25,6 @@ PLATFORMS = [
|
|||||||
SWITCH_DOMAIN,
|
SWITCH_DOMAIN,
|
||||||
]
|
]
|
||||||
MANAGER = "manager"
|
MANAGER = "manager"
|
||||||
OPTIONS = "options"
|
|
||||||
|
|
||||||
# MQTT Topics
|
# MQTT Topics
|
||||||
TOPIC_OPENZWAVE = "OpenZWave"
|
TOPIC_OPENZWAVE = "OpenZWave"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ozw",
|
"documentation": "https://www.home-assistant.io/integrations/ozw",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-openzwave-mqtt==1.3.2"
|
"python-openzwave-mqtt[mqtt-client]==1.4.0"
|
||||||
],
|
],
|
||||||
"after_dependencies": [
|
"after_dependencies": [
|
||||||
"mqtt"
|
"mqtt"
|
||||||
|
@ -11,10 +11,18 @@
|
|||||||
},
|
},
|
||||||
"start_addon": {
|
"start_addon": {
|
||||||
"title": "Enter the OpenZWave add-on configuration",
|
"title": "Enter the OpenZWave add-on configuration",
|
||||||
"data": {"usb_path": "[%key:common::config_flow::data::usb_path%]", "network_key": "Network Key"}
|
"data": {
|
||||||
|
"usb_path": "[%key:common::config_flow::data::usb_path%]",
|
||||||
|
"network_key": "Network Key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hassio_confirm": {
|
||||||
|
"title": "Set up OpenZWave integration with the OpenZWave add-on"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"addon_info_failed": "Failed to get OpenZWave add-on info.",
|
"addon_info_failed": "Failed to get OpenZWave add-on info.",
|
||||||
"addon_install_failed": "Failed to install the OpenZWave add-on.",
|
"addon_install_failed": "Failed to install the OpenZWave add-on.",
|
||||||
"addon_set_config_failed": "Failed to set OpenZWave configuration.",
|
"addon_set_config_failed": "Failed to set OpenZWave configuration.",
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
"addon_info_failed": "Failed to get OpenZWave add-on info.",
|
"addon_info_failed": "Failed to get OpenZWave add-on info.",
|
||||||
"addon_install_failed": "Failed to install the OpenZWave add-on.",
|
"addon_install_failed": "Failed to install the OpenZWave add-on.",
|
||||||
"addon_set_config_failed": "Failed to set OpenZWave configuration.",
|
"addon_set_config_failed": "Failed to set OpenZWave configuration.",
|
||||||
|
"already_configured": "Device is already configured",
|
||||||
|
"already_in_progress": "Configuration flow is already in progress",
|
||||||
"mqtt_required": "The MQTT integration is not set up",
|
"mqtt_required": "The MQTT integration is not set up",
|
||||||
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
},
|
},
|
||||||
@ -14,6 +16,9 @@
|
|||||||
"install_addon": "Please wait while the OpenZWave add-on installation finishes. This can take several minutes."
|
"install_addon": "Please wait while the OpenZWave add-on installation finishes. This can take several minutes."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
|
"hassio_confirm": {
|
||||||
|
"title": "Set up OpenZWave integration with the OpenZWave add-on"
|
||||||
|
},
|
||||||
"install_addon": {
|
"install_addon": {
|
||||||
"title": "The OpenZWave add-on installation has started"
|
"title": "The OpenZWave add-on installation has started"
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.components import websocket_api
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from .const import ATTR_CONFIG_PARAMETER, ATTR_CONFIG_VALUE, DOMAIN, MANAGER, OPTIONS
|
from .const import ATTR_CONFIG_PARAMETER, ATTR_CONFIG_VALUE, DOMAIN, MANAGER
|
||||||
from .lock import ATTR_USERCODE
|
from .lock import ATTR_USERCODE
|
||||||
|
|
||||||
TYPE = "type"
|
TYPE = "type"
|
||||||
@ -461,7 +461,7 @@ def websocket_refresh_node_info(hass, connection, msg):
|
|||||||
"""Tell OpenZWave to re-interview a node."""
|
"""Tell OpenZWave to re-interview a node."""
|
||||||
|
|
||||||
manager = hass.data[DOMAIN][MANAGER]
|
manager = hass.data[DOMAIN][MANAGER]
|
||||||
options = hass.data[DOMAIN][OPTIONS]
|
options = manager.options
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def forward_node(node):
|
def forward_node(node):
|
||||||
|
@ -1794,7 +1794,7 @@ python-nest==4.1.0
|
|||||||
python-nmap==0.6.1
|
python-nmap==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt==1.3.2
|
python-openzwave-mqtt[mqtt-client]==1.4.0
|
||||||
|
|
||||||
# homeassistant.components.qbittorrent
|
# homeassistant.components.qbittorrent
|
||||||
python-qbittorrent==0.4.1
|
python-qbittorrent==0.4.1
|
||||||
|
@ -884,7 +884,7 @@ python-miio==0.5.4
|
|||||||
python-nest==4.1.0
|
python-nest==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.ozw
|
# homeassistant.components.ozw
|
||||||
python-openzwave-mqtt==1.3.2
|
python-openzwave-mqtt[mqtt-client]==1.4.0
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.12
|
python-songpal==0.12
|
||||||
|
@ -253,3 +253,12 @@ def mock_uninstall_addon():
|
|||||||
"homeassistant.components.hassio.async_uninstall_addon"
|
"homeassistant.components.hassio.async_uninstall_addon"
|
||||||
) as uninstall_addon:
|
) as uninstall_addon:
|
||||||
yield uninstall_addon
|
yield uninstall_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="get_addon_discovery_info")
|
||||||
|
def mock_get_addon_discovery_info():
|
||||||
|
"""Mock get add-on discovery info."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hassio.async_get_addon_discovery_info"
|
||||||
|
) as get_addon_discovery_info:
|
||||||
|
yield get_addon_discovery_info
|
||||||
|
@ -9,6 +9,14 @@ from homeassistant.components.ozw.const import DOMAIN
|
|||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
ADDON_DISCOVERY_INFO = {
|
||||||
|
"addon": "OpenZWave",
|
||||||
|
"host": "host1",
|
||||||
|
"port": 1234,
|
||||||
|
"username": "name1",
|
||||||
|
"password": "pass1",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="supervisor")
|
@pytest.fixture(name="supervisor")
|
||||||
def mock_supervisor_fixture():
|
def mock_supervisor_fixture():
|
||||||
@ -44,7 +52,7 @@ def mock_addon_installed(addon_info):
|
|||||||
def mock_addon_options(addon_info):
|
def mock_addon_options(addon_info):
|
||||||
"""Mock add-on options."""
|
"""Mock add-on options."""
|
||||||
addon_info.return_value["options"] = {}
|
addon_info.return_value["options"] = {}
|
||||||
return addon_info
|
return addon_info.return_value["options"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="set_addon_options")
|
@pytest.fixture(name="set_addon_options")
|
||||||
@ -361,3 +369,122 @@ async def test_install_addon_failure(hass, supervisor, addon_installed, install_
|
|||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
assert result["reason"] == "addon_install_failed"
|
assert result["reason"] == "addon_install_failed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_supervisor_discovery(hass, supervisor, addon_running, addon_options):
|
||||||
|
"""Test flow started from Supervisor discovery."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
addon_options["device"] = "/test"
|
||||||
|
addon_options["network_key"] = "abc123"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HASSIO},
|
||||||
|
data=ADDON_DISCOVERY_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ozw.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.ozw.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TITLE
|
||||||
|
assert result["data"] == {
|
||||||
|
"usb_path": "/test",
|
||||||
|
"network_key": "abc123",
|
||||||
|
"use_addon": True,
|
||||||
|
"integration_created_addon": False,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_clean_discovery_on_user_create(
|
||||||
|
hass, supervisor, addon_running, addon_options
|
||||||
|
):
|
||||||
|
"""Test discovery flow is cleaned up when a user flow is finished."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
addon_options["device"] = "/test"
|
||||||
|
addon_options["network_key"] = "abc123"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HASSIO},
|
||||||
|
data=ADDON_DISCOVERY_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ozw.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.ozw.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"use_addon": False}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TITLE
|
||||||
|
assert result["data"] == {
|
||||||
|
"usb_path": None,
|
||||||
|
"network_key": None,
|
||||||
|
"use_addon": False,
|
||||||
|
"integration_created_addon": False,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_discovery_with_user_flow(
|
||||||
|
hass, supervisor, addon_running, addon_options
|
||||||
|
):
|
||||||
|
"""Test discovery flow is aborted if a user flow is in progress."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HASSIO},
|
||||||
|
data=ADDON_DISCOVERY_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_in_progress"
|
||||||
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_discovery_with_existing_entry(
|
||||||
|
hass, supervisor, addon_running, addon_options
|
||||||
|
):
|
||||||
|
"""Test discovery flow is aborted if an entry already exists."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=DOMAIN)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HASSIO},
|
||||||
|
data=ADDON_DISCOVERY_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
@ -5,6 +5,7 @@ from homeassistant.components.ozw import DOMAIN, PLATFORMS, const
|
|||||||
|
|
||||||
from .common import setup_ozw
|
from .common import setup_ozw
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +24,18 @@ async def test_init_entry(hass, generic_data):
|
|||||||
assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE)
|
assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_without_mqtt(hass):
|
||||||
|
"""Test setting up config entry without mqtt integration setup."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="OpenZWave",
|
||||||
|
connection_class=config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def test_unload_entry(hass, generic_data, switch_msg, caplog):
|
async def test_unload_entry(hass, generic_data, switch_msg, caplog):
|
||||||
"""Test unload the config entry."""
|
"""Test unload the config entry."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
@ -128,3 +141,75 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog):
|
|||||||
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
|
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
|
||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||||
assert "Failed to uninstall the OpenZWave add-on" in caplog.text
|
assert "Failed to uninstall the OpenZWave add-on" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_with_addon(hass, get_addon_discovery_info):
|
||||||
|
"""Test set up entry using OpenZWave add-on."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="OpenZWave",
|
||||||
|
connection_class=config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.ozw.MQTTClient", autospec=True) as mock_client:
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_client.return_value.start_client.call_count == 1
|
||||||
|
|
||||||
|
# Verify integration + platform loaded.
|
||||||
|
assert "ozw" in hass.config.components
|
||||||
|
for platform in PLATFORMS:
|
||||||
|
assert platform in hass.config.components, platform
|
||||||
|
assert f"{platform}.{DOMAIN}" in hass.config.components, f"{platform}.{DOMAIN}"
|
||||||
|
|
||||||
|
# Verify services registered
|
||||||
|
assert hass.services.has_service(DOMAIN, const.SERVICE_ADD_NODE)
|
||||||
|
assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_without_addon_info(hass, get_addon_discovery_info):
|
||||||
|
"""Test set up entry using OpenZWave add-on but missing discovery info."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="OpenZWave",
|
||||||
|
connection_class=config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
get_addon_discovery_info.return_value = None
|
||||||
|
|
||||||
|
with patch("homeassistant.components.ozw.MQTTClient", autospec=True) as mock_client:
|
||||||
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
|
||||||
|
assert mock_client.return_value.start_client.call_count == 0
|
||||||
|
assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_entry_with_addon(
|
||||||
|
hass, get_addon_discovery_info, generic_data, switch_msg, caplog
|
||||||
|
):
|
||||||
|
"""Test unload the config entry using the OpenZWave add-on."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="OpenZWave",
|
||||||
|
connection_class=config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
|
||||||
|
|
||||||
|
with patch("homeassistant.components.ozw.MQTTClient", autospec=True) as mock_client:
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_client.return_value.start_client.call_count == 1
|
||||||
|
assert entry.state == config_entries.ENTRY_STATE_LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
|
||||||
|
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
|
||||||
|
Loading…
x
Reference in New Issue
Block a user