Fix climate entity creation when Shelly WallDisplay uses external relay as actuator (#115216)
* Fix climate entity creation when Shelly WallDisplay uses external relay as actuator * More comments * Wrap condition into function --------- Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
This commit is contained in:
parent
b3124aa7ed
commit
51bceb1c99
5 changed files with 62 additions and 6 deletions
|
@ -132,7 +132,11 @@ def async_setup_rpc_entry(
|
||||||
climate_ids = []
|
climate_ids = []
|
||||||
for id_ in climate_key_ids:
|
for id_ in climate_key_ids:
|
||||||
climate_ids.append(id_)
|
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):
|
if is_rpc_thermostat_internal_actuator(coordinator.device.status):
|
||||||
# Wall Display relay is used as the thermostat actuator,
|
# Wall Display relay is used as the thermostat actuator,
|
||||||
# we need to remove a switch entity
|
# we need to remove a switch entity
|
||||||
|
|
|
@ -43,6 +43,7 @@ from .utils import (
|
||||||
is_block_channel_type_light,
|
is_block_channel_type_light,
|
||||||
is_rpc_channel_type_light,
|
is_rpc_channel_type_light,
|
||||||
is_rpc_thermostat_internal_actuator,
|
is_rpc_thermostat_internal_actuator,
|
||||||
|
is_rpc_thermostat_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -140,12 +141,19 @@ def async_setup_rpc_entry(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if coordinator.model == MODEL_WALL_DISPLAY:
|
if coordinator.model == MODEL_WALL_DISPLAY:
|
||||||
if not is_rpc_thermostat_internal_actuator(coordinator.device.status):
|
# There are three configuration scenarios for WallDisplay:
|
||||||
# Wall Display relay is not used as the thermostat actuator,
|
# - relay mode (no thermostat)
|
||||||
# we need to remove a climate entity
|
# - 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_}"
|
unique_id = f"{coordinator.mac}-thermostat:{id_}"
|
||||||
async_remove_shelly_entity(hass, "climate", unique_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
|
continue
|
||||||
|
|
||||||
switch_ids.append(id_)
|
switch_ids.append(id_)
|
||||||
|
|
|
@ -500,3 +500,8 @@ def async_remove_shelly_rpc_entities(
|
||||||
if entity_id := entity_reg.async_get_entity_id(domain, DOMAIN, f"{mac}-{key}"):
|
if entity_id := entity_reg.async_get_entity_id(domain, DOMAIN, f"{mac}-{key}"):
|
||||||
LOGGER.debug("Removing entity: %s", entity_id)
|
LOGGER.debug("Removing entity: %s", entity_id)
|
||||||
entity_reg.async_remove(entity_id)
|
entity_reg.async_remove(entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
def is_rpc_thermostat_mode(ident: int, status: dict[str, Any]) -> bool:
|
||||||
|
"""Return True if 'thermostat:<IDent>' is present in the status."""
|
||||||
|
return f"thermostat:{ident}" in status
|
||||||
|
|
|
@ -25,7 +25,12 @@ from homeassistant.components.climate import (
|
||||||
from homeassistant.components.shelly.const import DOMAIN
|
from homeassistant.components.shelly.const import DOMAIN
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
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.core import HomeAssistant, State
|
||||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
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)
|
entry = entity_registry.async_get(climate_entity_id)
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.unique_id == "123456789ABC-thermostat:0"
|
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"
|
||||||
|
|
|
@ -330,6 +330,7 @@ async def test_wall_display_relay_mode(
|
||||||
|
|
||||||
new_status = deepcopy(mock_rpc_device.status)
|
new_status = deepcopy(mock_rpc_device.status)
|
||||||
new_status["sys"]["relay_in_thermostat"] = False
|
new_status["sys"]["relay_in_thermostat"] = False
|
||||||
|
new_status.pop("thermostat:0")
|
||||||
monkeypatch.setattr(mock_rpc_device, "status", new_status)
|
monkeypatch.setattr(mock_rpc_device, "status", new_status)
|
||||||
|
|
||||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue