-
-
Notifications
You must be signed in to change notification settings - Fork 28.4k
/
climate.py
231 lines (202 loc) · 8.32 KB
/
climate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
"""The lookin integration climate platform."""
from __future__ import annotations
import logging
from typing import Any, Final, cast
from aiolookin import Climate, MeteoSensor
from aiolookin.models import UDPCommandType, UDPEvent
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
ATTR_HVAC_MODE,
FAN_AUTO,
FAN_HIGH,
FAN_LOW,
FAN_MIDDLE,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_FAN_MODE,
SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE,
SWING_BOTH,
SWING_OFF,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TEMPERATURE,
PRECISION_WHOLE,
TEMP_CELSIUS,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, TYPE_TO_PLATFORM
from .entity import LookinCoordinatorEntity
from .models import LookinData
SUPPORT_FLAGS: int = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_SWING_MODE
LOOKIN_FAN_MODE_IDX_TO_HASS: Final = [FAN_AUTO, FAN_LOW, FAN_MIDDLE, FAN_HIGH]
LOOKIN_SWING_MODE_IDX_TO_HASS: Final = [SWING_OFF, SWING_BOTH]
LOOKIN_HVAC_MODE_IDX_TO_HASS: Final = [
HVAC_MODE_OFF,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
]
HASS_TO_LOOKIN_HVAC_MODE: dict[str, int] = {
mode: idx for idx, mode in enumerate(LOOKIN_HVAC_MODE_IDX_TO_HASS)
}
HASS_TO_LOOKIN_FAN_MODE: dict[str, int] = {
mode: idx for idx, mode in enumerate(LOOKIN_FAN_MODE_IDX_TO_HASS)
}
HASS_TO_LOOKIN_SWING_MODE: dict[str, int] = {
mode: idx for idx, mode in enumerate(LOOKIN_SWING_MODE_IDX_TO_HASS)
}
MIN_TEMP: Final = 16
MAX_TEMP: Final = 30
LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the climate platform for lookin from a config entry."""
lookin_data: LookinData = hass.data[DOMAIN][config_entry.entry_id]
entities = []
for remote in lookin_data.devices:
if TYPE_TO_PLATFORM.get(remote["Type"]) != Platform.CLIMATE:
continue
uuid = remote["UUID"]
coordinator = lookin_data.device_coordinators[uuid]
device: Climate = coordinator.data
entities.append(
ConditionerEntity(
uuid=uuid,
device=device,
lookin_data=lookin_data,
coordinator=coordinator,
)
)
async_add_entities(entities)
class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity):
"""An aircon or heat pump."""
_attr_current_humidity: float | None = None # type: ignore
_attr_temperature_unit = TEMP_CELSIUS
_attr_supported_features: int = SUPPORT_FLAGS
_attr_fan_modes: list[str] = LOOKIN_FAN_MODE_IDX_TO_HASS
_attr_swing_modes: list[str] = LOOKIN_SWING_MODE_IDX_TO_HASS
_attr_hvac_modes: list[str] = LOOKIN_HVAC_MODE_IDX_TO_HASS
_attr_min_temp = MIN_TEMP
_attr_max_temp = MAX_TEMP
_attr_target_temperature_step = PRECISION_WHOLE
def __init__(
self,
uuid: str,
device: Climate,
lookin_data: LookinData,
coordinator: DataUpdateCoordinator,
) -> None:
"""Init the ConditionerEntity."""
super().__init__(coordinator, uuid, device, lookin_data)
self._async_update_from_data()
@property
def _climate(self) -> Climate:
return cast(Climate, self.coordinator.data)
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set the hvac mode of the device."""
if (mode := HASS_TO_LOOKIN_HVAC_MODE.get(hvac_mode)) is None:
return
self._climate.hvac_mode = mode
await self._async_update_conditioner()
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the temperature of the device."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
self._climate.temp_celsius = int(temperature)
lookin_index = LOOKIN_HVAC_MODE_IDX_TO_HASS
if hvac_mode := kwargs.get(ATTR_HVAC_MODE):
self._climate.hvac_mode = HASS_TO_LOOKIN_HVAC_MODE[hvac_mode]
elif self._climate.hvac_mode == lookin_index.index(HVAC_MODE_OFF):
#
# If the device is off, and the user didn't specify an HVAC mode
# (which is the default when using the HA UI), the device won't turn
# on without having an HVAC mode passed.
#
# We picked the hvac mode based on the current temp if its available
# since only some units support auto, but most support either heat
# or cool otherwise we set auto since we don't have a way to make
# an educated guess.
#
meteo_data: MeteoSensor = self._meteo_coordinator.data
current_temp = meteo_data.temperature
if not current_temp:
self._climate.hvac_mode = lookin_index.index(HVAC_MODE_AUTO)
elif current_temp >= self._climate.temp_celsius:
self._climate.hvac_mode = lookin_index.index(HVAC_MODE_COOL)
else:
self._climate.hvac_mode = lookin_index.index(HVAC_MODE_HEAT)
await self._async_update_conditioner()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set the fan mode of the device."""
if (mode := HASS_TO_LOOKIN_FAN_MODE.get(fan_mode)) is None:
return
self._climate.fan_mode = mode
await self._async_update_conditioner()
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set the swing mode of the device."""
if (mode := HASS_TO_LOOKIN_SWING_MODE.get(swing_mode)) is None:
return
self._climate.swing_mode = mode
await self._async_update_conditioner()
async def _async_update_conditioner(self) -> None:
"""Update the conditioner state from the climate data."""
self.coordinator.async_set_updated_data(self._climate)
await self._lookin_protocol.update_conditioner(climate=self._climate)
def _async_update_from_data(self) -> None:
"""Update attrs from data."""
meteo_data: MeteoSensor = self._meteo_coordinator.data
self._attr_current_temperature = meteo_data.temperature
self._attr_current_humidity = int(meteo_data.humidity)
self._attr_target_temperature = self._climate.temp_celsius
self._attr_fan_mode = LOOKIN_FAN_MODE_IDX_TO_HASS[self._climate.fan_mode]
self._attr_swing_mode = LOOKIN_SWING_MODE_IDX_TO_HASS[self._climate.swing_mode]
self._attr_hvac_mode = LOOKIN_HVAC_MODE_IDX_TO_HASS[self._climate.hvac_mode]
@callback
def _async_update_meteo_from_value(self, event: UDPEvent) -> None:
"""Update temperature and humidity from UDP event."""
self._attr_current_temperature = float(int(event.value[:4], 16)) / 10
self._attr_current_humidity = float(int(event.value[-4:], 16)) / 10
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._async_update_from_data()
super()._handle_coordinator_update()
@callback
def _async_push_update(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
self._climate.update_from_status(event.value)
self.coordinator.async_set_updated_data(self._climate)
async def async_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.ir,
self._uuid,
self._async_push_update,
)
)
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.meteo,
None,
self._async_update_meteo_from_value,
)
)
return await super().async_added_to_hass()