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
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: Последнее полученное состояние осушителя.
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__() без обязательных параметров.
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
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
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).
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).