Add manufacturer matching support to zeroconf (#48810)
We plan on matching with _airplay which means we need to able to limit to specific manufacturers to avoid generating flows for integrations with the wrong manufacturer
This commit is contained in:
parent
c2d98f1905
commit
493bd4cdca
2 changed files with 99 additions and 1 deletions
|
@ -279,6 +279,13 @@ async def _async_start_zeroconf_browser(
|
||||||
else:
|
else:
|
||||||
uppercase_mac = None
|
uppercase_mac = None
|
||||||
|
|
||||||
|
if "manufacturer" in info["properties"]:
|
||||||
|
lowercase_manufacturer: str | None = info["properties"][
|
||||||
|
"manufacturer"
|
||||||
|
].lower()
|
||||||
|
else:
|
||||||
|
lowercase_manufacturer = None
|
||||||
|
|
||||||
# Not all homekit types are currently used for discovery
|
# Not all homekit types are currently used for discovery
|
||||||
# so not all service type exist in zeroconf_types
|
# so not all service type exist in zeroconf_types
|
||||||
for entry in zeroconf_types.get(service_type, []):
|
for entry in zeroconf_types.get(service_type, []):
|
||||||
|
@ -295,6 +302,14 @@ async def _async_start_zeroconf_browser(
|
||||||
and not fnmatch.fnmatch(lowercase_name, entry["name"])
|
and not fnmatch.fnmatch(lowercase_name, entry["name"])
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
if (
|
||||||
|
lowercase_manufacturer is not None
|
||||||
|
and "manufacturer" in entry
|
||||||
|
and not fnmatch.fnmatch(
|
||||||
|
lowercase_manufacturer, entry["manufacturer"]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
hass.add_job(
|
hass.add_job(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
|
|
|
@ -104,6 +104,24 @@ def get_zeroconf_info_mock(macaddress):
|
||||||
return mock_zc_info
|
return mock_zc_info
|
||||||
|
|
||||||
|
|
||||||
|
def get_zeroconf_info_mock_manufacturer(manufacturer):
|
||||||
|
"""Return info for get_service_info for an zeroconf device."""
|
||||||
|
|
||||||
|
def mock_zc_info(service_type, name):
|
||||||
|
return ServiceInfo(
|
||||||
|
service_type,
|
||||||
|
name,
|
||||||
|
addresses=[b"\n\x00\x00\x14"],
|
||||||
|
port=80,
|
||||||
|
weight=0,
|
||||||
|
priority=0,
|
||||||
|
server="name.local.",
|
||||||
|
properties={b"manufacturer": manufacturer.encode()},
|
||||||
|
)
|
||||||
|
|
||||||
|
return mock_zc_info
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass, mock_zeroconf):
|
async def test_setup(hass, mock_zeroconf):
|
||||||
"""Test configured options for a device are loaded via config entry."""
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
with patch.object(
|
with patch.object(
|
||||||
|
@ -237,7 +255,7 @@ async def test_service_with_invalid_name(hass, mock_zeroconf, caplog):
|
||||||
assert "Failed to get info for device name" in caplog.text
|
assert "Failed to get info for device name" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_match(hass, mock_zeroconf):
|
async def test_zeroconf_match_macaddress(hass, mock_zeroconf):
|
||||||
"""Test configured options for a device are loaded via config entry."""
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
def http_only_service_update_mock(zeroconf, services, handlers):
|
def http_only_service_update_mock(zeroconf, services, handlers):
|
||||||
|
@ -274,6 +292,39 @@ async def test_zeroconf_match(hass, mock_zeroconf):
|
||||||
assert mock_config_flow.mock_calls[0][1][0] == "shelly"
|
assert mock_config_flow.mock_calls[0][1][0] == "shelly"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_match_manufacturer(hass, mock_zeroconf):
|
||||||
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
|
def http_only_service_update_mock(zeroconf, services, handlers):
|
||||||
|
"""Call service update handler."""
|
||||||
|
handlers[0](
|
||||||
|
zeroconf,
|
||||||
|
"_airplay._tcp.local.",
|
||||||
|
"s1000._airplay._tcp.local.",
|
||||||
|
ServiceStateChange.Added,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
zc_gen.ZEROCONF,
|
||||||
|
{"_airplay._tcp.local.": [{"domain": "samsungtv", "manufacturer": "samsung*"}]},
|
||||||
|
clear=True,
|
||||||
|
), patch.object(
|
||||||
|
hass.config_entries.flow, "async_init"
|
||||||
|
) as mock_config_flow, patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock
|
||||||
|
) as mock_service_browser:
|
||||||
|
mock_zeroconf.get_service_info.side_effect = (
|
||||||
|
get_zeroconf_info_mock_manufacturer("Samsung Electronics")
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
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] == "samsungtv"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_no_match(hass, mock_zeroconf):
|
async def test_zeroconf_no_match(hass, mock_zeroconf):
|
||||||
"""Test configured options for a device are loaded via config entry."""
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
|
@ -306,6 +357,38 @@ async def test_zeroconf_no_match(hass, mock_zeroconf):
|
||||||
assert len(mock_config_flow.mock_calls) == 0
|
assert len(mock_config_flow.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_no_match_manufacturer(hass, mock_zeroconf):
|
||||||
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
|
def http_only_service_update_mock(zeroconf, services, handlers):
|
||||||
|
"""Call service update handler."""
|
||||||
|
handlers[0](
|
||||||
|
zeroconf,
|
||||||
|
"_airplay._tcp.local.",
|
||||||
|
"s1000._airplay._tcp.local.",
|
||||||
|
ServiceStateChange.Added,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
zc_gen.ZEROCONF,
|
||||||
|
{"_airplay._tcp.local.": [{"domain": "samsungtv", "manufacturer": "samsung*"}]},
|
||||||
|
clear=True,
|
||||||
|
), patch.object(
|
||||||
|
hass.config_entries.flow, "async_init"
|
||||||
|
) as mock_config_flow, patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock
|
||||||
|
) as mock_service_browser:
|
||||||
|
mock_zeroconf.get_service_info.side_effect = (
|
||||||
|
get_zeroconf_info_mock_manufacturer("Not Samsung Electronics")
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
|
assert len(mock_config_flow.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_homekit_match_partial_space(hass, mock_zeroconf):
|
async def test_homekit_match_partial_space(hass, mock_zeroconf):
|
||||||
"""Test configured options for a device are loaded via config entry."""
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
with patch.dict(
|
with patch.dict(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue