Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import math
7 from typing import Any
8 
9 from pycomfoconnect import (
10  CMD_FAN_MODE_AWAY,
11  CMD_FAN_MODE_HIGH,
12  CMD_FAN_MODE_LOW,
13  CMD_FAN_MODE_MEDIUM,
14  CMD_MODE_AUTO,
15  CMD_MODE_MANUAL,
16  SENSOR_FAN_SPEED_MODE,
17  SENSOR_OPERATING_MODE_BIS,
18 )
19 
20 from homeassistant.components.fan import FanEntity, FanEntityFeature
21 from homeassistant.core import HomeAssistant
22 from homeassistant.helpers.dispatcher import async_dispatcher_connect
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
26  percentage_to_ranged_value,
27  ranged_value_to_percentage,
28 )
29 from homeassistant.util.scaling import int_states_in_range
30 
31 from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 CMD_MAPPING = {
36  0: CMD_FAN_MODE_AWAY,
37  1: CMD_FAN_MODE_LOW,
38  2: CMD_FAN_MODE_MEDIUM,
39  3: CMD_FAN_MODE_HIGH,
40 }
41 
42 SPEED_RANGE = (1, 3) # away is not included in speeds and instead mapped to off
43 
44 PRESET_MODE_AUTO = "auto"
45 PRESET_MODES = [PRESET_MODE_AUTO]
46 
47 
49  hass: HomeAssistant,
50  config: ConfigType,
51  add_entities: AddEntitiesCallback,
52  discovery_info: DiscoveryInfoType | None = None,
53 ) -> None:
54  """Set up the ComfoConnect fan platform."""
55  ccb = hass.data[DOMAIN]
56 
57  add_entities([ComfoConnectFan(ccb)], True)
58 
59 
61  """Representation of the ComfoConnect fan platform."""
62 
63  _attr_icon = "mdi:air-conditioner"
64  _attr_should_poll = False
65  _attr_supported_features = (
66  FanEntityFeature.SET_SPEED
67  | FanEntityFeature.PRESET_MODE
68  | FanEntityFeature.TURN_OFF
69  | FanEntityFeature.TURN_ON
70  )
71  _enable_turn_on_off_backwards_compatibility = False
72  _attr_preset_modes = PRESET_MODES
73  current_speed: float | None = None
74 
75  def __init__(self, ccb: ComfoConnectBridge) -> None:
76  """Initialize the ComfoConnect fan."""
77  self._ccb_ccb = ccb
78  self._attr_name_attr_name = ccb.name
79  self._attr_unique_id_attr_unique_id = ccb.unique_id
80  self._attr_preset_mode_attr_preset_mode = None
81 
82  async def async_added_to_hass(self) -> None:
83  """Register for sensor updates."""
84  _LOGGER.debug("Registering for fan speed")
85  self.async_on_removeasync_on_remove(
87  self.hasshass,
88  SIGNAL_COMFOCONNECT_UPDATE_RECEIVED.format(SENSOR_FAN_SPEED_MODE),
89  self._handle_speed_update_handle_speed_update,
90  )
91  )
92  self.async_on_removeasync_on_remove(
94  self.hasshass,
95  SIGNAL_COMFOCONNECT_UPDATE_RECEIVED.format(SENSOR_OPERATING_MODE_BIS),
96  self._handle_mode_update_handle_mode_update,
97  )
98  )
99  await self.hasshass.async_add_executor_job(
100  self._ccb_ccb.comfoconnect.register_sensor, SENSOR_FAN_SPEED_MODE
101  )
102  await self.hasshass.async_add_executor_job(
103  self._ccb_ccb.comfoconnect.register_sensor, SENSOR_OPERATING_MODE_BIS
104  )
105 
106  def _handle_speed_update(self, value: float) -> None:
107  """Handle update callbacks."""
108  _LOGGER.debug(
109  "Handle update for fan speed (%d): %s", SENSOR_FAN_SPEED_MODE, value
110  )
111  self.current_speedcurrent_speed = value
112  self.schedule_update_ha_stateschedule_update_ha_state()
113 
114  def _handle_mode_update(self, value: int) -> None:
115  """Handle update callbacks."""
116  _LOGGER.debug(
117  "Handle update for operating mode (%d): %s",
118  SENSOR_OPERATING_MODE_BIS,
119  value,
120  )
121  self._attr_preset_mode_attr_preset_mode = PRESET_MODE_AUTO if value == -1 else None
122  self.schedule_update_ha_stateschedule_update_ha_state()
123 
124  @property
125  def percentage(self) -> int | None:
126  """Return the current speed percentage."""
127  if self.current_speedcurrent_speed is None:
128  return None
129  return ranged_value_to_percentage(SPEED_RANGE, self.current_speedcurrent_speed)
130 
131  @property
132  def speed_count(self) -> int:
133  """Return the number of speeds the fan supports."""
134  return int_states_in_range(SPEED_RANGE)
135 
136  def turn_on(
137  self,
138  percentage: int | None = None,
139  preset_mode: str | None = None,
140  **kwargs: Any,
141  ) -> None:
142  """Turn on the fan."""
143  if preset_mode:
144  self.set_preset_modeset_preset_modeset_preset_mode(preset_mode)
145  return
146 
147  if percentage is None:
148  self.set_percentageset_percentageset_percentage(1) # Set fan speed to low
149  else:
150  self.set_percentageset_percentageset_percentage(percentage)
151 
152  def turn_off(self, **kwargs: Any) -> None:
153  """Turn off the fan (to away)."""
154  self.set_percentageset_percentageset_percentage(0)
155 
156  def set_percentage(self, percentage: int) -> None:
157  """Set fan speed percentage."""
158  _LOGGER.debug("Changing fan speed percentage to %s", percentage)
159 
160  if percentage == 0:
161  cmd = CMD_FAN_MODE_AWAY
162  else:
163  speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage))
164  cmd = CMD_MAPPING[speed]
165 
166  self._ccb_ccb.comfoconnect.cmd_rmi_request(cmd)
167 
168  def set_preset_mode(self, preset_mode: str) -> None:
169  """Set new preset mode."""
170  if not self.preset_modespreset_modes or preset_mode not in self.preset_modespreset_modes:
171  raise ValueError(f"Invalid preset mode: {preset_mode}")
172 
173  _LOGGER.debug("Changing preset mode to %s", preset_mode)
174  if preset_mode == PRESET_MODE_AUTO:
175  # force set it to manual first
176  self._ccb_ccb.comfoconnect.cmd_rmi_request(CMD_MODE_MANUAL)
177  # now set it to auto so any previous percentage set gets undone
178  self._ccb_ccb.comfoconnect.cmd_rmi_request(CMD_MODE_AUTO)
None __init__(self, ComfoConnectBridge ccb)
Definition: fan.py:75
None turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:141
None set_preset_mode(self, str preset_mode)
Definition: __init__.py:375
None set_percentage(self, int percentage)
Definition: __init__.py:336
list[str]|None preset_modes(self)
Definition: __init__.py:540
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: fan.py:53
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
float percentage_to_ranged_value(tuple[float, float] low_high_range, float percentage)
Definition: percentage.py:81
int ranged_value_to_percentage(tuple[float, float] low_high_range, float value)
Definition: percentage.py:64
int int_states_in_range(tuple[float, float] low_high_range)
Definition: scaling.py:61