Алиса в вашем умном доме. Или Маруся. Или Салют

от автора

Тема умного дома не нова, думаю у большинства читателей есть не менее одного умного устройства дома. В свою очередь, автор интересуется темой умного дома и интеграцией «всего со всем» уже некоторое время и даже написал для Хабра несколько статей в 2018г.

Всё дальнейшее повествование имеет смысл читать, если читатель согласен с мыслью, что правильный умный дом может быть только on premises. Никаких облаков, никаких SmartLife, MiHome и иже с ними. Ничего против них не имею, но подразумеваю, что прикладная логика работы умного дома не может зависеть от доступности провайдера умного дома, и даже больше, от наличия Интернета. В частности, пользователи SmartLife/Tuya наверняка знают, сколько времени нужно, например, чтобы на экране смартфона появился пульт управления телевизором. За это время сцена для недетских глаз на голубом экране вполне может пройти кульминацию. Однако, из всего сказанного нужно сделать исключение для голосовых помощников. Они по природе своей пока должны жить у провайдера, лишь одной ногой опираясь на умную колонку.

Голосовые помощники стали неотъемлемой частью повседневного быта и основательно вошли в нашу жизнь. Cкорость их работы не напрягает. И то, что без них всё ещё можно обойтись — тоже понятно. При этом сама по себе тема управления умным домом с помощью голосовых команд гораздо старше голосовых помощников. В частности, на базе описанного мною в 2018 умного дома OpenHAB, я также экспериментировал с распознаванием голосовых команд. Причем использовал как встроенные инструменты (bindings), так и сторонние приложения с интеграцией по REST API. Однако, практически сразу было понятно, что это «баловство», демонстрация технологии без практического применения. Ибо, неудобно.

Голосовые помощники же в умных колонках закрыли ту страницу и открыли новые возможности. Появились соответствующие дополнения для OpenHAB и HomeAssistant. Далее для простоты я буду считать эти две системы самыми распространенными. В частности, обе развернуты у меня в двух разных локациях.

Впервые я подключил с помощью готового дополнения Алису к HomeAssistant в 2021 году. Однако, это подключение периодически ломалось по разным причинам. И в самый ответственный момент, когда гостям нужно было продемонстрировать «Алиса, выключи свет в коридоре», эта функция оказывалась нерабочей 🙂 Окончательно всё сломалось недавно, когда дополнение к HomeAssistant заявило, что моя версия HomeAssistant устарела и надо обновляться. И в этот момент пришло понимание, что готовые программы хорошо — а своя, родная и понятная, будет лучше. Похожая, хоть и не такая же, история была и с дополнением к OpenHAB.

Но, прежде чем что-то разрабатывать, нужно понять, что хочется получить. Я себе представлял это так:

1.       Конечная цель — управление голосом. Не просто управление, а взаимодействие. Когда не только говоришь «Алиса, закрой штору на кухне», но и спрашиваешь «Алиса, какая температура в комнате?». Это подразумевает широкую поддержку протокола умного дома Яндекса.

2.       Контролируемый мною код, работоспособность которого — моя забота и ответственность. Говоря по-простому: «не зависеть от дяди». И, в частности, система должна интегрировать только те устройства, которые мне нужно. А не все подряд.

3.       Возможность интеграции всех используемых мной умных домов по REST API (напомню, это OpenHAB и HomeAssistant), а также самодельных устройств на базе Arduino/ESP8266/ESP32, которых наберется до двух десятков. Под интеграцией понимается интеграция с умным домом Яндекса, в котором живет Алиса. Эта постановка приводит нас к пониманию, что планируемый софт будет называться «шлюз». И будет standalone-приложением, а не дополнением.

Забегая немного вперед, скажу что все цели достигнуты, некоторые с нюансами. Но, главное: легко и непринужденно оказалось можно пойти дальше, а именно:

4.       Сделать возможной интеграцию с устройствами не только по REST API, но и по MQTT. И еще сделать  возможной интеграцию с помощью исполняемых файлов. Это вообще уже похоже на швейцарский нож и открывает неограниченные возможности трансформации транспортов и протоколов, правда требует дальнейшей разработки или, как минимум, написания bash-скриптов.

5.       Подключить умный дом от VK со встроенным голосовым помощником Маруся. Он поддерживает несколько протоколов и один из них — протокол Яндекса.

6.       Разработать и подключить к шлюзу смартап от Сбера с голосовым помощником Салют. Теперь на кухне у меня умная колонка с Алисой, а в гостиной умная колонка с Салютом и я могу сравнивать их работу. И откликаются они на разные имена.

7.       Разработать и подключить универсальный навык Алисы для взаимодействия с умным домом. Он понадобился как раз, чтобы обойти некоторые нюансы голосового взаимодействия с навыком типа «умный дом».

А если совсем далеко посмотреть, то оказывается, что:

8.       HomeAssistant, OpenHAB, разные облачные умные дома типа Tuya, Xiaomi, Broadlink и т.д. вообще могут быть лишними. По крайней мере в качестве интерфейсов для взаимодействия с умным домом. В самом деле, есть приложение «Умный дом» от Яндекса, у которого прекрасный интерфейс и возможность добавлять сценарии. Аналогично и у VK. Для подавляющего большинства применений умных устройств этого будет достаточно. Но, при этом мы сократим количество компонентов в цепочках интеграции и ограничим рост энтропии. Ключевое слово здесь zigbee2mqtt, о чем поговорим позже.

Итак, за дело. Для начала определим технологический стек. У каждого программиста своя история, свой опыт и привычки. Мои привычки — Linux, С++ и MySQL. Так исторически сложилось. И есть наработанные библиотеки на C++, которые ускоряют разработку. Добавим к этому фантастические возможности, открываемые с помощью ИИ и окажется, что C++ не такой уж и странный выбор. Ну, а MySQL — стандарт де-факто для web-приложений. В этот момент как раз возникает первая сложность. Для интеграции шлюза с умным домом от Яндекса понадобится связывание аккаунтов по спецификации OAuth 2.0. Это описано здесь. В свою очередь это означает, что у шлюза должен быть web-интерфейс. Для этого надо использовать какой-то готовый фреймворк. И такой есть: Wt. А в нем есть все нужные виджеты для создания интерфейсных форм, в нем есть работа с http(s) запросами, в нем есть работа с Json. Есть и работа с базами данных и на этой основе можно было бы использовать готовую модель авторизации. Но, я привык работать с «родным» MySQL C++ коннектором xdevapi, поэтому буду использовать эту библиотеку, состоящую из нескольких пакетов. А форму авторизации «нарисую» сам. Всё это у меня работает и на Ubuntu, и на Red OS, и на Alt Linux. Да, чуть не забыл, понадобится привычный любому программисту умного дома инструмент — mosquitto с заголовочными файлами.

Останавливаться на деталях самой разработки я не буду. Предлагаю познакомиться с исходным кодом, скриншотами экранов шлюза, инструкциями по сборке и настройке вот здесь.

Как мне кажется, интересно рассказать о том, как в творческих муках рождается код, приносящий удовлетворение создателю. И что получается, если мыслить системно, наперёд.

Самое главное, конечно, — это понимание того, что мы будем жить по правилам, диктуемым тем, кто предоставляет API для интеграции. Речь идет о Яндексе. У Яндекса лаконичная, но точная и достаточная для реализации шлюза документация. Хотя бы один раз её надо изучить от начала до конца, чтобы сложилось общее представление о разрабатываемом взаимодействии и чтобы это помогло принять архитектурные решения для шлюза. Когда общая концепция становится понятной, домашней страницей становится описание протокола. Мы видим, что все устройства классифицированы, детально определены все запросы и ответы для взаимодействия с партнерским умным домом (т.е. нашим шлюзом).

Итак, первое что мы делаем — учимся разбирать запросы Яндекса и отвечать ему так, чтобы не было ошибок. Все это можно проверять в песочнице. Скелет обработчиков запросов от Яндекса мне написала… Алиса. За что ей большое спасибо. И самый первый запрос — связывание аккаунтов. Для чего у нас должны быть готовы: 1) форма авторизации, 2) сообщение о согласии на связывание. Форма у меня была написана ранее, для другого приложения, я ее переиспользовал, см.файлы authwidgets.* из репозитория.

Второе, что нам нужно сделать — научиться хранить описания наших устройств в таком виде, чтобы его легко можно было трансформировать в Json, потому что Яндекс работает с Json. И здесь самым правильным мне показалось — просто хранить описание ровно в том виде, в котором мы будем отправлять описание устройств Яндексу в ответ на его запрос об описаниях. Это запрос GET на адрес https://наш_шлюз//v1.0/user/devices. Отсюда рождается незамысловатая таблица devices, в которой всего два поля. Одно id_for_yandex, соответствующее полю id во всех запросах Яндекса, а второе device с текстовым описанием устройства в формате Json в соответствии с протоколом Яндекса.

Третье, что нам нужно сделать — придумать универсальный механизм трансформации запросов Яндекса в запросы к устройствам/умным домам и механизм трансформации ответов. В моем хозяйстве HomeAssistant, zigbee2mqtt, zwave2mqtt понимают Json, а любимый OpenHAB и устройства собственной разработки работают с plain text. Решение приходит само собой — у нас уже определен формат хранения описания устройства — это текст, оформленный в виде Json. Так кто мешает расширить его дополнительными ключами/объектами, где будем хранить описание API к нашим устройствам? При этом мы не сможем использовать напрямую получившийся Json для отправки описания устройства в Яндекс. Ну и не страшно. Здесь как при создании скульптуры: чтобы получился шедевр, нужно с каменной глыбы просто убрать все лишнее. Лишними будут добавленные элементы, которые мы просто не будем отправлять Яндексу.

Итак, трансформация. Первое что приходит в голову, что у каждого устройства — один IP-адрес, один тип передаваемых данных. Тогда можно выделить объект:

«request» : {    «host»: «ip_address_of_device:port/».     «headers»: {        "Authorization": "token_from_your_smarthome",         "Content-Type": "text/plain"     }}

Далее, следуя логике протокола Яндекса, связываем умения/свойства (capabilities/properties) с нашими запросами. Добавляем объект «api»:

«api»: {     "devices.capabilities.on_off": [       {          "instance": "on",         "response": {           "1" : true,           "0" : false         },         "query": {           "method": "GET",           "url": "ctrl/button1"         },                  "action": {           "method": "PUT",           "true":  "ctrl",           "false": "ctrl",           "multipart": true,           "data_true":  "switch1=1",           "data_false": "switch1=0"         }       }     ]   }

Здесь логика такая. Формально, экземпляров каждого типа умения или свойства может быть несколько. Они разделяются по значению ключа «instance». Тогда, мы в описании API указываем умение/свойство, в примере это умение «devices.capabilities.on_off», которое будет массивом экземпляров, различающихся ключом «instance». Дальше совсем просто. Запросы Яндекса могут быть типа query — запрос состояния умения/свойства или action — только для умений, исполнение действия. Каждому типу запроса соотносим объект. Например, видим, что для получения статуса используем метод GET, итоговый адрес будет сложен из ранее определенного ключа «host» и ключа «url». Запросы GET не содержат данных, хотя их можно добавить по аналогии с action. А вот запросы типа «action» для этого устройства — PUT, да еще и типа «multipart», как будто это запрос от web-формы. Далее надо трансформировать саму команду от Яндекса в команду устройству. Для указанного умения Яндекс может прислать или true (включить), или false (выключить). В терминах устройства это, соответственно «1» или «0». Так и поступаем. Преобразуем команды Яндекса в текстовые имена ключей, которым будут соответствовать команды устройству. Имя = «data_команда_от_Яндекса», значение = «что_мы_отправляем_устройству». В данном примере у нас multipart, поэтому отправляются не просто «1» и «0», а «switch1=1» или «switch1=0». Важно: у устройства могут быть разные endpoint для разных команд. Так, например устроен API у HomeAssistant. Для этого мы добавляем ключи типа «команда_от_Яндекса»: «endpoint_устройства». Для команды true получилось «true»:  «ctrl». Т.е. итоговый адрес, куда будет отправлена команда будет «ip_address_of_device:port/ctrl».

Осталось разобрать ответ устройства, чтобы сформировать ответ Яндексу. Надо заметить, что запрос от Яндекса — синхронный, он будет ждать ответа. А вот устройства могут отвечать асинхронно, если подключены по MQTT, например. Тогда нужно делать псевдосинхронную логику ожидания ответа от устройства. А если устройство еще и отправляет статусы только по мере их физического обновления, как например датчики на батарейках, то задача становится нетривиальной. Но, об этом чуть позже. Итак, реализуем псевдосинхронную логику с помощью таймаута в 2 секунды и глобальной текстовой переменной. В это время будем просто ждать ответа от устройства. Это разумное время для ответа, не раздражающее длительностью. Итак, ответ получен. В нашем случае это plain text и как его трактовать описано в объекте response. Прямое соответствие ответа значению, которое необходимо вернуть Яндексу. Если ничего не указывать, то ответ устройства будет взят напрямую, без трансформации. Объект response один и для ответов на запрос статуса и для ответов на исполнение команды. Пока это правило ни одним из устройств не было поломано.

Описанный пример «api» относится к самодельному устройству. А вот как выглядит «api» для zigbee-датчика температуры и влажности, подключенного к HomeAssistant:

 "request": {     "host": "http://homeassistant.local:8123/",     "headers" : {         "Authorization": "Bearer token_from_your_homeassistant",         "Content-Type": "application/json"     }   },   "api": {     "devices.properties.float": [       {          "instance": "humidity",         "query": {           "method": "GET",           "url": "api/states/sensor.ext_h"         },                  "response": {           "key": "state"         }       },       {          "instance": "temperature",         "query": {           "method": "GET",           "url": "api/states/sensor.ext_t"         },                  "response": {           "key": "state"         }       },       {         "instance": "battery_level",         "query": {           "method": "GET",           "url": "api/states/sensor.flat_ext_t_battery"         },                  "response": {           "key": "state"         }       }     ]   }

В этом примере есть только свойства и только одного типа «devices.properties.float», но три разновидности в массиве: «temperature», «humidity», «battery_level». Если в объекте «response» указан ключ «key», то это означает, что ответ у нас в виде Json и из него надо взять поле «state». Берется всегда из первого уровня вложенности. Это правило пока никем не было нарушено. Объектов action нет в описании, это датчик.

Вот прямо пока всё получается очень просто. Но есть случаи и посложнее. Вот так будет выглядеть управление шторой напрямую через zigbee2mqtt:

   "request": {     "host": "mqtt://your_mosquitto_ip:1883",     "topic": "z2m/LivingRoom_CurtainRight",     "subscribe": true    },     "devices.capabilities.range": [      {       "instance": "open",       "query": {         "method": ""       },                "action": {         "method": "z2m/LivingRoom_CurtainRight/set",         "data_0":   {"position": 0},         "data_10":  {"position": 10},         "data_20":  {"position": 20},         "data_30":  {"position": 30},         "data_40":  {"position": 40},         "data_50":  {"position": 50},         "data_60":  {"position": 60},         "data_70":  {"position": 70},         "data_80":  {"position": 80},         "data_90":  {"position": 90},         "data_100": {"position": 100}       },       "response": {         "key": "position",         "0":    0,         "10":  10,         "20":  20,         "30":  30,         "40":  40,         "50":  50,         "60":  60,         "70":  70,         "80":  80,         "90":  90,         "100": 100       }      }     ]

Видно, что положение шторы определяется с шагом 10. Это указывается в описании умения для Яндекса в полном соответствии с протоколом Яндекса:

            "type": "devices.capabilities.range",            "retrievable": true,            "reportable": true,            "parameters": {               "instance": "open",               "unit": "unit.percent",               "range": {                  "min": 0,                  "max": 100,                  "precision": 10               }            }

           В примере еще видно, что нет запроса типа query. Потому что взаимодействие идет по MQTT и такого запроса для устройства нет. Интересно, что если бы мы подключались к устройству через OpenHAB, то словарь данных, нам бы не понадобился. OpenHAB работает с plain text и умеет преобразовать строки в числа. А ответ, в свою очередь, от устройства мы всегда получаем в виде строки.

Если определен порядок трансформации запросов устройству и ответов устройства, то можно добавить новые типы транспорта. Например, MQTT или даже исполняемый файл (exec://путь_к_исполняемому_файлу). Лишь бы можно было отправлять сообщения и получать ответы в уже описанном формате. Собственно, в примере как раз взаимодействие идет по MQTT. Отличия следующие: «host» содержит адрес сервера mosquitto. Появилось поле «topic». Оно используется для получения статуса устройства. И появилось поле «subscribe». Если оно есть и установлено в true, то шлюз не будет ожидать ответа от устройства при получении запроса от Яндекса, а возьмёт статус, сохраненный в базе данных. Тоже самое будет, если устройство не отвечает по MQTT более таймаута в 2 секунды. Большее количество примеров описано в файле README.docx в репозитории.

В целом получается довольно универсальная конструкция, которую можно дальше развивать и делать еще более универсальной.

И наконец, четвертое, что нужно сделать. Обеспечить репортинг статусов помимо штатных ответов на запросы Яндекса о статусах. Протокол Яндекса это позволяет. Для этого есть два основных способа:

  1. Опрос устройства по таймеру. Для этого в описание устройства можем добавить ключ «interval», значение в секундах. С этим интервалом шлюз будет опрашивать устройство, и, если статус изменился, отправлять его в Яндекс. Делает это той же самой процедурой, что по запросу Яндекса и отправляет статус в Яндекс.

  2. Прямой вызов шлюза со стороны устройства. Я назвал такой тип вызова «signal». Смысл его в том, что устройство при изменении статуса сигнализирует шлюзу «Опроси меня и отправь мой ответ Яндексу». И шлюз делает это той же самой процедурой, что и по запросу от Яндекса.

Второй вариант особенно важен, так как позволяет онлайн отслеживать статусы устройств в интерфейсе приложения «Умный дом» Яндекса. С помощью HomeAssistant или OpenHAB обеспечить вызов шлюза очень просто. Для HomeAssistant достаточно написать автоматизацию, которая срабатывает при изменении состояния объекта и в этой автоматизации сделать REST-вызов. В OpenHAB — еще проще, просто написать правило: при update нужного item вызвать curl или воспользоваться соответсвующим binding для отправки http-запроса. Кстати, в таких конфигурациях автоматически решается вопрос с хранением последнего статуса батарейных датчиков. Их хранит умный дом и шлюзу не надо беспокоиться о сохранении предыдущего статуса. На любой запрос Яндекса о статусе умный дом просто возвращает текущее сохраненное значение и не обязан опрашивать устройство.

Следующая возможность, которая открывается, если всё спроектировано корректно — подключать умный дом другого производителя, если он поддерживает протокол Яндекса, и такой есть. Это умный дом от VK с голосовым помощником Маруся. О том, как это настроить, описано вот здесь. Конечно, для этого шлюз должен различать разные Client Id от Яндекса и VK. Основное отличие умного дома VK в том, что ему нельзя отправить статус и описание устройств по инициативе шлюза. Это делает сервис VK только по своим запросам, которые присылает регулярно. Тем не менее в логике работы шлюза предусмотрена возможность отправки статуса всем голосовым помощникам по протоколу Яндекса. Просто для VK она не подключена в конфигурационном файле шлюза.

В начале статьи мы говорили о том, что возможно локальный умный дом и не нужен, когда есть шлюз. Поясню. Большинство современных устройств умного дома работает по протоколу Zigbee. Обычно для работы таких устройств требуется аппаратный Zigbee-шлюз, который в свою очередь подключается к проприетарному облачному умному дому. Который, в свою очередь может быть связан с аккаунтом Яндекса и также поддерживать голосовые команды Алисы. Всё хорошо, кроме медленной работы нативного приложения и вообще зависимости от облачного провайдера. Без всего этого можно обойтись, если подключать zigbee-устройства к локально установленному сервису zigbee2mqtt. Он заменяет аппаратный шлюз и не содержит бизнес-логики умного дома. Установка и настройка его несложны и подробно описаны. А дальше разрабатываемый шлюз может работать с ним напрямую. Таким образом, нам становится ненужным ни локальный умный дом, ни облачный. При условии, конечно, что уровня сложности сценариев в умном доме Яндекса (или VK) будет достаточно. Все эти рассуждения актуальны и для аналогичного сервиса zwave2mqtt. В репозитории, в README.docx ,есть описание одного устройства, работающего напрямую с zwave2mqtt.

Еще, в начале статьи, в пункте 7, мы говорили о некоторых нюансах работы голосового помощника в навыке «умный дом». Например, если у вас настроены две локации в приложении «Умный дом» Яндекса, то голосовой помощник может требовать уточнений, где именно исполнить команду, причем даже если ты уже указал это в аудиокоманде. Кроме этого, иногда не удается получить информацию о температуре, например. В моем случае Алиса иногда называет совершенно непонятно откуда взятую температуру, вовсе не с датчика. Почему это происходит, я пока не разобрался. Однако, это уже вселило определенную неуверенность в ответах, которые дает Алиса. В свою очередь это привело меня к мысли протестировать работу голосового помощника Салют от Сбера. Однако, оказалось, что Сбер работает только с юридическими лицами. А навык типа «Smart Home MQTT DIY» на момент написания шлюза находился в разработке. Оставалась одна возможность: разработать смартап и оставить его в стадии тестирования. Для разработки смартапа в экосистеме Сбера есть несколько инструментов, я выбрал Graph для SmartApp. О том, как создать свой смартап можно почитать здесь. Мы же сосредоточимся на продумывании концепции, которую можно выработать хорошо покопавшись в документации к Салюту, а затем и к Алисе. Задача состоит в том, чтобы голосовой помощник извлек из вашей аудиокоманды так называемые интенты или намерения. Их можно формализовать и превратить в ограниченный набор нормализованных слов, которые можно использовать в качестве команд для бэкенда, которым будет выступать наш шлюз. Вот так выглядит смартап, который был разработан для выполнения базовых задач: включить/выключить, получить информацию с датчиков:

Скриншот реального смартапа

Скриншот реального смартапа

Смартап позволяет делать произвольные https-запросы, чем мы и воспользуемся, после извлечения интентов из голосовой команды. Все что нам остается, это определить формат запросов и endpoint на стороне шлюза для приема запросов от Салюта. Формат очень простой: { «room»: «xxx», «entity»: «yyy», «request»: «action/query», «value»: value }.

Важно, что значения xxx и ууу — это нормализованные токены, собственно их получение и есть предмет настройки смартапа в экосистеме Сбера. Добавляем в шлюз обработчик запросов, который на вход получает название комнаты (передается из интента в диалоге Салюта) и название объекта для включения (также берется из интента). Поскольку интент может подразумевать разные слова, например для света ($entity): «свет», «люстра», то сделана таблица соответствия sb_requests, которая связывает пару переданных полей $room+$entity с набором идентификаторов Яндекса, а также запросом, который надо послать устройству(ам), заменив ключевое слово $value, на переданное из диалога значение value. Аналогично со статусами: при разборе статуса шлюз обрабатывает разные типы ответов, заданных разными ключевыми словами (начинаются с $). Справочник действий и шаблонов статусов хранится в таблице sb_codes. Ответы генерируются шлюзом на основе этих шаблонов. Таким образом обработчик команд Салюта является как бы прокси между экосистемой Сбера и описанным выше обработчиком команд от умного дома Яндекса. Но не полностью. И Яндексу сервис не отвечает, кроме случаев изменения статуса. Более подробно логику работы см. в файлах main.cpp и ya_hub.sql из репозитория.

Главное преимущество, которое дает подход смартапа  — это уверенность, что я получу показания именно с того датчика, о котором спрашиваю. Так как искуственный интеллект не вмешивается или мало вмешивается в логику извлечения интента. Она описана довольно жесткой конструкцией, см.граф выше, из которой невозможно «выпрыгнуть».

Успех с Салютом навел на мысль, что нужно разработать универсальный навык и для Алисы. Логика описания и извлечения интентов у Алисы совершенно иная. Документация весьма подробная. Вот так выглядит описание грамматики интента, например:

Скриншот реальной настройки грамматики навыка

Скриншот реальной настройки грамматики навыка

А вот так выглядит справочник сущностей:

Скриншот справочника сущностей реального навыка

Скриншот справочника сущностей реального навыка

В примере видно, что нормализованными формами слов будут livingroom, kitchen и т.д. Поскольку, в отличие от Салюта, у Алисы определен API, то сделан транслятор запросов Алисы в формат, аналогичный разработанному для Салюта. При этом, поскольку значения полей xxx и ууу могут быть только английскими, сделан дополнительно словарь перевода английских токенов от Яндекса в русские от Салюта. Это позволяет сохранить саму логику запросов и управления устройствами такую же и один раз уже определенную для Салюта. Более подробно логику работы см. в файлах main.cpp и ya_hub.sql.

В случае с навыком Алисы путь запроса получается довольно длинным. Сначала работает распознавание, навык присылает запрос, из которого мы вытаскиваем интент. Затем переводим токены с английского на русский, потом формируем запрос по формату, разработанному для Салюта и уже его обрабатываем теми же процедурами, что и для Салюта, который в свою очередь является прокси для навыка «умный дом» от Яндекса. Кажется много, но всё происходит мгновенно и дольше приходится ждать собственно ответа от устройства. В общем, теперь я могу быть уверен, что после запуска навыка Алисы я получу точно температуру со своего датчика. И ровно ее же получу, дав аналогичную команду Салюту.

 Дополнительным удобством Алисы является возможность задания команды вместе с активацией навыка. Т.е. не нужно сначала активировать навык, а можно сразу дать команду «Алиса, спроси у навыка мой дом, какая температура на улице». В ответ сразу получим температуру с уличного датчика, без приветствия.

Еще одно важное замечание в отношении универсальных навыков/смартапов. Им возвращается текст, который голосовой помощник будет проговаривать. Это привело меня к мысли, что числовые данные, например температуру или влажность придется переводить в пропись для последующей озвучки. Для этого, на пару с alice.yandex.ru, разработал небольшую библиотеку, использованную в шлюзе и доступную в соседнем репозитории.

ИТОГО. Разработанный шлюз позволил подключить:

  1. Навык типа «умный дом» от Яндекса с Алисой.

  2. Скилл типа «умный дом» от VK c Марусей.

  3. Смартап своей разработки в экосистеме Сбера с Салютом.

  4. Навык своей разработки в экосистеме Яндекса с Алисой.

Шлюз позволил использовать голосовых помощников с:

  1. HomeAssistant по REST API.

  2. OpenHAB по REST API.

  3. Zigbee2mqtt по MQTT минуя умный дом.

  4. Zwave2mqtt по MQTT минуя умный дом.

  5. Напрямую с устройствами на базе ESP8266 и по REST API и по MQTT и с помощью bash-файла. Во всех случаях минуя умный дом.

Точно так же можно настроить работу с устройствами на базе прошивок Tasmota, иных шлюзов, заменяющих проприетарные умные дома, например broadlink-mqtt и т.д. 

Итак, мы рассмотрели создание универсального шлюза для российских голосовых помощников: Алисы, Салюта и Маруси. У меня не было цели всё разобрать до мельчайших деталей, учитывая что исходный код открыт и прокомментирован. Скорее хотелось продемонстрировать подход, который позволяет интегрировать голосовых помощников в повседневную жизнь без зависимости от вендорских решений и даже без универсальных программных умных домов.

ссылка на оригинал статьи https://habr.com/ru/articles/1028556/