Controllers.smart_plug

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

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

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

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

Поддерживает получение состояния (напряжение, сила тока, потребляемая мощность) и включение/выключение розетки.

Attributes: device_id: Идентификатор устройства в Яндекс Умном доме. round_digits: Кортеж из трёх значений для округления (вольты, амперы, ватты). state_retry_count: Количество попыток получения состояния. state_retry_delay: Задержка между попытками в секундах. state: Последнее полученное состояние розетки.

SmartPlugController( device_id, api_key, round_digits=None, state_retry_count=3, state_retry_delay=2)
36    def __init__(
37        self,
38        device_id,
39        api_key,
40        round_digits=None,
41        state_retry_count=3,
42        state_retry_delay=2,
43    ):
44        """Инициализирует контроллер розетки.
45
46        Args:
47            device_id: Идентификатор устройства в Яндекс Умном доме.
48            api_key: Токен авторизации Yandex IoT API.
49            round_digits: Кортеж с количеством знаков после запятой.
50            state_retry_count: Количество попыток получения состояния.
51            state_retry_delay: Задержка между попытками в секундах.
52        """
53
54        self.device_id = device_id
55
56        Controller.__init__(self)
57        YandexDataSource.__init__(self, api_key)
58
59        self.round_digits = round_digits or self._default_round_digits
60        self.state_retry_count = state_retry_count
61        self.state_retry_delay = state_retry_delay
62        self.state = self.get_state()

Инициализирует контроллер розетки.

Args: device_id: Идентификатор устройства в Яндекс Умном доме. api_key: Токен авторизации Yandex IoT API. round_digits: Кортеж с количеством знаков после запятой. state_retry_count: Количество попыток получения состояния. state_retry_delay: Задержка между попытками в секундах.

device_id
round_digits
state_retry_count
state_retry_delay
state
@observe(name='Функция для получения состояния розетки')
def get_state(self):
 81    @observe(name="Функция для получения состояния розетки")
 82    def get_state(self):
 83        """Получает состояние розетки через Yandex IoT API.
 84
 85        Выполняет GET-запрос с повторными попытками. Разбирает
 86        capabilities и properties устройства: напряжение, ток,
 87        потребляемую мощность.
 88
 89        Returns:
 90            dict: Словарь с ключами device_id, name, online, power,
 91                  voltage, amperage, power_consumption. При ошибках
 92                  возвращает значения по умолчанию.
 93        """
 94
 95        for attempt in range(self.state_retry_count):
 96            try:
 97                response = requests.get(
 98                    self.url + f"/{self.device_id}",
 99                    headers=self.yandex_headers,
100                    timeout=10,
101                )
102                response.raise_for_status()
103
104                device = response.json()
105
106                state = {
107                    "device_id": self.device_id,
108                    "name": device.get("name", ""),
109                    "online": device.get("state", "unknown"),
110                }
111
112                # Обрабатываем capabilities и properties как в первой функции
113                for capability in device.get("capabilities", []):
114                    if capability.get("type") == "devices.capabilities.on_off":
115                        state["power"] = capability.get("state", {}).get("value", False)
116
117                for property in device.get("properties", []):
118                    prop_type = property.get("type")
119                    value = property.get("state", {}).get("value")
120
121                    if prop_type == "devices.properties.float":
122                        parameters = property.get("parameters", {})
123                        instance = parameters.get("instance", "")
124
125                        if "voltage" in instance:
126                            state["voltage"] = value
127                        elif "amperage" in instance:
128                            state["amperage"] = value
129                        elif "power" in instance:
130                            state["power_consumption"] = value
131
132                self.state = state
133                return state
134            except requests.exceptions.RequestException as e:
135                logger.error(f"Попытка {attempt + 1}/{self.state_retry_count}: {e}")
136                if attempt < self.state_retry_count - 1:
137                    time.sleep(self.state_retry_delay)
138
139        self.state = {
140            "device_id": self.device_id,
141            "name": "",
142            "online": "unknown",
143            "power": False,
144            "voltage": 0,
145            "amperage": 0,
146            "power_consumption": 0,
147        }
148        logger.error(
149            f"Не удалось получить состояние розетки после {self.state_retry_count} попыток"
150        )
151        return self.state

Получает состояние розетки через Yandex IoT API.

Выполняет GET-запрос с повторными попытками. Разбирает capabilities и properties устройства: напряжение, ток, потребляемую мощность.

Returns: dict: Словарь с ключами device_id, name, online, power, voltage, amperage, power_consumption. При ошибках возвращает значения по умолчанию.

@observe(name='Управление розеткой (включение/выключение)')
def control_plug(self, turn_on):
153    @observe(name="Управление розеткой (включение/выключение)")
154    def control_plug(self, turn_on):
155        """Включает или выключает розетку через Yandex IoT API.
156
157        Отправляет POST-запрос с действием on_off на устройство.
158
159        Args:
160            turn_on: True для включения, False для выключения.
161
162        Returns:
163            bool: True если команда выполнена успешно, False при ошибке.
164        """
165        try:
166            payload = {
167                "devices": [
168                    {
169                        "id": self.device_id,
170                        "actions": [
171                            {
172                                "type": "devices.capabilities.on_off",
173                                "state": {"instance": "on", "value": turn_on},
174                            }
175                        ],
176                    }
177                ]
178            }
179
180            response = requests.post(
181                self.url + "/actions",
182                headers=self.yandex_headers,
183                json=payload,
184                timeout=10,
185            )
186
187            response.raise_for_status()
188
189            action_text = "включена" if turn_on else "выключена"
190            logger.debug(f"Розетка успешно {action_text}")
191
192            return True
193
194        except requests.exceptions.RequestException as e:
195            logger.error(f"Ошибка при управлении розеткой: {e}")
196            if hasattr(e, "response") and e.response is not None:
197                logger.error(f"Статус код: {e.response.status_code}")
198                logger.error(f"Ответ: {e.response.text}")
199            return False
200        except (TypeError, ValueError, KeyError) as e:
201            logger.error(f"Неожиданная ошибка при управлении розеткой: {e}")
202            return False

Включает или выключает розетку через Yandex IoT API.

Отправляет POST-запрос с действием on_off на устройство.

Args: turn_on: True для включения, False для выключения.

Returns: bool: True если команда выполнена успешно, False при ошибке.