Add set_profile service for Vallox integration (#120225)
* Add set_profile service for Vallox integration * Merge profile constants, use str input for service * add service test and some related refactoring * Change service uom to 'minutes' Co-authored-by: Sebastian Lövdahl <slovdahl@hibox.fi> * Update icons.js format after rebase * Translate profile names for service * Fix test using wrong dict --------- Co-authored-by: Sebastian Lövdahl <slovdahl@hibox.fi>
This commit is contained in:
parent
45ab6e9b06
commit
af62e8267f
8 changed files with 146 additions and 17 deletions
|
@ -22,6 +22,7 @@ from .const import (
|
||||||
DEFAULT_FAN_SPEED_HOME,
|
DEFAULT_FAN_SPEED_HOME,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
I18N_KEY_TO_VALLOX_PROFILE,
|
||||||
)
|
)
|
||||||
from .coordinator import ValloxDataUpdateCoordinator
|
from .coordinator import ValloxDataUpdateCoordinator
|
||||||
|
|
||||||
|
@ -61,6 +62,18 @@ SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED = vol.Schema(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ATTR_PROFILE = "profile"
|
||||||
|
ATTR_DURATION = "duration"
|
||||||
|
|
||||||
|
SERVICE_SCHEMA_SET_PROFILE = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_PROFILE): vol.In(I18N_KEY_TO_VALLOX_PROFILE),
|
||||||
|
vol.Optional(ATTR_DURATION): vol.All(
|
||||||
|
vol.Coerce(int), vol.Clamp(min=1, max=65535)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ServiceMethodDetails(NamedTuple):
|
class ServiceMethodDetails(NamedTuple):
|
||||||
"""Details for SERVICE_TO_METHOD mapping."""
|
"""Details for SERVICE_TO_METHOD mapping."""
|
||||||
|
@ -72,6 +85,7 @@ class ServiceMethodDetails(NamedTuple):
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_HOME = "set_profile_fan_speed_home"
|
SERVICE_SET_PROFILE_FAN_SPEED_HOME = "set_profile_fan_speed_home"
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_AWAY = "set_profile_fan_speed_away"
|
SERVICE_SET_PROFILE_FAN_SPEED_AWAY = "set_profile_fan_speed_away"
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_BOOST = "set_profile_fan_speed_boost"
|
SERVICE_SET_PROFILE_FAN_SPEED_BOOST = "set_profile_fan_speed_boost"
|
||||||
|
SERVICE_SET_PROFILE = "set_profile"
|
||||||
|
|
||||||
SERVICE_TO_METHOD = {
|
SERVICE_TO_METHOD = {
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_HOME: ServiceMethodDetails(
|
SERVICE_SET_PROFILE_FAN_SPEED_HOME: ServiceMethodDetails(
|
||||||
|
@ -86,6 +100,9 @@ SERVICE_TO_METHOD = {
|
||||||
method="async_set_profile_fan_speed_boost",
|
method="async_set_profile_fan_speed_boost",
|
||||||
schema=SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED,
|
schema=SERVICE_SCHEMA_SET_PROFILE_FAN_SPEED,
|
||||||
),
|
),
|
||||||
|
SERVICE_SET_PROFILE: ServiceMethodDetails(
|
||||||
|
method="async_set_profile", schema=SERVICE_SCHEMA_SET_PROFILE
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,6 +200,22 @@ class ValloxServiceHandler:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def async_set_profile(
|
||||||
|
self, profile: str, duration: int | None = None
|
||||||
|
) -> bool:
|
||||||
|
"""Activate profile for given duration."""
|
||||||
|
_LOGGER.debug("Activating profile %s for %s min", profile, duration)
|
||||||
|
try:
|
||||||
|
await self._client.set_profile(
|
||||||
|
I18N_KEY_TO_VALLOX_PROFILE[profile], duration
|
||||||
|
)
|
||||||
|
except ValloxApiException as err:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error setting profile %d for duration %s: %s", profile, duration, err
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
async def async_handle(self, call: ServiceCall) -> None:
|
async def async_handle(self, call: ServiceCall) -> None:
|
||||||
"""Dispatch a service call."""
|
"""Dispatch a service call."""
|
||||||
service_details = SERVICE_TO_METHOD.get(call.service)
|
service_details = SERVICE_TO_METHOD.get(call.service)
|
||||||
|
|
|
@ -22,14 +22,15 @@ DEFAULT_FAN_SPEED_HOME = 50
|
||||||
DEFAULT_FAN_SPEED_AWAY = 25
|
DEFAULT_FAN_SPEED_AWAY = 25
|
||||||
DEFAULT_FAN_SPEED_BOOST = 65
|
DEFAULT_FAN_SPEED_BOOST = 65
|
||||||
|
|
||||||
VALLOX_PROFILE_TO_PRESET_MODE_SETTABLE = {
|
I18N_KEY_TO_VALLOX_PROFILE = {
|
||||||
VALLOX_PROFILE.HOME: "Home",
|
"home": VALLOX_PROFILE.HOME,
|
||||||
VALLOX_PROFILE.AWAY: "Away",
|
"away": VALLOX_PROFILE.AWAY,
|
||||||
VALLOX_PROFILE.BOOST: "Boost",
|
"boost": VALLOX_PROFILE.BOOST,
|
||||||
VALLOX_PROFILE.FIREPLACE: "Fireplace",
|
"fireplace": VALLOX_PROFILE.FIREPLACE,
|
||||||
|
"extra": VALLOX_PROFILE.EXTRA,
|
||||||
}
|
}
|
||||||
|
|
||||||
VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE = {
|
VALLOX_PROFILE_TO_PRESET_MODE = {
|
||||||
VALLOX_PROFILE.HOME: "Home",
|
VALLOX_PROFILE.HOME: "Home",
|
||||||
VALLOX_PROFILE.AWAY: "Away",
|
VALLOX_PROFILE.AWAY: "Away",
|
||||||
VALLOX_PROFILE.BOOST: "Boost",
|
VALLOX_PROFILE.BOOST: "Boost",
|
||||||
|
@ -37,8 +38,8 @@ VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE = {
|
||||||
VALLOX_PROFILE.EXTRA: "Extra",
|
VALLOX_PROFILE.EXTRA: "Extra",
|
||||||
}
|
}
|
||||||
|
|
||||||
PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE = {
|
PRESET_MODE_TO_VALLOX_PROFILE = {
|
||||||
value: key for (key, value) in VALLOX_PROFILE_TO_PRESET_MODE_SETTABLE.items()
|
value: key for (key, value) in VALLOX_PROFILE_TO_PRESET_MODE.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
VALLOX_CELL_STATE_TO_STR = {
|
VALLOX_CELL_STATE_TO_STR = {
|
||||||
|
|
|
@ -23,8 +23,8 @@ from .const import (
|
||||||
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
|
||||||
MODE_OFF,
|
MODE_OFF,
|
||||||
MODE_ON,
|
MODE_ON,
|
||||||
PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE,
|
PRESET_MODE_TO_VALLOX_PROFILE,
|
||||||
VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE,
|
VALLOX_PROFILE_TO_PRESET_MODE,
|
||||||
)
|
)
|
||||||
from .coordinator import ValloxDataUpdateCoordinator
|
from .coordinator import ValloxDataUpdateCoordinator
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
self._attr_unique_id = str(self._device_uuid)
|
self._attr_unique_id = str(self._device_uuid)
|
||||||
self._attr_preset_modes = list(PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE)
|
self._attr_preset_modes = list(PRESET_MODE_TO_VALLOX_PROFILE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
|
@ -108,7 +108,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
||||||
def preset_mode(self) -> str | None:
|
def preset_mode(self) -> str | None:
|
||||||
"""Return the current preset mode."""
|
"""Return the current preset mode."""
|
||||||
vallox_profile = self.coordinator.data.profile
|
vallox_profile = self.coordinator.data.profile
|
||||||
return VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE.get(vallox_profile)
|
return VALLOX_PROFILE_TO_PRESET_MODE.get(vallox_profile)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percentage(self) -> int | None:
|
def percentage(self) -> int | None:
|
||||||
|
@ -204,7 +204,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
profile = PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE[preset_mode]
|
profile = PRESET_MODE_TO_VALLOX_PROFILE[preset_mode]
|
||||||
await self._client.set_profile(profile)
|
await self._client.set_profile(profile)
|
||||||
|
|
||||||
except ValloxApiException as err:
|
except ValloxApiException as err:
|
||||||
|
@ -220,7 +220,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
||||||
Returns true if speed has been changed, false otherwise.
|
Returns true if speed has been changed, false otherwise.
|
||||||
"""
|
"""
|
||||||
vallox_profile = (
|
vallox_profile = (
|
||||||
PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE[preset_mode]
|
PRESET_MODE_TO_VALLOX_PROFILE[preset_mode]
|
||||||
if preset_mode is not None
|
if preset_mode is not None
|
||||||
else self.coordinator.data.profile
|
else self.coordinator.data.profile
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
},
|
},
|
||||||
"set_profile_fan_speed_boost": {
|
"set_profile_fan_speed_boost": {
|
||||||
"service": "mdi:speedometer"
|
"service": "mdi:speedometer"
|
||||||
|
},
|
||||||
|
"set_profile": {
|
||||||
|
"service": "mdi:fan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ from .const import (
|
||||||
METRIC_KEY_MODE,
|
METRIC_KEY_MODE,
|
||||||
MODE_ON,
|
MODE_ON,
|
||||||
VALLOX_CELL_STATE_TO_STR,
|
VALLOX_CELL_STATE_TO_STR,
|
||||||
VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE,
|
VALLOX_PROFILE_TO_PRESET_MODE,
|
||||||
)
|
)
|
||||||
from .coordinator import ValloxDataUpdateCoordinator
|
from .coordinator import ValloxDataUpdateCoordinator
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class ValloxProfileSensor(ValloxSensorEntity):
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the value reported by the sensor."""
|
"""Return the value reported by the sensor."""
|
||||||
vallox_profile = self.coordinator.data.profile
|
vallox_profile = self.coordinator.data.profile
|
||||||
return VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE.get(vallox_profile)
|
return VALLOX_PROFILE_TO_PRESET_MODE.get(vallox_profile)
|
||||||
|
|
||||||
|
|
||||||
# There is a quirk with respect to the fan speed reporting. The device keeps on reporting the last
|
# There is a quirk with respect to the fan speed reporting. The device keeps on reporting the last
|
||||||
|
|
|
@ -27,3 +27,24 @@ set_profile_fan_speed_boost:
|
||||||
min: 0
|
min: 0
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
|
|
||||||
|
set_profile:
|
||||||
|
fields:
|
||||||
|
profile:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
translation_key: "profile"
|
||||||
|
options:
|
||||||
|
- "home"
|
||||||
|
- "away"
|
||||||
|
- "boost"
|
||||||
|
- "fireplace"
|
||||||
|
- "extra"
|
||||||
|
duration:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
max: 65535
|
||||||
|
unit_of_measurement: "minutes"
|
||||||
|
|
|
@ -133,6 +133,31 @@
|
||||||
"description": "[%key:component::vallox::services::set_profile_fan_speed_home::fields::fan_speed::description%]"
|
"description": "[%key:component::vallox::services::set_profile_fan_speed_home::fields::fan_speed::description%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"set_profile": {
|
||||||
|
"name": "Activate profile for duration",
|
||||||
|
"description": "Activate a profile and optionally set duration.",
|
||||||
|
"fields": {
|
||||||
|
"profile": {
|
||||||
|
"name": "Profile",
|
||||||
|
"description": "Profile to activate"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"name": "Duration",
|
||||||
|
"description": "Activation duration, if omitted device uses stored duration. Duration of 65535 activates profile without timeout. Duration only applies to Boost, Fireplace and Extra profiles."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"profile": {
|
||||||
|
"options": {
|
||||||
|
"home": "Home",
|
||||||
|
"away": "Away",
|
||||||
|
"boost": "Boost",
|
||||||
|
"fireplace": "Fireplace",
|
||||||
|
"extra": "Extra"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import pytest
|
||||||
from vallox_websocket_api import Profile
|
from vallox_websocket_api import Profile
|
||||||
|
|
||||||
from homeassistant.components.vallox import (
|
from homeassistant.components.vallox import (
|
||||||
|
ATTR_DURATION,
|
||||||
|
ATTR_PROFILE,
|
||||||
ATTR_PROFILE_FAN_SPEED,
|
ATTR_PROFILE_FAN_SPEED,
|
||||||
|
I18N_KEY_TO_VALLOX_PROFILE,
|
||||||
|
SERVICE_SET_PROFILE,
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_AWAY,
|
SERVICE_SET_PROFILE_FAN_SPEED_AWAY,
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_BOOST,
|
SERVICE_SET_PROFILE_FAN_SPEED_BOOST,
|
||||||
SERVICE_SET_PROFILE_FAN_SPEED_HOME,
|
SERVICE_SET_PROFILE_FAN_SPEED_HOME,
|
||||||
|
@ -12,7 +16,7 @@ from homeassistant.components.vallox import (
|
||||||
from homeassistant.components.vallox.const import DOMAIN
|
from homeassistant.components.vallox.const import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .conftest import patch_set_fan_speed
|
from .conftest import patch_set_fan_speed, patch_set_profile
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
@ -47,3 +51,45 @@ async def test_create_service(
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
set_fan_speed.assert_called_once_with(profile, 30)
|
set_fan_speed.assert_called_once_with(profile, 30)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("profile", "duration"),
|
||||||
|
[
|
||||||
|
("home", None),
|
||||||
|
("home", 15),
|
||||||
|
("away", None),
|
||||||
|
("away", 15),
|
||||||
|
("boost", None),
|
||||||
|
("boost", 15),
|
||||||
|
("fireplace", None),
|
||||||
|
("fireplace", 15),
|
||||||
|
("extra", None),
|
||||||
|
("extra", 15),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_set_profile_service(
|
||||||
|
hass: HomeAssistant, mock_entry: MockConfigEntry, profile: str, duration: int | None
|
||||||
|
) -> None:
|
||||||
|
"""Test service for setting profile and duration."""
|
||||||
|
# Act
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with patch_set_profile() as set_profile:
|
||||||
|
service_data = {ATTR_PROFILE: profile} | (
|
||||||
|
{ATTR_DURATION: duration} if duration is not None else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_PROFILE,
|
||||||
|
service_data=service_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
set_profile.assert_called_once_with(
|
||||||
|
I18N_KEY_TO_VALLOX_PROFILE[profile], duration
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue