Create switch entity when no temp sensor installed for my-PV integration (#184124)

This commit is contained in:
rrooggiieerr
2026-10-05 14:14:48 +02:00
committed by GitHub
parent 5514439a9d
commit 2135026f71
3 changed files with 261 additions and 46 deletions
+59 -10
View File
@@ -1,6 +1,8 @@
"""Creates Switch entities for the my-PV Home Assistant integration."""
from typing import Any, override
from typing import Any, Final, override
from my_pv import MyPVDeviceMainMode
from homeassistant.components.switch import (
SwitchDeviceClass,
@@ -14,7 +16,20 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import MyPVConfigEntry
from .const import DOMAIN
from .entity import MyPVSetupEntity
from .entity import MyPVBaseEntity, MyPVSetupEntity
ENTITY_DESCRIPTIONS: Final[dict[str, SwitchEntityDescription]] = {
"bstmode": SwitchEntityDescription(
key="bstmode",
entity_category=EntityCategory.CONFIG,
device_class=SwitchDeviceClass.SWITCH,
translation_key="bstmode",
),
"devmode": SwitchEntityDescription(
key="devmode",
device_class=SwitchDeviceClass.SWITCH,
),
}
async def async_setup_entry(
@@ -24,20 +39,26 @@ async def async_setup_entry(
) -> None:
"""Set up the my-PV switch."""
coordinator = config_entry.runtime_data
entities = []
entities: list[SwitchEntity] = []
config = coordinator.device.get_setup_configuration("bstmode")
if config and config.get("type") == "boolean":
entity_description = SwitchEntityDescription(
key="bstmode",
entity_category=EntityCategory.CONFIG,
device_class=SwitchDeviceClass.SWITCH,
translation_key="bstmode",
)
entities.append(
MyPVSwitch(
coordinator,
entity_description,
ENTITY_DESCRIPTIONS["bstmode"],
coordinator.device.serial_number,
)
)
if (
coordinator.device.supports_main_mode(MyPVDeviceMainMode.HOT_WATER)
and coordinator.device.current_temperature is None
):
entities.append(
MyPVWaterHeaterSwitch(
coordinator,
ENTITY_DESCRIPTIONS["devmode"],
coordinator.device.serial_number,
)
)
@@ -74,3 +95,31 @@ class MyPVSwitch(MyPVSetupEntity, SwitchEntity):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unknown_error"
)
class MyPVWaterHeaterSwitch(MyPVBaseEntity, SwitchEntity):
"""my-PV water heater switch."""
_attr_name = None
@property
@override
def is_on(self) -> bool | None:
"""Return if the switch is on."""
return self.coordinator.device.is_on
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the water heater on."""
if not await self.coordinator.turn_on():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unknown_error"
)
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the water heater off."""
if not await self.coordinator.turn_off():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unknown_error"
)
+201 -35
View File
@@ -17,7 +17,13 @@ from homeassistant.components.switch import (
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
from homeassistant.const import (
ATTR_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
from homeassistant.helpers import entity_registry as er
@@ -37,16 +43,15 @@ def setup_values(setup_values: dict[str, Any]) -> dict[str, Any]:
return {**setup_values, "bstmode": False}
@pytest.mark.usefixtures("mock_my_pv_client")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test successful setup of a switch."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
@@ -56,23 +61,69 @@ async def test_switch(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_water_heater_switch_no_temp_sensor(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
) -> None:
"""Test if a switch for the water heater is created when there is no temperature sensor installed."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("switch.my_pv_ac_elwa_2")
assert state.state == STATE_ON
async def test_water_heater_switch_temp_sensor(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
) -> None:
"""Test that no switch for the water heater is created when there is a temperature sensor installed."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = 30
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("switch.my_pv_ac_elwa_2")
assert state is None
@pytest.mark.parametrize(
("entity", "method", "expected_args"),
[
("switch.my_pv_ac_elwa_2", "turn_on", ()),
("switch.my_pv_ac_elwa_2_boost_mode", "set_setup_value", ("bstmode", True)),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_unavailable_not_connected(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
) -> None:
"""Test if a switch is unavailable when not connected."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
mock_my_pv_client.connected = False
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("switch.my_pv_ac_elwa_2_boost_mode")
state = hass.states.get(entity)
assert state.state == STATE_UNAVAILABLE
@@ -83,7 +134,6 @@ async def test_switch_unavailable_setup_value_none(
mock_my_pv_client: AsyncMock,
) -> None:
"""Test if a switch is unavailable when setup value is None."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
@@ -96,99 +146,156 @@ async def test_switch_unavailable_setup_value_none(
assert state.state == STATE_UNAVAILABLE
@pytest.mark.parametrize(
("entity", "method", "expected_args"),
[
("switch.my_pv_ac_elwa_2", "turn_on", ()),
("switch.my_pv_ac_elwa_2_boost_mode", "set_setup_value", ("bstmode", True)),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_turn_on(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
) -> None:
"""Test setting value."""
"""Test turn on."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
mock_my_pv_client.is_on = False
mock_my_pv_client.get_setup_value = Mock(return_value=False)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
mock_my_pv_client.get_setup_value = Mock(return_value=False)
client_method = getattr(mock_my_pv_client, method)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "switch.my_pv_ac_elwa_2_boost_mode",
ATTR_ENTITY_ID: entity,
},
blocking=True,
)
mock_my_pv_client.set_setup_value.assert_awaited_once_with("bstmode", True)
client_method.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(
("entity", "method", "expected_args"),
[
("switch.my_pv_ac_elwa_2", "turn_off", ()),
("switch.my_pv_ac_elwa_2_boost_mode", "set_setup_value", ("bstmode", False)),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_turn_off(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
) -> None:
"""Test setting value."""
"""Test turn off."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
mock_my_pv_client.is_on = True
mock_my_pv_client.get_setup_value = Mock(return_value=True)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
mock_my_pv_client.get_setup_value = Mock(return_value=True)
client_method = getattr(mock_my_pv_client, method)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: "switch.my_pv_ac_elwa_2_boost_mode",
ATTR_ENTITY_ID: entity,
},
blocking=True,
)
mock_my_pv_client.set_setup_value.assert_awaited_once_with("bstmode", False)
client_method.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(
("entity", "method", "expected_args"),
[
("switch.my_pv_ac_elwa_2", "turn_on", ()),
("switch.my_pv_ac_elwa_2_boost_mode", "set_setup_value", ("bstmode", True)),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_toggle(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
) -> None:
"""Test setting value."""
"""Test toggle."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
mock_my_pv_client.is_on = False
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("switch.my_pv_ac_elwa_2_boost_mode")
assert state.state == "off"
state = hass.states.get(entity)
assert state.state == STATE_OFF
client_method = getattr(mock_my_pv_client, method)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{
ATTR_ENTITY_ID: "switch.my_pv_ac_elwa_2_boost_mode",
ATTR_ENTITY_ID: entity,
},
blocking=True,
)
mock_my_pv_client.set_setup_value.assert_awaited_once_with("bstmode", True)
client_method.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(
("entity", "method", "expected_args"),
[
("switch.my_pv_ac_elwa_2", "turn_on", ()),
("switch.my_pv_ac_elwa_2_boost_mode", "set_setup_value", ("bstmode", True)),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_toggle_returns_false(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
) -> None:
"""Test for HomeAssistantError when set_setup_value returns false."""
"""Test for HomeAssistantError when toggle method returns false."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
mock_my_pv_client.is_on = False
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
mock_my_pv_client.set_setup_value = AsyncMock(return_value=False)
client_method = getattr(mock_my_pv_client, method)
client_method.return_value = False
with (
pytest.raises(HomeAssistantError),
@@ -197,22 +304,74 @@ async def test_switch_toggle_returns_false(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{
ATTR_ENTITY_ID: "switch.my_pv_ac_elwa_2_boost_mode",
ATTR_ENTITY_ID: entity,
},
blocking=True,
)
mock_my_pv_client.set_setup_value.assert_awaited_once_with("bstmode", True)
client_method.assert_awaited_once_with(*expected_args)
state = hass.states.get("switch.my_pv_ac_elwa_2_boost_mode")
assert state.state == "off"
state = hass.states.get(entity)
assert state.state == STATE_OFF
@pytest.mark.parametrize(
("error", "expected_ha_error"),
(
"entity",
"method",
"expected_args",
"error",
"expected_ha_error",
"expected_state",
),
[
(MyPVConnectionError(), HomeAssistantError),
(MyPVAuthenticationError(), ConfigEntryAuthFailed),
(MyPVTooManyRequestsError(), HomeAssistantError),
(
"switch.my_pv_ac_elwa_2",
"turn_off",
(),
MyPVConnectionError(),
HomeAssistantError,
STATE_ON,
),
(
"switch.my_pv_ac_elwa_2",
"turn_off",
(),
MyPVAuthenticationError(),
ConfigEntryAuthFailed,
STATE_ON,
),
(
"switch.my_pv_ac_elwa_2",
"turn_off",
(),
MyPVTooManyRequestsError(),
HomeAssistantError,
STATE_ON,
),
(
"switch.my_pv_ac_elwa_2_boost_mode",
"set_setup_value",
("bstmode", True),
MyPVConnectionError(),
HomeAssistantError,
STATE_OFF,
),
(
"switch.my_pv_ac_elwa_2_boost_mode",
"set_setup_value",
("bstmode", True),
MyPVAuthenticationError(),
ConfigEntryAuthFailed,
STATE_OFF,
),
(
"switch.my_pv_ac_elwa_2_boost_mode",
"set_setup_value",
("bstmode", True),
MyPVTooManyRequestsError(),
HomeAssistantError,
STATE_OFF,
),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@@ -220,17 +379,24 @@ async def test_switch_toggle_raises_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
entity: str,
method: str,
expected_args: tuple,
error: MyPVConnectionError | MyPVAuthenticationError,
expected_ha_error: type[HomeAssistantError],
expected_state: str,
) -> None:
"""Test for HomeAssistantError when set_setup_value raises error."""
"""Test for HomeAssistantError when method raises error."""
with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SWITCH]):
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
mock_my_pv_client.set_setup_value = AsyncMock(side_effect=error)
client_method = getattr(mock_my_pv_client, method)
client_method.side_effect = error
with (
pytest.raises(expected_ha_error),
@@ -238,10 +404,10 @@ async def test_switch_toggle_raises_error(
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{ATTR_ENTITY_ID: "switch.my_pv_ac_elwa_2_boost_mode"},
{ATTR_ENTITY_ID: entity},
blocking=True,
)
mock_my_pv_client.set_setup_value.assert_awaited_once_with("bstmode", True)
client_method.assert_awaited_once_with(*expected_args)
state = hass.states.get("switch.my_pv_ac_elwa_2_boost_mode")
assert state.state == "off"
state = hass.states.get(entity)
assert state.state == expected_state
+1 -1
View File
@@ -56,7 +56,7 @@ async def test_water_heater_no_temp_sensor(
mock_config_entry: MockConfigEntry,
mock_my_pv_client: AsyncMock,
) -> None:
"""Test if a water_heater is not created when there is no temperature sensor connected."""
"""Test if a water_heater is not created when there is no temperature sensor installed."""
mock_config_entry.add_to_hass(hass)
mock_my_pv_client.current_temperature = None