Сервер умного дома Яндекс Алисы в Colab

При разработке хочется тратить минимум времени на создание окружения для развертывания разработки. В идеале, чтобы все в облаке, а на локальном ПК минимум дополнительного ПО. Для решения этой задачи может помочь Docker или сервисы вроде Digital Ocean, но хочется ещё большей скорости развертывания. 🙂

Возникла задача потестировать работу умного дома Яндекс и отладить как работает голосовое управление. В качестве языка взял Python и решил, что хорошо бы запустить серверную часть умного дома Yandex на бесплатном Colab. Такой вариант работает только для целей отладки и тестирования, поскольку Colab периодически сбрасывает сессии пользователя.

Первым делом надо было решить задачу развертывания веб сервера. Я выбрал Gunicorn.

Кроме того нужен доступ к Gunicorn снаружи, чтобы при публикации навыка Алисы можно было указать Яндексу домен на котором работает веб служба. Для проброса фиксированного доменного имени в виртуальную машину Colab я использовал LocalTunnel. Статья по настройке Gunicorn и localtunnel для запуска в Colab здесь.

Следующая часть — разработка самого веб сервиса. Для этого я форкнул проект https://github.com/ClusterM/alice-smart-home. Он довольно хорошо документирован, однако при развертывании в Colab есть ряд нюансов, которые рассмотрю.

Настройка и запуск проекта

Для упрощения разветывания я написал небольшой скрипт на Python, во первых строках которого инициализирую необходимые модули:

import threading
from flask import Flask
import logging
import os.path
from os import path
!npm install -g npm
!pip install gunicorn
!npm install -g localtunnel

Клонирую fork-нутый репозиторий git. Fork сделал для правок описания устройств, чтобы не генерировать описание программмно. Хотя, тоже вариант. 🙂

!git clone https://github.com/Warlib1975/alice-smart-home.git

Далее необходимо обновить WSGI файл в корне проекта:

alice_dir = "/content/alice-smart-home" #Colab path where alice-smart-home project located after git clone.

#Generate WSGI file in the root dir
def generateWSGI(path):
  text =  "import sys\r\n"\
          "sys.path.insert(0, \'" + path + "\')\r\n"\
          "\r\n"\
          "from alice import app as application"
  
  with open(alice_dir.rstrip('/') + '/alice.wsgi', 'w') as f:
    f.write(text)

generateWSGI(alice_dir)   

Путь директории в Colab копируется при клике мышью на троеточие справа от имени директории.

Далее сформируем настройки для регистрации навыка Алисы в умном доме Яндекса:

subdomain = "alicesmarthomeXXXXXX"
domain = subdomain + ".loca.lt"
clientIdentifier = "SomeIdentifierXXXXX"
clientPassword = "XXXXXXXXXXXXXXXXX"
APIAuthorizationEndpoint = "https://" + domain + "/auth/"
tokenEndpoint = "https://" + domain + "/token/"
print("Настройки навыка Yandex умного дома")
print("---------------------------------------------------------------")
print("Идентификатор приложения:", clientIdentifier) 
print("Секрет приложения:", clientPassword)
print("Endpoint URL:", "https://" + domain)
print("URL авторизации:", APIAuthorizationEndpoint)
print("URL для получения токена:", tokenEndpoint)
print("---------------------------------------------------------------")

Для работы проекта необходимо наличие двух папок с доступами на запись:

tokens_dir = alice_dir.rstrip('/') + "/tokens"
users_dir = alice_dir.rstrip('/') + "/users"

#Create needed pathes and set RW permissions
if not path.isdir(users_dir):
  os.mkdir(users_dir)

if not path.isdir(tokens_dir):
  os.mkdir(tokens_dir)

!chmod go-rwx $tokens_dir $users_dir

В папке tokens кешируются токены для доступа пользователя к устройствам умного дома. В файле с именем токена находится имя пользователя.

Далее нужно обновить пути в файле config.py

def generateConfig(clientIdentifier, clientPassword, rootPath, isDebug = False):
  text =  "import logging\r\n"\
          "\r\n"\
          "CLIENT_ID = \"" + clientIdentifier + "\"\r\n"\
          "CLIENT_SECRET = \"" + clientPassword + "\"\r\n"\
          "USERS_DIRECTORY = \"" + users_dir + "\"\r\n"\
          "TOKENS_DIRECTORY = \"" + tokens_dir + "\"\r\n"\
          "DEVICES_DIRECTORY = \"" + rootPath.rstrip('/') + "/devices\""\
          
  if isDebug:
    text += "\r\n\r\n"\
            "# Uncomment to enable logging\r\n"\
            "LOG_FILE = \"" + rootPath.rstrip('/') + "/alice.log\"\r\n"\
            "LOG_LEVEL = logging.DEBUG\r\n"\
            "LOG_FORMAT = \"%(asctime)s %(remote_addr)s %(user)s %(message)s\"\r\n"\
            "LOG_DATE_FORMAT = \"%Y-%m-%d %H:%M:%S\""\

  with open(rootPath + "/config.py", 'w') as f:
    f.write(text)   

generateConfig(clientIdentifier, clientPassword, alice_dir, isDebug = True)           

Чтобы при повторных клонированиях репозитория не нужно было перерегистрировать устрйоства в навыках Yandex, я скопировал имя токена и пересоздаю его при запуске скрипта:

#Create demo_user token for device. 
def generateTestToken(tokens_path):
  text =  "demo_user"
  test_token_file = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX'
  
  with open(tokens_path.rstrip('/') + '/' + test_token_file, 'w') as f:
    f.write(text)

#!!! JUST FOR TESTING. COMMENT WHEN REAL WORK !!!
generateTestToken(tokens_dir)

Ну и скрипт для запуска в Colab веб сервера Gunicorn:

#Execute web server Gunicorn
def execGunicorn(appName, dir, workersNum = 2, port = 8000):
  print("Exec localtunnel with port:", port)
  !gunicorn --workers $workersNum -b localhost:$port --chdir $dir $appName:app --timeout 900 > gunicorn.log 2>&1 &

if __name__ == '__main__':
  gunicorn_thread = threadi

Стартую localtunnel для проброса к Gunicorn обращений с выбранного доменного имени:

#Execute tunneling from outside
def execLocalTunnel(domainName, port = 8000):
  print("Exec localtunnel with domain name:", domainName, "and port:" + str(port) + ".")
  domainName = domainName.lower()
  !lt --port $port --subdomain $domainName > lt.log 2>&1 
 
if __name__ == '__main__':
  my_thread = threading.Thread(target=execLocalTunnel, args=(subdomain, 8000))
  my_thread.start()

Для теста нужно подредактировать описание устройства из исходного проекта. Для начала сделаю его максимально коротким:

def pc_query(capability_type, instance):
    if capability_type == "devices.capabilities.on_off":
        return True

def pc_action(capability_type, instance, value, relative):
    if capability_type == "devices.capabilities.on_off":
        #if value:
        #else:
        return "DONE"

Если файл с описанием устройства задан именем pc.json, то имена методов обрабатывающих запросы в одноименном файле pc.py называются с префиксом «pc_».

Создание навыка Алисы

Собственно, дальше нужно создать навык Алисы:

  • Стартуем скрипт. Сервер должен полноценно функционировать для публикации навыка. Он-же сформирует все необходимые пути для заполнения формы настройки.
  • Прописываем авторизационные данные в файл имя-пользователя.json. В проекте это demo_user.json в котром задан пароль «test» и дан доступ к ранее описанному устройству pc.
{
    "password": "test",
    "devices": [
        "pc"
    ]
}
  • Переходим на https://dialogs.yandex.ru/, авторизуемся и нажимаем «Создать навык» -> «Создать диалог» -> «Умный дом».
  • Заполняем название (не принципиально).
  • Заполняем Endpoint URL, скопировав значение после запуска кода формирования путей: https://ваш-домен/
  • «Тип доступа» -> «Приватный». В этом случае происходит мгновенная модерация, т.е. навык сразу публикуется.
  • «Официальный навык» -> «Нет»
  • Заполняем остальные поля и загружаем иконку. Иконка — обязательное поле. Без него навык не будет создан.
  • Нажимаем «Авторизация» -> «Создать»
  • Копируем из заданного скрипте идентификатор приложения и секрет.
  • Копируем URL авторизации: https://ваш-домен/auth/
  • Копируем URL для получения токена: https://ваш-домен/token/
  • «Сохранить»->»Cохранить».
  • «Опубликовать».
  • Если сервер работает нормально и отвечает на все тестовые запросы, то навык будет опубликован.

После добавления устройства происходит загрузка описания из тестового JSON:

{
    "name": "Компьютер",
    "description": "Основной компьютер",
    "room": "Моя комната",
    "type": "devices.types.switch",
    "capabilities": [
        {
            "type": "devices.capabilities.on_off",
            "retrievable": true
        }
    ],
    "device_info": {
        "manufacturer": "Cluster",
        "model": "0",
        "hw_version": "1.0",
        "sw_version": "1.0"
    }
}

Вот как выглядит описание устройства помещенного в определенную комнату в логе запросов Яндекса:

Spread the love
Запись опубликована в рубрике IT рецепты с метками , . Добавьте в закладки постоянную ссылку.

Обсуждение закрыто.