Skip to content

Fan

fan

fan domain implementation.

SPEC module-attribute

SPEC: DomainSpec[Fan] = register_domain(DomainSpec(name='fan', entity_cls=Fan))

The DomainSpec registered with the shared DomainRegistry.

Fan

Bases: Entity

A Home Assistant fan entity.

The public API uses intent-specific actions (on, off, toggle, set_percentage, set_preset_mode, set_direction, oscillate) and exposes structured state (is_on, percentage, preset_mode, preset_modes, oscillating, direction) rather than raw service calls.

Methods that depend on optional fan capabilities degrade safely: if the underlying hardware does not advertise the relevant FanEntityFeature bit in supported_features, the call becomes a no-op that logs a debug message instead of raising. Callers that need to know whether an action will actually be dispatched can pre-check with the supports_* properties.

Source code in src/haclient/domains/fan.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
class Fan(Entity):
    """A Home Assistant fan entity.

    The public API uses intent-specific actions (``on``, ``off``,
    ``toggle``, ``set_percentage``, ``set_preset_mode``,
    ``set_direction``, ``oscillate``) and exposes structured state
    (``is_on``, ``percentage``, ``preset_mode``, ``preset_modes``,
    ``oscillating``, ``direction``) rather than raw service calls.

    Methods that depend on optional fan capabilities degrade safely: if
    the underlying hardware does not advertise the relevant
    ``FanEntityFeature`` bit in ``supported_features``, the call becomes
    a no-op that logs a debug message instead of raising. Callers that
    need to know whether an action will actually be dispatched can
    pre-check with the ``supports_*`` properties.
    """

    domain = "fan"

    # -- Listener decorators ------------------------------------------

    def on_turn_on(self, func: ValueChangeHandler) -> ValueChangeHandler:
        """Register a listener for when the fan turns on.

        Parameters
        ----------
        func : callable
            Sync or async callable invoked with ``(old_state, new_state)``
            on every transition into the ``on`` state.

        Returns
        -------
        callable
            The same *func*, returned for decorator use.
        """
        return self._register_state_transition_listener("on", func)

    def on_turn_off(self, func: ValueChangeHandler) -> ValueChangeHandler:
        """Register a listener for when the fan turns off.

        Parameters
        ----------
        func : callable
            Sync or async callable invoked with ``(old_state, new_state)``
            on every transition into the ``off`` state.

        Returns
        -------
        callable
            The same *func*, returned for decorator use.
        """
        return self._register_state_transition_listener("off", func)

    def on_speed_change(self, func: ValueChangeHandler) -> ValueChangeHandler:
        """Register a listener for fan speed (``percentage``) changes.

        Parameters
        ----------
        func : callable
            Callable receiving the new ``percentage`` value as
            ``(old_value, new_value)``.

        Returns
        -------
        callable
            The same *func*, returned for decorator use.
        """
        return self._register_attr_listener("percentage", func)

    def on_direction_change(self, func: ValueChangeHandler) -> ValueChangeHandler:
        """Register a listener for fan direction changes.

        Parameters
        ----------
        func : callable
            Callable receiving the new direction string as
            ``(old_value, new_value)``.

        Returns
        -------
        callable
            The same *func*, returned for decorator use.
        """
        return self._register_attr_listener("direction", func)

    # -- Feature detection --------------------------------------------

    def _has_feature(self, mask: int) -> bool:
        """Return ``True`` when ``supported_features`` advertises *mask*.

        Parameters
        ----------
        mask : int
            One of the ``FanEntityFeature`` bit constants.

        Returns
        -------
        bool
            ``True`` if the entity reports an integer
            ``supported_features`` bitmask, otherwise ``False``.
        """
        features = self.attributes.get("supported_features")
        if not isinstance(features, int):
            return False
        return bool(features & mask)

    @property
    def supports_set_speed(self) -> bool:
        """Whether the device advertises ``FanEntityFeature.SET_SPEED``."""
        return self._has_feature(_FEATURE_SET_SPEED)

    @property
    def supports_oscillate(self) -> bool:
        """Whether the device advertises ``FanEntityFeature.OSCILLATE``."""
        return self._has_feature(_FEATURE_OSCILLATE)

    @property
    def supports_direction(self) -> bool:
        """Whether the device advertises ``FanEntityFeature.DIRECTION``."""
        return self._has_feature(_FEATURE_DIRECTION)

    @property
    def supports_preset_mode(self) -> bool:
        """Whether the device advertises ``FanEntityFeature.PRESET_MODE``."""
        return self._has_feature(_FEATURE_PRESET_MODE)

    # -- State properties ---------------------------------------------

    @property
    def is_on(self) -> bool:
        """Whether the fan is currently on."""
        return self.state == "on"

    @property
    def percentage(self) -> int | None:
        """Current fan speed in percent, or ``None`` when not reported."""
        value = self.attributes.get("percentage")
        return int(value) if isinstance(value, (int, float)) else None

    @property
    def preset_mode(self) -> str | None:
        """Active preset mode, or ``None`` when the device has none."""
        value = self.attributes.get("preset_mode")
        return str(value) if isinstance(value, str) else None

    @property
    def preset_modes(self) -> list[str]:
        """Preset modes supported by the device.

        Returns an empty list when the device does not advertise modes.
        Non-string entries in the underlying attribute are filtered out.
        """
        modes = self.attributes.get("preset_modes")
        if not isinstance(modes, list):
            return []
        return [m for m in modes if isinstance(m, str)]

    @property
    def oscillating(self) -> bool | None:
        """Whether the fan is currently oscillating.

        Returns ``None`` when the device does not report this attribute.
        """
        value = self.attributes.get("oscillating")
        return bool(value) if isinstance(value, bool) else None

    @property
    def direction(self) -> str | None:
        """Current fan direction (``"forward"`` or ``"reverse"``).

        Returns ``None`` when the device does not report a direction.
        """
        value = self.attributes.get("direction")
        return str(value) if isinstance(value, str) else None

    # -- Actions ------------------------------------------------------

    async def on(self) -> None:
        """Turn the fan on.

        Invokes the ``fan.turn_on`` Home Assistant service. No feature
        check is performed.

        Raises
        ------
        CommandError
            If Home Assistant rejects the service call.
        HTTPError
            If the REST call returns a non-2xx response.
        TimeoutError
            If the call exceeds the configured request timeout.
        ConnectionClosedError
            If the WebSocket disconnects mid-call.
        """
        await self._call_service("turn_on")

    async def off(self) -> None:
        """Turn the fan off.

        Invokes the ``fan.turn_off`` Home Assistant service.

        Raises
        ------
        CommandError
            If Home Assistant rejects the service call.
        HTTPError
            If the REST call returns a non-2xx response.
        TimeoutError
            If the call exceeds the configured request timeout.
        ConnectionClosedError
            If the WebSocket disconnects mid-call.
        """
        await self._call_service("turn_off")

    async def toggle(self) -> None:
        """Toggle the fan state.

        Invokes the ``fan.toggle`` Home Assistant service.

        Raises
        ------
        CommandError
            If Home Assistant rejects the service call.
        HTTPError
            If the REST call returns a non-2xx response.
        TimeoutError
            If the call exceeds the configured request timeout.
        ConnectionClosedError
            If the WebSocket disconnects mid-call.
        """
        await self._call_service("toggle")

    async def set_percentage(self, percentage: int) -> None:
        """Set the fan speed, in percent.

        Parameters
        ----------
        percentage : int
            Target speed between 0 and 100 (inclusive). ``0`` typically
            turns the fan off.

        Raises
        ------
        ValueError
            If *percentage* is outside the 0-100 range.

        Notes
        -----
        Degrades safely: if the fan does not advertise the
        ``SET_SPEED`` feature, this method logs a debug message and
        returns without raising. Callers can pre-check with
        `supports_set_speed`.
        """
        value = int(percentage)
        if not 0 <= value <= 100:
            raise ValueError("percentage must be between 0 and 100")
        if not self.supports_set_speed:
            _LOGGER.debug(
                "set_percentage() unsupported for %s; skipping (no FanEntityFeature.SET_SPEED)",
                self.entity_id,
            )
            return
        await self._call_service("set_percentage", {"percentage": value})

    async def set_preset_mode(self, mode: str) -> None:
        """Activate a named preset mode, when supported.

        Parameters
        ----------
        mode : str
            Preset mode to activate. Must be one of `preset_modes` when
            the device reports any.

        Raises
        ------
        ValueError
            If the device reports `preset_modes` and *mode* is not in
            that list.

        Notes
        -----
        Degrades safely: if the fan does not advertise the
        ``PRESET_MODE`` feature, or reports no preset modes at all,
        this method logs a debug message and returns without raising.
        Callers can pre-check with `supports_preset_mode`.
        """
        if not self.supports_preset_mode:
            _LOGGER.debug(
                "set_preset_mode() unsupported for %s; skipping (no FanEntityFeature.PRESET_MODE)",
                self.entity_id,
            )
            return
        modes = self.preset_modes
        if not modes:
            # Graceful degradation: device exposes no preset modes.
            _LOGGER.debug(
                "set_preset_mode() skipped for %s; device reports no preset_modes",
                self.entity_id,
            )
            return
        if mode not in modes:
            raise ValueError(
                f"preset_mode {mode!r} not in preset_modes {modes!r}",
            )
        await self._call_service("set_preset_mode", {"preset_mode": mode})

    async def set_direction(self, direction: str) -> None:
        """Set the fan rotation direction, when supported.

        Parameters
        ----------
        direction : str
            Either ``"forward"`` or ``"reverse"``.

        Raises
        ------
        ValueError
            If *direction* is not ``"forward"`` or ``"reverse"``.

        Notes
        -----
        Degrades safely: if the fan does not advertise the
        ``DIRECTION`` feature, this method logs a debug message and
        returns without raising. Callers can pre-check with
        `supports_direction`.
        """
        value = str(direction)
        if value not in _VALID_DIRECTIONS:
            raise ValueError(
                f"direction must be one of {sorted(_VALID_DIRECTIONS)!r}, got {direction!r}",
            )
        if not self.supports_direction:
            _LOGGER.debug(
                "set_direction() unsupported for %s; skipping (no FanEntityFeature.DIRECTION)",
                self.entity_id,
            )
            return
        await self._call_service("set_direction", {"direction": value})

    async def oscillate(self, oscillating: bool) -> None:
        """Toggle oscillation on or off, when supported.

        Parameters
        ----------
        oscillating : bool
            ``True`` to oscillate, ``False`` to stop oscillating.

        Notes
        -----
        Degrades safely: if the fan does not advertise the
        ``OSCILLATE`` feature, this method logs a debug message and
        returns without raising. Callers can pre-check with
        `supports_oscillate`.
        """
        if not self.supports_oscillate:
            _LOGGER.debug(
                "oscillate() unsupported for %s; skipping (no FanEntityFeature.OSCILLATE)",
                self.entity_id,
            )
            return
        await self._call_service("oscillate", {"oscillating": bool(oscillating)})

supports_set_speed property

supports_set_speed: bool

Whether the device advertises FanEntityFeature.SET_SPEED.

supports_oscillate property

supports_oscillate: bool

Whether the device advertises FanEntityFeature.OSCILLATE.

supports_direction property

supports_direction: bool

Whether the device advertises FanEntityFeature.DIRECTION.

supports_preset_mode property

supports_preset_mode: bool

Whether the device advertises FanEntityFeature.PRESET_MODE.

is_on property

is_on: bool

Whether the fan is currently on.

percentage property

percentage: int | None

Current fan speed in percent, or None when not reported.

preset_mode property

preset_mode: str | None

Active preset mode, or None when the device has none.

preset_modes property

preset_modes: list[str]

Preset modes supported by the device.

Returns an empty list when the device does not advertise modes. Non-string entries in the underlying attribute are filtered out.

oscillating property

oscillating: bool | None

Whether the fan is currently oscillating.

Returns None when the device does not report this attribute.

direction property

direction: str | None

Current fan direction ("forward" or "reverse").

Returns None when the device does not report a direction.

on_turn_on

on_turn_on(func: ValueChangeHandler) -> ValueChangeHandler

Register a listener for when the fan turns on.

Parameters:

Name Type Description Default
func callable

Sync or async callable invoked with (old_state, new_state) on every transition into the on state.

required

Returns:

Type Description
callable

The same func, returned for decorator use.

Source code in src/haclient/domains/fan.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def on_turn_on(self, func: ValueChangeHandler) -> ValueChangeHandler:
    """Register a listener for when the fan turns on.

    Parameters
    ----------
    func : callable
        Sync or async callable invoked with ``(old_state, new_state)``
        on every transition into the ``on`` state.

    Returns
    -------
    callable
        The same *func*, returned for decorator use.
    """
    return self._register_state_transition_listener("on", func)

on_turn_off

on_turn_off(func: ValueChangeHandler) -> ValueChangeHandler

Register a listener for when the fan turns off.

Parameters:

Name Type Description Default
func callable

Sync or async callable invoked with (old_state, new_state) on every transition into the off state.

required

Returns:

Type Description
callable

The same func, returned for decorator use.

Source code in src/haclient/domains/fan.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def on_turn_off(self, func: ValueChangeHandler) -> ValueChangeHandler:
    """Register a listener for when the fan turns off.

    Parameters
    ----------
    func : callable
        Sync or async callable invoked with ``(old_state, new_state)``
        on every transition into the ``off`` state.

    Returns
    -------
    callable
        The same *func*, returned for decorator use.
    """
    return self._register_state_transition_listener("off", func)

on_speed_change

on_speed_change(func: ValueChangeHandler) -> ValueChangeHandler

Register a listener for fan speed (percentage) changes.

Parameters:

Name Type Description Default
func callable

Callable receiving the new percentage value as (old_value, new_value).

required

Returns:

Type Description
callable

The same func, returned for decorator use.

Source code in src/haclient/domains/fan.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def on_speed_change(self, func: ValueChangeHandler) -> ValueChangeHandler:
    """Register a listener for fan speed (``percentage``) changes.

    Parameters
    ----------
    func : callable
        Callable receiving the new ``percentage`` value as
        ``(old_value, new_value)``.

    Returns
    -------
    callable
        The same *func*, returned for decorator use.
    """
    return self._register_attr_listener("percentage", func)

on_direction_change

on_direction_change(func: ValueChangeHandler) -> ValueChangeHandler

Register a listener for fan direction changes.

Parameters:

Name Type Description Default
func callable

Callable receiving the new direction string as (old_value, new_value).

required

Returns:

Type Description
callable

The same func, returned for decorator use.

Source code in src/haclient/domains/fan.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def on_direction_change(self, func: ValueChangeHandler) -> ValueChangeHandler:
    """Register a listener for fan direction changes.

    Parameters
    ----------
    func : callable
        Callable receiving the new direction string as
        ``(old_value, new_value)``.

    Returns
    -------
    callable
        The same *func*, returned for decorator use.
    """
    return self._register_attr_listener("direction", func)

on async

on() -> None

Turn the fan on.

Invokes the fan.turn_on Home Assistant service. No feature check is performed.

Raises:

Type Description
CommandError

If Home Assistant rejects the service call.

HTTPError

If the REST call returns a non-2xx response.

TimeoutError

If the call exceeds the configured request timeout.

ConnectionClosedError

If the WebSocket disconnects mid-call.

Source code in src/haclient/domains/fan.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
async def on(self) -> None:
    """Turn the fan on.

    Invokes the ``fan.turn_on`` Home Assistant service. No feature
    check is performed.

    Raises
    ------
    CommandError
        If Home Assistant rejects the service call.
    HTTPError
        If the REST call returns a non-2xx response.
    TimeoutError
        If the call exceeds the configured request timeout.
    ConnectionClosedError
        If the WebSocket disconnects mid-call.
    """
    await self._call_service("turn_on")

off async

off() -> None

Turn the fan off.

Invokes the fan.turn_off Home Assistant service.

Raises:

Type Description
CommandError

If Home Assistant rejects the service call.

HTTPError

If the REST call returns a non-2xx response.

TimeoutError

If the call exceeds the configured request timeout.

ConnectionClosedError

If the WebSocket disconnects mid-call.

Source code in src/haclient/domains/fan.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
async def off(self) -> None:
    """Turn the fan off.

    Invokes the ``fan.turn_off`` Home Assistant service.

    Raises
    ------
    CommandError
        If Home Assistant rejects the service call.
    HTTPError
        If the REST call returns a non-2xx response.
    TimeoutError
        If the call exceeds the configured request timeout.
    ConnectionClosedError
        If the WebSocket disconnects mid-call.
    """
    await self._call_service("turn_off")

toggle async

toggle() -> None

Toggle the fan state.

Invokes the fan.toggle Home Assistant service.

Raises:

Type Description
CommandError

If Home Assistant rejects the service call.

HTTPError

If the REST call returns a non-2xx response.

TimeoutError

If the call exceeds the configured request timeout.

ConnectionClosedError

If the WebSocket disconnects mid-call.

Source code in src/haclient/domains/fan.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
async def toggle(self) -> None:
    """Toggle the fan state.

    Invokes the ``fan.toggle`` Home Assistant service.

    Raises
    ------
    CommandError
        If Home Assistant rejects the service call.
    HTTPError
        If the REST call returns a non-2xx response.
    TimeoutError
        If the call exceeds the configured request timeout.
    ConnectionClosedError
        If the WebSocket disconnects mid-call.
    """
    await self._call_service("toggle")

set_percentage async

set_percentage(percentage: int) -> None

Set the fan speed, in percent.

Parameters:

Name Type Description Default
percentage int

Target speed between 0 and 100 (inclusive). 0 typically turns the fan off.

required

Raises:

Type Description
ValueError

If percentage is outside the 0-100 range.

Notes

Degrades safely: if the fan does not advertise the SET_SPEED feature, this method logs a debug message and returns without raising. Callers can pre-check with supports_set_speed.

Source code in src/haclient/domains/fan.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
async def set_percentage(self, percentage: int) -> None:
    """Set the fan speed, in percent.

    Parameters
    ----------
    percentage : int
        Target speed between 0 and 100 (inclusive). ``0`` typically
        turns the fan off.

    Raises
    ------
    ValueError
        If *percentage* is outside the 0-100 range.

    Notes
    -----
    Degrades safely: if the fan does not advertise the
    ``SET_SPEED`` feature, this method logs a debug message and
    returns without raising. Callers can pre-check with
    `supports_set_speed`.
    """
    value = int(percentage)
    if not 0 <= value <= 100:
        raise ValueError("percentage must be between 0 and 100")
    if not self.supports_set_speed:
        _LOGGER.debug(
            "set_percentage() unsupported for %s; skipping (no FanEntityFeature.SET_SPEED)",
            self.entity_id,
        )
        return
    await self._call_service("set_percentage", {"percentage": value})

set_preset_mode async

set_preset_mode(mode: str) -> None

Activate a named preset mode, when supported.

Parameters:

Name Type Description Default
mode str

Preset mode to activate. Must be one of preset_modes when the device reports any.

required

Raises:

Type Description
ValueError

If the device reports preset_modes and mode is not in that list.

Notes

Degrades safely: if the fan does not advertise the PRESET_MODE feature, or reports no preset modes at all, this method logs a debug message and returns without raising. Callers can pre-check with supports_preset_mode.

Source code in src/haclient/domains/fan.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
async def set_preset_mode(self, mode: str) -> None:
    """Activate a named preset mode, when supported.

    Parameters
    ----------
    mode : str
        Preset mode to activate. Must be one of `preset_modes` when
        the device reports any.

    Raises
    ------
    ValueError
        If the device reports `preset_modes` and *mode* is not in
        that list.

    Notes
    -----
    Degrades safely: if the fan does not advertise the
    ``PRESET_MODE`` feature, or reports no preset modes at all,
    this method logs a debug message and returns without raising.
    Callers can pre-check with `supports_preset_mode`.
    """
    if not self.supports_preset_mode:
        _LOGGER.debug(
            "set_preset_mode() unsupported for %s; skipping (no FanEntityFeature.PRESET_MODE)",
            self.entity_id,
        )
        return
    modes = self.preset_modes
    if not modes:
        # Graceful degradation: device exposes no preset modes.
        _LOGGER.debug(
            "set_preset_mode() skipped for %s; device reports no preset_modes",
            self.entity_id,
        )
        return
    if mode not in modes:
        raise ValueError(
            f"preset_mode {mode!r} not in preset_modes {modes!r}",
        )
    await self._call_service("set_preset_mode", {"preset_mode": mode})

set_direction async

set_direction(direction: str) -> None

Set the fan rotation direction, when supported.

Parameters:

Name Type Description Default
direction str

Either "forward" or "reverse".

required

Raises:

Type Description
ValueError

If direction is not "forward" or "reverse".

Notes

Degrades safely: if the fan does not advertise the DIRECTION feature, this method logs a debug message and returns without raising. Callers can pre-check with supports_direction.

Source code in src/haclient/domains/fan.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
async def set_direction(self, direction: str) -> None:
    """Set the fan rotation direction, when supported.

    Parameters
    ----------
    direction : str
        Either ``"forward"`` or ``"reverse"``.

    Raises
    ------
    ValueError
        If *direction* is not ``"forward"`` or ``"reverse"``.

    Notes
    -----
    Degrades safely: if the fan does not advertise the
    ``DIRECTION`` feature, this method logs a debug message and
    returns without raising. Callers can pre-check with
    `supports_direction`.
    """
    value = str(direction)
    if value not in _VALID_DIRECTIONS:
        raise ValueError(
            f"direction must be one of {sorted(_VALID_DIRECTIONS)!r}, got {direction!r}",
        )
    if not self.supports_direction:
        _LOGGER.debug(
            "set_direction() unsupported for %s; skipping (no FanEntityFeature.DIRECTION)",
            self.entity_id,
        )
        return
    await self._call_service("set_direction", {"direction": value})

oscillate async

oscillate(oscillating: bool) -> None

Toggle oscillation on or off, when supported.

Parameters:

Name Type Description Default
oscillating bool

True to oscillate, False to stop oscillating.

required
Notes

Degrades safely: if the fan does not advertise the OSCILLATE feature, this method logs a debug message and returns without raising. Callers can pre-check with supports_oscillate.

Source code in src/haclient/domains/fan.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
async def oscillate(self, oscillating: bool) -> None:
    """Toggle oscillation on or off, when supported.

    Parameters
    ----------
    oscillating : bool
        ``True`` to oscillate, ``False`` to stop oscillating.

    Notes
    -----
    Degrades safely: if the fan does not advertise the
    ``OSCILLATE`` feature, this method logs a debug message and
    returns without raising. Callers can pre-check with
    `supports_oscillate`.
    """
    if not self.supports_oscillate:
        _LOGGER.debug(
            "oscillate() unsupported for %s; skipping (no FanEntityFeature.OSCILLATE)",
            self.entity_id,
        )
        return
    await self._call_service("oscillate", {"oscillating": bool(oscillating)})