Fixes for Plugwise Select (#176787)

This commit is contained in:
Bouwe Westerdijk
2026-07-20 09:49:19 +02:00
committed by GitHub
parent a5e196cfbb
commit b589e7e294
5 changed files with 320 additions and 6 deletions
+11 -5
View File
@@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
LOCATION,
SELECT_DHW_MODE,
SELECT_GATEWAY_MODE,
SELECT_REGULATION_MODE,
@@ -106,9 +107,12 @@ class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity):
self._attr_unique_id = f"{device_id}-{entity_description.key}"
self.entity_description = entity_description
self._location = device_id
if (location := self.device.get("location")) is not None:
self._location = location
self._device_or_location = device_id
if (
self.entity_description.key in (SELECT_SCHEDULE, SELECT_ZONE_PROFILE)
and (location := self.device.get(LOCATION)) is not None
):
self._device_or_location = location
@property
@override
@@ -127,8 +131,10 @@ class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change to the selected entity option.
self._location and STATE_ON are required for the thermostat-schedule select.
The appliance ID (= device_id) is required for the dhw_mode select.
The location ID is required for the thermostat schedule and zone_profile selects.
STATE_ON is required for the thermostat schedule select.
"""
await self.coordinator.api.set_select(
self.entity_description.key, self._location, option, STATE_ON
self.entity_description.key, self._device_or_location, option, STATE_ON
)
+28
View File
@@ -268,6 +268,34 @@ def mock_smile_anna(chosen_env: str, cooling_present: bool) -> Generator[MagicMo
yield api
@pytest.fixture
def mock_smile_anna_loria() -> Generator[MagicMock]:
"""Create a Mock Anna-connected-to-Loria-heatpump type for testing."""
chosen_env = "anna_loria_cooling_active"
data = _read_json(chosen_env, "data")
with patch(
"homeassistant.components.plugwise.coordinator.Smile", autospec=True
) as api_mock:
api = api_mock.return_value
api.async_update.return_value = data
api.connect.return_value = Version("4.3.8")
api.cooling_present = True
api.gateway_id = "9ff0569b4984459fb243af64c0901894"
api.heater_id = "bfb5ee0a88e14e5f97bfa725a760cc49"
api.reboot = True
api.smile = build_smile(
hostname="smile98765",
model="Gateway",
model_id="smile_thermo",
name="Smile Anna",
type="thermostat",
version="4.3.8",
)
yield api
@pytest.fixture
def mock_smile_anna_p1() -> Generator[MagicMock]:
"""Create a Mock Anna-P1 type for testing."""
@@ -0,0 +1,96 @@
{
"582dfbdace4d4aeb832923ce7d1ddda0": {
"active_preset": "home",
"available_schedules": ["Winter", "Test ", "off"],
"climate_mode": "auto",
"control_state": "cooling",
"dev_class": "thermostat",
"firmware": "2018-02-08T11:15:53+01:00",
"hardware": "6539-1301-5002",
"location": "15da035090b847e7a21f93e08c015ebc",
"model": "ThermoTouch",
"name": "Anna",
"preset_modes": ["away", "vacation", "no_frost", "home", "asleep"],
"select_schedule": "Winter",
"sensors": {
"illuminance": 45.0,
"setpoint_high": 23.5,
"setpoint_low": 4.0,
"temperature": 24.1
},
"temperature_offset": {
"lower_bound": -2.0,
"resolution": 0.1,
"setpoint": 0.0,
"upper_bound": 2.0
},
"thermostat": {
"lower_bound": 4.0,
"resolution": 0.1,
"setpoint_high": 23.5,
"setpoint_low": 4.0,
"upper_bound": 30.0
},
"vendor": "Plugwise"
},
"9ff0569b4984459fb243af64c0901894": {
"binary_sensors": {
"plugwise_notification": false
},
"dev_class": "gateway",
"firmware": "4.3.8",
"hardware": "AME Smile 2.0 board",
"location": "674b657c138a41a291d315d7471deb06",
"mac_address": "C493000278E2",
"model": "Gateway",
"model_id": "smile_thermo",
"name": "Smile Anna",
"notifications": {},
"sensors": {
"outdoor_temperature": 15.5
},
"vendor": "Plugwise"
},
"bfb5ee0a88e14e5f97bfa725a760cc49": {
"available": true,
"binary_sensors": {
"cooling_enabled": true,
"cooling_state": true,
"dhw_state": false,
"flame_state": false,
"heating_state": false
},
"dev_class": "heater_central",
"dhw_modes": ["off", "auto", "boost", "eco", "comfort"],
"location": "674b657c138a41a291d315d7471deb06",
"max_dhw_temperature": {
"lower_bound": 35.0,
"resolution": 0.01,
"setpoint": 53.0,
"upper_bound": 60.0
},
"maximum_boiler_temperature": {
"lower_bound": 25.0,
"resolution": 0.01,
"setpoint": 40.0,
"upper_bound": 45.0
},
"model": "Generic heater/cooler",
"model_id": "173",
"name": "OpenTherm",
"select_dhw_mode": "auto",
"sensors": {
"dhw_temperature": 52.9,
"intended_boiler_temperature": 0.0,
"modulation_level": 100,
"outdoor_air_temperature": 17.2,
"return_temperature": 26.3,
"water_temperature": 25.3
},
"switches": {
"cooling_ena_switch": true,
"dhw_cm_switch": true
},
"vendor": "Atlantic"
}
}
@@ -645,3 +645,129 @@
'state': 'GF7 Woonkamer',
})
# ---
# name: test_anna_entities_with_dhw_mode_select[platforms0-True-anna_loria_cooling_active][select.anna_thermostat_schedule-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'Winter',
'Test ',
'off',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.anna_thermostat_schedule',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Thermostat schedule',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Thermostat schedule',
'platform': 'plugwise',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'select_schedule',
'unique_id': '582dfbdace4d4aeb832923ce7d1ddda0-select_schedule',
'unit_of_measurement': None,
})
# ---
# name: test_anna_entities_with_dhw_mode_select[platforms0-True-anna_loria_cooling_active][select.anna_thermostat_schedule-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Anna Thermostat schedule',
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'Winter',
'Test ',
'off',
]),
}),
'context': <ANY>,
'entity_id': 'select.anna_thermostat_schedule',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Winter',
})
# ---
# name: test_anna_entities_with_dhw_mode_select[platforms0-True-anna_loria_cooling_active][select.opentherm_dhw_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'off',
'auto',
'boost',
'eco',
'comfort',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.opentherm_dhw_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'DHW mode',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'DHW mode',
'platform': 'plugwise',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'select_dhw_mode',
'unique_id': 'bfb5ee0a88e14e5f97bfa725a760cc49-select_dhw_mode',
'unit_of_measurement': None,
})
# ---
# name: test_anna_entities_with_dhw_mode_select[platforms0-True-anna_loria_cooling_active][select.opentherm_dhw_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'OpenTherm DHW mode',
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'off',
'auto',
'boost',
'eco',
'comfort',
]),
}),
'context': <ANY>,
'entity_id': 'select.opentherm_dhw_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'auto',
})
# ---
+59 -1
View File
@@ -6,6 +6,8 @@ import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.plugwise.const import (
SELECT_DHW_MODE,
SELECT_GATEWAY_MODE,
SELECT_REGULATION_MODE,
SELECT_SCHEDULE,
SELECT_ZONE_PROFILE,
@@ -97,11 +99,28 @@ async def test_adam_select_regulation_mode(
assert mock_smile_adam_heat_cool.set_select.call_count == 1
mock_smile_adam_heat_cool.set_select.assert_called_with(
SELECT_REGULATION_MODE,
"bc93488efab249e5bc54fd7e175a6f91",
"da224107914542988a88561b4452b0f6",
"heating",
"on",
)
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.adam_gateway_mode",
ATTR_OPTION: "vacation",
},
blocking=True,
)
assert mock_smile_adam_heat_cool.set_select.call_count == 2
mock_smile_adam_heat_cool.set_select.assert_called_with(
SELECT_GATEWAY_MODE,
"da224107914542988a88561b4452b0f6",
"vacation",
"on",
)
@pytest.mark.parametrize("chosen_env", ["m_adam_heating"], indirect=True)
@pytest.mark.parametrize("cooling_present", [True], indirect=True)
@@ -155,3 +174,42 @@ async def test_anna_select_unavailable_schedule_mode(
},
blocking=True,
)
@pytest.mark.parametrize("chosen_env", ["anna_loria_cooling_active"], indirect=True)
@pytest.mark.parametrize("cooling_present", [True], indirect=True)
@pytest.mark.parametrize("platforms", [(SELECT_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_anna_entities_with_dhw_mode_select(
hass: HomeAssistant,
mock_smile_anna: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
) -> None:
"""Test Anna select snapshot with multiple dhw_modes."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
async def test_anna_select_dhw_mode(
hass: HomeAssistant,
mock_smile_anna_loria: MagicMock,
init_integration: MockConfigEntry,
) -> None:
"""Test changing the dhw_mode select."""
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.opentherm_dhw_mode",
ATTR_OPTION: "boost",
},
blocking=True,
)
assert mock_smile_anna_loria.set_select.call_count == 1
mock_smile_anna_loria.set_select.assert_called_with(
SELECT_DHW_MODE,
"bfb5ee0a88e14e5f97bfa725a760cc49",
"boost",
"on",
)