run

Главный цикл приложения e-house.

Инициализирует все компоненты (ассистенты, контроллеры, источники данных, мессенджеры) и запускает их в бесконечном цикле. Периодически проверяет изменения в конфигурационном файле и перезагружает компоненты при необходимости.

  1"""
  2Главный цикл приложения e-house.
  3
  4Инициализирует все компоненты (ассистенты, контроллеры, источники данных,
  5мессенджеры) и запускает их в бесконечном цикле. Периодически проверяет
  6изменения в конфигурационном файле и перезагружает компоненты при
  7необходимости.
  8"""
  9
 10import time
 11import os
 12import copy
 13import sys
 14
 15from langfuse.decorators import observe
 16from loguru import logger
 17
 18from Assistants.climate import ClimateAssistant
 19from Assistants.dog_heater import DogHeaterAssistant
 20from Assistants.lf_cleaner import LangfuseCleanerAssistant
 21from Assistants.voltage import VoltageAssistant
 22from Controllers.climate import ClimateController
 23from Messengers.telegram import TelegramBot, TelegramTimeouts
 24from Utilities.time_utils import format_time
 25from config import config
 26
 27from Assistants import DogWaterAssistant
 28from Assistants.scheduled import ScheduledAssistant
 29from Controllers.smart_plug import SmartPlugController
 30from DataSources.openweather import OpenWeatherDataSource
 31
 32
 33def stop_all(assistants):
 34    """Останавливает всех ассистентов.
 35
 36    Вызывает метод stop() у каждого ассистента в обратном порядке.
 37
 38    Args:
 39        assistants: Список ассистентов для остановки.
 40    """
 41    for assistant in reversed(assistants):
 42        if hasattr(assistant, "stop") and callable(getattr(assistant, "stop")):
 43            assistant.stop()
 44
 45
 46def start_all(assistants):
 47    """Запускает всех ассистентов.
 48
 49    Вызывает метод start() у каждого ассистента.
 50
 51    Args:
 52        assistants: Список ассистентов для запуска.
 53    """
 54    for assistant in assistants:
 55        if hasattr(assistant, "start") and callable(getattr(assistant, "start")):
 56            assistant.start()
 57
 58
 59def init_logger():
 60    """Инициализация логгера"""
 61
 62    logger.remove()  # удаляем стандартный обработчик
 63    # Добавляем обработчик с поддержкой UTF-8
 64    logger.add(
 65        sys.stderr,
 66        level=config.yaml["General"]["Logger"]["Level"],
 67        #    format="{time:YYYY-MM-DD HH:mm:ss} - {level} - {name} - {message}",
 68        colorize=True,
 69    )
 70
 71    logger.debug("Логгер инициализирован")
 72
 73
 74@observe(name="Инициализация компонентов")
 75def init_components():
 76    """Инициализация компонентов"""
 77    init_logger()
 78
 79    assistants = []
 80    bot = None
 81
 82    if config.yaml["Messengers"]["Telegram"]["ENABLED"]:
 83        telegram_bot = TelegramBot(
 84            config.yaml["Messengers"]["Telegram"]["NAME"],
 85            os.getenv("TELEGRAM_TOKEN"),
 86            config.yaml["Messengers"]["Telegram"]["CHAT_ID"],
 87            TelegramTimeouts(
 88                config.yaml["Messengers"]["Telegram"]["TIMEOUTS"]["REQUEST"],
 89                config.yaml["Messengers"]["Telegram"]["TIMEOUTS"]["POLLING"],
 90            ),
 91        )
 92        assistants.append(telegram_bot)
 93        bot = telegram_bot
 94        logger.info(f'Мессенджер "{config.yaml["Messengers"]["Telegram"]["NAME"]}" включен')
 95
 96    # Initialize devices and data sources
 97    smart_plug = SmartPlugController(
 98        config.yaml["Devices"]["SmartPlug"]["DEVICE_ID"],
 99        os.getenv("YANDEX_TOKEN"),
100        round_digits=(
101            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("VOLTAGE_ROUNDING", 0),
102            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("AMPERAGE_ROUNDING", 0),
103            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("CONSUMPTION_ROUNDING", 0),
104        ),
105        state_retry_count=config.yaml
106        .get("Devices", {})
107        .get("SmartPlug", {})
108        .get("STATE_RETRY_COUNT", 3),
109        state_retry_delay=config.yaml
110        .get("Devices", {})
111        .get("SmartPlug", {})
112        .get("STATE_RETRY_DELAY", 2),
113    )
114    openweather = OpenWeatherDataSource(
115        os.getenv("OPENWEATHER_API_KEY"), config.yaml["OpenWeather"]["CITY"]
116    )
117
118    temp_controller = ClimateController(
119        config.yaml["Devices"]["ClimateSensor"]["DEVICE_ID"],
120        os.getenv("YANDEX_TOKEN"),
121        round_digits=(config.yaml.get("Devices", {}).get("ClimateSensor", {}).get("ROUNDING", 0),),
122        state_retry_count=config.yaml
123        .get("Devices", {})
124        .get("ClimateSensor", {})
125        .get("STATE_RETRY_COUNT", 3),
126        state_retry_delay=config.yaml
127        .get("Devices", {})
128        .get("ClimateSensor", {})
129        .get("STATE_RETRY_DELAY", 2),
130    )
131
132    dog_climate = ClimateController(
133        config.yaml["Devices"]["DogClimateSensor"]["DEVICE_ID"],
134        os.getenv("YANDEX_TOKEN"),
135        round_digits=(
136            config.yaml.get("Devices", {}).get("DogClimateSensor", {}).get("ROUNDING", 0),
137        ),
138        state_retry_count=config.yaml
139        .get("Devices", {})
140        .get("DogClimateSensor", {})
141        .get("STATE_RETRY_COUNT", 3),
142        state_retry_delay=config.yaml
143        .get("Devices", {})
144        .get("DogClimateSensor", {})
145        .get("STATE_RETRY_DELAY", 2),
146    )
147
148    # Initialize assistants based on config
149    if config.yaml["Assistants"]["DogWater"]["ENABLED"]:
150        dog_water = DogWaterAssistant(
151            config.yaml["Assistants"]["DogWater"]["NAME"],
152            smart_plug,
153            openweather,
154            config.yaml["Assistants"]["DogWater"]["TEMP_THRESHOLD"],
155            bot,
156        )
157        dog_water_scheduler = ScheduledAssistant(
158            dog_water, config.yaml["Assistants"]["DogWater"]["CHECK_INTERVAL"]
159        )
160        assistants.append(dog_water_scheduler)
161        logger.info(f'Ассистент "{config.yaml["Assistants"]["DogWater"]["NAME"]}" включен')
162
163    if config.yaml["Assistants"]["Voltage"]["ENABLED"]:
164        voltage = VoltageAssistant(
165            config.yaml["Assistants"]["Voltage"]["NAME"],
166            smart_plug,
167            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_THRESHOLD_LOW", 200),
168            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_THRESHOLD_HIGH", 250),
169            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_ROUNDING", 0),
170            bot,
171        )
172        voltage_scheduler = ScheduledAssistant(
173            voltage, config.yaml["Assistants"]["Voltage"]["CHECK_INTERVAL"]
174        )
175        assistants.append(voltage_scheduler)
176        logger.info(f'Ассистент "{config.yaml["Assistants"]["Voltage"]["NAME"]}" включен')
177
178    if config.yaml["Assistants"]["Temperature"]["ENABLED"]:
179        temperature = ClimateAssistant(
180            config.yaml["Assistants"]["Temperature"]["NAME"],
181            temp_controller,
182            config.yaml.get("Assistants", {}).get("Temperature", {}).get("TEMP_THRESHOLD_LOW", 23),
183            config.yaml.get("Assistants", {}).get("Temperature", {}).get("TEMP_THRESHOLD_HIGH", 25),
184            config.yaml
185            .get("Assistants", {})
186            .get("Temperature", {})
187            .get("HUMIDITY_THRESHOLD_LOW", 25),
188            config.yaml
189            .get("Assistants", {})
190            .get("Temperature", {})
191            .get("HUMIDITY_THRESHOLD_HIGH", 50),
192            config.yaml.get("Assistants", {}).get("Temperature", {}).get("ROUNDING", 0),
193            bot,
194        )
195        temp_scheduler = ScheduledAssistant(
196            temperature, config.yaml["Assistants"]["Temperature"]["CHECK_INTERVAL"]
197        )
198        assistants.append(temp_scheduler)
199        logger.info(f'Ассистент "{config.yaml["Assistants"]["Temperature"]["NAME"]}" включен')
200
201    if config.yaml["Assistants"]["LangfuseCleaner"]["ENABLED"]:
202        lf_cleaner = LangfuseCleanerAssistant(
203            project_slug="e-house",
204            period=config.yaml
205            .get("Assistants", {})
206            .get("LangfuseCleaner", {})
207            .get("CLEAN_PERIOD", 1),
208        )
209        lf_cleaner_scheduler = ScheduledAssistant(
210            lf_cleaner,
211            config.yaml
212            .get("Assistants", {})
213            .get("LangfuseCleaner", {})
214            .get("SCHEDULE_INTERVAL", 60),
215        )
216        assistants.append(lf_cleaner_scheduler)
217        logger.info(f'Ассистент "{config.yaml["Assistants"]["LangfuseCleaner"]["NAME"]}" включен')
218
219    if config.yaml["Assistants"]["DogHeater"]["ENABLED"]:
220        dog_heater = DogHeaterAssistant(
221            config.yaml["Assistants"]["DogHeater"]["NAME"],
222            smart_plug,
223            dog_climate,
224            config.yaml["Assistants"]["DogHeater"]["TEMP_THRESHOLD"],
225            bot,
226        )
227        dog_heater_scheduler = ScheduledAssistant(
228            dog_heater, config.yaml["Assistants"]["DogHeater"]["CHECK_INTERVAL"]
229        )
230        assistants.append(dog_heater_scheduler)
231        logger.info(f'Ассистент "{config.yaml["Assistants"]["DogHeater"]["NAME"]}" включен')
232
233    return assistants
234
235
236def main():
237    """Основная функция приложения.
238
239    Инициализирует компоненты, запускает их и входит в бесконечный цикл.
240    В цикле проверяет изменения конфигурации и при необходимости
241    перезагружает ассистентов. Обрабатывает KeyboardInterrupt и
242    неожиданные ошибки.
243    """
244
245    assistants = []
246    try:
247        logger.info(
248            "Интервал перезагрузки config.yml: "
249            + format_time(config.yaml["General"]["RELOAD_TIMEOUT"])
250        )
251        assistants = init_components()
252        start_all(assistants)
253
254        # tracer.end_span(span)
255        # tracer.flush()
256
257        while True:
258            old_config = copy.deepcopy(config.yaml)
259            config.load_config()
260            if config.yaml != old_config:
261                try:
262                    stop_all(assistants)
263                    new_assistants = init_components()
264                    start_all(new_assistants)
265                except Exception as err:
266                    logger.error(f"Не удалось перезапустить ассистентов: {err}", exc_info=True)
267                    start_all(assistants)
268                    time.sleep(config.yaml["General"]["RELOAD_TIMEOUT"])
269                    continue
270                assistants = new_assistants
271                logger.debug("config.yml изменен, перезагружен")
272            time.sleep(config.yaml["General"]["RELOAD_TIMEOUT"])
273
274    except KeyboardInterrupt:
275        stop_all(assistants)
276        logger.info("Работа скрипта остановлена пользователем")
277    except Exception as e:
278        logger.error(f"Неожиданная ошибка: {e}")
279
280
281if __name__ == "__main__":
282    main()
def stop_all(assistants):
34def stop_all(assistants):
35    """Останавливает всех ассистентов.
36
37    Вызывает метод stop() у каждого ассистента в обратном порядке.
38
39    Args:
40        assistants: Список ассистентов для остановки.
41    """
42    for assistant in reversed(assistants):
43        if hasattr(assistant, "stop") and callable(getattr(assistant, "stop")):
44            assistant.stop()

Останавливает всех ассистентов.

Вызывает метод stop() у каждого ассистента в обратном порядке.

Args: assistants: Список ассистентов для остановки.

def start_all(assistants):
47def start_all(assistants):
48    """Запускает всех ассистентов.
49
50    Вызывает метод start() у каждого ассистента.
51
52    Args:
53        assistants: Список ассистентов для запуска.
54    """
55    for assistant in assistants:
56        if hasattr(assistant, "start") and callable(getattr(assistant, "start")):
57            assistant.start()

Запускает всех ассистентов.

Вызывает метод start() у каждого ассистента.

Args: assistants: Список ассистентов для запуска.

def init_logger():
60def init_logger():
61    """Инициализация логгера"""
62
63    logger.remove()  # удаляем стандартный обработчик
64    # Добавляем обработчик с поддержкой UTF-8
65    logger.add(
66        sys.stderr,
67        level=config.yaml["General"]["Logger"]["Level"],
68        #    format="{time:YYYY-MM-DD HH:mm:ss} - {level} - {name} - {message}",
69        colorize=True,
70    )
71
72    logger.debug("Логгер инициализирован")

Инициализация логгера

@observe(name='Инициализация компонентов')
def init_components():
 75@observe(name="Инициализация компонентов")
 76def init_components():
 77    """Инициализация компонентов"""
 78    init_logger()
 79
 80    assistants = []
 81    bot = None
 82
 83    if config.yaml["Messengers"]["Telegram"]["ENABLED"]:
 84        telegram_bot = TelegramBot(
 85            config.yaml["Messengers"]["Telegram"]["NAME"],
 86            os.getenv("TELEGRAM_TOKEN"),
 87            config.yaml["Messengers"]["Telegram"]["CHAT_ID"],
 88            TelegramTimeouts(
 89                config.yaml["Messengers"]["Telegram"]["TIMEOUTS"]["REQUEST"],
 90                config.yaml["Messengers"]["Telegram"]["TIMEOUTS"]["POLLING"],
 91            ),
 92        )
 93        assistants.append(telegram_bot)
 94        bot = telegram_bot
 95        logger.info(f'Мессенджер "{config.yaml["Messengers"]["Telegram"]["NAME"]}" включен')
 96
 97    # Initialize devices and data sources
 98    smart_plug = SmartPlugController(
 99        config.yaml["Devices"]["SmartPlug"]["DEVICE_ID"],
100        os.getenv("YANDEX_TOKEN"),
101        round_digits=(
102            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("VOLTAGE_ROUNDING", 0),
103            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("AMPERAGE_ROUNDING", 0),
104            config.yaml.get("Devices", {}).get("SmartPlug", {}).get("CONSUMPTION_ROUNDING", 0),
105        ),
106        state_retry_count=config.yaml
107        .get("Devices", {})
108        .get("SmartPlug", {})
109        .get("STATE_RETRY_COUNT", 3),
110        state_retry_delay=config.yaml
111        .get("Devices", {})
112        .get("SmartPlug", {})
113        .get("STATE_RETRY_DELAY", 2),
114    )
115    openweather = OpenWeatherDataSource(
116        os.getenv("OPENWEATHER_API_KEY"), config.yaml["OpenWeather"]["CITY"]
117    )
118
119    temp_controller = ClimateController(
120        config.yaml["Devices"]["ClimateSensor"]["DEVICE_ID"],
121        os.getenv("YANDEX_TOKEN"),
122        round_digits=(config.yaml.get("Devices", {}).get("ClimateSensor", {}).get("ROUNDING", 0),),
123        state_retry_count=config.yaml
124        .get("Devices", {})
125        .get("ClimateSensor", {})
126        .get("STATE_RETRY_COUNT", 3),
127        state_retry_delay=config.yaml
128        .get("Devices", {})
129        .get("ClimateSensor", {})
130        .get("STATE_RETRY_DELAY", 2),
131    )
132
133    dog_climate = ClimateController(
134        config.yaml["Devices"]["DogClimateSensor"]["DEVICE_ID"],
135        os.getenv("YANDEX_TOKEN"),
136        round_digits=(
137            config.yaml.get("Devices", {}).get("DogClimateSensor", {}).get("ROUNDING", 0),
138        ),
139        state_retry_count=config.yaml
140        .get("Devices", {})
141        .get("DogClimateSensor", {})
142        .get("STATE_RETRY_COUNT", 3),
143        state_retry_delay=config.yaml
144        .get("Devices", {})
145        .get("DogClimateSensor", {})
146        .get("STATE_RETRY_DELAY", 2),
147    )
148
149    # Initialize assistants based on config
150    if config.yaml["Assistants"]["DogWater"]["ENABLED"]:
151        dog_water = DogWaterAssistant(
152            config.yaml["Assistants"]["DogWater"]["NAME"],
153            smart_plug,
154            openweather,
155            config.yaml["Assistants"]["DogWater"]["TEMP_THRESHOLD"],
156            bot,
157        )
158        dog_water_scheduler = ScheduledAssistant(
159            dog_water, config.yaml["Assistants"]["DogWater"]["CHECK_INTERVAL"]
160        )
161        assistants.append(dog_water_scheduler)
162        logger.info(f'Ассистент "{config.yaml["Assistants"]["DogWater"]["NAME"]}" включен')
163
164    if config.yaml["Assistants"]["Voltage"]["ENABLED"]:
165        voltage = VoltageAssistant(
166            config.yaml["Assistants"]["Voltage"]["NAME"],
167            smart_plug,
168            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_THRESHOLD_LOW", 200),
169            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_THRESHOLD_HIGH", 250),
170            config.yaml.get("Assistants", {}).get("Voltage", {}).get("VOLTAGE_ROUNDING", 0),
171            bot,
172        )
173        voltage_scheduler = ScheduledAssistant(
174            voltage, config.yaml["Assistants"]["Voltage"]["CHECK_INTERVAL"]
175        )
176        assistants.append(voltage_scheduler)
177        logger.info(f'Ассистент "{config.yaml["Assistants"]["Voltage"]["NAME"]}" включен')
178
179    if config.yaml["Assistants"]["Temperature"]["ENABLED"]:
180        temperature = ClimateAssistant(
181            config.yaml["Assistants"]["Temperature"]["NAME"],
182            temp_controller,
183            config.yaml.get("Assistants", {}).get("Temperature", {}).get("TEMP_THRESHOLD_LOW", 23),
184            config.yaml.get("Assistants", {}).get("Temperature", {}).get("TEMP_THRESHOLD_HIGH", 25),
185            config.yaml
186            .get("Assistants", {})
187            .get("Temperature", {})
188            .get("HUMIDITY_THRESHOLD_LOW", 25),
189            config.yaml
190            .get("Assistants", {})
191            .get("Temperature", {})
192            .get("HUMIDITY_THRESHOLD_HIGH", 50),
193            config.yaml.get("Assistants", {}).get("Temperature", {}).get("ROUNDING", 0),
194            bot,
195        )
196        temp_scheduler = ScheduledAssistant(
197            temperature, config.yaml["Assistants"]["Temperature"]["CHECK_INTERVAL"]
198        )
199        assistants.append(temp_scheduler)
200        logger.info(f'Ассистент "{config.yaml["Assistants"]["Temperature"]["NAME"]}" включен')
201
202    if config.yaml["Assistants"]["LangfuseCleaner"]["ENABLED"]:
203        lf_cleaner = LangfuseCleanerAssistant(
204            project_slug="e-house",
205            period=config.yaml
206            .get("Assistants", {})
207            .get("LangfuseCleaner", {})
208            .get("CLEAN_PERIOD", 1),
209        )
210        lf_cleaner_scheduler = ScheduledAssistant(
211            lf_cleaner,
212            config.yaml
213            .get("Assistants", {})
214            .get("LangfuseCleaner", {})
215            .get("SCHEDULE_INTERVAL", 60),
216        )
217        assistants.append(lf_cleaner_scheduler)
218        logger.info(f'Ассистент "{config.yaml["Assistants"]["LangfuseCleaner"]["NAME"]}" включен')
219
220    if config.yaml["Assistants"]["DogHeater"]["ENABLED"]:
221        dog_heater = DogHeaterAssistant(
222            config.yaml["Assistants"]["DogHeater"]["NAME"],
223            smart_plug,
224            dog_climate,
225            config.yaml["Assistants"]["DogHeater"]["TEMP_THRESHOLD"],
226            bot,
227        )
228        dog_heater_scheduler = ScheduledAssistant(
229            dog_heater, config.yaml["Assistants"]["DogHeater"]["CHECK_INTERVAL"]
230        )
231        assistants.append(dog_heater_scheduler)
232        logger.info(f'Ассистент "{config.yaml["Assistants"]["DogHeater"]["NAME"]}" включен')
233
234    return assistants

Инициализация компонентов

def main():
237def main():
238    """Основная функция приложения.
239
240    Инициализирует компоненты, запускает их и входит в бесконечный цикл.
241    В цикле проверяет изменения конфигурации и при необходимости
242    перезагружает ассистентов. Обрабатывает KeyboardInterrupt и
243    неожиданные ошибки.
244    """
245
246    assistants = []
247    try:
248        logger.info(
249            "Интервал перезагрузки config.yml: "
250            + format_time(config.yaml["General"]["RELOAD_TIMEOUT"])
251        )
252        assistants = init_components()
253        start_all(assistants)
254
255        # tracer.end_span(span)
256        # tracer.flush()
257
258        while True:
259            old_config = copy.deepcopy(config.yaml)
260            config.load_config()
261            if config.yaml != old_config:
262                try:
263                    stop_all(assistants)
264                    new_assistants = init_components()
265                    start_all(new_assistants)
266                except Exception as err:
267                    logger.error(f"Не удалось перезапустить ассистентов: {err}", exc_info=True)
268                    start_all(assistants)
269                    time.sleep(config.yaml["General"]["RELOAD_TIMEOUT"])
270                    continue
271                assistants = new_assistants
272                logger.debug("config.yml изменен, перезагружен")
273            time.sleep(config.yaml["General"]["RELOAD_TIMEOUT"])
274
275    except KeyboardInterrupt:
276        stop_all(assistants)
277        logger.info("Работа скрипта остановлена пользователем")
278    except Exception as e:
279        logger.error(f"Неожиданная ошибка: {e}")

Основная функция приложения.

Инициализирует компоненты, запускает их и входит в бесконечный цикл. В цикле проверяет изменения конфигурации и при необходимости перезагружает ассистентов. Обрабатывает KeyboardInterrupt и неожиданные ошибки.