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