Skip to content

Commit

Permalink
Add support for USB discovery to zwave_js (home-assistant#54938)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Hjelmare <[email protected]>
  • Loading branch information
bdraco and MartinHjelmare authored Aug 21, 2021
1 parent 33f6601 commit a7d8e2b
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 7 deletions.
73 changes: 70 additions & 3 deletions homeassistant/components/zwave_js/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
from zwave_js_server.version import VersionInfo, get_server_version

from homeassistant import config_entries, exceptions
from homeassistant.components import usb
from homeassistant.components.hassio import is_hassio
from homeassistant.const import CONF_URL
from homeassistant.const import CONF_NAME, CONF_URL
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import (
AbortFlow,
Expand Down Expand Up @@ -286,6 +287,7 @@ def __init__(self) -> None:
"""Set up flow instance."""
super().__init__()
self.use_addon = False
self._title: str | None = None

@property
def flow_manager(self) -> config_entries.ConfigEntriesFlowManager:
Expand All @@ -309,6 +311,64 @@ async def async_step_user(

return await self.async_step_manual()

async def async_step_usb(self, discovery_info: dict[str, str]) -> FlowResult:
"""Handle USB Discovery."""
if not is_hassio(self.hass):
return self.async_abort(reason="discovery_requires_supervisor")
if self._async_current_entries():
return self.async_abort(reason="already_configured")
if self._async_in_progress():
return self.async_abort(reason="already_in_progress")

vid = discovery_info["vid"]
pid = discovery_info["pid"]
serial_number = discovery_info["serial_number"]
device = discovery_info["device"]
manufacturer = discovery_info["manufacturer"]
description = discovery_info["description"]
# The Nortek sticks are a special case since they
# have a Z-Wave and a Zigbee radio. We need to reject
# the Zigbee radio.
if vid == "10C4" and pid == "8A2A" and "Z-Wave" not in description:
return self.async_abort(reason="not_zwave_device")
# Zooz uses this vid/pid, but so do 2652 sticks
if vid == "10C4" and pid == "EA60" and "2652" in description:
return self.async_abort(reason="not_zwave_device")

addon_info = await self._async_get_addon_info()
if addon_info.state not in (AddonState.NOT_INSTALLED, AddonState.NOT_RUNNING):
return self.async_abort(reason="already_configured")

await self.async_set_unique_id(
f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
)
self._abort_if_unique_id_configured()
dev_path = await self.hass.async_add_executor_job(usb.get_serial_by_id, device)
self.usb_path = dev_path
self._title = usb.human_readable_device_name(
dev_path,
serial_number,
manufacturer,
description,
vid,
pid,
)
self.context["title_placeholders"] = {CONF_NAME: self._title}
return await self.async_step_usb_confirm()

async def async_step_usb_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle USB Discovery confirmation."""
if user_input is None:
return self.async_show_form(
step_id="usb_confirm",
description_placeholders={CONF_NAME: self._title},
data_schema=vol.Schema({}),
)

return await self.async_step_on_supervisor({CONF_USE_ADDON: True})

async def async_step_manual(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
Expand Down Expand Up @@ -352,6 +412,9 @@ async def async_step_hassio(self, discovery_info: dict[str, Any]) -> FlowResult:
This flow is triggered by the Z-Wave JS add-on.
"""
if self._async_in_progress():
return self.async_abort(reason="already_in_progress")

self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"
try:
version_info = await async_get_version_info(self.hass, self.ws_address)
Expand Down Expand Up @@ -422,7 +485,7 @@ async def async_step_configure_addon(

return await self.async_step_start_addon()

usb_path = addon_config.get(CONF_ADDON_DEVICE, self.usb_path or "")
usb_path = addon_config.get(CONF_ADDON_DEVICE) or self.usb_path or ""
network_key = addon_config.get(CONF_ADDON_NETWORK_KEY, self.network_key or "")

data_schema = vol.Schema(
Expand All @@ -446,7 +509,7 @@ async def async_step_finish_addon_setup(
discovery_info = await self._async_get_addon_discovery_info()
self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"

if not self.unique_id:
if not self.unique_id or self.context["source"] == config_entries.SOURCE_USB:
if not self.version_info:
try:
self.version_info = await async_get_version_info(
Expand All @@ -471,6 +534,10 @@ async def async_step_finish_addon_setup(
@callback
def _async_create_entry_from_vars(self) -> FlowResult:
"""Return a config entry for the flow."""
# Abort any other flows that may be in progress
for progress in self._async_in_progress():
self.hass.config_entries.flow.async_abort(progress["flow_id"])

return self.async_create_entry(
title=TITLE,
data={
Expand Down
9 changes: 7 additions & 2 deletions homeassistant/components/zwave_js/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"documentation": "https://www.home-assistant.io/integrations/zwave_js",
"requirements": ["zwave-js-server-python==0.29.0"],
"codeowners": ["@home-assistant/z-wave"],
"dependencies": ["http", "websocket_api"],
"iot_class": "local_push"
"dependencies": ["usb", "http", "websocket_api"],
"iot_class": "local_push",
"usb": [
{"vid":"0658","pid":"0200"},
{"vid":"10C4","pid":"8A2A"},
{"vid":"10C4","pid":"EA60"}
]
}
8 changes: 7 additions & 1 deletion homeassistant/components/zwave_js/strings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"config": {
"flow_title": "{name}",
"step": {
"manual": {
"data": {
"url": "[%key:common::config_flow::data::url%]"
}
},
"usb_confirm": {
"description": "Do you want to setup {name} with the Z-Wave JS add-on?"
},
"on_supervisor": {
"title": "Select connection method",
"description": "Do you want to use the Z-Wave JS Supervisor add-on?",
Expand Down Expand Up @@ -44,7 +48,9 @@
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
"addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"discovery_requires_supervisor": "Discovery requires the supervisor.",
"not_zwave_device": "Discovered device is not a Z-Wave device."
},
"progress": {
"install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes.",
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/zwave_js/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
"already_configured": "Device is already configured",
"already_in_progress": "Configuration flow is already in progress",
"cannot_connect": "Failed to connect"
"cannot_connect": "Failed to connect",
"discovery_requires_supervisor": "Discovery requires the supervisor.",
"not_zwave_device": "Discovered device is not a Z-Wave device."
},
"error": {
"addon_start_failed": "Failed to start the Z-Wave JS add-on. Check the configuration.",
"cannot_connect": "Failed to connect",
"invalid_ws_url": "Invalid websocket URL",
"unknown": "Unexpected error"
},
"flow_title": "{name}",
"progress": {
"install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes.",
"start_addon": "Please wait while the Z-Wave JS add-on start completes. This may take some seconds."
Expand Down Expand Up @@ -48,6 +51,9 @@
},
"start_addon": {
"title": "The Z-Wave JS add-on is starting."
},
"usb_confirm": {
"description": "Do you want to setup {name} with the Z-Wave JS add-on?"
}
}
},
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/generated/usb.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,20 @@
"domain": "zha",
"vid": "10C4",
"pid": "8A2A"
},
{
"domain": "zwave_js",
"vid": "0658",
"pid": "0200"
},
{
"domain": "zwave_js",
"vid": "10C4",
"pid": "8A2A"
},
{
"domain": "zwave_js",
"vid": "10C4",
"pid": "EA60"
}
]
Loading

0 comments on commit a7d8e2b

Please sign in to comment.