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()
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: Список ассистентов для остановки.
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: Список ассистентов для запуска.
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("Логгер инициализирован")
Инициализация логгера
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
Инициализация компонентов
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 и неожиданные ошибки.