Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Platform for Flexit AC units with CI66 Modbus adapter."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import voluptuous as vol
9 
11  PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA,
12  ClimateEntity,
13  ClimateEntityFeature,
14  HVACAction,
15  HVACMode,
16 )
18  CALL_TYPE_REGISTER_HOLDING,
19  CALL_TYPE_REGISTER_INPUT,
20  DEFAULT_HUB,
21  ModbusHub,
22  get_hub,
23 )
24 from homeassistant.const import (
25  ATTR_TEMPERATURE,
26  CONF_NAME,
27  CONF_SLAVE,
28  DEVICE_DEFAULT_NAME,
29  UnitOfTemperature,
30 )
31 from homeassistant.core import HomeAssistant
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
35 
36 CALL_TYPE_WRITE_REGISTER = "write_register"
37 CONF_HUB = "hub"
38 
39 PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend(
40  {
41  vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
42  vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)),
43  vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string,
44  }
45 )
46 
47 _LOGGER = logging.getLogger(__name__)
48 
49 
51  hass: HomeAssistant,
52  config: ConfigType,
53  async_add_entities: AddEntitiesCallback,
54  discovery_info: DiscoveryInfoType | None = None,
55 ) -> None:
56  """Set up the Flexit Platform."""
57  modbus_slave = config.get(CONF_SLAVE)
58  name = config.get(CONF_NAME)
59  hub = get_hub(hass, config[CONF_HUB])
60  async_add_entities([Flexit(hub, modbus_slave, name)], True)
61 
62 
64  """Representation of a Flexit AC unit."""
65 
66  _attr_fan_modes = ["Off", "Low", "Medium", "High"]
67  _attr_hvac_mode = HVACMode.COOL
68  _attr_hvac_modes = [HVACMode.COOL]
69  _attr_supported_features = (
70  ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
71  )
72  _attr_temperature_unit = UnitOfTemperature.CELSIUS
73  _enable_turn_on_off_backwards_compatibility = False
74 
75  def __init__(
76  self, hub: ModbusHub, modbus_slave: int | None, name: str | None
77  ) -> None:
78  """Initialize the unit."""
79  self._hub_hub = hub
80  self._attr_name_attr_name = name
81  self._slave_slave = modbus_slave
82  self._attr_fan_mode_attr_fan_mode = None
83  self._filter_hours_filter_hours: int | None = None
84  self._filter_alarm_filter_alarm: int | None = None
85  self._heat_recovery_heat_recovery: int | None = None
86  self._heater_enabled_heater_enabled: int | None = None
87  self._heating_heating: int | None = None
88  self._cooling_cooling: int | None = None
89  self._alarm_alarm = False
90  self._outdoor_air_temp_outdoor_air_temp: float | None = None
91 
92  async def async_update(self) -> None:
93  """Update unit attributes."""
94  self._attr_target_temperature_attr_target_temperature = await self._async_read_temp_from_register_async_read_temp_from_register(
95  CALL_TYPE_REGISTER_HOLDING, 8
96  )
97  self._attr_current_temperature_attr_current_temperature = await self._async_read_temp_from_register_async_read_temp_from_register(
98  CALL_TYPE_REGISTER_INPUT, 9
99  )
100  res = await self._async_read_int16_from_register_async_read_int16_from_register(CALL_TYPE_REGISTER_HOLDING, 17)
101  if self.fan_modesfan_modes and res < len(self.fan_modesfan_modes):
102  self._attr_fan_mode_attr_fan_mode = self.fan_modesfan_modes[res]
103  self._filter_hours_filter_hours = await self._async_read_int16_from_register_async_read_int16_from_register(
104  CALL_TYPE_REGISTER_INPUT, 8
105  )
106  # # Mechanical heat recovery, 0-100%
107  self._heat_recovery_heat_recovery = await self._async_read_int16_from_register_async_read_int16_from_register(
108  CALL_TYPE_REGISTER_INPUT, 14
109  )
110  # # Heater active 0-100%
111  self._heating_heating = await self._async_read_int16_from_register_async_read_int16_from_register(
112  CALL_TYPE_REGISTER_INPUT, 15
113  )
114  # # Cooling active 0-100%
115  self._cooling_cooling = await self._async_read_int16_from_register_async_read_int16_from_register(
116  CALL_TYPE_REGISTER_INPUT, 13
117  )
118  # # Filter alarm 0/1
119  self._filter_alarm_filter_alarm = await self._async_read_int16_from_register_async_read_int16_from_register(
120  CALL_TYPE_REGISTER_INPUT, 27
121  )
122  # # Heater enabled or not. Does not mean it's necessarily heating
123  self._heater_enabled_heater_enabled = await self._async_read_int16_from_register_async_read_int16_from_register(
124  CALL_TYPE_REGISTER_INPUT, 28
125  )
126  self._outdoor_air_temp_outdoor_air_temp = await self._async_read_temp_from_register_async_read_temp_from_register(
127  CALL_TYPE_REGISTER_INPUT, 11
128  )
129 
130  actual_air_speed = await self._async_read_int16_from_register_async_read_int16_from_register(
131  CALL_TYPE_REGISTER_INPUT, 48
132  )
133 
134  if self._heating_heating:
135  self._attr_hvac_action_attr_hvac_action = HVACAction.HEATING
136  elif self._cooling_cooling:
137  self._attr_hvac_action_attr_hvac_action = HVACAction.COOLING
138  elif self._heat_recovery_heat_recovery:
139  self._attr_hvac_action_attr_hvac_action = HVACAction.IDLE
140  elif actual_air_speed:
141  self._attr_hvac_action_attr_hvac_action = HVACAction.FAN
142  else:
143  self._attr_hvac_action_attr_hvac_action = HVACAction.OFF
144 
145  @property
146  def extra_state_attributes(self) -> dict[str, Any]:
147  """Return device specific state attributes."""
148  return {
149  "filter_hours": self._filter_hours_filter_hours,
150  "filter_alarm": self._filter_alarm_filter_alarm,
151  "heat_recovery": self._heat_recovery_heat_recovery,
152  "heating": self._heating_heating,
153  "heater_enabled": self._heater_enabled_heater_enabled,
154  "cooling": self._cooling_cooling,
155  "outdoor_air_temp": self._outdoor_air_temp_outdoor_air_temp,
156  }
157 
158  async def async_set_temperature(self, **kwargs: Any) -> None:
159  """Set new target temperature."""
160  if (target_temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
161  _LOGGER.error("Received invalid temperature")
162  return
163 
164  if await self._async_write_int16_to_register_async_write_int16_to_register(8, int(target_temperature * 10)):
165  self._attr_target_temperature_attr_target_temperature = target_temperature
166  else:
167  _LOGGER.error("Modbus error setting target temperature to Flexit")
168 
169  async def async_set_fan_mode(self, fan_mode: str) -> None:
170  """Set new fan mode."""
171  if self.fan_modesfan_modes and await self._async_write_int16_to_register_async_write_int16_to_register(
172  17, self.fan_modesfan_modes.index(fan_mode)
173  ):
174  self._attr_fan_mode_attr_fan_mode = fan_mode
175  else:
176  _LOGGER.error("Modbus error setting fan mode to Flexit")
177 
178  # Based on _async_read_register in ModbusThermostat class
180  self, register_type: str, register: int
181  ) -> int:
182  """Read register using the Modbus hub slave."""
183  result = await self._hub_hub.async_pb_call(self._slave_slave, register, 1, register_type)
184  if result is None:
185  _LOGGER.error("Error reading value from Flexit modbus adapter")
186  return -1
187 
188  return int(result.registers[0])
189 
191  self, register_type: str, register: int
192  ) -> float:
193  result = float(
194  await self._async_read_int16_from_register_async_read_int16_from_register(register_type, register)
195  )
196  if not result:
197  return -1
198  return result / 10.0
199 
200  async def _async_write_int16_to_register(self, register: int, value: int) -> bool:
201  result = await self._hub_hub.async_pb_call(
202  self._slave_slave, register, value, CALL_TYPE_WRITE_REGISTER
203  )
204  if not result:
205  return False
206  return True
None async_set_fan_mode(self, str fan_mode)
Definition: climate.py:169
bool _async_write_int16_to_register(self, int register, int value)
Definition: climate.py:200
float _async_read_temp_from_register(self, str register_type, int register)
Definition: climate.py:192
int _async_read_int16_from_register(self, str register_type, int register)
Definition: climate.py:181
None __init__(self, ModbusHub hub, int|None modbus_slave, str|None name)
Definition: climate.py:77
None async_set_temperature(self, **Any kwargs)
Definition: climate.py:158
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: climate.py:55
ModbusHub get_hub(HomeAssistant hass, str name)
Definition: __init__.py:445