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:
parent
f6b6e2f7d1
commit
16cc4aed06
2 changed files with 84 additions and 11 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue