Add zeroconf support to roomba (#93309)
parent
40c74ad5b7
commit
ab0d35df92
|
@ -10,7 +10,7 @@ from roombapy.getpassword import RoombaPassword
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, core
|
from homeassistant import config_entries, core
|
||||||
from homeassistant.components import dhcp
|
from homeassistant.components import dhcp, zeroconf
|
||||||
from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD
|
from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
@ -85,17 +85,31 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return OptionsFlowHandler(config_entry)
|
return OptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
async def async_step_zeroconf(
|
||||||
|
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle zeroconf discovery."""
|
||||||
|
return await self._async_step_discovery(
|
||||||
|
discovery_info.host, discovery_info.hostname.lower().rstrip(".local.")
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
||||||
"""Handle dhcp discovery."""
|
"""Handle dhcp discovery."""
|
||||||
self._async_abort_entries_match({CONF_HOST: discovery_info.ip})
|
return await self._async_step_discovery(
|
||||||
|
discovery_info.ip, discovery_info.hostname
|
||||||
|
)
|
||||||
|
|
||||||
if not discovery_info.hostname.startswith(("irobot-", "roomba-")):
|
async def _async_step_discovery(self, ip_address: str, hostname: str) -> FlowResult:
|
||||||
|
"""Handle any discovery."""
|
||||||
|
self._async_abort_entries_match({CONF_HOST: ip_address})
|
||||||
|
|
||||||
|
if not hostname.startswith(("irobot-", "roomba-")):
|
||||||
return self.async_abort(reason="not_irobot_device")
|
return self.async_abort(reason="not_irobot_device")
|
||||||
|
|
||||||
self.host = discovery_info.ip
|
self.host = ip_address
|
||||||
self.blid = _async_blid_from_hostname(discovery_info.hostname)
|
self.blid = _async_blid_from_hostname(hostname)
|
||||||
await self.async_set_unique_id(self.blid)
|
await self.async_set_unique_id(self.blid)
|
||||||
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
|
self._abort_if_unique_id_configured(updates={CONF_HOST: ip_address})
|
||||||
|
|
||||||
# Because the hostname is so long some sources may
|
# Because the hostname is so long some sources may
|
||||||
# truncate the hostname since it will be longer than
|
# truncate the hostname since it will be longer than
|
||||||
|
@ -103,7 +117,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
# going for a longer hostname we abort so the user
|
# going for a longer hostname we abort so the user
|
||||||
# does not see two flows if discovery fails.
|
# does not see two flows if discovery fails.
|
||||||
for progress in self._async_in_progress():
|
for progress in self._async_in_progress():
|
||||||
flow_unique_id = progress["context"]["unique_id"]
|
flow_unique_id: str = progress["context"]["unique_id"]
|
||||||
if flow_unique_id.startswith(self.blid):
|
if flow_unique_id.startswith(self.blid):
|
||||||
return self.async_abort(reason="short_blid")
|
return self.async_abort(reason="short_blid")
|
||||||
if self.blid.startswith(flow_unique_id):
|
if self.blid.startswith(flow_unique_id):
|
||||||
|
|
|
@ -24,5 +24,15 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["paho_mqtt", "roombapy"],
|
"loggers": ["paho_mqtt", "roombapy"],
|
||||||
"requirements": ["roombapy==1.6.8"]
|
"requirements": ["roombapy==1.6.8"],
|
||||||
|
"zeroconf": [
|
||||||
|
{
|
||||||
|
"type": "_amzn-alexa._tcp.local.",
|
||||||
|
"name": "irobot-*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "_amzn-alexa._tcp.local.",
|
||||||
|
"name": "roomba-*"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,6 +279,16 @@ ZEROCONF = {
|
||||||
"domain": "apple_tv",
|
"domain": "apple_tv",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"_amzn-alexa._tcp.local.": [
|
||||||
|
{
|
||||||
|
"domain": "roomba",
|
||||||
|
"name": "irobot-*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "roomba",
|
||||||
|
"name": "roomba-*",
|
||||||
|
},
|
||||||
|
],
|
||||||
"_androidtvremote2._tcp.local.": [
|
"_androidtvremote2._tcp.local.": [
|
||||||
{
|
{
|
||||||
"domain": "androidtv_remote",
|
"domain": "androidtv_remote",
|
||||||
|
|
|
@ -5,7 +5,7 @@ import pytest
|
||||||
from roombapy import RoombaConnectionError, RoombaInfo
|
from roombapy import RoombaConnectionError, RoombaInfo
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components import dhcp
|
from homeassistant.components import dhcp, zeroconf
|
||||||
from homeassistant.components.roomba import config_flow
|
from homeassistant.components.roomba import config_flow
|
||||||
from homeassistant.components.roomba.const import CONF_BLID, CONF_CONTINUOUS, DOMAIN
|
from homeassistant.components.roomba.const import CONF_BLID, CONF_CONTINUOUS, DOMAIN
|
||||||
from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_PASSWORD
|
from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_PASSWORD
|
||||||
|
@ -16,16 +16,46 @@ from tests.common import MockConfigEntry
|
||||||
MOCK_IP = "1.2.3.4"
|
MOCK_IP = "1.2.3.4"
|
||||||
VALID_CONFIG = {CONF_HOST: MOCK_IP, CONF_BLID: "BLID", CONF_PASSWORD: "password"}
|
VALID_CONFIG = {CONF_HOST: MOCK_IP, CONF_BLID: "BLID", CONF_PASSWORD: "password"}
|
||||||
|
|
||||||
DHCP_DISCOVERY_DEVICES = [
|
DISCOVERY_DEVICES = [
|
||||||
dhcp.DhcpServiceInfo(
|
(
|
||||||
ip=MOCK_IP,
|
config_entries.SOURCE_DHCP,
|
||||||
macaddress="50:14:79:DD:EE:FF",
|
dhcp.DhcpServiceInfo(
|
||||||
hostname="irobot-blid",
|
ip=MOCK_IP,
|
||||||
|
macaddress="50:14:79:DD:EE:FF",
|
||||||
|
hostname="irobot-blid",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
dhcp.DhcpServiceInfo(
|
(
|
||||||
ip=MOCK_IP,
|
config_entries.SOURCE_DHCP,
|
||||||
macaddress="80:A5:89:DD:EE:FF",
|
dhcp.DhcpServiceInfo(
|
||||||
hostname="roomba-blid",
|
ip=MOCK_IP,
|
||||||
|
macaddress="80:A5:89:DD:EE:FF",
|
||||||
|
hostname="roomba-blid",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
config_entries.SOURCE_ZEROCONF,
|
||||||
|
zeroconf.ZeroconfServiceInfo(
|
||||||
|
host=MOCK_IP,
|
||||||
|
hostname="irobot-blid.local.",
|
||||||
|
name="irobot-blid._amzn-alexa._tcp.local.",
|
||||||
|
type="_amzn-alexa._tcp.local.",
|
||||||
|
port=443,
|
||||||
|
properties={},
|
||||||
|
addresses=[MOCK_IP],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
config_entries.SOURCE_ZEROCONF,
|
||||||
|
zeroconf.ZeroconfServiceInfo(
|
||||||
|
host=MOCK_IP,
|
||||||
|
hostname="roomba-blid.local.",
|
||||||
|
name="roomba-blid._amzn-alexa._tcp.local.",
|
||||||
|
type="_amzn-alexa._tcp.local.",
|
||||||
|
port=443,
|
||||||
|
properties={},
|
||||||
|
addresses=[MOCK_IP],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -625,9 +655,10 @@ async def test_form_user_discovery_and_password_fetch_gets_connection_refused(
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES)
|
@pytest.mark.parametrize("discovery_data", DISCOVERY_DEVICES)
|
||||||
async def test_dhcp_discovery_and_roomba_discovery_finds(
|
async def test_dhcp_discovery_and_roomba_discovery_finds(
|
||||||
hass: HomeAssistant, discovery_data
|
hass: HomeAssistant,
|
||||||
|
discovery_data: tuple[str, dhcp.DhcpServiceInfo | zeroconf.ZeroconfServiceInfo],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we can process the discovery from dhcp and roomba discovery matches the device."""
|
"""Test we can process the discovery from dhcp and roomba discovery matches the device."""
|
||||||
|
|
||||||
|
@ -635,14 +666,15 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(
|
||||||
roomba_connected=True,
|
roomba_connected=True,
|
||||||
master_state={"state": {"reported": {"name": "myroomba"}}},
|
master_state={"state": {"reported": {"name": "myroomba"}}},
|
||||||
)
|
)
|
||||||
|
source, discovery = discovery_data
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.roomba.config_flow.RoombaDiscovery", _mocked_discovery
|
"homeassistant.components.roomba.config_flow.RoombaDiscovery", _mocked_discovery
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_DHCP},
|
context={"source": source},
|
||||||
data=discovery_data,
|
data=discovery,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue