Zigbee без облака: проверено на Миди с quasard¶
На Станции Миди проверены восстановление существующей Zigbee-сети и сопряжение новой лампы через локальный quasard, без облака Алисы. Радиочипом и устройствами продолжает управлять штатный quasar; сервер передаёт ему директивы Alice API.
Границы проверки
Результаты получены 26–27 октября 2026 года на Миди (Cucumber). Это исследование с собственным серверным расширением, а не штатная функция исходного quasard или готовая интеграция ZHA/Zigbee2MQTT. Сопряжение и голосовые команды проверены только с Aqara T1; другие Zigbee-устройства, модели Станций и прошивки не проверялись.
Что работает без облака¶
Миди может сохранить существующую Zigbee-сеть и принимать новые устройства через
локальный бэкенд. На Aqara T1 (lumi.light.acn014) проверены сопряжение, назначение
комнаты и имени, а также голосовое управление яркостью и температурой белого.
Лампа находится в комнате «Кухня» и называется «Лампочка».
| Действие | Проверенные голосовые команды |
|---|---|
| Установить яркость | «Алиса, установи яркость света на кухне на 30 процентов», затем — «Алиса, установи яркость света на кухне на 100 процентов» |
| Изменить яркость относительно текущей | «Алиса, сделай свет на кухне тусклее» и «Алиса, сделай свет на кухне ярче» |
| Изменить температуру белого | «Алиса, сделай свет на кухне тёплым» и «Алиса, сделай свет на кухне холодным» |
| Обратиться к лампе по имени | «Алиса, установи яркость лампочки на 50 процентов» |
Для выбора света по комнате и по имени нужны соответствующие локальные метаданные на колонке. Одного сопряжения с Zigbee-сетью для этого недостаточно. Выбор RGB-цвета этими проверками не подтверждён.
Ниже описан внутренний протокол для разработки своего бэкенда. Готового универсального установщика сопряжения здесь пока нет: потребуется расширение quasard, отправляющее бинарные директивы. В проверенной реализации сопряжение запускается отдельно от голосового помощника и не передаётся на усмотрение LLM.
Что нужно доработать в quasard¶
Для сопряжения использовано собственное расширение quasard. Оно добавляет три
HTTP-команды и переводит их в бинарные директивы для штатного quasar на колонке.
Схема alice_server/aliceapi.desc и кодировщик aliceapi.encode_server уже есть
в quasard; Zigbee-драйвер и прошивку радиочипа менять не требуется.
Минимальный модуль сопряжения —
contrib/quasard-zigbee-operator.py.
Это сокращённый пример из рабочей установки для одной Миди. На колонке эта версия
проверена поиском и отменой: прошивка приняла обе директивы со статусом 0. Подтверждение
устройства (finish) с ней не повторялось. В модуле оставлены только поиск, принятие
и отмена; доступ разрешён с loopback сервера. Схему директив модуль читает сам
из aliceapi.desc, а кадр собирает публичной aliceapi.encode_server.
- Скопируйте модуль в исходники quasard как
alice_server/zigbee_operator.py. -
В
alice_server/app.py, внутри функцииcreate_app(), добавьте передapp.include_router(build_router(cfg, store)): -
Перезапустите quasard своим обычным способом. При запуске в Docker оба файла должны попасть в образ или быть подключены отдельными read-only mounts по путям соответствующего пакета
alice_serverв контейнере. - Убедитесь, что Миди подключена к quasard по protobuf Alice API. Расширение
выбирает единственную зарегистрированную колонку с платформой
cucumber; при нескольких Миди потребуется явный выбор устройства в коде.
В рабочей установке был также добавлен специальный вызов assign_kitchen для
передачи заранее подготовленных имени и комнаты через set_iot_user_info.
В пример он не включён: файл содержал полный набор метаданных конкретного дома.
Как устроена такая директива, описано в разделе Имя и комната.
Сопряжение через пример не назначает комнате и лампе голосовые имена автоматически.
Команды сопряжения¶
Команды выполняются на сервере quasard. В примерах он доступен по
http://127.0.0.1:18080; замените адрес и порт на свои. Для Docker с bridge-сетью
loopback хоста и контейнера различаются: вызывайте API внутри контейнера либо
настройте точный разрешённый адрес хоста в модуле, сохранив ограничение доступа.
Если quasard в host-сети слушает не loopback, а адрес хоста, запрос с самого хоста
может прийти с адресом LAN-интерфейса и получить 403. Тогда задайте источник явно:
curl --interface 127.0.0.1 ... к тому же адресу quasard.
Эти маршруты появляются только после установки расширения.
Откройте поиск на 120 секунд, затем переведите лампу в режим сопряжения по её инструкции:
curl --fail-with-body -X POST http://127.0.0.1:18080/_zigbee/start \
-H 'Content-Type: application/json' \
-d '{}'
Ответ содержит session_id: сохраните его на случай отмены. status: sent
означает отправку директивы. Расширение не принимает найденное устройство автоматически.
Из текущего discovery callback нужны его числовой CallbackId и ID обнаруженного
endpoint. Их нужно получить из protobuf-события станции, декодированного схемой
Alice API; отдельного HTTP-метода чтения callback в этом примере нет.
После получения callback задайте его реальные значения и подтвердите устройство:
CALLBACK_ID='123' # замените на CallbackId текущего callback
ENDPOINT_ID='ID_ИЗ_CALLBACK' # замените на ID найденного endpoint
curl --fail-with-body -X POST http://127.0.0.1:18080/_zigbee/finish \
-H 'Content-Type: application/json' \
-d "{\"callback_id\": $CALLBACK_ID, \"accepted_ids\": [\"$ENDPOINT_ID\"]}"
Для отмены вместо подтверждения подставьте session_id из ответа start:
SESSION_ID='UUID_ИЗ_ОТВЕТА_START'
curl --fail-with-body -X POST http://127.0.0.1:18080/_zigbee/cancel \
-H 'Content-Type: application/json' \
-d "{\"session_id\": \"$SESSION_ID\"}"
Это пример интеграции для своего бэкенда: автоматическое чтение callback и интерфейс выбора найденного устройства ещё потребуется реализовать. Голосовые команды управления светом из таблицы выше проверены после сопряжения и назначения метаданных.
Сопряжение через Alice API¶
В исследованной прошивке JSON-команда через Glagol externalCommandBypass
для discovery отвергалась из-за отсутствия reflection. Рабочий путь — бинарные
сообщения NAlice.TIotDiscoveryCapability из схемы alice_server/aliceapi.desc
через уже установленное protobuf-соединение колонки с локальным uniproxy.
Директива отправляется в Event.Push.Directives с Type: client_action,
именем команды и PayloadRaw — base64 от сериализованного protobuf-сообщения.
Base64 здесь относится к представлению в серверном коде; по соединению передаётся
бинарный Alice API.
| Этап | Директива и существенные поля |
|---|---|
| Начать поиск | TStartDiscoveryDirective, Name: iot_start_discovery_directive, новый SessionId, Protocols, TimeoutMs, DeviceLimit, Mode, ProtocolParameters |
| Принять обнаруженное устройство | TFinishDiscoveryDirective, Name: iot_finish_discovery_directive, актуальный CallbackId, список AcceptedEndpoints с Id и Protocol |
| Отменить поиск | TCancelDiscoveryDirective, Name: iot_cancel_discovery_directive, SessionId текущего поиска |
В проверке поиск открывался только для Zigbee на 120 секунд, с лимитом одного
устройства и режимом INTERACTIVE. Ограничение одного устройства задавалось также
в ProtocolParameters.ZigbeeParameters.DeviceLimit.
Принятие требует актуального callback
Первое обнаружение завершилось тайм-аутом ожидания подтверждения бэкендом.
Попытка подтвердить только адрес устройства, без актуального CallbackId,
тоже была отклонена. Успешный вариант передал CallbackId текущего discovery
callback и AcceptedEndpoints с протоколом PROTOCOL_ZIGBEE.
В стартовой директиве и в AcceptedEndpoints используются разные enum протоколов.
В проверенном коде Zigbee кодируется соответственно как 0 и 1; переносить
одно числовое значение между полями нельзя. Значения следует сверять со схемой.
Серверное подтверждение отправки директивы ещё не означает завершение сопряжения.
Проверка результата включала ответ прошивки Finish discovery ... code=0
и наличие нового устройства одновременно в network.dat и device.lmdb.
Имя и комната — отдельные метаданные¶
Сопряжение с радиосетью и назначение имени для голосового управления — разные этапы.
В исследованной конфигурации метаданные передавались директивой set_iot_user_info.
По разбору парсера прошивки:
| Сообщение | Поле | Содержимое |
|---|---|---|
Директива set_iot_user_info |
1 | Name |
Директива set_iot_user_info |
2 | bytes сериализованного user info |
| User info | 1 | комнаты |
| User info | 3 | устройства |
| Запись устройства | 6 | внешний endpoint |
| Запись устройства | 9 | ID комнаты |
Это частично восстановленный внутренний формат, а не полная схема для произвольного
редактирования. Новая запись была подготовлена по совместимой лампе с OnOff, range
и color_setting, с новым UUID, endpoint и именем. Индексы имени исходной лампы
(поля 22–24) не копировались. Остальные записи сохранены побайтно; результат
проверен чтением local_iot_user_info на колонке.
Перед изменением нужна свежая копия user info: повторная отправка старого полного набора может затереть более поздние изменения дома. Для изменения имени или комнаты не требуется восстанавливать сетевые базы Zigbee. Удаление записи из локальных метаданных также не равнозначно удалению устройства из радиосети или облачного аккаунта.
Базы сети и восстановление¶
network.dat и device.lmdb должны описывать согласованное состояние сети.
В разобранном случае в первом файле оказалось пять узлов, а во втором — четыре
устройства: лишняя запись имела широковещательный IEEE-адрес FFFFFFFFFFFFFFFF.
Quasar сообщал Databases mismatch, переходил к восстановлению через бэкенд
и не создавал обработчики устройств, хотя радиочип сохранял прежнюю сеть.
После точечного удаления только этой записи, с сохранением остальных байтов,
прошивка сообщила Restored network from filesystem и Network init is successful.
Зарегистрированы пять endpoint для четырёх устройств: три лампы и два канала
выключателя. Число endpoint поэтому не следует напрямую сравнивать с числом устройств.
Это разбор конкретного сбоя
Databases mismatch не означает, что нужно удалять записи вслепую.
Бинарный network.dat нельзя отправлять как payload
iot_restore_networks_directive: это разные форматы. В проверке такая попытка
создала пустую программную базу и потребовала возврата исходных файлов.
Для резервирования нужны согласованные сетевые базы и связанные локальные
IoT-метаданные. В исследовании копия создавалась при краткой приостановке
platformd сигналом STOP с последующим CONT, без перезапуска. Это временно
приостанавливает его работу и требует гарантированного продолжения процесса;
копия меняющихся файлов на ходу не считается согласованным снимком.
После сопряжения и изменения метаданных нужен новый снимок. Восстановление старого состояния теряет последующие привязки, имена и комнаты; скрипт восстановления должен соответствовать выбранному снимку. Отключение серверного расширения само по себе не удаляет уже сопряжённые устройства.
Архивы содержат ключи Zigbee и личные метаданные. Их, содержимое user info, идентификаторы устройств и операторские журналы публиковать нельзя.