Fix duplicated discovered homekit devices (#24178)

This commit is contained in:
Jc2k 2019-05-29 23:50:00 +01:00 committed by Paulus Schoutsen
parent 6667138b73
commit 9303a56d8f
4 changed files with 122 additions and 21 deletions

View file

@ -3,6 +3,7 @@
"abort": {
"accessory_not_found_error": "Cannot add pairing as device can no longer be found.",
"already_configured": "Accessory is already configured with this controller.",
"already_in_progress": "Config flow for device is already in progress.",
"already_paired": "This accessory is already paired to another device. Please reset the accessory and try again.",
"ignored_model": "HomeKit support for this model is blocked as a more feature complete native integration is available.",
"invalid_config_entry": "This device is showing as ready to pair but there is already a conflicting config entry for it in Home Assistant that must first be removed.",

View file

@ -131,10 +131,17 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
paired = not status_flags & 0x01
# pylint: disable=unsupported-assignment-operation
self.context['hkid'] = hkid
self.context['title_placeholders'] = {
'name': discovery_info['name'].replace('._hap._tcp.local.', ''),
}
# If multiple HomekitControllerFlowHandler end up getting created
# for the same accessory dont let duplicates hang around
active_flows = self._async_in_progress()
if any(hkid == flow['context']['hkid'] for flow in active_flows):
return self.async_abort(reason='already_in_progress')
# The configuration number increases every time the characteristic map
# needs updating. Some devices use a slightly off-spec name so handle
# both cases.

View file

@ -33,7 +33,8 @@
"ignored_model": "HomeKit support for this model is blocked as a more feature complete native integration is available.",
"already_configured": "Accessory is already configured with this controller.",
"invalid_config_entry": "This device is showing as ready to pair but there is already a conflicting config entry for it in Home Assistant that must first be removed.",
"accessory_not_found_error": "Cannot add pairing as device can no longer be found."
"accessory_not_found_error": "Cannot add pairing as device can no longer be found.",
"already_in_progress": "Config flow for device is already in progress."
}
}
}

View file

@ -47,6 +47,15 @@ def _setup_flow_handler(hass):
return flow
async def _setup_flow_zeroconf(hass, discovery_info):
result = await hass.config_entries.flow.async_init(
'homekit_controller',
context={'source': 'zeroconf'},
data=discovery_info,
)
return result
async def test_discovery_works(hass):
"""Test a device being discovered."""
discovery_info = {
@ -67,7 +76,10 @@ async def test_discovery_works(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -122,7 +134,10 @@ async def test_discovery_works_upper_case(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -175,7 +190,10 @@ async def test_discovery_works_missing_csharp(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -210,6 +228,29 @@ async def test_discovery_works_missing_csharp(hass):
assert result['data'] == pairing.pairing_data
async def test_abort_duplicate_flow(hass):
"""Already paired."""
discovery_info = {
'name': 'TestDevice',
'host': '127.0.0.1',
'port': 8080,
'properties': {
'md': 'TestDevice',
'id': '00:00:00:00:00:00',
'c#': 1,
'sf': 1,
}
}
result = await _setup_flow_zeroconf(hass, discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
result = await _setup_flow_zeroconf(hass, discovery_info)
assert result['type'] == 'abort'
assert result['reason'] == 'already_in_progress'
async def test_pair_already_paired_1(hass):
"""Already paired."""
discovery_info = {
@ -229,7 +270,10 @@ async def test_pair_already_paired_1(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'abort'
assert result['reason'] == 'already_paired'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
async def test_discovery_ignored_model(hass):
@ -251,7 +295,10 @@ async def test_discovery_ignored_model(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'abort'
assert result['reason'] == 'ignored_model'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
async def test_discovery_invalid_config_entry(hass):
@ -280,7 +327,10 @@ async def test_discovery_invalid_config_entry(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# Discovery of a HKID that is in a pairable state but for which there is
# already a config entry - in that case the stale config entry is
@ -314,7 +364,10 @@ async def test_discovery_already_configured(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'abort'
assert result['reason'] == 'already_configured'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
assert conn.async_config_num_changed.call_count == 0
@ -344,7 +397,10 @@ async def test_discovery_already_configured_config_change(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'abort'
assert result['reason'] == 'already_configured'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
assert conn.async_refresh_entity_map.call_args == mock.call(2)
@ -369,7 +425,10 @@ async def test_pair_unable_to_pair(hass):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -406,7 +465,10 @@ async def test_pair_abort_errors_on_start(hass, exception, expected):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device refuses to enter pairing mode
with mock.patch.object(flow.controller, 'start_pairing') as start_pairing:
@ -415,7 +477,10 @@ async def test_pair_abort_errors_on_start(hass, exception, expected):
assert result['type'] == 'abort'
assert result['reason'] == expected
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
@pytest.mark.parametrize("exception,expected", PAIRING_START_FORM_ERRORS)
@ -439,7 +504,10 @@ async def test_pair_form_errors_on_start(hass, exception, expected):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device refuses to enter pairing mode
with mock.patch.object(flow.controller, 'start_pairing') as start_pairing:
@ -448,7 +516,10 @@ async def test_pair_form_errors_on_start(hass, exception, expected):
assert result['type'] == 'form'
assert result['errors']['pairing_code'] == expected
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
@pytest.mark.parametrize("exception,expected", PAIRING_FINISH_ABORT_ERRORS)
@ -472,7 +543,10 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -487,7 +561,10 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected):
})
assert result['type'] == 'abort'
assert result['reason'] == expected
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
@pytest.mark.parametrize("exception,expected", PAIRING_FINISH_FORM_ERRORS)
@ -511,7 +588,10 @@ async def test_pair_form_errors_on_finish(hass, exception, expected):
result = await flow.async_step_zeroconf(discovery_info)
assert result['type'] == 'form'
assert result['step_id'] == 'pair'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
@ -526,7 +606,10 @@ async def test_pair_form_errors_on_finish(hass, exception, expected):
})
assert result['type'] == 'form'
assert result['errors']['pairing_code'] == expected
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
async def test_import_works(hass):
@ -743,7 +826,10 @@ async def test_parse_new_homekit_json(hass):
assert result['type'] == 'create_entry'
assert result['title'] == 'TestDevice'
assert result['data']['AccessoryPairingID'] == '00:00:00:00:00:00'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
async def test_parse_old_homekit_json(hass):
@ -801,7 +887,10 @@ async def test_parse_old_homekit_json(hass):
assert result['type'] == 'create_entry'
assert result['title'] == 'TestDevice'
assert result['data']['AccessoryPairingID'] == '00:00:00:00:00:00'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}
async def test_parse_overlapping_homekit_json(hass):
@ -872,4 +961,7 @@ async def test_parse_overlapping_homekit_json(hass):
assert result['type'] == 'create_entry'
assert result['title'] == 'TestDevice'
assert result['data']['AccessoryPairingID'] == '00:00:00:00:00:00'
assert flow.context == {'title_placeholders': {'name': 'TestDevice'}}
assert flow.context == {
'hkid': '00:00:00:00:00:00',
'title_placeholders': {'name': 'TestDevice'}
}