Add zeroconf support to roomba (#93309)

pull/93324/head^2
J. Nick Koston 2023-05-21 09:12:19 -05:00 committed by GitHub
parent 40c74ad5b7
commit ab0d35df92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 22 deletions

View File

@ -10,7 +10,7 @@ from roombapy.getpassword import RoombaPassword
import voluptuous as vol
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.core import callback
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."""
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:
"""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")
self.host = discovery_info.ip
self.blid = _async_blid_from_hostname(discovery_info.hostname)
self.host = ip_address
self.blid = _async_blid_from_hostname(hostname)
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
# 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
# does not see two flows if discovery fails.
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):
return self.async_abort(reason="short_blid")
if self.blid.startswith(flow_unique_id):

View File

@ -24,5 +24,15 @@
"documentation": "https://www.home-assistant.io/integrations/roomba",
"iot_class": "local_push",
"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-*"
}
]
}

View File

@ -279,6 +279,16 @@ ZEROCONF = {
"domain": "apple_tv",
},
],
"_amzn-alexa._tcp.local.": [
{
"domain": "roomba",
"name": "irobot-*",
},
{
"domain": "roomba",
"name": "roomba-*",
},
],
"_androidtvremote2._tcp.local.": [
{
"domain": "androidtv_remote",

View File

@ -5,7 +5,7 @@ import pytest
from roombapy import RoombaConnectionError, RoombaInfo
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.const import CONF_BLID, CONF_CONTINUOUS, DOMAIN
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"
VALID_CONFIG = {CONF_HOST: MOCK_IP, CONF_BLID: "BLID", CONF_PASSWORD: "password"}
DHCP_DISCOVERY_DEVICES = [
dhcp.DhcpServiceInfo(
ip=MOCK_IP,
macaddress="50:14:79:DD:EE:FF",
hostname="irobot-blid",
DISCOVERY_DEVICES = [
(
config_entries.SOURCE_DHCP,
dhcp.DhcpServiceInfo(
ip=MOCK_IP,
macaddress="50:14:79:DD:EE:FF",
hostname="irobot-blid",
),
),
dhcp.DhcpServiceInfo(
ip=MOCK_IP,
macaddress="80:A5:89:DD:EE:FF",
hostname="roomba-blid",
(
config_entries.SOURCE_DHCP,
dhcp.DhcpServiceInfo(
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
@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES)
@pytest.mark.parametrize("discovery_data", DISCOVERY_DEVICES)
async def test_dhcp_discovery_and_roomba_discovery_finds(
hass: HomeAssistant, discovery_data
hass: HomeAssistant,
discovery_data: tuple[str, dhcp.DhcpServiceInfo | zeroconf.ZeroconfServiceInfo],
) -> None:
"""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,
master_state={"state": {"reported": {"name": "myroomba"}}},
)
source, discovery = discovery_data
with patch(
"homeassistant.components.roomba.config_flow.RoombaDiscovery", _mocked_discovery
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=discovery_data,
context={"source": source},
data=discovery,
)
await hass.async_block_till_done()