From 9eca755ce40940fa48e09f657c7cbfba43e7ba85 Mon Sep 17 00:00:00 2001 From: sirmrak Date: Sun, 28 Jun 2026 13:41:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?custom=5Fcomponents/rtkey=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/rtkey/__init__.py | 687 +++++++++++++++++++++++++ custom_components/rtkey/button.py | 93 ++++ custom_components/rtkey/camera.py | 236 +++++++++ custom_components/rtkey/config_flow.py | 74 +++ custom_components/rtkey/icon.png | Bin 0 -> 48178 bytes 5 files changed, 1090 insertions(+) create mode 100644 custom_components/rtkey/__init__.py create mode 100644 custom_components/rtkey/button.py create mode 100644 custom_components/rtkey/camera.py create mode 100644 custom_components/rtkey/config_flow.py create mode 100644 custom_components/rtkey/icon.png diff --git a/custom_components/rtkey/__init__.py b/custom_components/rtkey/__init__.py new file mode 100644 index 0000000..02beb3e --- /dev/null +++ b/custom_components/rtkey/__init__.py @@ -0,0 +1,687 @@ +import asyncio +import functools +import logging +import time +from datetime import datetime, timezone +from zoneinfo import ZoneInfo + +import requests +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import selector + +DOMAIN = "rtkey" +PLATFORMS = [Platform.CAMERA, Platform.IMAGE, Platform.BUTTON, Platform.SENSOR] + +_LOGGER = logging.getLogger(__name__) + +# === Константы настроек === +CONF_NAME = "name" +CONF_TOKEN = "token" +CONF_CAMERA_IMAGE_REFRESH_INTERVAL = "camera_image_refresh_interval" +CONF_SCREENSHOT_QUALITY = "screenshot_quality" +CONF_LOG_LEVEL = "log_level" + +# Настройки событий +CONF_EVENTS_ENABLED = "events_enabled" +CONF_EVENTS_REFRESH_INTERVAL = "events_refresh_interval" +CONF_EVENT_TIME_LAG = "event_time_lag" +CONF_PRE_EVENT_SECONDS = "pre_event_seconds" +CONF_EVENT_SCREENSHOT_ENABLED = "event_screenshot_enabled" +CONF_EVENT_SCREENSHOT_QUALITY = "event_screenshot_quality" +CONF_EVENT_ARCHIVE_ENABLED = "event_archive_enabled" +CONF_ARCHIVE_DURATION = "archive_duration" +CONF_EVENT_SENSOR_ENABLED = "event_sensor_enabled" + +TOKEN_REFRESH_BUFFER = 300 + +DATA_SCHEMA = { + vol.Required(CONF_NAME): str, + vol.Required(CONF_TOKEN): str, +} + +OPTIONS_SCHEMA = { + vol.Optional(CONF_NAME): str, + vol.Optional(CONF_TOKEN): str, + + vol.Optional(CONF_CAMERA_IMAGE_REFRESH_INTERVAL, default=60): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=600, step=30, + mode=selector.NumberSelectorMode.SLIDER, + unit_of_measurement="сек", + ) + ), + + vol.Optional(CONF_SCREENSHOT_QUALITY, default="medium"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value="small", label="Маленькое (быстрее)"), + selector.SelectOptionDict(value="medium", label="Среднее"), + selector.SelectOptionDict(value="large", label="Большое"), + selector.SelectOptionDict(value="precise", label="Точное (медленнее)"), + ], + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + + vol.Optional(CONF_LOG_LEVEL, default="INFO"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value="DEBUG", label="DEBUG (подробно)"), + selector.SelectOptionDict(value="INFO", label="INFO (обычный)"), + selector.SelectOptionDict(value="WARNING", label="WARNING (только предупреждения)"), + selector.SelectOptionDict(value="ERROR", label="ERROR (только ошибки)"), + ], + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + + vol.Optional(CONF_EVENTS_ENABLED, default=True): bool, + + vol.Optional(CONF_EVENTS_REFRESH_INTERVAL, default=15): selector.NumberSelector( + selector.NumberSelectorConfig( + min=5, max=60, step=5, + mode=selector.NumberSelectorMode.SLIDER, + unit_of_measurement="сек", + ) + ), + + vol.Optional(CONF_EVENT_TIME_LAG, default=25): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=60, step=5, + mode=selector.NumberSelectorMode.SLIDER, + unit_of_measurement="сек", + ) + ), + + vol.Optional(CONF_PRE_EVENT_SECONDS, default=10): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=30, step=5, + mode=selector.NumberSelectorMode.SLIDER, + unit_of_measurement="сек", + ) + ), + + vol.Optional(CONF_EVENT_SCREENSHOT_ENABLED, default=True): bool, + + vol.Optional(CONF_EVENT_SCREENSHOT_QUALITY, default="large"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value="small", label="Маленькое"), + selector.SelectOptionDict(value="medium", label="Среднее"), + selector.SelectOptionDict(value="large", label="Большое"), + selector.SelectOptionDict(value="precise", label="Точное"), + ], + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + + vol.Optional(CONF_EVENT_ARCHIVE_ENABLED, default=True): bool, + + vol.Optional(CONF_ARCHIVE_DURATION, default=30): selector.NumberSelector( + selector.NumberSelectorConfig( + min=10, max=120, step=10, + mode=selector.NumberSelectorMode.SLIDER, + unit_of_measurement="сек", + ) + ), + + vol.Optional(CONF_EVENT_SENSOR_ENABLED, default=True): bool, +} + + +class RTKeyCamerasApi: + def __init__(self, hass: HomeAssistant, entry: ConfigEntry): + self.hass = hass + self.entry = entry + self.token = entry.options.get(CONF_TOKEN) or entry.data.get(CONF_TOKEN, "") + self.screenshot_quality = entry.options.get(CONF_SCREENSHOT_QUALITY, "medium") + + # Настройки событий + self.events_enabled = entry.options.get(CONF_EVENTS_ENABLED, True) + self.events_refresh_interval = entry.options.get(CONF_EVENTS_REFRESH_INTERVAL, 15) + self.event_time_lag = entry.options.get(CONF_EVENT_TIME_LAG, 25) + self.pre_event_seconds = entry.options.get(CONF_PRE_EVENT_SECONDS, 10) + self.event_screenshot_enabled = entry.options.get(CONF_EVENT_SCREENSHOT_ENABLED, True) + self.event_screenshot_quality = entry.options.get(CONF_EVENT_SCREENSHOT_QUALITY, "large") + self.event_archive_enabled = entry.options.get(CONF_EVENT_ARCHIVE_ENABLED, True) + self.archive_duration = entry.options.get(CONF_ARCHIVE_DURATION, 30) + self.event_sensor_enabled = entry.options.get(CONF_EVENT_SENSOR_ENABLED, True) + + log_level = entry.options.get(CONF_LOG_LEVEL, "INFO") + _LOGGER.setLevel(getattr(logging, log_level, logging.INFO)) + _LOGGER.info(f"📊 Уровень логирования: {log_level}") + + self.user_token = None + self.lock = asyncio.Lock() + + # Информация о камерах + self.camera_tokens = {} + self._cameras_cache = None + self._cameras_cache_ts = 0 + + # Кэш картинок + self._image_cache = {} + self._image_cache_lock = asyncio.Lock() + + # ✅ НОВОЕ: Отслеживание недоступных камер (404) + self._unavailable_cameras: set[str] = set() + + # ✅ НОВОЕ: Флаг протухших токенов (403) + self._tokens_invalid = False + self._tokens_refresh_lock = asyncio.Lock() + + # Данные событий + self.last_events = {} + self._last_request_ids = {} + self.rfid_names = {} + self.intercom_camera_map = {} + self.intercom_ids = [] + self._events_polling_task: asyncio.Task | None = None + self._event_listeners = [] + + self._local_tz = ZoneInfo(hass.config.time_zone) + _LOGGER.info(f"🕐 Временная зона HA: {hass.config.time_zone}") + + def build_device_name(self, name: str) -> str: + return name.strip() if name else "RT Key Device" + + def get_camera_name(self, camera_id: str) -> str: + """Получаем название камеры по её ID.""" + token_info = self.camera_tokens.get(camera_id) + if token_info: + return token_info.get("title", "Неизвестная камера") + return "Неизвестная камера" + + def is_camera_available(self, camera_id: str) -> bool: + """Проверяем, доступна ли камера (не в списке недоступных).""" + return camera_id not in self._unavailable_cameras + + def register_event_listener(self, callback): + if callback not in self._event_listeners: + self._event_listeners.append(callback) + + def unregister_event_listener(self, callback): + if callback in self._event_listeners: + self._event_listeners.remove(callback) + + def _notify_event_listeners(self, intercom_id: str): + for callback in self._event_listeners: + try: + callback(intercom_id) + except Exception as e: + _LOGGER.error(f"Ошибка в event listener: {e}") + + async def _fetch_cameras(self): + """Получаем информацию о камерах (токены для стрима и скриншотов).""" + if self._cameras_cache and (time.time() - self._cameras_cache_ts < 60): + return self._cameras_cache + + try: + _LOGGER.debug("Запрос camera_video_data/list...") + r = await self.hass.async_add_executor_job( + functools.partial( + requests.get, + "https://keyapis.key.rt.ru/vc/api/v1/camera_video_data/list?paging.limit=100&paging.offset=0", + headers={"Authorization": f"Bearer {self.token}"}, + timeout=20, + ) + ) + if r.status_code == 200: + data = r.json() + devices = data.get("data", []) + + if devices: + self.user_token = devices[0].get("userToken") + + for device in devices: + uid = device.get("uid") + if uid: + self.camera_tokens[uid] = { + "title": device.get("title", "Устройство"), + "category_type": device.get("category", {}).get("type", "unknown"), + "category_title": device.get("category", {}).get("title", ""), + "model": device.get("model", ""), + "vendor": device.get("vendor", ""), + "serial_number": device.get("serialNumber", ""), + "mac": device.get("mac", ""), + "ip": device.get("ip", ""), + "status_type": device.get("status", {}).get("type", "unknown"), + "status_title": device.get("status", {}).get("title", ""), + "streamer_token": device.get("streamerToken"), + "screenshot_token": device.get("screenshotToken"), + "user_token": device.get("userToken"), + } + + self._cameras_cache = devices + self._cameras_cache_ts = time.time() + _LOGGER.info(f"✅ Загружено {len(self.camera_tokens)} устройств") + return devices + else: + _LOGGER.error(f"camera_video_data/list вернул {r.status_code}") + except Exception as e: + _LOGGER.error(f"Ошибка получения камер: {e}") + return [] + + async def refresh_tokens(self): + """🔄 Принудительно обновляем все токены (сбрасываем кэш).""" + async with self._tokens_refresh_lock: + _LOGGER.info("🔄 Принудительное обновление токенов...") + self._cameras_cache = None + self._cameras_cache_ts = 0 + await self._fetch_cameras() + self._tokens_invalid = False + _LOGGER.info(f"✅ Токены обновлены для {len(self.camera_tokens)} устройств") + + async def _fetch_intercoms(self): + """Получаем информацию о домофонах из отдельного API.""" + try: + _LOGGER.debug("Запрос домофонов из household.key.rt.ru...") + r = await self.hass.async_add_executor_job( + functools.partial( + requests.get, + "https://household.key.rt.ru/api/v2/app/devices/intercom", + headers={"Authorization": f"Bearer {self.token}"}, + timeout=15, + ) + ) + if r.status_code == 200: + data = r.json() + + self._build_intercom_mappings(data) + + devices = data.get("data", {}).get("devices", []) + self.intercom_ids = [str(d.get("id")) for d in devices if d.get("id")] + + _LOGGER.info(f"✅ Загружено {len(self.intercom_ids)} домофонов: {self.intercom_ids}") + return data + else: + _LOGGER.error(f"intercom вернул {r.status_code}") + except Exception as e: + _LOGGER.error(f"Ошибка получения домофонов: {e}") + return {"data": {"devices": []}} + + def _build_intercom_mappings(self, data: dict): + devices = data.get("data", {}).get("devices", []) + for device in devices: + intercom_id = device.get("id") + camera_id = device.get("camera_id") + if intercom_id and camera_id: + self.intercom_camera_map[str(intercom_id)] = camera_id + + for code in device.get("inter_codes", []): + rfid_id = code.get("id") + name = code.get("name_by_user", "") + if rfid_id and name: + self.rfid_names[rfid_id] = name + + _LOGGER.debug( + f"Построены маппинги: {len(self.intercom_camera_map)} камер домофонов, " + f"{len(self.rfid_names)} ключей" + ) + + async def get_cameras_info(self): + await self._fetch_cameras() + + items = [] + for uid, info in self.camera_tokens.items(): + items.append({ + "id": uid, + "title": info["title"], + "category_type": info["category_type"], + "status_title": info["status_title"], + "model": info["model"], + "vendor": info["vendor"], + "serial_number": info["serial_number"], + "mac": info["mac"], + "ip": info["ip"], + # ✅ Добавляем флаг доступности + "available": self.is_camera_available(uid), + }) + + return {"data": {"items": items}} + + async def get_intercoms_info(self): + if not self.intercom_ids: + return await self._fetch_intercoms() + return await self._fetch_intercoms() + + + async def get_camera_image(self, camera_id: str, retry: bool = False) -> bytes | None: + """Получаем скриншот камеры с обработкой ошибок 403 и 404.""" + async with self._image_cache_lock: + if camera_id in self._image_cache: + img, ts = self._image_cache[camera_id] + if time.time() - ts < 4: + return img + + token_info = self.camera_tokens.get(camera_id) + screenshot_token = token_info.get("screenshot_token") if token_info else None + user_token = (token_info.get("user_token") or self.user_token) if token_info else None + + # ✅ Получаем название камеры для логов + camera_name = self.get_camera_name(camera_id) + + try: + url = f"https://key.rt.ru/screenshot/image/{self.screenshot_quality}/{camera_id}/last.jpg" + if screenshot_token: + url += f"?token={screenshot_token}&_={int(time.time() * 1000)}" + + _LOGGER.debug(f"Запрос скриншота для {camera_name} ({camera_id}): {url}") + r = await self.hass.async_add_executor_job( + functools.partial( + requests.get, + url, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8", + "Referer": "https://key.rt.ru/", + "Origin": "https://key.rt.ru", + "X-UTOKEN": user_token or self.token, + }, + timeout=15, + ) + ) + + # ✅ Обработка 403 — токен протух + if r.status_code == 403 and not retry: + _LOGGER.warning( + f"⚠️ Токен для {camera_name} ({camera_id}) протух (403), запускаем обновление..." + ) + self._tokens_invalid = True + await self.refresh_tokens() + return await self.get_camera_image(camera_id, retry=True) + + if r.status_code == 403 and retry: + _LOGGER.error( + f"❌ Повторный 403 для {camera_name} ({camera_id}) — проблема с авторизацией" + ) + return None + + # ✅ Обработка 404 — камера недоступна + if r.status_code == 404: + if camera_id not in self._unavailable_cameras: + _LOGGER.warning( + f"📷 Камера \"{camera_name}\" ({camera_id}) недоступна (404), помечаем как offline" + ) + self._unavailable_cameras.add(camera_id) + return None + + if r.status_code == 200 and len(r.content) > 2000: + image_data = r.content + async with self._image_cache_lock: + self._image_cache[camera_id] = (image_data, time.time()) + + # ✅ Если камера была недоступна — помечаем как восстановленную + if camera_id in self._unavailable_cameras: + self._unavailable_cameras.discard(camera_id) + _LOGGER.info(f"✅ Камера \"{camera_name}\" ({camera_id}) снова доступна") + + _LOGGER.info(f"✅ Скриншот получен {camera_name} ({camera_id}) — {len(image_data)} байт") + return image_data + else: + _LOGGER.warning( + f"Скриншот для {camera_name} ({camera_id}): статус {r.status_code}, размер {len(r.content)}" + ) + except Exception as e: + _LOGGER.error(f"Ошибка скриншота {camera_name} ({camera_id}): {e}") + return None + + + async def get_event_screenshot(self, camera_id: str, event_timestamp: int) -> bytes | None: + """Получаем скриншот на момент события (с учётом лага).""" + token_info = self.camera_tokens.get(camera_id) + screenshot_token = token_info.get("screenshot_token") if token_info else None + user_token = (token_info.get("user_token") or self.user_token) if token_info else None + + adjusted_timestamp = event_timestamp - self.event_time_lag + + url = f"https://key.rt.ru/screenshot/image/{self.event_screenshot_quality}/{camera_id}/{adjusted_timestamp}.jpg" + if screenshot_token: + url += f"?token={screenshot_token}" + + try: + _LOGGER.debug(f"Запрос скриншота события для {camera_id}: {url}") + r = await self.hass.async_add_executor_job( + functools.partial( + requests.get, + url, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8", + "Referer": "https://key.rt.ru/", + "Origin": "https://key.rt.ru", + "X-UTOKEN": user_token or self.token, + }, + timeout=15, + ) + ) + + if r.status_code == 200 and len(r.content) > 1000: + _LOGGER.info(f"✅ Скриншот события получен {camera_id} ({len(r.content)} байт)") + return r.content + else: + _LOGGER.warning(f"Скриншот события {camera_id}: статус {r.status_code}") + except Exception as e: + _LOGGER.error(f"Ошибка скриншота события {camera_id}: {e}") + return None + + def get_event_archive_url(self, camera_id: str, event_timestamp: int) -> str | None: + token_info = self.camera_tokens.get(camera_id) + streamer_token = token_info.get("streamer_token") if token_info else None + + adjusted_timestamp = event_timestamp - self.event_time_lag - self.pre_event_seconds + + base = f"https://live-vdk4.camera.rt.ru/stream/{camera_id}/{adjusted_timestamp}.mp4" + params = "mp4-fragment-length=0.5&mp4-use-speed=0&mp4-afiller=1" + + if streamer_token: + return f"{base}?{params}&token={streamer_token}" + return f"{base}?{params}" + + async def get_camera_stream_url(self, camera_id: str) -> str | None: + token_info = self.camera_tokens.get(camera_id) + streamer_token = token_info.get("streamer_token") if token_info else None + + base = f"https://live-vdk4.camera.rt.ru/stream/{camera_id}/live.mp4" + params = "mp4-fragment-length=0.5&mp4-use-speed=0&mp4-afiller=1" + if streamer_token: + return f"{base}?{params}&token={streamer_token}" + return f"{base}?{params}" + + async def open_intercom(self, intercom_id: str): + try: + await self.hass.async_add_executor_job( + functools.partial( + requests.post, + f"https://household.key.rt.ru/api/v2/app/devices/{intercom_id}/open", + headers={"Authorization": f"Bearer {self.token}"}, + json={}, + timeout=10, + ) + ) + _LOGGER.info(f"✅ Открыт домофон {intercom_id}") + except Exception as e: + _LOGGER.error(f"Ошибка открытия {intercom_id}: {e}") + + def _parse_iso_to_timestamp(self, iso_str: str) -> int: + try: + dt = datetime.fromisoformat(iso_str.replace("Z", "+00:00")) + return int(dt.timestamp()) + except Exception as e: + _LOGGER.error(f"Ошибка парсинга времени {iso_str}: {e}") + return 0 + + def _format_local_time(self, iso_str: str) -> str: + try: + dt_utc = datetime.fromisoformat(iso_str.replace("Z", "+00:00")) + dt_local = dt_utc.astimezone(self._local_tz) + return dt_local.strftime("%Y-%m-%d %H:%M:%S") + except Exception as e: + _LOGGER.error(f"Ошибка форматирования времени {iso_str}: {e}") + return iso_str + + def _get_event_description(self, event: dict) -> str: + event_type = event.get("event_type", "") + rfid_id = event.get("rfid_id") + + if rfid_id and rfid_id in self.rfid_names: + return self.rfid_names[rfid_id] + + descriptions = { + "api_open_remote": "Открытие через приложение", + "face_open_remote": "Открытие по лицу", + "pin_code_open_remote": "Открытие по пин-коду", + "code_open_local": "Открытие по коду", + "rfid_open_local": "Открытие по ключу", + "dtmf_open_local": "Открытие через телефон", + } + + if event.get("rfid") and event_type == "rfid_open_local": + return f"Ключ {event.get('rfid')}" + + return descriptions.get(event_type, event_type) + + async def get_last_event(self, intercom_id: str) -> dict | None: + try: + now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + url = ( + f"https://events.key.rt.ru/api/v2/events/list?" + f"begin_raised_at=2018-01-01T00:00:00Z&" + f"end_raised_at={now_iso}&" + f"device_ids={intercom_id}&" + f"limit=1&" + f"sort_by=raised_at&" + f"offset=0" + ) + + r = await self.hass.async_add_executor_job( + functools.partial( + requests.get, + url, + headers={ + "Authorization": f"Bearer {self.token}", + "Accept": "application/json", + }, + timeout=15, + ) + ) + + if r.status_code == 200: + data = r.json() + items = data.get("data", {}).get("items", []) + if items: + return items[0] + else: + _LOGGER.warning(f"Events API вернул {r.status_code} для {intercom_id}") + except Exception as e: + _LOGGER.error(f"Ошибка получения событий для {intercom_id}: {e}") + return None + + async def update_last_event(self, intercom_id: str, event: dict): + camera_id = self.intercom_camera_map.get(str(intercom_id)) + if not camera_id: + _LOGGER.warning(f"Не найден camera_id для домофона {intercom_id}") + return + + event_timestamp = self._parse_iso_to_timestamp(event.get("raised_at", "")) + if not event_timestamp: + return + + screenshot_data = None + if self.event_screenshot_enabled: + screenshot_data = await self.get_event_screenshot(camera_id, event_timestamp) + + archive_url = None + if self.event_archive_enabled: + archive_url = self.get_event_archive_url(camera_id, event_timestamp) + + self.last_events[intercom_id] = { + "event": event, + "camera_id": camera_id, + "timestamp": event_timestamp, + "local_time": self._format_local_time(event.get("raised_at", "")), + "description": self._get_event_description(event), + "event_type": event.get("event_type", ""), + "screenshot_data": screenshot_data, + "archive_url": archive_url, + } + + _LOGGER.info( + f"🔔 Новое событие для домофона {intercom_id}: " + f"{self.last_events[intercom_id]['description']} " + f"в {self.last_events[intercom_id]['local_time']}" + ) + + self._notify_event_listeners(intercom_id) + + async def _events_polling_loop(self): + _LOGGER.info(f"🔄 Запущен цикл опроса событий (интервал {self.events_refresh_interval}с)") + + while True: + try: + for intercom_id in self.intercom_ids: + last_event = await self.get_last_event(intercom_id) + if not last_event: + continue + + request_id = last_event.get("request_id") + last_request_id = self._last_request_ids.get(intercom_id) + + if request_id and request_id != last_request_id: + self._last_request_ids[intercom_id] = request_id + await self.update_last_event(intercom_id, last_event) + + except Exception as e: + _LOGGER.error(f"Ошибка в цикле опроса событий: {e}") + + await asyncio.sleep(self.events_refresh_interval) + + async def start_events_polling(self): + if not self.events_enabled: + _LOGGER.info("Отслеживание событий отключено") + return + + if self._events_polling_task and not self._events_polling_task.done(): + return + + await self._fetch_intercoms() + + self._events_polling_task = asyncio.create_task(self._events_polling_loop()) + + async def stop_events_polling(self): + if self._events_polling_task and not self._events_polling_task.done(): + self._events_polling_task.cancel() + try: + await self._events_polling_task + except asyncio.CancelledError: + pass + self._events_polling_task = None + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + cameras_api = RTKeyCamerasApi(hass, entry) + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + "cameras_api": cameras_api + } + + entry.add_update_listener(update_listener) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + await cameras_api.start_events_polling() + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + cameras_api = hass.data[DOMAIN][entry.entry_id]["cameras_api"] + await cameras_api.stop_events_polling() + + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + await hass.config_entries.async_reload(entry.entry_id) \ No newline at end of file diff --git a/custom_components/rtkey/button.py b/custom_components/rtkey/button.py new file mode 100644 index 0000000..1a93ada --- /dev/null +++ b/custom_components/rtkey/button.py @@ -0,0 +1,93 @@ +from typing import Any + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo + +from . import DOMAIN, RTKeyCamerasApi, _LOGGER + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Any, +) -> None: + cameras_api: RTKeyCamerasApi = hass.data[DOMAIN][config_entry.entry_id]["cameras_api"] + intercoms_info = await cameras_api.get_intercoms_info() + + entities = [ + RTKeyOpenButton(hass, config_entry, cameras_api, intercom_info) + for intercom_info in intercoms_info.get("data", {}).get("devices", []) + ] + async_add_entities(entities) + + +class RTKeyOpenButton(ButtonEntity): + """Кнопка открытия домофона.""" + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + cameras_api: RTKeyCamerasApi, + intercom_info: dict, + ) -> None: + self.hass = hass + self.cameras_api = cameras_api + self.intercom_id = str(intercom_info["id"]) + self.camera_id = intercom_info.get("camera_id") + + self.device_name = cameras_api.build_device_name( + intercom_info.get("name_by_user") or + intercom_info.get("description") or + intercom_info.get("name_by_company", "Домофон") + ) + + self._attr_unique_id = f"open_button_{self.intercom_id}" + self._attr_name = "Открыть" + self._attr_icon = "mdi:door-open" + + # Сохраняем информацию из API домофонов + self._device_serial = intercom_info.get("serial_number", "") + self._device_group = ", ".join(intercom_info.get("device_group", [])) + self._is_active = intercom_info.get("is_active", False) + self._capabilities = [ + cap.get("name") for cap in intercom_info.get("capabilities", []) if cap.get("setup") + ] + self._inter_codes_count = len(intercom_info.get("inter_codes", [])) + + async def async_press(self) -> None: + """Вызывается при нажатии на кнопку.""" + _LOGGER.info(f"🔘 Нажата кнопка открытия для {self.device_name}") + await self.cameras_api.open_intercom(self.intercom_id) + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Атрибуты, которые будут видны в карточке сущности в HA.""" + attrs = { + "Статус": "Активен" if self._is_active else "Неактивен", + } + + if self._device_serial: + attrs["Серийный номер"] = self._device_serial + if self._device_group: + attrs["Группа"] = self._device_group + if self._inter_codes_count > 0: + attrs["Ключей доступа"] = self._inter_codes_count + if self._capabilities: + attrs["Возможности"] = ", ".join(self._capabilities[:5]) + if len(self._capabilities) > 5: + attrs["Возможности"] += f" (и еще {len(self._capabilities) - 5})" + + return attrs + + @property + def device_info(self) -> DeviceInfo: + device_id = self.camera_id or self.intercom_id + return { + "identifiers": {(DOMAIN, device_id)}, + "name": self.device_name, + "manufacturer": "RT Key", + "model": "Домофон", + } \ No newline at end of file diff --git a/custom_components/rtkey/camera.py b/custom_components/rtkey/camera.py new file mode 100644 index 0000000..79053b6 --- /dev/null +++ b/custom_components/rtkey/camera.py @@ -0,0 +1,236 @@ +import datetime +from typing import Any + +from homeassistant.components.camera import Camera, CameraEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.entity import EntityCategory + +from . import _LOGGER, DOMAIN, RTKeyCamerasApi, TOKEN_REFRESH_BUFFER + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Any, +) -> None: + cameras_api: RTKeyCamerasApi = hass.data[DOMAIN][config_entry.entry_id]["cameras_api"] + cameras_info = await cameras_api.get_cameras_info() + + entities = [ + RTKeyCamera(hass, config_entry, cameras_api, camera_info) + for camera_info in cameras_info["data"]["items"] + ] + + # Добавляем архивные камеры событий для домофонов + if cameras_api.event_archive_enabled: + intercoms_info = await cameras_api.get_intercoms_info() + for intercom_info in intercoms_info.get("data", {}).get("devices", []): + entities.append( + RTKeyEventCamera(hass, config_entry, cameras_api, intercom_info) + ) + else: + _LOGGER.info("Архивные видео событий отключены") + + async_add_entities(entities) + + +class RTKeyCamera(Camera): + """Live-камера (для всех устройств — и камер, и домофонов).""" + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + cameras_api: RTKeyCamerasApi, + camera_info: dict, + ) -> None: + super().__init__() + + self.hass = hass + self.cameras_api = cameras_api + self.camera_id = camera_info["id"] + + self.device_name = cameras_api.build_device_name(camera_info.get("title", "Устройство")) + + self._device_model = camera_info.get("model", "") + self._device_vendor = camera_info.get("vendor", "") + self._device_serial = camera_info.get("serial_number", "") + self._device_mac = camera_info.get("mac", "") + self._device_ip = camera_info.get("ip", "") + self._device_status_title = camera_info.get("status_title", "Неизвестно") + + self._attr_unique_id = f"camera_{self.camera_id}" + self._attr_name = self.device_name + self._attr_supported_features = CameraEntityFeature.STREAM + + self._unsub_stream_refresh: Any = None + + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + + self._unsub_stream_refresh = async_track_time_interval( + self.hass, + self._stream_refresh, + datetime.timedelta(seconds=TOKEN_REFRESH_BUFFER), + ) + _LOGGER.debug(f"Таймер обновления стрима запущен для {self.device_name}") + + async def async_will_remove_from_hass(self) -> None: + if self._unsub_stream_refresh: + self._unsub_stream_refresh() + self._unsub_stream_refresh = None + await super().async_will_remove_from_hass() + + async def _stream_refresh(self, now: datetime.datetime) -> None: + try: + # Если токены протухли — обновляем их перед получением URL + if self.cameras_api._tokens_invalid: + _LOGGER.debug(f"Обнаружены протухшие токены, обновляем для {self.device_name}") + await self.cameras_api.refresh_tokens() + + url = await self.stream_source() + if url and self.stream and getattr(self.stream, "source", None) != url: + _LOGGER.debug(f"Обновляем URL стрима для {self.device_name}") + self.stream.update_source(url) + except Exception as e: + _LOGGER.error(f"Ошибка обновления стрима для {self.camera_id}: {e}") + + async def stream_source(self) -> str | None: + return await self.cameras_api.get_camera_stream_url(self.camera_id) + + async def async_camera_image( + self, width: int | None = None, height: int | None = None + ) -> bytes | None: + return await self.cameras_api.get_camera_image(self.camera_id) + + @property + def available(self) -> bool: + """Камера доступна, если она не в списке недоступных.""" + return self.cameras_api.is_camera_available(self.camera_id) + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Атрибуты с динамической проверкой доступности камеры.""" + if self.cameras_api.is_camera_available(self.camera_id): + status = self._device_status_title + else: + status = "❌ Нет связи" + + attrs = {"Статус": status} + if self._device_model: + attrs["Модель"] = self._device_model + if self._device_serial: + attrs["Серийный номер"] = self._device_serial + if self._device_mac and self._device_mac != "00:00:00:00:00:00": + attrs["MAC"] = self._device_mac + if self._device_ip and self._device_ip != "0.0.0.0": + attrs["IP"] = self._device_ip + return attrs + + @property + def device_info(self) -> DeviceInfo: + info = { + "identifiers": {(DOMAIN, self.camera_id)}, + "name": self.device_name, + "manufacturer": self._device_vendor or "RT Key", + } + if self._device_model: + info["model"] = self._device_model + if self._device_serial: + info["serial_number"] = self._device_serial + return info + + +class RTKeyEventCamera(Camera): + """Камера для просмотра архивного видео на момент последнего события.""" + + _attr_should_poll = False + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + cameras_api: RTKeyCamerasApi, + intercom_info: dict, + ) -> None: + super().__init__() + + self.hass = hass + self.cameras_api = cameras_api + self.intercom_id = str(intercom_info["id"]) + self.camera_id = intercom_info.get("camera_id") + + self.device_name = cameras_api.build_device_name( + intercom_info.get("name_by_user") + or intercom_info.get("description") + or intercom_info.get("name_by_company", "Домофон") + ) + + self._attr_unique_id = f"event_camera_{self.intercom_id}" + self._attr_name = "Архив события" + self._attr_supported_features = CameraEntityFeature.STREAM + + self._event_description = None + self._event_time = None + self._archive_url = None + + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + self.cameras_api.register_event_listener(self._handle_event_update) + + if self.intercom_id in self.cameras_api.last_events: + self._update_from_event() + + async def async_will_remove_from_hass(self) -> None: + self.cameras_api.unregister_event_listener(self._handle_event_update) + await super().async_will_remove_from_hass() + + @callback + def _handle_event_update(self, intercom_id: str): + if intercom_id == self.intercom_id: + self._update_from_event() + + def _update_from_event(self): + event_info = self.cameras_api.last_events.get(self.intercom_id) + if not event_info: + return + + archive_url = event_info.get("archive_url") + if archive_url: + self._archive_url = archive_url + self._event_description = event_info.get("description") + self._event_time = event_info.get("local_time") + + if self.stream: + _LOGGER.debug(f"Обновляем URL архива для {self.device_name}") + self.stream.update_source(archive_url) + + self.async_write_ha_state() + + async def stream_source(self) -> str | None: + return self._archive_url + + @property + def extra_state_attributes(self) -> dict[str, Any]: + attrs = {} + if self._event_description: + attrs["Событие"] = self._event_description + if self._event_time: + attrs["Время"] = self._event_time + if self._archive_url: + attrs["URL архива"] = self._archive_url + return attrs + + @property + def device_info(self) -> DeviceInfo: + device_id = self.camera_id or self.intercom_id + return { + "identifiers": {(DOMAIN, device_id)}, + "name": self.device_name, + "manufacturer": "RT Key", + "model": "Домофон", + } \ No newline at end of file diff --git a/custom_components/rtkey/config_flow.py b/custom_components/rtkey/config_flow.py new file mode 100644 index 0000000..020b862 --- /dev/null +++ b/custom_components/rtkey/config_flow.py @@ -0,0 +1,74 @@ +import voluptuous as vol +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + OptionsFlowWithConfigEntry, +) +from homeassistant.core import callback + +from . import ( + CONF_NAME, + CONF_TOKEN, + CONF_CAMERA_IMAGE_REFRESH_INTERVAL, + CONF_SCREENSHOT_QUALITY, + CONF_LOG_LEVEL, + CONF_EVENTS_ENABLED, + CONF_EVENTS_REFRESH_INTERVAL, + CONF_EVENT_TIME_LAG, + CONF_PRE_EVENT_SECONDS, + CONF_EVENT_SCREENSHOT_ENABLED, + CONF_EVENT_SCREENSHOT_QUALITY, + CONF_EVENT_ARCHIVE_ENABLED, + CONF_ARCHIVE_DURATION, + CONF_EVENT_SENSOR_ENABLED, + DATA_SCHEMA, + OPTIONS_SCHEMA, + DOMAIN, +) + + +class RTKeyOptionsFlow(OptionsFlowWithConfigEntry): + async def async_step_init(self, user_input=None): + if user_input is not None: + return self.async_create_entry( + title=self.config_entry.data.get(CONF_NAME, "RT Key"), + data=user_input, + ) + + current_options = dict(self.options) + if CONF_TOKEN not in current_options and CONF_TOKEN in self.config_entry.data: + current_options[CONF_TOKEN] = self.config_entry.data[CONF_TOKEN] + + return self.async_show_form( + step_id="init", + data_schema=self.add_suggested_values_to_schema( + vol.Schema(OPTIONS_SCHEMA), current_options + ), + ) + + +class RTKeyConfigFlow(ConfigFlow, domain=DOMAIN): + VERSION = 1 + MINOR_VERSION = 1 + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> RTKeyOptionsFlow: + return RTKeyOptionsFlow(config_entry) + + async def async_step_user(self, user_input=None): + if user_input is not None: + return self.async_create_entry( + title=user_input.get(CONF_NAME, "RT Key"), + data=user_input, + options=user_input, + ) + + # ✅ Добавляем add_suggested_values_to_schema для корректной работы переводов + return self.async_show_form( + step_id="user", + data_schema=self.add_suggested_values_to_schema( + vol.Schema(DATA_SCHEMA).extend(OPTIONS_SCHEMA), + {}, # Пустые значения по умолчанию для первой настройки + ), + ) \ No newline at end of file diff --git a/custom_components/rtkey/icon.png b/custom_components/rtkey/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a76898a3e2e371c23200f250608196acc365e8f4 GIT binary patch literal 48178 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

yfh=#)Us{$2$d=(pVizU3j3mwxLtSOh~9xiPSA zUAlMsV4iPqdsf~J?6%y$9PiC$T6`S{f4s>U)YBR-&JW0wmd%*4dJxi()K5hzQgfOO za>GO!Iz0_U4_;EDEo4d7%(y?j#{DV#OSh&|BS zqrE5jW!ea+dGsk!p-Il6UTllR&p1Cpu(2MQ^TvZTXgOkS1<-$MJYCQ;6>?=%{2S@@ z8>LJ>pW=DHQcnBp5SK%j5VJH9MgxIt?DD;G+xu5Nj?Lmy17~<6;kfw29!x#U*py&< zR(E(gd>9Yz@kXAj?x21Pj>;HHYrpNbMiMdnd&v2#Mgh>$&9u#{$jVAq_2Dl8WA?%F zru~POyeVJY@uekXKd)cBkJX==Y{6wF`;CaRiJyge!DNcKA7i!66KOPvS5 zKdlu6aeBPGgggCi`$5D1QirW=xikbZ&Z$amoY%OtcOTIla145#m0!lgeT&{#WH;A9 zqxi>eD=h|X47rI5LN1)#{0B-xZb_G;kDjDCSUfEE*4fiZW*Xk811cDS&J*-}7C@X$ zhgZ7j%39EZVh2+>?yy?=oXm9iXY`*j88>g4@@Xmr@+6iEHRi0ol-b5jlb8ME)1(av zTqn+Vu1k>6$>?_aOQbT)nc(!up0PWpETs}AaCnl*N|97tQn9bPNPx@^Nb+b)WI2D z=1>AhLI#dsO7HOxZ6!A}wJ5r}p8n%ahkGn`?m4!hzfW&KLrCI1snxJ zO$O^)?gdkv)YQTIepMm^gXqMWi)&wZvO1{lSHna(rU2sa_F&!&@6V!m+E#N;&NpYC zb4?qZL0u}qY9k(B@Ud@%akE!hpQN)%Xa%RMnSo^_%ZU9@1pdcM(VlH>;-_$%qlb+Jt=|vAMW!|H`h4A| z13nzK(G6rAeowBn_C{?z?4|9|>-yh!9V8d-SJa#pmuY(>Z|eT;KYRH2l$ zBo_g1>SI7{sE1*XE=nPks7oiAjt64tHm7p$qiL$T_vAp~sXmRo4^pzXO0;dHP5Q=X z(zkV0qNa?vy7t1$s_O0gp=Ka>iPi39rgzHKO(CZ)Xg{#NBz!`H%c4F0f-Z;izqlcK z?PMw@_b>xGH~4{+>6X^utuPtmVeHO(pkbY*YKg>=8iYd^1u8mA#5)7M_cYm3?b4^D4#*QL;O%cvXnLgLfX zhQ~gokTvpDj{3sLgFa4-M}xPNV2u(cEo~X|%zjrgH)-ieTNp>x!(V)t!vOp~W&O8Q zV-2R!*=GU0b;wOF&QWG2vf1f3FIFi~s|OglsexF<9nND2ckus^9&KtJ@{zru-jYYQ zs)x0b;=BL)9Pj{i=|{8sd6Y&7aP^q#SW^Q-EqyWRTOly37m=f>2_WZ9qOt_SDR+Q@H4wLVDQ^-Pz=5a#$xB+(QE zY$?~-(MjgJoXQs;cjunVWCQfucyEgcjoH7~>z6ynN#wHwFpCc{7Xr+bv9W(P>Sl_2 z-<9HO6I(*8KGH(TNp>7?1W6BIlAf|>M&DyHs4CP4O_EHs^o~Nb>qS4zR_|8sCF*TKU-6)E`fw;kbnk#eL?*>s)k@<>1}uR31^SQX*6Ah_QhvJZ(5&3-!D~r* zQ%80e-s#41N}{U~7~d?qaqLD_I#etOkJ=A-%6C*45xs5u<)=joBVnpu6qZ)@aktT5 zTm3DY;*Uw}aQyeze)Zhz>zR|@;DHddVN6`ziCc8TLBc>m$;#LfdSYy*0lUo|!nwLm zfajwVs5U7&l5USS>rSLW$pCupRykNvb0h+g<)u1;APL~NQ+pxzMi=N!B z{`?m9^-J@b>e_bf)D=M~OAw7m3%2f|;#r7bA#+8tMP*KT0#y@{ zOQyVM`j)d$*QP)h6(*hwfq>!(Iax{jfr-lxOm@UdIKYTDaco4;uw41OD(&fD^4NE- zBKf985mJh%j7=^c07RUg_#=MBS<$e;;Ur-f1E}{J* zmU~JdPbd?>(od)&h1NfgcO2e)gxdf?q_u3pXl{x*dkrpxD8vd>ET`I#M6>`;FL3@Y z^cs|m`W;x-O6i9GvFPtq&5_c#^HdNzgIK38{JYzQvK$7b3;z)%?>`(Dm_d;Z*4oLxr8 z#DyT9)(){PrmFgrdGIKp+W{LjeDCrRSoA7h#=tmiYX;DMN z?dFV<9l{g%#Yo&@d6vx0u5%uJSJh&%3ci8d1@C-deSmo!Mz8^q+*{J06F(-mfNZ^l zEE|V3iiXbT5W}>7blohKp2>4|7w(yxh%!3jql0b=MO~&BUv;ZJNYw(S#LQGiR(YDx zc{n#=`;(Ecj^^W-M~1c);_41U_aBbfc25+TqFL<^g#2iKb5fzIEV}sN3f_Ni*;g>ubBkwJN}8E*$V7P??2z| zXMj#jg4a~bS7hd*zxDt_U<)KWK$bNWO)m9(zy6SK$a!zd253HXy`U2medv;Koc}R} z+ecsT$KaVOc5E?OKqaOBI-vAwb|az%D_uS}ru%`>=O6!;|6B~1usm~!{#f(pkf46b zq1$#*gH$6BSeUk4w_1>9p!!((HpQoVGsUrJ`(;`QcIaz!N}H!CFNPVkZ~bYn-BWm= z6CC6+qpfMgn==mq_5rF~anItVc};FEVhkJ#d>f*TP6ES(0*^-j7--2#Y;>eON@FkspzWBvy=UPZZnw0x}CLkd*DeS#aH?5f;J3~2p%SlfCS(0Tpdm(2;?&v`SwKXV`yPn0{c zzA~sEV01C@{!rR}o8`$NusXs{4M;g{B%`3@7KS(F9b$_2)!~`uHa0+xBj0zm;)4~~ zZ7I=#{6M09ex_S;G$OnRnjqIF(*RkJtx~KPMPxl3uEjl1`!6()Lf+NUuDm>RgwpWI zLA8{Dp}dx8k>9sQuL+YYvng|xx(4DGBbM65`F!TDbhxVC-Ax;*t?>+)kpuwB-jpXX z*3{8p!$eavcuNdEM5g0bBx{_jKxW_}uah)NT=x6tUM`vt-NX>hj}3QM9DId3%H)3$ zHf6F=GtBo!_~3Sz*KMD4vw;S0;S-E%Nz`UB8&YN&*vro~NWaZ{ zw6WeFbBc7x+C&t^)QC1`c_kk_oNyHzgKQ9MWGlA?0vMWj8Lwu{w*}D(7=eY0+H5q! zq8DAbb;rrwsvlqI7Limwr!ILE(z)++SruAMHwl%o0sph@6l6{e8gIZB_2eQgH(^I! ze&mR7FwWUD8VSzh6q;kFFSv*QZ0gf=wS9|SqsDifT$$!@#mH@Ff&F+=(K&fM&)h_Z z+Oq$9nbexYV(h_;$xwJQU2`Ofuj+%`OhxOfGBD_LA#P29@o5|{i(E!gPtRwPLa7Dm)|O-iLD z-Q960m!m_(>VEHZaru|9L@Wa!QVn;2m_mDnW&`scqbKnJP^|9t+1*>B}x2ztnq2cqZC zf?{IdVlP?05JTZRZsQ(QF&M296PmSrReT;2L_IZbnY_KeryP!@p--mOPUchv%$Rh%kxc;p z!l7lbW9POcv#0mL(y(RG^7YnoR=f)w*rxK#W^kt!vf5L@XtVQaXzO4Np)Vo5^1f%C zwE*`Np0J2v!bZzJ`Ic(M7TDPB`&>Bl!F(`;Dep1<$K9HOF#&sMO{ti6hN6k)(roJ5 z(2XBg)zP$;UgTHR4@&^U&N@NmwVrXPR9zLRRafe8$X87@0a-WYe3T9H8fAH`FQgw%NhB)1HSa$qd(TX8w4p3I+uCAy z@lAGm{r!K;Nsm9&DS_VRRQt*1MZIB{D$0;*(}_nnIa5}vd%4R0Y_)mXq>)b+Zj!O~ z?Ls7+(uTRJ8U9Tk=s^uML6=EkzDj=&_H{T^9x5Vy&o z7-=0^CEcqPkDDp5R=!k3!?1!91!3=TLwf9I6~<)V8ztjFQSj&f6Z`$66dLl{-FdLY zOfr;N=y|Guirtri@}OiUJqD(CQLK9EC#-XUwzs`A^1C|1+uBrNY8hWIO@bdiA1ysO z;B@_;mTAqf-_umk^WB>aXlqB$kj6i5R9mz36`H-YpJ`aE!Z`Ig|H}+f65b;vWYyfs zlDP4qsft_YGp%uzXUR{2JXpYg9Piq9!A)+TS;bZRmC*u7D3HSXCG1Ow_t^tohcy2Y2$`!!iOY~Ye_2xh`WHjyDT;f1idV?ts8(9EzbQ&{9$ofleu zQ7zQ9QDNT}jFY%m1k7Ddwl9oSd7ymL%TNTVVT@;_zKNL*^u{d=1e4@|=54M1Z;fFb zlYBzwmFekf*@mN6v#$kzk@U16Y735@P(+oBNWeosDE`M3cO&s$lIp+m5a4moI>Rfn zJ7zM{Na%{(^&yOcCq2+(tH~WVZvkS3b@kMYUNftW9(ua4ZC!aov~;9TmiDN5J`m_e zv1Sfya#+@WS~35q+Kkt{scQNPPgeMU86z(@{yMSs^pTN2k>oI+EPEVY_OnmD)Z#0& zDuLf#(OnuAAe4nv?E!3IbNpwr-vOa4!PI%IHK+9wGhOzK_F_`eZt8{F7iqh_&yBhT z^Ifk#{)`#>sqmt+`9Xy1C=1_YccV308p?)%^KP;01B0Y?$4_q;Q>=j461M%buJo3i zePVNcTP8p$t)v@n@bmkI88?|_J%Q5h^R^tf6;X2F4$ukk=q2pmVVQx$y8TO^hwHH_ zoo*U7!qCT6h#>Bhbd(b)%=&HDGc|CfpckiL=a5F$8m~}v#IqX=xxj?@Y=|N$I8|r;LsZ^Wcr;dtdJ!-k*EMvTUv0 z(eaj+pMgv5(epieflM(9?-(BZpJRB*pvc&>1CmX2!`Bn^%-f`X@_*yc=wrrNz{EIn)&9%PE$ykItXTtS?+G18kZx^uo5NU5MmS$N+o=UuhfT|rTcLyR%Zw*|q9%^wr3g=5zPMCPgB0%l zvESVnfkcU*+46P<1F_pG8gW_~Fa7{;TIO1hdu}Q}rIoc3J^AD{=8kQ;s;g+MRi4Xz zGrHw^GTqwy?2s*lesty6SMPz2zJi(XAB04!f3tAfZwUC6s*=LuNt>hwu1h_5g0I697!XmFb2ss@^2EcvS7ZaXyw?bla@~{O2-`g+?He_l@0~$^I(&XsD(cBs&V0dQq&UO_lyDi#e}R;q z@xC2~&iw?Qt8c7YLH2$G6*{2y*X|mM!MM~>MIFTto>=Fq+fEcu3cohGcxrV;bLvoQ zI`-Tw-l*zZZ!WD!+_k%3bZu{d)_#TW@M%so*~sG}H(QV#W&q$h9N;*e>%LeGefVnS zMEX;+bylEDv~%d=st1kot_883Pn|w`Ux&_-OiXN(Ou9B|4Wi)?i$b_hsH&rBu8$CV z0s+1SwO%O-Z{z;5>rS`y1F1&59*yQ7CNr-88Rb?rznS3#JQ_g)Qx{^yMm;umLz52y zgE;EJLRT-M^>!q^ZY2Tz$ErU^rAmcJ-8L{OID#?stoYVelKO_oCZ%^b8svz?TAtMy z7?QujOH2}d&@_t8Em0OQG+d>!ZRsN4PDyt%+ohN~vE3WQK!|W>R!tFLGSak_TDMVz zg^Y-U^HFBhMlJ&gXqD&ulB5+C?KT%aOggA&T^ENf;q=6cX&1H=Do>jBg6; zbjoG})c&g@;K1%~Mt?+CS<#Nn~edr_HdUS_CDQ3I$ zeV=;Zd>Jq4eMT+md-?qqU1oDB;Di!|HAsBvNnDPl8@vLf{vpIo{#ce$`*@saM{6Jo zG%bO$xA&y|JvjYe%4sOr_a#g%E*VGq`WKWgtEST68DfIoJXc5{e_1nLyQq`=v{};n+ z>ne?-XCKaF7VS!dHE%0s$Gc?Nw0=#BCw$xp*GnDr9&_~Z>=^BaB>+i={CJ_l&DoiO z9Z652B+GltNYO-nHfv6|;3v1uhxcACS`M{|F`Jh~IE=olnJI2vF;{w$QrWeNeJX6K zYRdhYl|wOSlFNGp5sEixicY6N z>7!rG+7@iJ0Ty$6b_#zf>w7%wHB%hf%=s8Qz|4GF$0@3mG!9moT<*u2szQu3Oac%C zo;1E0dOOQzg{y+&k^1kMy_g@R=HKvD#6HCOn-r-2DX*;TaDMkEMdm(HJyKOB{7Vh0 zq=L9b(%2$cSo|Fr9@#o45Q16*R;IJ_&N)UhD)hscz7vA?Y|&0O0i^l%El|4)Srsxt zQ<*lUhL@u6Kr46X`S6n3cf!fd+3H67$KTfcF84UhS#jyQMDJgTekjA>RZC1(jIIpm zIGUUI&PKsdhQ&Sc9AKTp4C{9>rq zzQp^z^nR%ND=*D9Eq%ACfT)m12{s<=%Nz0V4L2gIj&8?-ohvF5xfqu8WeEPgKy=A* zbJM_bC`NxZwQ&^w#~)r{SA`YZL!mIOFMHc_krfxiBaispS6lw7$&771AM&h~<@?*1 zn^QE7o-2M*$?T}}TDPL-Ud0ba3RgVsLvM1(z1ixktS*f8ve;_}Q%tC-%l@y?q@8O| zKj;!oEiqU>hV-c`&$c8PGpIoIPPuM7)887r&vA&nr|O&t%JH2?4gTmzF9+3h33^qn zdW*HyRo<<+3+02rsEi-d&R)D*o6%k}0XVr$FF0a${N_A0{ts@8`a|`2$N7g^ql_Si zed6LsPn3i4O;5(UcE!JSsnJ< z@?ngA=}OqFhDPl;CmMV86kYikTlb)>eU^G^RCqp!aTdim-gUgU=&y$;Ol~=@YTq)8 zNDYa!B@|Zs+m=5Mu6Q;Ue*=t^AkcX3s2Qkgx`lk$JZ1+NZNU6#lEcP^@;zhqql!^d zLPuAY;FwXamFX5~Cp@B?AFD?Wn(-ZRzJgdHb-);wvJFjP5e~O|B;Pu_6%v&^z_n|t zQ5z&GUY*VcHEEN=1E~M`&@Q1YFlzFLbY54$cQ2cB*7Pi>%h{Ph?ofQ?b*1a zE4a!|J1RTwL$_+{uqyY8Zn1IG#W(Dz^P+c7rotVOMmIgsZX52fQepAaysq&1ikj|U ze>SGG+l~TE*^-%be&oVKr=7l7hSiybe8`X^jZS`b9xdAn3%e|1D{`5pJ$+{X4EyBs z8TVhl3WP!~?sK7;$JP5&{YZ?K)~kg;nIRXl@p#yltpXp7{%jOr%gJR_2F%%^^E2^M zmq81G*&_9qM-y=~N-;EUj$N1VvOy>hJw0f>#A4)5)&SB7bD|K_g>#j4JJ-{?&>N%A zrK$xZ)8rXS!%7yFljYx|POt}ylafg{kNkOYljX+FLfyk7v`Y@V<9ovqME;TJTLM2H zf7`4zAs#$?$xUZ51^X&f!t1E9rE-O$l{%RD4fT(4u)q$LhSA)&keJFsXM7RWsLDK* z)r6NdkKDhFC|#(yE*uXRsWHlozFs0$1+2ll?gp%nrBQN8Kv{C^Pzrl^d``Q0=hb)T`O%_1F=@!+rTw*R*IXY!wdb6`C? zwf$D4j6>fxj%uiUG@-t4`t>2&C7rzBflAcN$!KiRZ;wonR^zE=err>9O%Dc|7 z@}Y$rSadn^7M`n}D7T*6I3BHrq`y+yA^f6KSX6V_TxWa9L1FjHiz@J&Ms<@z)WYm?}v+>ElB2QjS_W^ifqJIH{ogmkK$~ z7HR@mCEjH$>Dq(e@YKE(0oU~%N&Wi+Ux@F+CqM>B)k2{)v)%?_I7#X%2RLiZyTt-B?5b7 zhlV=1!pM7D!PSO&W6P0zAjTa<*>PvXk757b!K%Oi9l zH^UZKsmc?g9tictG;bQSHDa4ptsxbJVtl8#SE^aogs~dJ8PX#95v?3`oIHgM=sezj za^j^^Av8e#X6y!+=iOG9=zhDQH&THz$p}=cEdj#-b6xjZl#NP7Fa8~erVTo~g7}h$ z|2()j9g|$%Fe8T+9)>Kh0hJX6!X6HtuHFfe2<-OUD&CTOtF`7Las*bogqtx&QrFFA7lZbvCs!cNSyZ6o!G&yg%H`#f z>x<{7NQ*W(dB`#^wYkd9GwRN`Bd6}ahKMqnZ^wJh73C2Iwp+Z^vSw#e3Lyzryq9-a zvTU~&6SFLGLURRp-XV4x-_H@x&DsN!9WDgvIKbdI!MFC`54ZorC(VC5ikdj^_V(yW zbSpfZ5N@78P!8NyqnT2TZNXQ^fjB^e^1*A#>J)YtNF&4vh#JmSb%`g{DPP9G74ka4DaIdM%Q25XY}^yPN4hN zyvLm@tGBg=&%Fq~e!K~T_dfyQJ&)vb=kh`i){9$%%YFYPhM^!DObPKxoBudBS#Is& zQ@JTrY@K7KO=KFFiX)UoZthC?Jw^Ir$`o?W*8`ku;KRH7G6F~5Q|r{?U^M?iPO*g$ zh#Hd6|2XgWrHp*Uxlt%#tN^Emcdosvtr1^#*r>wB>~h!O!-1qqG5~Tsi06=T{cTB7 z|1>yw^RIOtG^exW0<)#6uTm@E48%Dy+{E(pkMy|lL5&gqmc_xkSb+|!d5>!YmPk4+ zL2U_?ke^+U8=fNX2An$GmVl3*oSK>EgiozCN#Ba(6NZSo+@P>c z@)yl||7&hfhHQ43hXbE!Q{K|$!GkmnWqRb9>pXx1^ zgH%+^7L=Fj++B-7H4ZQsEpYM_!R2L}gQwnXcQs8RLS%}h7;{T{i16Uc9$U!z&EFu+ z=7k2>1`Eg(cE&{@D0#E8v~n#Qr!Bv$bvW~gS~~&U0swx0uPPLrLDFQ8p>7P{ytiCE zVJB2A8X-}D!F@lTmre`*dqu23Zt4|DV(NxAMP_!i2PX#*@53*q=Fd~(DtpFuk>6J~ zCbUwlV_640)Boni{A3GOOO5#lXrwnP(7VRz?%q$AS zGtXwth30sbOFVeavZiwJg*J~~b(~%Xs>#t)%7+DFx5imsvoY?$iZ7Dh=c%&PioD;> z;n*(tx)VVz_f*7GM(5cd&1+r7%9q0P!`}s5>6)jg-kE-i!H(K+DoKUQvs)^uwFaFL z#?1YEZ^q_=AYCC|WsK&IRsbSW>Cb|ZltH3so|t5^c?&~_8VazFLn~$X`y-F#)*SlZ zDlBzPl~tE%zwZPPzX>(2MVk7>wy7Y!_g3=^BhqXMX3j}h^=|3pKp7uV>`P=QRq(~m zC9Y)5cozr!cysPTUPjByq|NrOKbNH^%e*1<*I8D-c?Bjmn_)b z3s)xQcPKkLm(+`i<+TqN6s;E1e8n|r&ZZWZg(VfReTB45)98|l8*!vuIxp|lyDx6F z=Y8c0t@xQrF(g;n0!Iu;?2te>Hr6)lYCE-{Kr2`08okXkb0xyy$T?w}l8Vvp`YEW0 zjIfO!{`S>BBksXM0!!;#V-v!~o0rc5-))EI1{LKh33$j9G4!4}(U!>=I8^8rw)@@W zD(Ldtgb_VimQ`SA*uv6)_;D-{qf|yQFgyon2;f~&(rR6{fVFgkqP}-R*ljzhN`@;l zzj7kgk)xl8@vfx40!4-w3NhxL7`hh8?wl^HPvpDVPB z`mqEm=9sHFy#^O5e}FCodiUpwhGrL>KWX2D;p9-^Xa9*}3kBnJ>*F;z(SD*PDyS$Q z(SAm%MmlFHI0dn$m)bzpTl$48njryFty6t;pl*|jF9k>L2e;H{P$FoTaAK=JW@gHR zAWU!H0%)5@n%ob@@my8>+MpFE+srg~8tR6O5N5{27aqChXr-~cxkZUd?mrJaW`8dOfaCCz3}B+$Gm#K*3eOG-lzmaUYKkD!9nG#(1rFenMf zdCkF6zG)@n7~F7ddExxWozl4a>@Zj8)yUDyub`^@kH3G;yvXsPCZw&nNWqrK)!)yS zO<>>zPuk^!8}uN@Ai(y)``zo6&ErB?cXZW^Mz~tpVpWeMm!1jt_>`V8iPGX*gVrj;B97>9*AoAeDS{Pj%#;ZnTy2*m z)?1lSy#-*HVHC_gv(s&p*Us9PP(yu``{SZ~2=x)*8^?5oE+`n+=imq}$|;B-3EOKj zQ3Shd_167WRbZwW^@SD1#Ta#3?VJ8p0)IwcuiEi%Wk3gURciNf zo*jsWvtB~#_K{bv{B>EL+!A^Rp(g!;{ONL0HjbM0 z9IwT0Ba6x5yYb>G<6%^eTI{|q05ocjc6qJImPd;sD2Yol=6`JyRu5l}bGe49*0Huw zv!$Oc2XT@g)Fvc|qi=9nc+fF4(e(bE(C+?~pox2v&-S@(O_4@7|7}$@ecfGH0uFs7%`DCCY(_Bk)Vi3uH^h)+ni>B3(DO#jHQ=v)o-Sc?Vt&i_7D~!i;d> zgXYSZ{safP+v1g5Z=`y}j%u94cs~A);9_dJKkG(bB^TpboiOujRh{)y(NPW({{1A8 zjj$*Qsh7R1(y0tJSk)5dhy5yXG;_CTfrLYF1YHv_wjM=Pu{m`qrP&plP3pT*fJ>PjsZehm7-SjPt9>3)!xED;&my zbAGq-F63hOpZ()8234Zr|YcG2*;u zM=g|bShl|~*B*IZ%9vO+#%_JmhBfgG9J1kzgr zQN(9IB{GRlq%6sNXEAp_zmTQiTM318GTrAFgM}MQTsN#PHn$gXYi0ONY%*^)4R$}z z#nC;rr{oiLqP&(mD3p?<-84 zC93WU8h3To%R`52o-G+e!Xrj5)vB5BW+<~QROr@mXr=lC?(4_9ZyPOpj^#*lQ6kIy zo4iZ{8Z_UxpAWkjypfA+alVclTGNsN)yJ2^23<1M?WtZg^=9a1TQlB>P0zUW!SFo9 zEZg7WVCV8oVo0izeRn1%M1KF#cilPihjhna1kaSIo@;f|x8}RQW_N{+-58BSvlThM zuThz&*qpcE!-vQ%NLc4g1)PB-v7&CB?%YYH=$lj$b2V=0xh|=6NRdpD?9%R=fP!Sk0}#FBTerWS}rNb>boBYH&@T8-rC2 z7Faw(xu^e@-1$#@z#z9(!*mjgFSd7`7>LEl+#RD*P#c1l=M-UN^Ts$HMji9quf^BZzS zd)DbtGrqi<0~hHqp}Pm~jfWmCZ&e)7gyK3Y|MUM+3+5?9pdbDy&0(>MGhO zOxxZM4bo?kW3mC@uz2b z;Nxx6XZh0xd;4bXAheL~ALSlC2uprA zMa-Zu55!oRuWoL>OHA4P0=teO*X*sA^J+omfNGPa@KZmmoM^fO=h}EOj6%EAcHIi= zhy{xn0zq|e@q?FlzJqS5FBe+v85Y0W!TG#M20bcWhZ2dtVEm>sQzGMIe9fz$O8k&! z`AxYS(WP!ZEeDxn#mR^(;fICX0{}aO;i2ixRvMINp`w^efU6Mqcur)s&Bju@QglI9 zGJMaGjbspo$u1((ASa4!`k6DA00)rzcK~#Cn}16)@4nk@#GTES1!l+)l}B~?X53A9NsqI=2~)ubo%rWqCYIxY9${1xxyq(ia(_0J!s&4$oB+;>jI8*8T+ zPCS+Wk|%)@BR`M$}& zC3tJ4!c{GRtd&qwp2F+79j;~}L|S>c>;3#LPr4`YpH=&;sE~qi!^&-!-d>^wG<3$)%JMD{)F_|Xd)vK-@nWfXWt6ZG^ zwbSK)xHuSLW6=z6p#!|^#Inn`bLM_B9&M%%aJZ}!7 z{BPUM8F>p;)-IC0N&$K6-z({yNqZY3#@wpUYP|z z+Nr?)_>t`7nGKbXw?`v}sd!x% zuz-QYEls9NIJ~JPgI)G!4#mSm#A~&}cil*p2&Na=Y;HR{hME<O{`2(foU2#qU1An;Ikh-ws*^A*@ zFnFRL&%Zkd+G?(YHkky`lF6e;)L4$v2e3M z2(&5SP_AR=6slTV-HiUZh?cnh*;LZswgr}RUnm4V@wFBM-|PHM?{eh=0x~}$-nnEQ zufHOr+V7fLgDX-XG_JWc$dP_1eiQBwb}>$c>*r8`_^&%J`nHJF6V#)wAFG4wibh5$ zgqOq40o9S6BaxjaOKZ4!j~r17#Tf<=`NM(xVp@o(6(_N0v5DeEJMNlAzE_=4yiSpV z;`&Z~q;W3wq3YXe?O&!(TcF$Y+YL=;FDtrU+GsjW1A}KlbL*OY5909~YZ(p4vDg*lN0R5#1pC)N8UEDcv7GP3 zO{7x~z=X62)N8vD`SVJ^k@|@(^;eFoyTqxzIMe}}l;}mtNM0(A@G&gp1sQYGF(Ad6_`q8=3K(oF>u{n*d^CkYSb07_4 zCI!8smT;KDd*_&K<{CXnTYjiB+K(C&Z{bC_t{hHw7DcZH&by*M9)0RgkpdJM;P6UO zPk}j(F3YseJZ-xK9aKCZea_D(G%xH8?F%)(Kq4z-zzp>0^zJ3n;G|L5f(*H-HL0B! z0%0n`3LX*%Dh(B%na!$fU^5W#r<+9u5TE-aLX%Hx;iDa>5KEli!1xZ4e@>$oGl0yw zK10mvVe8?C7^cDp!vWW~-W@`_&)_XBO0?>sk6~H^xf&so@@W2wSb^Plwf$Uoomife zDN+;uHpsyM**`25plQY@=mZM>JKC2&LWtLyYfm-P?+`Et_@Us5T;3ul>x-^LgGx+NPXB4JMzTL|+g>lD4oaz-|HJ%S4v=A4vc`nJot0zqRkv%CIeZ_b!{tIq3 z@-d6NK|R(-VjcfPuvui^t)HUZ_v?%e$1e+FYJ-FnMWVNqz^@@n0v55Vxm05j;|)2H z{?iB}vItx5$1h)`hfjou1nwCyZ+F9M*!gPjefFnnq@Uh&b2xh$yR_r~7uEo3UxHqK zF8jc5|KUP7bDDsYnwvzsn{L5h#OVm!M2m?W|Icc;H}h#?LI??GIX6JjXN5rfz{dnt zSx$c`+bx!ri1(Baek9HOff;X+>??YUU{u|)f>&z>p^ln@mp9E8( z%4T#9Kd!}0Jki=S+OPe%5-QR>iu&BSs=IK87h=3N!MM4n?y>fW;+Q7kC2wTJ*3%lB z&kJ06*a_En|tA2g9?8*=IC-2P`RQ z{#Ijn{>aQD49%oZ6kAA)YqLf~4FJJd^*#+k*GtAgnvU`7GGGn;psVgTfa82(Yy0gk zT9VEl6Zy_*gG>kHH8ocS7j@!;KCGSDuNA6as#}B~eql+nX7?djhPo;UB0|0)ez}G|VowxY z)$r=K?3z4k3jqlcI)?J;uQvWji&Yi-cbIRTLYLU%91unXtql(y7x94cY^C%@Zd8LY zNw0An5lXfa^jA?WT6P`}v_@l>hjY zdLDKDS&;--B1YvYP0?AV z3`_L@BF$i+wAYGG00`kLKgV0I6H2{4dCnNXTG9f0+%`%w15p5^jAc%j!>wQ6)X}z zmPZs~&yZ7uZ;HBImpXwI|Ni&q+`}0|#9|m4R7AG=qzI%C{QIA#yaf3n?@&$@h5N~X z)xs%`^C}BSyZo@di}ALK`4!1(3_Q+hzYT(J>Ae9mSxY{HPgJ8l@+@w_e}kCVbvH6xJjWYiFGCima=A6&1ve#7MuH&IpJA>L$5 zvYrJnsQUYz1*bIUwwWx;n?c)sbD;>St&e%UU=CNg$cisPt~?R8;$s2Um4-CWo>J}l z9)C3hLjuXwS^z8g1+-1R#0Gn2?HbX(o(Ff?~&?Wan^@kwe7a2@<1~ zCG+w|RoHuhOEF)h?q=fSJJM z!gC~764^Nb|1$G`LyY*a)n*`odK61ID=ltllDqRtU1_!Y7XNsW@;u&~Z?N0V(scx$ ze*j^319TrKJ1tb;L_pOX6jJ#!uB9!Hv!+X#kB(~%<(7{TIpKISDd*w_1 zN`+Q;v&xYMwnYzHIx6b;VHT-#s}f@dM7PQpRGN-iJ{Z#u4@0@fQSG6IU- zf0Bvf4`x#hG$(_{&A1^R+6hJWB7|M>%ljf~)L#zY$_~L8TInNZR!Vb<{0Mi>1q2?N zm)cxv2P$1^m^)@&ESj@`dN~=MGpW;_r9GGA=mdq)v_v6n?I_s2g#;e$4zYmpWji#; zESHDa8V=OVc=Xa(@aynu102`-9uCac{X*; z8x7jP;lt;M3B{P6v$Y+~NdFiu>UeQ+X=~pw`NYGv1MlAD=C!BG4M~zYP1D62=YV{chW+O>ImYa~SBuVkO8^PQfqCndvMY?xW<%5?W;_ zG6kuaL7vVuOs`v{yp(_5*?gp6NwL`9X}$+@iSX}ODvfo&`7gpeC7o|~fEuYk(X1Kk zn%UBE{3ar<3`g}1^ED#wzoF1S4*X6EiwNt0*l6