1 """Class to hold all fan accessories."""
6 from pyhap.const
import CATEGORY_FAN
19 SERVICE_SET_DIRECTION,
20 SERVICE_SET_PERCENTAGE,
21 SERVICE_SET_PRESET_MODE,
26 ATTR_SUPPORTED_FEATURES,
34 from .accessories
import TYPES, HomeAccessory
39 CHAR_ROTATION_DIRECTION,
42 CHAR_TARGET_FAN_STATE,
47 from .util
import cleanup_name_for_homekit
49 _LOGGER = logging.getLogger(__name__)
52 @TYPES.register("Fan")
54 """Generate a Fan accessory for a fan entity.
56 Currently supports: state, speed, oscillate, direction.
60 """Initialize a new Fan accessory object."""
61 super().
__init__(*args, category=CATEGORY_FAN)
62 self.chars: list[str] = []
72 features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
73 percentage_step = state.attributes.get(ATTR_PERCENTAGE_STEP, 1)
74 self.preset_modes: list[str] |
None = state.attributes.get(ATTR_PRESET_MODES)
76 if features & FanEntityFeature.DIRECTION:
77 self.chars.append(CHAR_ROTATION_DIRECTION)
78 if features & FanEntityFeature.OSCILLATE:
79 self.chars.append(CHAR_SWING_MODE)
80 if features & FanEntityFeature.SET_SPEED:
81 self.chars.append(CHAR_ROTATION_SPEED)
82 if self.preset_modes
and len(self.preset_modes) == 1:
83 self.chars.append(CHAR_TARGET_FAN_STATE)
85 serv_fan = self.add_preload_service(SERV_FANV2, self.chars)
86 self.set_primary_service(serv_fan)
87 self.
char_activechar_active = serv_fan.configure_char(CHAR_ACTIVE, value=0)
95 if CHAR_ROTATION_DIRECTION
in self.chars:
97 CHAR_ROTATION_DIRECTION, value=0
100 if CHAR_ROTATION_SPEED
in self.chars:
104 self.
char_speedchar_speed = serv_fan.configure_char(
107 properties={PROP_MIN_STEP: percentage_step},
110 if self.preset_modes
and len(self.preset_modes) == 1:
112 CHAR_TARGET_FAN_STATE,
115 elif self.preset_modes:
116 for preset_mode
in self.preset_modes:
117 preset_serv = self.add_preload_service(
118 SERV_SWITCH, CHAR_NAME, unique_id=preset_mode
120 serv_fan.add_linked_service(preset_serv)
121 preset_serv.configure_char(
123 value=cleanup_name_for_homekit(
124 f
"{self.display_name} {preset_mode}"
128 def setter_callback(value: int, preset_mode: str = preset_mode) ->
None:
131 self.
preset_mode_charspreset_mode_chars[preset_mode] = preset_serv.configure_char(
134 setter_callback=setter_callback,
137 if CHAR_SWING_MODE
in self.chars:
138 self.
char_swingchar_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0)
140 serv_fan.setter_callback = self.
_set_chars_set_chars
143 _LOGGER.debug(
"Fan _set_chars: %s", char_values)
144 if CHAR_ACTIVE
in char_values:
145 if char_values[CHAR_ACTIVE]:
152 if not self.
char_speedchar_speed
or CHAR_ROTATION_SPEED
not in char_values:
161 if CHAR_SWING_MODE
in char_values:
163 if CHAR_ROTATION_DIRECTION
in char_values:
164 self.
set_directionset_direction(char_values[CHAR_ROTATION_DIRECTION])
168 if CHAR_ROTATION_SPEED
in char_values:
169 self.
set_percentageset_percentage(char_values[CHAR_ROTATION_SPEED])
170 if CHAR_TARGET_FAN_STATE
in char_values:
174 """Set auto call came from HomeKit."""
175 params: dict[str, Any] = {ATTR_ENTITY_ID: self.
entity_identity_id}
177 assert self.preset_modes
179 "%s: Set auto to 1 (%s)", self.
entity_identity_id, self.preset_modes[0]
181 params[ATTR_PRESET_MODE] = self.preset_modes[0]
182 self.
async_call_serviceasync_call_service(FAN_DOMAIN, SERVICE_SET_PRESET_MODE, params)
183 elif current_state := self.
hasshass.states.get(self.
entity_identity_id):
184 percentage: float = current_state.attributes.get(ATTR_PERCENTAGE)
or 50.0
185 params[ATTR_PERCENTAGE] = percentage
186 _LOGGER.debug(
"%s: Set auto to 0", self.
entity_identity_id)
190 """Set preset_mode if call came from HomeKit."""
192 "%s: Set preset_mode %s to %d", self.
entity_identity_id, preset_mode, value
194 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
196 params[ATTR_PRESET_MODE] = preset_mode
197 self.
async_call_serviceasync_call_service(FAN_DOMAIN, SERVICE_SET_PRESET_MODE, params)
202 """Set state if call came from HomeKit."""
203 _LOGGER.debug(
"%s: Set state to %d", self.
entity_identity_id, value)
204 service = SERVICE_TURN_ON
if value == 1
else SERVICE_TURN_OFF
205 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
209 """Set state if call came from HomeKit."""
210 _LOGGER.debug(
"%s: Set direction to %d", self.
entity_identity_id, value)
211 direction = DIRECTION_REVERSE
if value == 1
else DIRECTION_FORWARD
212 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_DIRECTION: direction}
213 self.
async_call_serviceasync_call_service(FAN_DOMAIN, SERVICE_SET_DIRECTION, params, direction)
216 """Set state if call came from HomeKit."""
217 _LOGGER.debug(
"%s: Set oscillating to %d", self.
entity_identity_id, value)
218 oscillating = value == 1
219 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_OSCILLATING: oscillating}
220 self.
async_call_serviceasync_call_service(FAN_DOMAIN, SERVICE_OSCILLATE, params, oscillating)
223 """Set state if call came from HomeKit."""
224 _LOGGER.debug(
"%s: Set speed to %d", self.
entity_identity_id, value)
225 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_PERCENTAGE: value}
226 self.
async_call_serviceasync_call_service(FAN_DOMAIN, SERVICE_SET_PERCENTAGE, params, value)
230 """Update fan after state change."""
232 state = new_state.state
233 attributes = new_state.attributes
234 if state
in (STATE_ON, STATE_OFF):
235 self.
_state_state = 1
if state == STATE_ON
else 0
240 direction = new_state.attributes.get(ATTR_DIRECTION)
241 if direction
in (DIRECTION_FORWARD, DIRECTION_REVERSE):
242 hk_direction = 1
if direction == DIRECTION_REVERSE
else 0
246 if self.
char_speedchar_speed
is not None and state != STATE_OFF:
249 percentage = attributes.get(ATTR_PERCENTAGE)
261 if percentage == 0
and state == STATE_ON:
262 percentage =
max(1, self.
char_speedchar_speed.properties[PROP_MIN_STEP])
263 if percentage
is not None:
264 self.
char_speedchar_speed.set_value(percentage)
268 oscillating = attributes.get(ATTR_OSCILLATING)
269 if isinstance(oscillating, bool):
270 hk_oscillating = 1
if oscillating
else 0
271 self.
char_swingchar_swing.set_value(hk_oscillating)
273 current_preset_mode = attributes.get(ATTR_PRESET_MODE)
281 hk_value = 1
if preset_mode == current_preset_mode
else 0
282 char.set_value(hk_value)
None async_call_service(self, str domain, str service, dict[str, Any]|None service_data, Any|None value=None)
None async_update_state(self, State new_state)
None set_direction(self, int value)
None set_single_preset_mode(self, int value)
None _set_chars(self, dict[str, Any] char_values)
None set_oscillating(self, int value)
None async_update_state(self, State new_state)
None set_percentage(self, float value)
None set_state(self, int value)
None __init__(self, *Any args)
None set_preset_mode(self, int value, str preset_mode)