При разработке хочется тратить минимум времени на создание окружения для развертывания разработки. В идеале, чтобы все в облаке, а на локальном ПК минимум дополнительного ПО. Для решения этой задачи может помочь 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"
}
}Вот как выглядит описание устройства помещенного в определенную комнату в логе запросов Яндекса:
