Controllers.dehumidifier

Контроллер осушителя Яндекс Умного дома.

Позволяет получать состояние осушителя (влажность, температура, онлайн-статус, режим, скорость вентилятора) и управлять им (включить/выключить) через Yandex IoT API.

  1"""Контроллер осушителя Яндекс Умного дома.
  2
  3Позволяет получать состояние осушителя (влажность, температура,
  4онлайн-статус, режим, скорость вентилятора) и управлять им
  5(включить/выключить) через Yandex IoT API.
  6"""
  7
  8import time
  9
 10import requests
 11from langfuse import observe
 12from loguru import logger
 13
 14from Controllers.controller import Controller
 15from DataSources import YandexDataSource
 16
 17
 18class DehumidifierController(Controller, YandexDataSource):
 19    """Контроллер для управления осушителем Яндекс Умного дома.
 20
 21    Устройство devices.types.humidifier (TUYA RMD-312) — на самом деле
 22    осушитель. Яндекс отнес его к humidifier, но он сушит воздух.
 23    Устройство является одновременно сенсором (влажность, температура)
 24    и actuator (on_off, mode, fan_speed).
 25
 26    Attributes:
 27        device_id: Идентификатор устройства в Яндекс Умном доме.
 28        state_retry_count: Количество попыток получения состояния.
 29        state_retry_delay: Задержка между попытками в секундах.
 30        state: Последнее полученное состояние осушителя.
 31    """
 32
 33    def __init__(
 34        self,
 35        device_id: str,
 36        api_key: str,
 37        state_retry_count: int = 3,
 38        state_retry_delay: int = 2,
 39    ):
 40        self.device_id = device_id
 41        Controller.__init__(self)
 42        YandexDataSource.__init__(self, api_key)
 43        self.state_retry_count = state_retry_count
 44        self.state_retry_delay = state_retry_delay
 45        self.state = self.get_state()
 46
 47    def __str__(self) -> str:
 48        state = self.state
 49        name = state.get("name", "Осушитель")
 50        online = state.get("online", "unknown")
 51        power = state.get("power", False)
 52        humidity = state.get("humidity", 0)
 53        temperature = state.get("temperature", 0)
 54        mode = state.get("mode", "normal")
 55        fan_speed = state.get("fan_speed", "low")
 56        return (
 57            f"{name} [{online}]: {'включен' if power else 'выключен'}, "
 58            f"влажность {humidity}%, температура {temperature}°C, "
 59            f"режим {mode}, вентилятор {fan_speed}"
 60        )
 61
 62    @observe(name="Получение состояния осушителя")
 63    def get_state(self):
 64        for attempt in range(self.state_retry_count):
 65            try:
 66                response = requests.get(
 67                    self.url + f"/{self.device_id}",
 68                    headers=self.yandex_headers,
 69                    timeout=10,
 70                )
 71                response.raise_for_status()
 72                device = response.json()
 73
 74                state = {
 75                    "device_id": self.device_id,
 76                    "name": device.get("name", ""),
 77                    "online": device.get("state", "unknown"),
 78                }
 79
 80                for capability in device.get("capabilities", []):
 81                    cap_type = capability.get("type")
 82                    cap_state = capability.get("state", {})
 83                    if cap_type == "devices.capabilities.on_off":
 84                        state["power"] = cap_state.get("value", False)
 85                    elif cap_type == "devices.capabilities.mode":
 86                        instance = capability.get("parameters", {}).get("instance", "")
 87                        if instance == "cleanup_mode":
 88                            state["mode"] = cap_state.get("value", "normal")
 89                        elif instance == "fan_speed":
 90                            state["fan_speed"] = cap_state.get("value", "low")
 91                    elif cap_type == "devices.capabilities.toggle":
 92                        instance = capability.get("parameters", {}).get("instance", "")
 93                        if instance == "controls_locked":
 94                            state["controls_locked"] = cap_state.get("value", False)
 95
 96                for prop in device.get("properties", []):
 97                    if prop.get("type") == "devices.properties.float":
 98                        instance = prop.get("parameters", {}).get("instance", "")
 99                        value = prop.get("state", {}).get("value")
100                        if instance == "humidity":
101                            state["humidity"] = value
102                        elif instance == "temperature":
103                            state["temperature"] = value
104
105                self.state = state
106                return state
107            except requests.exceptions.RequestException as e:
108                logger.error(f"Попытка {attempt + 1}/{self.state_retry_count}: {e}")
109                if attempt < self.state_retry_count - 1:
110                    time.sleep(self.state_retry_delay)
111
112        self.state = {
113            "device_id": self.device_id,
114            "name": "",
115            "online": "unknown",
116            "power": False,
117            "humidity": 0,
118            "temperature": 0,
119            "mode": "normal",
120            "fan_speed": "low",
121            "controls_locked": False,
122        }
123        logger.error(
124            f"Не удалось получить состояние осушителя после {self.state_retry_count} попыток"
125        )
126        return self.state
127
128    @observe(name="Управление осушителем (включение/выключение)")
129    def control_dehumidifier(self, turn_on: bool) -> bool:
130        try:
131            payload = {
132                "devices": [
133                    {
134                        "id": self.device_id,
135                        "actions": [
136                            {
137                                "type": "devices.capabilities.on_off",
138                                "state": {"instance": "on", "value": turn_on},
139                            }
140                        ],
141                    }
142                ]
143            }
144            response = requests.post(
145                self.url + "/actions",
146                headers=self.yandex_headers,
147                json=payload,
148                timeout=10,
149            )
150            response.raise_for_status()
151            action_text = "включен" if turn_on else "выключен"
152            logger.debug(f"Осушитель успешно {action_text}")
153            return True
154        except requests.exceptions.RequestException as e:
155            logger.error(f"Ошибка при управлении осушителем: {e}")
156            if hasattr(e, "response") and e.response is not None:
157                logger.error(f"Статус код: {e.response.status_code}")
158                logger.error(f"Ответ: {e.response.text}")
159            return False
160        except (TypeError, ValueError, KeyError) as e:
161            logger.error(f"Неожиданная ошибка при управлении осушителем: {e}")
162            return False
163
164    @observe(name="Установка режима осушителя")
165    def set_mode(self, mode: str) -> bool:
166        """Устанавливает режим работы (quiet/normal/express)."""
167        try:
168            payload = {
169                "devices": [
170                    {
171                        "id": self.device_id,
172                        "actions": [
173                            {
174                                "type": "devices.capabilities.mode",
175                                "state": {"instance": "cleanup_mode", "value": mode},
176                            }
177                        ],
178                    }
179                ]
180            }
181            response = requests.post(
182                self.url + "/actions",
183                headers=self.yandex_headers,
184                json=payload,
185                timeout=10,
186            )
187            response.raise_for_status()
188            logger.debug(f"Режим осушителя установлен: {mode}")
189            return True
190        except requests.exceptions.RequestException as e:
191            logger.error(f"Ошибка при установке режима осушителя: {e}")
192            return False
193
194    @observe(name="Установка скорости вентилятора осушителя")
195    def set_fan_speed(self, speed: str) -> bool:
196        """Устанавливает скорость вентилятора (low/high)."""
197        try:
198            payload = {
199                "devices": [
200                    {
201                        "id": self.device_id,
202                        "actions": [
203                            {
204                                "type": "devices.capabilities.mode",
205                                "state": {"instance": "fan_speed", "value": speed},
206                            }
207                        ],
208                    }
209                ]
210            }
211            response = requests.post(
212                self.url + "/actions",
213                headers=self.yandex_headers,
214                json=payload,
215                timeout=10,
216            )
217            response.raise_for_status()
218            logger.debug(f"Скорость вентилятора осушителя установлена: {speed}")
219            return True
220        except requests.exceptions.RequestException as e:
221            logger.error(f"Ошибка при установке скорости вентилятора: {e}")
222            return False
class DehumidifierController(Controllers.controller.Controller, DataSources.yandex.YandexDataSource):
 19class DehumidifierController(Controller, YandexDataSource):
 20    """Контроллер для управления осушителем Яндекс Умного дома.
 21
 22    Устройство devices.types.humidifier (TUYA RMD-312) — на самом деле
 23    осушитель. Яндекс отнес его к humidifier, но он сушит воздух.
 24    Устройство является одновременно сенсором (влажность, температура)
 25    и actuator (on_off, mode, fan_speed).
 26
 27    Attributes:
 28        device_id: Идентификатор устройства в Яндекс Умном доме.
 29        state_retry_count: Количество попыток получения состояния.
 30        state_retry_delay: Задержка между попытками в секундах.
 31        state: Последнее полученное состояние осушителя.
 32    """
 33
 34    def __init__(
 35        self,
 36        device_id: str,
 37        api_key: str,
 38        state_retry_count: int = 3,
 39        state_retry_delay: int = 2,
 40    ):
 41        self.device_id = device_id
 42        Controller.__init__(self)
 43        YandexDataSource.__init__(self, api_key)
 44        self.state_retry_count = state_retry_count
 45        self.state_retry_delay = state_retry_delay
 46        self.state = self.get_state()
 47
 48    def __str__(self) -> str:
 49        state = self.state
 50        name = state.get("name", "Осушитель")
 51        online = state.get("online", "unknown")
 52        power = state.get("power", False)
 53        humidity = state.get("humidity", 0)
 54        temperature = state.get("temperature", 0)
 55        mode = state.get("mode", "normal")
 56        fan_speed = state.get("fan_speed", "low")
 57        return (
 58            f"{name} [{online}]: {'включен' if power else 'выключен'}, "
 59            f"влажность {humidity}%, температура {temperature}°C, "
 60            f"режим {mode}, вентилятор {fan_speed}"
 61        )
 62
 63    @observe(name="Получение состояния осушителя")
 64    def get_state(self):
 65        for attempt in range(self.state_retry_count):
 66            try:
 67                response = requests.get(
 68                    self.url + f"/{self.device_id}",
 69                    headers=self.yandex_headers,
 70                    timeout=10,
 71                )
 72                response.raise_for_status()
 73                device = response.json()
 74
 75                state = {
 76                    "device_id": self.device_id,
 77                    "name": device.get("name", ""),
 78                    "online": device.get("state", "unknown"),
 79                }
 80
 81                for capability in device.get("capabilities", []):
 82                    cap_type = capability.get("type")
 83                    cap_state = capability.get("state", {})
 84                    if cap_type == "devices.capabilities.on_off":
 85                        state["power"] = cap_state.get("value", False)
 86                    elif cap_type == "devices.capabilities.mode":
 87                        instance = capability.get("parameters", {}).get("instance", "")
 88                        if instance == "cleanup_mode":
 89                            state["mode"] = cap_state.get("value", "normal")
 90                        elif instance == "fan_speed":
 91                            state["fan_speed"] = cap_state.get("value", "low")
 92                    elif cap_type == "devices.capabilities.toggle":
 93                        instance = capability.get("parameters", {}).get("instance", "")
 94                        if instance == "controls_locked":
 95                            state["controls_locked"] = cap_state.get("value", False)
 96
 97                for prop in device.get("properties", []):
 98                    if prop.get("type") == "devices.properties.float":
 99                        instance = prop.get("parameters", {}).get("instance", "")
100                        value = prop.get("state", {}).get("value")
101                        if instance == "humidity":
102                            state["humidity"] = value
103                        elif instance == "temperature":
104                            state["temperature"] = value
105
106                self.state = state
107                return state
108            except requests.exceptions.RequestException as e:
109                logger.error(f"Попытка {attempt + 1}/{self.state_retry_count}: {e}")
110                if attempt < self.state_retry_count - 1:
111                    time.sleep(self.state_retry_delay)
112
113        self.state = {
114            "device_id": self.device_id,
115            "name": "",
116            "online": "unknown",
117            "power": False,
118            "humidity": 0,
119            "temperature": 0,
120            "mode": "normal",
121            "fan_speed": "low",
122            "controls_locked": False,
123        }
124        logger.error(
125            f"Не удалось получить состояние осушителя после {self.state_retry_count} попыток"
126        )
127        return self.state
128
129    @observe(name="Управление осушителем (включение/выключение)")
130    def control_dehumidifier(self, turn_on: bool) -> bool:
131        try:
132            payload = {
133                "devices": [
134                    {
135                        "id": self.device_id,
136                        "actions": [
137                            {
138                                "type": "devices.capabilities.on_off",
139                                "state": {"instance": "on", "value": turn_on},
140                            }
141                        ],
142                    }
143                ]
144            }
145            response = requests.post(
146                self.url + "/actions",
147                headers=self.yandex_headers,
148                json=payload,
149                timeout=10,
150            )
151            response.raise_for_status()
152            action_text = "включен" if turn_on else "выключен"
153            logger.debug(f"Осушитель успешно {action_text}")
154            return True
155        except requests.exceptions.RequestException as e:
156            logger.error(f"Ошибка при управлении осушителем: {e}")
157            if hasattr(e, "response") and e.response is not None:
158                logger.error(f"Статус код: {e.response.status_code}")
159                logger.error(f"Ответ: {e.response.text}")
160            return False
161        except (TypeError, ValueError, KeyError) as e:
162            logger.error(f"Неожиданная ошибка при управлении осушителем: {e}")
163            return False
164
165    @observe(name="Установка режима осушителя")
166    def set_mode(self, mode: str) -> bool:
167        """Устанавливает режим работы (quiet/normal/express)."""
168        try:
169            payload = {
170                "devices": [
171                    {
172                        "id": self.device_id,
173                        "actions": [
174                            {
175                                "type": "devices.capabilities.mode",
176                                "state": {"instance": "cleanup_mode", "value": mode},
177                            }
178                        ],
179                    }
180                ]
181            }
182            response = requests.post(
183                self.url + "/actions",
184                headers=self.yandex_headers,
185                json=payload,
186                timeout=10,
187            )
188            response.raise_for_status()
189            logger.debug(f"Режим осушителя установлен: {mode}")
190            return True
191        except requests.exceptions.RequestException as e:
192            logger.error(f"Ошибка при установке режима осушителя: {e}")
193            return False
194
195    @observe(name="Установка скорости вентилятора осушителя")
196    def set_fan_speed(self, speed: str) -> bool:
197        """Устанавливает скорость вентилятора (low/high)."""
198        try:
199            payload = {
200                "devices": [
201                    {
202                        "id": self.device_id,
203                        "actions": [
204                            {
205                                "type": "devices.capabilities.mode",
206                                "state": {"instance": "fan_speed", "value": speed},
207                            }
208                        ],
209                    }
210                ]
211            }
212            response = requests.post(
213                self.url + "/actions",
214                headers=self.yandex_headers,
215                json=payload,
216                timeout=10,
217            )
218            response.raise_for_status()
219            logger.debug(f"Скорость вентилятора осушителя установлена: {speed}")
220            return True
221        except requests.exceptions.RequestException as e:
222            logger.error(f"Ошибка при установке скорости вентилятора: {e}")
223            return False

Контроллер для управления осушителем Яндекс Умного дома.

Устройство devices.types.humidifier (TUYA RMD-312) — на самом деле осушитель. Яндекс отнес его к humidifier, но он сушит воздух. Устройство является одновременно сенсором (влажность, температура) и actuator (on_off, mode, fan_speed).

Attributes: device_id: Идентификатор устройства в Яндекс Умном доме. state_retry_count: Количество попыток получения состояния. state_retry_delay: Задержка между попытками в секундах. state: Последнее полученное состояние осушителя.

DehumidifierController( device_id: str, api_key: str, state_retry_count: int = 3, state_retry_delay: int = 2)
34    def __init__(
35        self,
36        device_id: str,
37        api_key: str,
38        state_retry_count: int = 3,
39        state_retry_delay: int = 2,
40    ):
41        self.device_id = device_id
42        Controller.__init__(self)
43        YandexDataSource.__init__(self, api_key)
44        self.state_retry_count = state_retry_count
45        self.state_retry_delay = state_retry_delay
46        self.state = self.get_state()

Инициализирует базовый контроллер.

Не выполняет сетевых или тяжёлых операций. Наследники могут вызывать super().__init__() без обязательных параметров.

device_id
state_retry_count
state_retry_delay
state
@observe(name='Получение состояния осушителя')
def get_state(self):
 63    @observe(name="Получение состояния осушителя")
 64    def get_state(self):
 65        for attempt in range(self.state_retry_count):
 66            try:
 67                response = requests.get(
 68                    self.url + f"/{self.device_id}",
 69                    headers=self.yandex_headers,
 70                    timeout=10,
 71                )
 72                response.raise_for_status()
 73                device = response.json()
 74
 75                state = {
 76                    "device_id": self.device_id,
 77                    "name": device.get("name", ""),
 78                    "online": device.get("state", "unknown"),
 79                }
 80
 81                for capability in device.get("capabilities", []):
 82                    cap_type = capability.get("type")
 83                    cap_state = capability.get("state", {})
 84                    if cap_type == "devices.capabilities.on_off":
 85                        state["power"] = cap_state.get("value", False)
 86                    elif cap_type == "devices.capabilities.mode":
 87                        instance = capability.get("parameters", {}).get("instance", "")
 88                        if instance == "cleanup_mode":
 89                            state["mode"] = cap_state.get("value", "normal")
 90                        elif instance == "fan_speed":
 91                            state["fan_speed"] = cap_state.get("value", "low")
 92                    elif cap_type == "devices.capabilities.toggle":
 93                        instance = capability.get("parameters", {}).get("instance", "")
 94                        if instance == "controls_locked":
 95                            state["controls_locked"] = cap_state.get("value", False)
 96
 97                for prop in device.get("properties", []):
 98                    if prop.get("type") == "devices.properties.float":
 99                        instance = prop.get("parameters", {}).get("instance", "")
100                        value = prop.get("state", {}).get("value")
101                        if instance == "humidity":
102                            state["humidity"] = value
103                        elif instance == "temperature":
104                            state["temperature"] = value
105
106                self.state = state
107                return state
108            except requests.exceptions.RequestException as e:
109                logger.error(f"Попытка {attempt + 1}/{self.state_retry_count}: {e}")
110                if attempt < self.state_retry_count - 1:
111                    time.sleep(self.state_retry_delay)
112
113        self.state = {
114            "device_id": self.device_id,
115            "name": "",
116            "online": "unknown",
117            "power": False,
118            "humidity": 0,
119            "temperature": 0,
120            "mode": "normal",
121            "fan_speed": "low",
122            "controls_locked": False,
123        }
124        logger.error(
125            f"Не удалось получить состояние осушителя после {self.state_retry_count} попыток"
126        )
127        return self.state
@observe(name='Управление осушителем (включение/выключение)')
def control_dehumidifier(self, turn_on: bool) -> bool:
129    @observe(name="Управление осушителем (включение/выключение)")
130    def control_dehumidifier(self, turn_on: bool) -> bool:
131        try:
132            payload = {
133                "devices": [
134                    {
135                        "id": self.device_id,
136                        "actions": [
137                            {
138                                "type": "devices.capabilities.on_off",
139                                "state": {"instance": "on", "value": turn_on},
140                            }
141                        ],
142                    }
143                ]
144            }
145            response = requests.post(
146                self.url + "/actions",
147                headers=self.yandex_headers,
148                json=payload,
149                timeout=10,
150            )
151            response.raise_for_status()
152            action_text = "включен" if turn_on else "выключен"
153            logger.debug(f"Осушитель успешно {action_text}")
154            return True
155        except requests.exceptions.RequestException as e:
156            logger.error(f"Ошибка при управлении осушителем: {e}")
157            if hasattr(e, "response") and e.response is not None:
158                logger.error(f"Статус код: {e.response.status_code}")
159                logger.error(f"Ответ: {e.response.text}")
160            return False
161        except (TypeError, ValueError, KeyError) as e:
162            logger.error(f"Неожиданная ошибка при управлении осушителем: {e}")
163            return False
@observe(name='Установка режима осушителя')
def set_mode(self, mode: str) -> bool:
165    @observe(name="Установка режима осушителя")
166    def set_mode(self, mode: str) -> bool:
167        """Устанавливает режим работы (quiet/normal/express)."""
168        try:
169            payload = {
170                "devices": [
171                    {
172                        "id": self.device_id,
173                        "actions": [
174                            {
175                                "type": "devices.capabilities.mode",
176                                "state": {"instance": "cleanup_mode", "value": mode},
177                            }
178                        ],
179                    }
180                ]
181            }
182            response = requests.post(
183                self.url + "/actions",
184                headers=self.yandex_headers,
185                json=payload,
186                timeout=10,
187            )
188            response.raise_for_status()
189            logger.debug(f"Режим осушителя установлен: {mode}")
190            return True
191        except requests.exceptions.RequestException as e:
192            logger.error(f"Ошибка при установке режима осушителя: {e}")
193            return False

Устанавливает режим работы (quiet/normal/express).

@observe(name='Установка скорости вентилятора осушителя')
def set_fan_speed(self, speed: str) -> bool:
195    @observe(name="Установка скорости вентилятора осушителя")
196    def set_fan_speed(self, speed: str) -> bool:
197        """Устанавливает скорость вентилятора (low/high)."""
198        try:
199            payload = {
200                "devices": [
201                    {
202                        "id": self.device_id,
203                        "actions": [
204                            {
205                                "type": "devices.capabilities.mode",
206                                "state": {"instance": "fan_speed", "value": speed},
207                            }
208                        ],
209                    }
210                ]
211            }
212            response = requests.post(
213                self.url + "/actions",
214                headers=self.yandex_headers,
215                json=payload,
216                timeout=10,
217            )
218            response.raise_for_status()
219            logger.debug(f"Скорость вентилятора осушителя установлена: {speed}")
220            return True
221        except requests.exceptions.RequestException as e:
222            logger.error(f"Ошибка при установке скорости вентилятора: {e}")
223            return False

Устанавливает скорость вентилятора (low/high).