1 """Platform allowing several fans to be grouped into one fan."""
3 from __future__
import annotations
5 from functools
import reduce
7 from operator
import ior
10 import voluptuous
as vol
18 PLATFORM_SCHEMA
as FAN_PLATFORM_SCHEMA,
20 SERVICE_SET_DIRECTION,
21 SERVICE_SET_PERCENTAGE,
30 ATTR_SUPPORTED_FEATURES,
43 from .entity
import GroupEntity
44 from .util
import attribute_equal, most_frequent_attribute, reduce_attribute
47 FanEntityFeature.SET_SPEED,
48 FanEntityFeature.DIRECTION,
49 FanEntityFeature.OSCILLATE,
50 FanEntityFeature.TURN_OFF,
51 FanEntityFeature.TURN_ON,
54 DEFAULT_NAME =
"Fan Group"
59 PLATFORM_SCHEMA = FAN_PLATFORM_SCHEMA.extend(
61 vol.Required(CONF_ENTITIES): cv.entities_domain(FAN_DOMAIN),
62 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
63 vol.Optional(CONF_UNIQUE_ID): cv.string,
67 _LOGGER = logging.getLogger(__name__)
73 async_add_entities: AddEntitiesCallback,
74 discovery_info: DiscoveryInfoType |
None =
None,
76 """Set up the Fan Group platform."""
78 [
FanGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES])]
84 config_entry: ConfigEntry,
85 async_add_entities: AddEntitiesCallback,
87 """Initialize Fan Group config entry."""
88 registry = er.async_get(hass)
89 entities = er.async_validate_entity_ids(
90 registry, config_entry.options[CONF_ENTITIES]
98 hass: HomeAssistant, name: str, validated_config: dict[str, Any]
100 """Create a preview sensor."""
104 validated_config[CONF_ENTITIES],
109 """Representation of a FanGroup."""
111 _attr_available: bool =
False
112 _enable_turn_on_off_backwards_compatibility =
False
114 def __init__(self, unique_id: str |
None, name: str, entities: list[str]) ->
None:
115 """Initialize a FanGroup entity."""
117 self._fans: dict[int, set[str]] = {flag: set()
for flag
in SUPPORTED_FLAGS}
122 self.
_is_on_is_on: bool |
None =
False
129 """Return the number of speeds the fan supports."""
134 """Return true if the entity is on."""
139 """Return the current speed as a percentage."""
144 """Return the current direction of the fan."""
149 """Return whether or not the fan is currently oscillating."""
156 new_state: State |
None,
158 """Update dictionaries with supported features."""
160 for values
in self._fans.values():
161 values.discard(entity_id)
163 features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
164 for feature
in SUPPORTED_FLAGS:
165 if features & feature:
166 self._fans[feature].
add(entity_id)
168 self._fans[feature].discard(entity_id)
171 """Set the speed of the fan, as a percentage."""
175 SERVICE_SET_PERCENTAGE,
176 FanEntityFeature.SET_SPEED,
177 {ATTR_PERCENTAGE: percentage},
181 """Oscillate the fan."""
184 FanEntityFeature.OSCILLATE,
185 {ATTR_OSCILLATING: oscillating},
189 """Set the direction of the fan."""
191 SERVICE_SET_DIRECTION,
192 FanEntityFeature.DIRECTION,
193 {ATTR_DIRECTION: direction},
198 percentage: int |
None =
None,
199 preset_mode: str |
None =
None,
202 """Turn on the fan."""
203 if percentage
is not None:
207 SERVICE_TURN_ON, FanEntityFeature.TURN_ON, {}
211 """Turn the fans off."""
213 SERVICE_TURN_OFF, FanEntityFeature.TURN_OFF, {}
217 self, service: str, support_flag: int, data: dict[str, Any]
219 """Call a service with all entities."""
220 await self.
hasshass.services.async_call(
223 {**data, ATTR_ENTITY_ID: self._fans[support_flag]},
229 """Call a service with all entities."""
230 await self.
hasshass.services.async_call(
239 """Return all the entity states for a supported flag."""
240 states: list[State] =
list(
241 filter(
None, [self.
hasshass.states.get(x)
for x
in self._fans[flag]])
246 """Set an attribute based on most frequent supported entities attributes."""
252 """Update state and attributes."""
257 if (state := self.
hasshass.states.get(entity_id))
is not None
261 self.
_attr_available_attr_available = any(state.state != STATE_UNAVAILABLE
for state
in states)
264 state.state
not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
for state
in states
271 self.
_is_on_is_on = any(state.state == STATE_ON
for state
in states)
274 FanEntityFeature.SET_SPEED
279 and percentage_states[0].attributes.get(ATTR_PERCENTAGE_STEP)
283 round(100 / percentage_states[0].attributes[ATTR_PERCENTAGE_STEP])
290 "_oscillating", FanEntityFeature.OSCILLATE, ATTR_OSCILLATING
293 "_direction", FanEntityFeature.DIRECTION, ATTR_DIRECTION
298 ior, [feature
for feature
in SUPPORTED_FLAGS
if self._fans[feature]], 0
None async_set_percentage(self, int percentage)
list[State] _async_states_by_support_flag(self, int flag)
None _set_attr_most_frequent(self, str attr, int flag, str entity_attr)
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
None _async_call_supported_entities(self, str service, int support_flag, dict[str, Any] data)
str|None current_direction(self)
None async_update_group_state(self)
None async_set_direction(self, str direction)
None async_update_supported_features(self, str entity_id, State|None new_state)
int|None percentage(self)
None async_set_percentage(self, int percentage)
bool|None oscillating(self)
_attr_extra_state_attributes
None async_turn_off(self, **Any kwargs)
None _async_call_all_entities(self, str service)
None async_oscillate(self, bool oscillating)
None __init__(self, str|None unique_id, str name, list[str] entities)
None async_turn_off(self, **Any kwargs)
bool add(self, _T matcher)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
FanGroup async_create_preview_fan(HomeAssistant hass, str name, dict[str, Any] validated_config)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
bool attribute_equal(list[State] states, str key)
Any|None most_frequent_attribute(list[State] states, str key)
Any reduce_attribute(list[State] states, str key, Any|None default=None, Callable[..., Any] reduce=mean_int)