From 3489ea30ddd7ece04553d40e874e81b67fc8bffa Mon Sep 17 00:00:00 2001 From: Dionisis Toulatos <47108480+dionisis2014@users.noreply.github.com> Date: Sat, 12 Apr 2025 20:30:06 +0300 Subject: [PATCH] Fix MQTT device discovery when using node_id (#142784) * Fix device discovery when using node_id * tests --------- Co-authored-by: jbouwh Co-authored-by: Jan Bouwhuis --- homeassistant/components/mqtt/discovery.py | 2 +- tests/components/mqtt/test_discovery.py | 172 ++++++++++++++++++++- 2 files changed, 166 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index a527e712615..4ebdbbb6236 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -254,7 +254,7 @@ def _generate_device_config( comp_config = config[CONF_COMPONENTS] for platform, discover_id in mqtt_data.discovery_already_discovered: ids = discover_id.split(" ") - component_node_id = ids.pop(0) + component_node_id = f"{ids.pop(1)} {ids.pop(0)}" if len(ids) > 2 else ids.pop(0) component_object_id = " ".join(ids) if not ids: continue diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index ee33cbcbaa1..ee559ef4235 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -388,23 +388,181 @@ async def test_only_valid_components( assert not mock_dispatcher_send.called -async def test_correct_config_discovery( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator +@pytest.mark.parametrize( + ("discovery_topic", "discovery_hash"), + [ + ("homeassistant/binary_sensor/bla/config", ("binary_sensor", "bla")), + ("homeassistant/binary_sensor/node/bla/config", ("binary_sensor", "node bla")), + ], + ids=["without_node", "with_node"], +) +async def test_correct_config_discovery_component( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + device_registry: dr.DeviceRegistry, + discovery_topic: str, + discovery_hash: tuple[str, str], ) -> None: """Test sending in correct JSON.""" await mqtt_mock_entry() + config_init = { + "name": "Beer", + "state_topic": "test-topic", + "unique_id": "bla001", + "device": {"identifiers": "0AFFD2", "name": "test_device1"}, + "o": {"name": "foobar"}, + } async_fire_mqtt_message( hass, - "homeassistant/binary_sensor/bla/config", - '{ "name": "Beer", "state_topic": "test-topic" }', + discovery_topic, + json.dumps(config_init), ) await hass.async_block_till_done() - state = hass.states.get("binary_sensor.beer") + state = hass.states.get("binary_sensor.test_device1_beer") assert state is not None - assert state.name == "Beer" - assert ("binary_sensor", "bla") in hass.data["mqtt"].discovery_already_discovered + assert state.name == "test_device1 Beer" + assert discovery_hash in hass.data["mqtt"].discovery_already_discovered + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is not None + assert device_entry.name == "test_device1" + + # Update the device and component + config_update = { + "name": "Milk", + "state_topic": "test-topic", + "unique_id": "bla001", + "device": {"identifiers": "0AFFD2", "name": "test_device2"}, + "o": {"name": "foobar"}, + } + async_fire_mqtt_message( + hass, + discovery_topic, + json.dumps(config_update), + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test_device1_beer") + + assert state is not None + assert state.name == "test_device2 Milk" + assert discovery_hash in hass.data["mqtt"].discovery_already_discovered + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is not None + assert device_entry.name == "test_device2" + + # Remove the device and component + async_fire_mqtt_message( + hass, + discovery_topic, + "", + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test_device1_beer") + + assert state is None + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is None + + +@pytest.mark.parametrize( + ("discovery_topic", "discovery_hash"), + [ + ("homeassistant/device/some_id/config", ("binary_sensor", "some_id bla")), + ( + "homeassistant/device/node_id/some_id/config", + ("binary_sensor", "some_id node_id bla"), + ), + ], + ids=["without_node", "with_node"], +) +async def test_correct_config_discovery_device( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + device_registry: dr.DeviceRegistry, + discovery_topic: str, + discovery_hash: tuple[str, str], +) -> None: + """Test sending in correct JSON.""" + await mqtt_mock_entry() + config_init = { + "cmps": { + "bla": { + "platform": "binary_sensor", + "name": "Beer", + "state_topic": "test-topic", + "unique_id": "bla001", + }, + }, + "device": {"identifiers": "0AFFD2", "name": "test_device1"}, + "o": {"name": "foobar"}, + } + async_fire_mqtt_message( + hass, + discovery_topic, + json.dumps(config_init), + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test_device1_beer") + + assert state is not None + assert state.name == "test_device1 Beer" + assert discovery_hash in hass.data["mqtt"].discovery_already_discovered + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is not None + assert device_entry.name == "test_device1" + + # Update the device and component + config_update = { + "cmps": { + "bla": { + "platform": "binary_sensor", + "name": "Milk", + "state_topic": "test-topic", + "unique_id": "bla001", + }, + }, + "device": {"identifiers": "0AFFD2", "name": "test_device2"}, + "o": {"name": "foobar"}, + } + async_fire_mqtt_message( + hass, + discovery_topic, + json.dumps(config_update), + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test_device1_beer") + + assert state is not None + assert state.name == "test_device2 Milk" + assert discovery_hash in hass.data["mqtt"].discovery_already_discovered + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is not None + assert device_entry.name == "test_device2" + + # Remove the device and component + async_fire_mqtt_message( + hass, + discovery_topic, + "", + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test_device1_beer") + + assert state is None + + device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) + assert device_entry is None @pytest.mark.parametrize(