Ensure homekit_controller recieves zeroconf c# updates (#35545)

* Ensure homekit_controller recieves zeroconf c# updates

If an integration has a homekit config flow step
homekit controller would not see updates for
devices that were paired with it and would not
rescan for changes.

* Only send updates to homekit controller if the device is paired

This avoids the device showing up a second time.

* remove debug

* fix refactor error
This commit is contained in:
J. Nick Koston 2020-05-12 14:59:29 -05:00 committed by GitHub
parent f6b6e2f7d1
commit 16cc4aed06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 11 deletions

View file

@ -43,6 +43,10 @@ HOMEKIT_TYPE = "_hap._tcp.local."
CONF_DEFAULT_INTERFACE = "default_interface"
DEFAULT_DEFAULT_INTERFACE = False
HOMEKIT_PROPERTIES = "properties"
HOMEKIT_PAIRED_STATUS_FLAG = "sf"
HOMEKIT_MODEL = "md"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
@ -178,8 +182,26 @@ def setup(hass, config):
_LOGGER.debug("Discovered new device %s %s", name, info)
# If we can handle it as a HomeKit discovery, we do that here.
if service_type == HOMEKIT_TYPE and handle_homekit(hass, info):
return
if service_type == HOMEKIT_TYPE:
handle_homekit(hass, info)
# Continue on here as homekit_controller
# still needs to get updates on devices
# so it can see when the 'c#' field is updated.
#
# We only send updates to homekit_controller
# if the device is already paired in order to avoid
# offering a second discovery for the same device
if (
HOMEKIT_PROPERTIES in info
and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES]
):
try:
if not int(info[HOMEKIT_PROPERTIES][HOMEKIT_PAIRED_STATUS_FLAG]):
return
except ValueError:
# HomeKit pairing status unknown
# likely bad homekit data
return
for domain in ZEROCONF[service_type]:
hass.add_job(
@ -203,10 +225,10 @@ def handle_homekit(hass, info) -> bool:
Return if discovery was forwarded.
"""
model = None
props = info.get("properties", {})
props = info.get(HOMEKIT_PROPERTIES, {})
for key in props:
if key.lower() == "md":
if key.lower() == HOMEKIT_MODEL:
model = props[key]
break

View file

@ -18,6 +18,9 @@ PROPERTIES = {
NON_ASCII_KEY: None,
}
HOMEKIT_STATUS_UNPAIRED = b"0"
HOMEKIT_STATUS_PAIRED = b"1"
@pytest.fixture
def mock_zeroconf():
@ -45,8 +48,8 @@ def get_service_info_mock(service_type, name):
)
def get_homekit_info_mock(model):
"""Return homekit info for get_service_info."""
def get_homekit_info_mock(model, pairing_status):
"""Return homekit info for get_service_info for an homekit device."""
def mock_homekit_info(service_type, name):
return ServiceInfo(
@ -57,7 +60,7 @@ def get_homekit_info_mock(model):
weight=0,
priority=0,
server="name.local.",
properties={b"md": model.encode()},
properties={b"md": model.encode(), b"sf": pairing_status},
)
return mock_homekit_info
@ -119,7 +122,9 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("LIFX bulb")
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"LIFX bulb", HOMEKIT_STATUS_UNPAIRED
)
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
assert len(mock_service_browser.mock_calls) == 1
@ -137,7 +142,7 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"Rachio-fa46ba"
"Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED
)
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -155,12 +160,58 @@ async def test_homekit_match_full(hass, mock_zeroconf):
) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("BSB002")
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"BSB002", HOMEKIT_STATUS_UNPAIRED
)
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
homekit_mock = get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED)
info = homekit_mock("_hap._tcp.local.", "BSB002._hap._tcp.local.")
import pprint
pprint.pprint(["homekit", info])
assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "hue"
async def test_homekit_already_paired(hass, mock_zeroconf):
"""Test that an already paired device is sent to homekit_controller."""
with patch.dict(
zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
), patch.object(
hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"tado", HOMEKIT_STATUS_PAIRED
)
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 2
assert mock_config_flow.mock_calls[0][1][0] == "tado"
assert mock_config_flow.mock_calls[1][1][0] == "homekit_controller"
async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
"""Test that missing paring data is not sent to homekit_controller."""
with patch.dict(
zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
), patch.object(
hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"tado", b"invalid"
)
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "hue"
assert mock_config_flow.mock_calls[0][1][0] == "tado"
async def test_info_from_service_non_utf8(hass):