В предыдущих сериях я:
- Накупил устройств от Xiaomi для умного дома и посредством паяльника заставил их работать в увлекательнной манере без родных серверов через home assistant habr.com/ru/post/496856
- Завернул web interface от home assistant в electron habr.com/ru/post/497880 с поддержкой нотификаций, менюшек, точбара итд (код тут github.com/bskaplou/home-assistant-electron)
Но по мере появления всё новых устройств стало вскрываться странное…
Например, что поддержки Aqara Wireless Relay 2ch (LLKZMK11LM) в интеграции xiaomi_aqara нет, а на открытие Issues в GitHub владельцы кода отвечали, что поддержать это реле невозможно без обновления прошивки устройства. «Что-то тут не так» — подумал я и пошел разбираться по коду, как так вышло. И так увлекся, что запилил интеграцию сначала для работы с реле через xiaomi_miio github.com/rytilahti/python-miio/pull/696, а затем реализовал поддержку кубика и кнопки Xiaomi для этой интеграции github.com/rytilahti/python-miio/pull/703, а теперь расскажу зачем и как.
Developer API
Когда-то давно Xiaomi выпуская шлюз (lumi.gateway.v3) укомплектовали его developer api и даже документацией к нему. Developer API это та штука что общается по UDP на портах 4321 и 9898. Такой ход очень понравился сообществу и оно ответило пачкой репозиториев, интегрирующих устройство во всё что можно. Работа с этим developer api легла в основу home assistant интеграции xiaomi_aqara github.com/home-assistant/core/tree/dev/homeassistant/components/xiaomi_aqara и всё было замечательно пока…
Пока Xiaomi не переклинило и они не стали забивать на поддержку developer api, в нем перестали появляться идентификаторы новых устройств. Искомое реле есть в списке устройств подключённых по zigbee к gateway, но там реле фигурирует с пустым идентификатором модели, без которого невозможно что-либо сделать. Затем, уже совсем недавно, Xiaomi выкатили обновление прошивки gateway после установки которой невозможно было включить developer api без паяльника, (если он не был включен до обновления) habr.com/ru/post/487768.
Оказавшись в самом центре этой Санта-Барбары, я всплакнул и пошел заказывать немецкий ZigBee донгл ConBee2 на амазоне, чтобы работать с zigbee устройствами без gateway. Но пока немцы в условиях апокалипсиса готовили к отправке мой заказ, я утомленный ожиданием стал копать глубже…
Более родной протокол MIIO
Miio — это более новый протокол работы с устройствами Xiaomi, подключаемыми к сети по wi-fi. Родное приложение Mi Home общается по этому протоколу и с wi-fi розетками, и с IR Remote, и пылесосами, и со всеми остальными и даже с моим xiaomi gateway v2. Это тоже UDP протокол, но на этот раз задействован порт не порт 4321, а порт 54321 (прекрасный метод выбора портов я считаю). «Опаньки» — подумал я: «Значит оно еще и другой протокол знает, а не попробовать ли подключить реле по этому новому протоколу?».
Протокол miio шифрует все сообщения токеном, но, к счастью, методы извлечения токена и шифрования/дешифрования уже были вскрыты и работа с ними реализована в нескольких библиотеках (https://github.com/rytilahti/python-miio/, github.com/aholstenson/miio). Я не первый, кто занялся интеграцией нового устройства по протоколу miio, поэтому путь был известен и я ему последовал.
Путь такой:
- Добываем security token устройств извлекая их из логов github.com/Maxmudjon/com.xiaomi-miio/blob/master/docs/obtain_token.md или из модифицированного приложения www.kapiba.ru/2017/11/mi-home.html
- Снимаем трафик между мобильным приложением и устройством
- Расшифровываем UDP пакеты с security token полученным в начале
- Учим код создавать аналогичные пакеты
Я снял трафик прямо со своего wi-fi роутера, к счастью для него есть кастомная прошивка asuswrt-merlin, в которую можно с помощью Entware поставить tcpdump. Утилита github.com/aholstenson/miio/blob/master/cli/commands/protocol/json-dump.js прекрасна, но дампы от tcpdump она не понимает, зато понимает json-dump генерируемый Wireshark. Для перегона бинарного дампа из tcpdump в json я использовал утилиту tshark. После этого увидел какими командами приложение mi home управляет реле я породил PR с добавлением поддержки реле github.com/rytilahti/python-miio/pull/696.
Заказ на ConBee2 я отменил, ибо программным методом и быстрее и веселее…
Зачем нам кузнец?
И вот значит я, с шашкой наголо, оказываюсь в ситуации, когда всё, вроде работает, но теперь в home assistant мой Xiamo Gateway 2 представлен двумя сущностями одна по протоколу developer api (интеграция xiaomi_aqara) и еще одна по проколу miio (интеграция xiaomi_miio github.com/home-assistant/core/tree/dev/homeassistant/components/xiaomi_miio). А еще я не люблю паять, хотелось и себе и людям жизнь упростить. Нужно было реализовать недостающую часть устройств в xiaomi_miio.
По большей части, после созидания программной обвязки это механическая работа:
- включил tcpdump
- создал в приложении Mi Home автоматизацию включающую действие которое я хочу отслеживать
- инициировал это автоматизацию
- расшифровал дамп
- копипаста из расшифровки в код
- посылаем команды с консоли
- доработка напильником до блеска
Но руки чешутся, а приключения зовут…
Ненастоящее miio устройство
Вскрытие показало, что в протоколе miio нет механизма колбеков. Сенсоры можно и раз в 20 секунд pullить, а вот для корректной работы кнопок и кубиков опрашивать устройство по 10 раз в секунду показалось плохой идеей. В этом парадоксе я увидел интересную деятельность, а механическим кодированием отснифанных пакетов заниматься не очень хотелось.
Исследование еще одной библиотеки работающий с протоколом miio github.com/aholstenson/miio/blob/master/lib/devices/gateway/developer-api.js удивило меня до глубины души. Авторы собрали комбайн который общается с устройством по протоколу miio, но для перехвата событий с кубиков и кнопок использует developer-api. Такой подход меня основательно напряг, приводит он к тому, что часть функционала начинает работать сразу, а для части таки надо паять устройство…
Снова вооружившись tcpdump и, доработав дешифратор функцией расшифровки от всех устройств за один запуск, стал слушать всё что летает по сети на UDP порт 54321. И вижу довольно интересную картину. Когда я создаю автоматизацию типа “Если повернули zigbee кубик, включить wi-fi розетку” из приложения в gateway по протоколу miio засылается json внутри поля data которого json закодированный в строку; “DSL” — подумал Штирлиц, их почему-то из покон веков все так пакуют… Детали тут github.com/rytilahti/python-miio/issues/699.
Просмотр содержимого этой строки показал, что реакция на действие не передается куда-то в облака, прямо в этой json программке фигурировал ip адрес и токен шифрования wi-fi розетки (этот абзац противоречит абзацу из первой серии, там я был не прав ;( гейт сам общается с дружественными устройствами — без помощи облаков). Да и сам скрипт напоминает автоматизацию из home assistant, есть параметры триггера, есть ip куда заслать реакцию. То есть никакого протокола pub/sub найти в дампах не удалось.
Зато родилась идея как всё-таки отловить все нужные события с кнопок и кубиков:
1. Эмулируем miio устройство в приложении, отвечаем на PINGи приложения и запросы от gateway
2. Пишем генератор скриптов для gateway для генерации по одному на пару ~ (устройство, событие)
3. Засылаем скрипты так чтобы при наступлении события в эмулятор приходил miio запрос
Пару дней заняло это всё реализовать и привести в божеский вид.
На текущий момент библиотека python-miio умеет работать и с реле и с кубиком и с кнопкой github.com/rytilahti/python-miio/pull/703
Остается примотать этот апдейт к home assistant и можно будет работать с Xiaomi gateway 2 без пайки и developer api.
Если кто готов потестировать на своих устройствах и/или добавить поддержку новых — присоединяйтесь :)
Еще в продаже есть славный Xiaomi 1C пылесос вот такой gigant-store.ru/goods/robot-pylesos-xiaomi-mijia-sweeping-robot-1c-ch и недоделка github.com/rytilahti/python-miio/tree/dreame_miot_mc1808 по его интеграции в home assistant. Думаю взяться… Надо оно?
Автор: nestor_mahno