diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index b368b38820e..81289bc1a9b 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -132,7 +132,11 @@ def async_setup_rpc_entry( climate_ids = [] for id_ in climate_key_ids: climate_ids.append(id_) - + # There are three configuration scenarios for WallDisplay: + # - relay mode (no thermostat) + # - thermostat mode using the internal relay as an actuator + # - thermostat mode using an external (from another device) relay as + # an actuator if is_rpc_thermostat_internal_actuator(coordinator.device.status): # Wall Display relay is used as the thermostat actuator, # we need to remove a switch entity diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 14fec43c58b..81b16d48ab8 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -43,6 +43,7 @@ from .utils import ( is_block_channel_type_light, is_rpc_channel_type_light, is_rpc_thermostat_internal_actuator, + is_rpc_thermostat_mode, ) @@ -140,12 +141,19 @@ def async_setup_rpc_entry( continue if coordinator.model == MODEL_WALL_DISPLAY: - if not is_rpc_thermostat_internal_actuator(coordinator.device.status): - # Wall Display relay is not used as the thermostat actuator, - # we need to remove a climate entity + # There are three configuration scenarios for WallDisplay: + # - relay mode (no thermostat) + # - thermostat mode using the internal relay as an actuator + # - thermostat mode using an external (from another device) relay as + # an actuator + if not is_rpc_thermostat_mode(id_, coordinator.device.status): + # The device is not in thermostat mode, we need to remove a climate + # entity unique_id = f"{coordinator.mac}-thermostat:{id_}" async_remove_shelly_entity(hass, "climate", unique_id) - else: + elif is_rpc_thermostat_internal_actuator(coordinator.device.status): + # The internal relay is an actuator, skip this ID so as not to create + # a switch entity continue switch_ids.append(id_) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index ce98e0d5c12..b7cb2f1476a 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -500,3 +500,8 @@ def async_remove_shelly_rpc_entities( if entity_id := entity_reg.async_get_entity_id(domain, DOMAIN, f"{mac}-{key}"): LOGGER.debug("Removing entity: %s", entity_id) entity_reg.async_remove(entity_id) + + +def is_rpc_thermostat_mode(ident: int, status: dict[str, Any]) -> bool: + """Return True if 'thermostat:' is present in the status.""" + return f"thermostat:{ident}" in status diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index 9fee3468f11..9946dd7640d 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -25,7 +25,12 @@ from homeassistant.components.climate import ( from homeassistant.components.shelly.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState -from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + STATE_ON, + STATE_UNAVAILABLE, +) from homeassistant.core import HomeAssistant, State from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers.device_registry import DeviceRegistry @@ -711,3 +716,36 @@ async def test_wall_display_thermostat_mode( entry = entity_registry.async_get(climate_entity_id) assert entry assert entry.unique_id == "123456789ABC-thermostat:0" + + +async def test_wall_display_thermostat_mode_external_actuator( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test Wall Display in thermostat mode with an external actuator.""" + climate_entity_id = "climate.test_name" + switch_entity_id = "switch.test_switch_0" + + new_status = deepcopy(mock_rpc_device.status) + new_status["sys"]["relay_in_thermostat"] = False + monkeypatch.setattr(mock_rpc_device, "status", new_status) + + await init_integration(hass, 2, model=MODEL_WALL_DISPLAY) + + # the switch entity should be created + state = hass.states.get(switch_entity_id) + assert state + assert state.state == STATE_ON + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 + + # the climate entity should be created + state = hass.states.get(climate_entity_id) + assert state + assert state.state == HVACMode.HEAT + assert len(hass.states.async_entity_ids(CLIMATE_DOMAIN)) == 1 + + entry = entity_registry.async_get(climate_entity_id) + assert entry + assert entry.unique_id == "123456789ABC-thermostat:0" diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py index fe2c4354afc..dd214c8841d 100644 --- a/tests/components/shelly/test_switch.py +++ b/tests/components/shelly/test_switch.py @@ -330,6 +330,7 @@ async def test_wall_display_relay_mode( new_status = deepcopy(mock_rpc_device.status) new_status["sys"]["relay_in_thermostat"] = False + new_status.pop("thermostat:0") monkeypatch.setattr(mock_rpc_device, "status", new_status) await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)