Как подружить Ростелеком Ключ и Home Assistant - «Умные Решения»
- Hodges
- 05-фев-2024, 22:32
- 0 комментариев
- 709 просмотров
Недавно ко мне в руки попал ключ для домофона «Ростелеком» непривычной прямоугольной формы. А вскоре после этого на моем телефоне появилось приложение «Ростелеком Ключ», которое позволило полностью перестать пользоваться физическим ключом. В этом приложении можно просматривать видео с различных камер вокруг дома и в подьезде, создавать временные коды для открытия дверей, а также открывать все двери дистанционно.
Мне сразу же захотелось добавить все эти функции в Home Assistant, который у меня уже управляет кучей устройств внутри квартиры. Ну а интеграция с сервисами «Ростелекома» позволит расширить радиус контроля Home Asstatant'а и при желании реализовать новые интересные автоматизации, например, открывать дверь подъезда, просто внимательно посмотрев в камеру домофона, и в этот момент предупреждать детей в квартире, что папа через 5 минут будет дома =)
Сайт предлагает скачать Android и iOS версии приложения «Ростелеком Ключ», однако неприметная кнопка «Войти» позволяет использовать его веб-версию.
Заходим с использованием телефона, привязанного к аккаунту «Ростелеком Ключ», далее переходим на https://key.rt.ru/main/pwa/dashboard и анализируем сетевые запросы, которые отправляет данное приложение.
Первым уходит запрос на получение списка камер. В нем присутствует Authorization Header, содержащий Bearer Token. Декодируем токен с помощью https://jwt.io/ и видим, что срок жизни токена составляет один год. Это значит, что мы смело можем сохранить данный токен и использовать его для последующих запросов к апи.
Ответ на запрос содержит полный список доступных камер, у которых есть следующий интересный аттрибут:
"screenshot_precise_url_template": "https://media‑vdk4.camera.rt.ru/image/precise/{size}/8f3a52fc‑aa67–48f6-aeaa‑fb1379e2c8f5/{timestamp}.jpg?token={cdn_token}"
Очевидно, что мы уже можем получать скриншот с каждой камеры, а вот как получить видеопоток, пока непонятно.
Далее нажимаем на просмотр видео с камеры и видим вот такой запрос: wss://live‑vdk4.camera.rt.ru/stream/8f3a52fc‑aa67–48f6-aeaa‑fb1379e2c8f5/1 706 625 123.mp4?mp4-fragment‑length=0.5&mp4-use‑speed=0&mp4-afiller=1
Видеопоток приходит порциями через вебсокет, что не очень удобно, и пока непонятно, как видео в таком формате можно добавить в Home Assistant. Я попробовал поискать информацию о стандартных протоколах передачи видео данных через вебсокеты, но не нашел никаких упоминаний о таких структурах данных:
Похоже, что «Ростелеком» использует какую‑то собственную реализацию.
И тут в голову приходит идея — а что если поменять wss на https? Пробуем, и такая ссылка работает. В новой вкладке браузера мы видим видео с нашей камеры в реальном времени!
Ну и наконец, пробуем открыть какую-нибудь дверь из приложения, и видим запрос https://household.key.rt.ru/api/v2/app/devices/23616/open с тем же самым Bearer Token. Пробуем повторить этот запрос с помощью утилиты curl, и дверь успешно открывается!
На этом исследовательская часть закончена — мы научились получать видеопотоки, скриншоты и открывать двери. Теперь остается добавить соответствующие сущности в Home Assistant.
Те, кому важен результат, могут сразу посмотреть на получившуюся интеграцию тут. Далее я кратко опишу основные шаги для создания интеграции.
Создание интеграций неплохо описано в документации Home Assistant. Также в качестве источника для вдохновения удобно пользоваться исходниками дефолтных интеграций и официальными их примерами.
Создание интеграции начнем с файла manifest.json:
{
"domain": "rtkey",
"name": "rtkey",
"documentation": "https://github.com/artgl/hass_rtkey",
"iot_class": "cloud_polling",
"version": "0.0.1",
"requirements": ["transliterate", "pyjwt"],
"config_flow": true
}
Обратите внимание на следующие атрибуты:
iot_class: "cloud_polling"
говорит Home Assistant, что наша интеграция требует активного интернет-соединения, и обновления сущностей могут происходит с задержкой в зависимости от частоты опроса (polling).
requirements
— дополнительные пакеты питона, которые использует наша интеграция.
config_flow: true
— наша интеграция конфигурируется не с помощью файла конфигурации, а с помощью мастера кофигурации, который запускается из настроек Home Assistant.
Добавляем файл config_flow.py. Он содержит класс с единственной функцией async_step_user, которая вызывается, когда юзер нажимает кнопку «Добавить интеграцию» в настройках Home Assistant. Подробнее о config_flow можно прочитать тут.
Добавляем файл init.py. В самом его начале определим спискок классов сущностей, которые будет создавать наша интеграция, в переменной PLATFORMS
. В текущем варианте создаются только сущности типа Image для скриншотов. В следущих версиях будут добавлены сущности Camera для видеопотоков и Button для действия «открыть дверь». Подробнее о классах сущностей можно почитать здесь. Для каждой платформы нужно будет добавить соотвествующий файл, в нашем случае это будет файл image.py
Далее в этом файле мы создадим две функции: async_setup_entry
и async_unload_entry
. Первая отвечает за создание экземпляра нашей интеграции при старте Home Assistant, вторая вызывается, когда пользователь решил удалить нашу интеграцию. Важный момент: некоторые интеграции работают только в единственном экземпляре и добавляют эту проверку в эту функцию. Но в нашем случае пользователь может иметь доступ к нескольким ключам «Ростелекома», и мы разрешим пользователю иметь несколько экземпляров нашей интеграции.
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
В нашем случае вся инициализация произойдет внутри файла image.py при инициализации соответствующей платформы.
Переходим к файлу image.py. Здесь тоже нужна функция async_setup_entry
, в которой мы получаем список доступных камер с помощью апи вызовов «Ростелекома» из первой части и создаем соответствующие сущности Home Assistant:
async def async_setup_entry(hass, config_entry, async_add_entities):
cameras_api = RTKeyCamerasApi(hass, config_entry)
cameras_info = await cameras_api.get_cameras_info()
entities = []
for camera_info in cameras_info["data"]["items"]:
camera_id = camera_info["id"]
entities.append(RTKeyCameraImageEntity(hass, config_entry, cameras_api, camera_id, camera_info))
async_add_entities(entities)
Класс RTKeyCameraImageEntity
является наследником класса Image и в минимальной реализации должен иметь только одну функцию, возвращающую картинку в бинарном виде:
class RTKeyCameraImageEntity(ImageEntity):
async def async_image(self) -> bytes | None:
return self.cameras_api.get_camera_image(self.camera_id)
Моя реализация в github содержит чуть больше кода, который отвечает за кэширование и автоматическое обновление картинки, если пользователь смотрит на нее в настоящий момент.