Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions homeassistant/components/knx/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import math
from typing import Any, Final
from typing import Any

from propcache.api import cached_property
from xknx.devices import Fan as XknxFan
Expand Down Expand Up @@ -32,12 +32,11 @@
CONF_GA_OSCILLATION,
CONF_GA_SPEED,
CONF_GA_STEP,
CONF_GA_SWITCH,
CONF_SPEED,
)
from .storage.util import ConfigExtractor

DEFAULT_PERCENTAGE: Final = 50


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -77,26 +76,24 @@ class _KnxFan(FanEntity):
_device: XknxFan
_step_range: tuple[int, int] | None

def _get_knx_speed(self, percentage: int) -> int:
"""Convert percentage to KNX speed value."""
if self._step_range is not None:
return math.ceil(percentage_to_ranged_value(self._step_range, percentage))
return percentage

async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed of the fan, as a percentage."""
if self._step_range:
step = math.ceil(percentage_to_ranged_value(self._step_range, percentage))
await self._device.set_speed(step)
else:
await self._device.set_speed(percentage)
await self._device.set_speed(self._get_knx_speed(percentage))

@cached_property
def supported_features(self) -> FanEntityFeature:
"""Flag supported features."""
flags = (
FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
)

flags = FanEntityFeature.TURN_ON | FanEntityFeature.TURN_OFF
if self._device.speed.initialized:
flags |= FanEntityFeature.SET_SPEED
if self._device.supports_oscillation:
flags |= FanEntityFeature.OSCILLATE

return flags

@property
Expand All @@ -118,21 +115,24 @@ def speed_count(self) -> int:
return super().speed_count
return int_states_in_range(self._step_range)

@property
def is_on(self) -> bool:
"""Return the current fan state of the device."""
return self._device.is_on

async def async_turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs: Any,
) -> None:
"""Turn on the fan."""
if percentage is None:
await self.async_set_percentage(DEFAULT_PERCENTAGE)
else:
await self.async_set_percentage(percentage)
speed = self._get_knx_speed(percentage) if percentage is not None else None
await self._device.turn_on(speed)

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
await self.async_set_percentage(0)
await self._device.turn_off()

async def async_oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
Expand Down Expand Up @@ -165,7 +165,12 @@ def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
group_address_oscillation_state=config.get(
FanSchema.CONF_OSCILLATION_STATE_ADDRESS
),
group_address_switch=config.get(FanSchema.CONF_SWITCH_ADDRESS),
group_address_switch_state=config.get(
FanSchema.CONF_SWITCH_STATE_ADDRESS
),
max_step=max_step,
sync_state=config.get(CONF_SYNC_STATE, True),
),
)
# FanSpeedMode.STEP if max_step is set
Expand Down Expand Up @@ -210,6 +215,8 @@ def __init__(
group_address_oscillation_state=knx_conf.get_state_and_passive(
CONF_GA_OSCILLATION
),
group_address_switch=knx_conf.get_write(CONF_GA_SWITCH),
group_address_switch_state=knx_conf.get_state_and_passive(CONF_GA_SWITCH),
max_step=max_step,
sync_state=knx_conf.get(CONF_SYNC_STATE),
)
Expand Down
41 changes: 31 additions & 10 deletions homeassistant/components/knx/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,19 +576,40 @@ class FanSchema(KNXPlatformSchema):
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
CONF_OSCILLATION_ADDRESS = "oscillation_address"
CONF_OSCILLATION_STATE_ADDRESS = "oscillation_state_address"
CONF_SWITCH_ADDRESS = "switch_address"
CONF_SWITCH_STATE_ADDRESS = "switch_state_address"

DEFAULT_NAME = "KNX Fan"

ENTITY_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(KNX_ADDRESS): ga_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
vol.Optional(CONF_OSCILLATION_ADDRESS): ga_list_validator,
vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_list_validator,
vol.Optional(FanConf.MAX_STEP): cv.byte,
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
}
ENTITY_SCHEMA = vol.All(
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(KNX_ADDRESS): ga_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
vol.Optional(CONF_SWITCH_ADDRESS): ga_list_validator,
vol.Optional(CONF_SWITCH_STATE_ADDRESS): ga_list_validator,
vol.Optional(CONF_OSCILLATION_ADDRESS): ga_list_validator,
vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_list_validator,
vol.Optional(FanConf.MAX_STEP): cv.byte,
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
}
),
vol.Any(
vol.Schema(
{vol.Required(KNX_ADDRESS): object},
extra=vol.ALLOW_EXTRA,
),
vol.Schema(
{vol.Required(CONF_SWITCH_ADDRESS): object},
extra=vol.ALLOW_EXTRA,
),
msg=(
f"At least one of '{KNX_ADDRESS}' or"
f" '{CONF_SWITCH_ADDRESS}' is required."
),
),
)


Expand Down
78 changes: 48 additions & 30 deletions homeassistant/components/knx/storage/entity_store_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,40 +224,58 @@
}
)

FAN_KNX_SCHEMA = vol.Schema(
{
vol.Required(CONF_SPEED): GroupSelect(
GroupSelectOption(
translation_key="percentage_mode",
schema={
vol.Required(CONF_GA_SPEED): GASelector(
write_required=True, valid_dpt="5.001"
),
},
FAN_KNX_SCHEMA = AllSerializeFirst(
vol.Schema(
{
vol.Optional(CONF_GA_SWITCH): GASelector(
write_required=True, valid_dpt="1"
),
GroupSelectOption(
translation_key="step_mode",
schema={
vol.Required(CONF_GA_STEP): GASelector(
write_required=True, valid_dpt="5.010"
),
vol.Required(FanConf.MAX_STEP, default=3): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=100,
step=1,
mode=selector.NumberSelectorMode.BOX,
)
),
},
vol.Optional(CONF_SPEED): GroupSelect(
GroupSelectOption(
translation_key="percentage_mode",
schema={
vol.Required(CONF_GA_SPEED): GASelector(
write_required=True, valid_dpt="5.001"
),
},
),
GroupSelectOption(
translation_key="step_mode",
schema={
vol.Required(CONF_GA_STEP): GASelector(
write_required=True, valid_dpt="5.010"
),
vol.Required(
FanConf.MAX_STEP, default=3
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=100,
step=1,
mode=selector.NumberSelectorMode.BOX,
)
),
},
),
collapsible=False,
),
collapsible=False,
vol.Optional(CONF_GA_OSCILLATION): GASelector(
write_required=True, valid_dpt="1"
),
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
}
),
vol.Any(
vol.Schema(
{vol.Required(CONF_GA_SWITCH): object},
extra=vol.ALLOW_EXTRA,
),
vol.Optional(CONF_GA_OSCILLATION): GASelector(
write_required=True, valid_dpt="1"
vol.Schema(
{vol.Required(CONF_SPEED): object},
extra=vol.ALLOW_EXTRA,
),
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
}
msg=("At least one of 'Switch' or 'Fan speed' is required."),
),
)


Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/knx/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@
"description": "Toggle oscillation of the fan.",
"label": "Oscillation"
},
"ga_switch": {
"description": "Group address to turn the fan on/off.",
"label": "Switch"
},
"speed": {
"description": "Control the speed of the fan.",
"ga_speed": {
Expand Down
24 changes: 23 additions & 1 deletion tests/components/knx/snapshots/test_websocket.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -1037,10 +1037,32 @@
dict({
'id': 1,
'result': list([
dict({
'name': 'ga_switch',
'optional': True,
'options': dict({
'passive': True,
'state': dict({
'required': False,
}),
'validDPTs': list([
dict({
'main': 1,
'sub': None,
}),
]),
'write': dict({
'required': True,
}),
}),
'required': False,
'type': 'knx_group_address',
}),
dict({
'collapsible': False,
'name': 'speed',
'required': True,
'optional': True,
'required': False,
'schema': list([
dict({
'schema': list([
Expand Down
Loading
Loading