OK Google, заведи мне машину

  • Tutorial


Будущее всё ближе. Лет 10 назад я и не мог подумать, что буду заводить машину с помощью голосовой команды!

Последние годы я с интересом наблюдал за бурным развитием голосовых ассистентов. После выхода Google Home Mini, решил что и мне уже пора попробовать, так как цена стала более-менее адекватной для «игрушки». Первый проект — интеграция голосового помощника с GSM модулем StarLine для автозапуска, контроля координат, напряжения аккумулятора и других параметров, отдаваемых сигнализацией автомобиля. Итак, поехали?

Наличие Google Home не обязательно, всё описанное далее будет работать и с приложением Google Assistant на телефоне. У меня установлен GSM/GPS модуль StarLine M31, но должно работать со всеми GSM сигнализациями от StarLine.

Общая схема приложения для Google Assistant




  • Google Home / Google Assistant отвечает за преобразование голоса в текст и обратно + взаимодействие со стандартными гугловскими сервисами. При вызове нашего приложения, Action в терминологии Google, запросы передаются на DialogFlow (API.AI на схеме).
  • DialogFlow — отвечает за определение схемы диалога, обработку текста запросов на естественном языке, выделение сущностей, формирование ответов и взаимодействие с внешним миром с помощью вызова WebHook при необходимости.
  • WebHook — WEB-сервис для взаимодействия с внешним миром. На вход подается ветка диалога (Intent) + параметры извлеченные из запроса (Entities). На выходе — ответ пользователю.

1. DialogFlow.com


Для начала нам надо создать приложение (agent) на dialogflow (бывший API.AI).
Регистрируемся с помощью Google аккаунта к которому у нас будет привязан Google Home.
К сожалению, русский язык пока не доступен для Google Assistant, выбираем английский.



Далее нам надо создать Intents. Intent в терминологии DialogFlow — одна из веток диалога отвечающая за определенное действие. В нашем случае это будут: GetBattery, GetTemperature, StartEngine, StopEngine. Так же существует Default Intent, срабатывающий в самом начале, обычно это приветствие и краткий рассказ о том, что можно делать с помощью данного приложения.
В каждом Intent нам необходимо указать примеры голосовых команд (User says), желательно по 5-10 разных вариантов.



Во всех Intents, кроме дефолтного, нам необходимо отправлять запросы к нашему скрипту (WebHook), поэтому ставим Fulfillment — Use webhook.



2. WebHook для взаимодействия с сервером Starline


Нам нужен скрипт который получает Intent из запроса от DialogFlow и дергает команды Starline. Быстрее всего у меня получилось реализовать это на Python+Flask.

Взаимодействие со StarLine взято отсюда + прочекано на актуальность снифером в браузере.
Для запуска на сервере я использовал gunicorn

gunicorn -b :3333 flask.starline:app

+ nginx в качестве реверс прокси.
Учтите, HTTPS обязателен!

starline.py
from flask import Flask, request
from flask_restful import reqparse, Resource, Api, abort
import requests
import logging

DEVICE_ID = 1234567 # Use HTTPS sniffer to find your DEVICE_ID in https://starline-online.ru/ traffic
LOGIN = 'YOUR_STARLINE_EMAIL'
PASS = 'YOUR_STARLINE_PASSWORD'

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
header = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest'}


def start_engine(): 
    with requests.Session() as session:
        t = session.get('https://starline-online.ru/', headers=header)
        login = session.post('https://starline-online.ru/user/login', {
            'LoginForm[login]': LOGIN,
            'LoginForm[pass]': PASS,
            'LoginForm[rememberMe]': 'off'}, headers=header)
        logging.debug(login.content)
        r0 = session.get('https://starline-online.ru/device', headers=header)
        logging.debug(r0.content)
        r = session.post('https://starline-online.ru/device/{0}/executeCommand'.format(DEVICE_ID), {
            'value': '1',
            'action': 'ign',
            'password': ''}, headers=header, timeout=1)
        logging.debug(r.status_code)
        logging.debug(r.content)
        logout = session.post('https://starline-online.ru/user/logout', {
            '': ''}, )
        return ('Engine started!')


def stop_engine(): 
    with requests.Session() as session:
        t = session.get('https://starline-online.ru/', headers=header)
        login = session.post('https://starline-online.ru/user/login', {
            'LoginForm[login]': LOGIN,
            'LoginForm[pass]': PASS,
            'LoginForm[rememberMe]': 'off'}, headers=header)
        logging.debug(login.content)
        r0 = session.get('https://starline-online.ru/device', headers=header)
        logging.debug(r0.content)
        r = session.post('https://starline-online.ru/device/{0}/executeCommand'.format(DEVICE_ID), {
            'value': '0',
            'action': 'ign',
            'password': ''}, headers=header)
        logging.debug(r.status_code)
        logging.debug(r.content)
        logout = session.post('https://starline-online.ru/user/logout', {
            '': ''}, )
        return ('Engine stopped!')


def get_params():
    with requests.Session() as session:
        t = session.get('https://starline-online.ru/', headers=header)
        login = session.post('https://starline-online.ru/user/login', {
            'LoginForm[login]': LOGIN,
            'LoginForm[pass]': PASS,
            'LoginForm[rememberMe]': 'off'}, headers=header)
        logging.debug(login.content)
        r0 = session.get('https://starline-online.ru/device', headers=header)
        logging.debug(r0.content)
        res_dict = r0.json()['answer']['devices'][0]

        logout = session.post('https://starline-online.ru/user/logout', {
            '': ''}, )
        return {'battery': res_dict['battery'], 'temperature': res_dict['ctemp']}


def get_battery_text():
    return ("Battery voltage {0} volts.".format(get_params()['battery']))


def get_temperature_text():
    return ("Temperature: {0} degrees.".format(get_params()['temperature']))


app = Flask(__name__)
app.config['BUNDLE_ERRORS'] = True
api = Api(app)


class ProccessGoogleRequest(Resource):
    def get(self):
        return {"status": "OK"}

    def post(self):
        req = request.get_json()
        logging.debug(request.get_json())
        response = ''
        if req['result']['metadata']['intentName'] == 'GetBattery':
            response = get_battery_text()
        if req['result']['metadata']['intentName'] == 'GetTemperature':
            response = get_temperature_text()
        if req['result']['metadata']['intentName'] == 'StartEngine':
            response = start_engine()
        if req['result']['metadata']['intentName'] == 'StopEngine':
            response = stop_engine()
        if response == '':
            abort(400, message='Intent not detected')
        return {"speech": response, "displayText": response}


api.add_resource(ProccessGoogleRequest, '/starline/')

if __name__ == '__main__':
    app.run(debug=False)


Да, пользуясь случаем, хочу обратиться к команде StarLine — ребята, почему бы не сделать нормальный API с документацией? Глядишь и интеграций со сторонними продуктами стало бы в разы больше?

3. Тестируем в симуляторе и на реальном усройстве


Для тестирования в DialogFlow заходим в Integrations -> Google Assistant -> INTEGRATION SETTINGS -> Test и попадаем в симулятор Actions on Google



А вот и результат тестирования в реальном мире

Единственный косяк, в данной версии он отвечает «Engine started» до реального запуска двигателя так как не успевает дождаться ответа от Starline.

Идеи:

1. Запрос местоположения у Google Assistant, озвучивание расстояния до машины (Starline умеет отдавать координаты). Пока непонятно как для WebHook на Python запросить местоположение Google Home.

2. Упростить интеграцию Google <-> Starline, тогда отпадёт необходимость хардкодить пароль. Без участия со стороны Starline, как я понимаю, это не возможно.

Известные проблемы:

1. Google Assistant не успевает дождаться от сервера Starline ответа о статусе запуска двигателя

2. Пока при тестировании можно использовать только дефолтное имя приложения(Invocation) — Hey Google, talk to my test app.

Полезные ссылки:

1. Видео от Google

2. Пример с использованием Entities
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 22
  • +8
    «Ok, Google, угони мне машину!»
    • 0

      делал себе управление освещением дома через ок гугл, с помощью таскера и автовойса, зачастую это удобнее чем заходить в приложение. до авто ещё не дошло, лень штатный иммобилайзер обходить.

      • 0

        Там у старлайна вроде как более хитрая система с обходом иммобилайзера сделана: сам чип в машине не хранится, а используется модуль, который эмулирует чип, и модуль этот работает только если его запустила сама сигналка(то есть не тупо подать питание, а передача какого-то ключа), а сигнализации подает сигнал на запуск твой пульт, в который привязан к сигнализации. Другое дело — уязвимости в протоколе между сигнализацией и пультом, но тут уже зависит от модели сигнализации. Опять же есть потенциальная уязвимость в модуле иммобилайзера, атаковать его я думаю вполне возможно.

        • 0
          Не, в моём случае это стандартный обходчик (чип от ключа в машине)
          • 0
            Для некоторых марок, действительно, можно не хоронить ключ в машине, а «интеллектуально» обходить штатный иммобилайзер. Полный список автомобилей можно посмотреть по ссылке.

            Для реализации используются различные уязвимости, но отдельных модуль не нужен. Команды на запуск двигателя или имитации наличия ключа посылаются по CAN или LIN интерфейсу из основного блока охранной системы. У старых Тойот есть еще IMMO IMMI интерфейс — по сути обычный UART.

            Такой подход гораздо безопаснее, чем обходчик с ключом или чипом внутри, так как злоумышленник не сможет отправить команду на запуск без авторизации, имея только физический доступ к системе.
            • 0
              Для реализации используются различные уязвимости

              Хотите сказать, что вы эксплуатируете уязвимости в конкретных марках авто? И так может сделать каждый?

              • 0
                И так может сделать каждый?

                Теоретически да. В том числе — с целью угона. Погуглите на ютюбе угона нет — ребята снимают ролики про то, как угнать автомобиль (и не дать этого сделать в их установочных центрах). Там мало хардкора, контент все таки для широкой публики, но общее представление получите.
                • 0

                  То есть получается, что иммобилайзер вообще не является сдерживающей защитой, раз любой угонщик может использовать его уязвимости и завести автомобиль даже близко не имея на руках чипа?

                  • 0
                    Иммобилайзер — защита от дилетанта. Посмотрите ролики в интернете, обычно иммобилайзер без наличия чипа обходится за мгновения либо добавлением в доверенные своего чипа, либо заменой ЭБУ на заранее подготовленный.
                    • 0
                      Зависит от марки автомобиля. Популярные корейцы дырявые, V*G этого года пока побеждаются только с помощью специального железа между антенной замка зажигания и блоком иммобилайзера. Но для многих автомобилей нужно производить расчеты на удаленном вычислительном кластере, то есть иметь достаточно серьезную инфраструктуру. Стоимость угона возрастает и может стать невыгодной для злоумышленников.
          • +5
            Это все удобно, но… Чем более «умная» сигнализация машины, тем больше у нее потенциальных точек, в которых могут быть дыры для угонщиков…

            Собственно, в свое время на люксовых машинах начал появляться бесключевой доступ… И в Ингушетии стало появляться много люксовых машин.
            • +1
              Единственный косяк, в данной версии он отвечает «Engine started» до реального запуска двигателя так как не успевает дождаться ответа от Starline.

              Можно изменить фразу на engine starts или trying start engine — тогда не будет косяка :-)
              • 0
                Лень — ты двигатель прогресса.
                Осталось реализовать все остальные датчики, основать стартап, и продаться гугл за много много денег.
                • 0
                  Да, пользуясь случаем, хочу обратиться к команде StarLine — ребята, почему бы не сделать нормальный API с документацией? Глядишь и интеграций со сторонними продуктами стало бы в разы больше?

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

                  Вашу статью сегодня бурно обсуждали, решили, что весной-летом этого года выпустим открытый API для всех DIY желающих. Так что спасибо :-)
                  • 0

                    Если не сложно, можете прокомментировать мой комментарий и правильно ли я понимаю работу сигнализации с модулем обхода иммобилайзера, без физического присутствия чипа.

                • 0
                  В интернетах, таки есть умельцы, которые некоторую функциональность API смогли «расковырять» и опубликовать (ищется на раз-два).
                  Я вчера смог открыть/закрыть автомобиль используя POST запрос.
                  • 0
                    Да, для автозапуска я использую именно этот подход.
                    • 0
                      Я не программист и не особо специалист, но имхо описанный Вами метод несколько отличается от такого:
                      POST https://dev.starline.ru/json/device/xxxxxx/set_param
                      Content-Type: application/json
                      Cookie: slnet=xxxxxxxxxxxxx
                      {"ign":1}
                      


                      Или же я просто не туда смотрю ¯\_(ツ)_/¯
                      • 0
                         r = session.post('https://starlineonline.ru/device/{0}/executeCommand'.format(DEVICE_ID), {
                                    'value': '1',
                                    'action': 'ign',
                                    'password': ''}
                        

                        Да, немного другой запрос, но суть та же.
                  • 0
                    От жеш красота, да. «Ок гугыл, старт» и ЧЕТЫРЕ машины ревут противоугонками.
                    ID старлайна как то учесть надо(если он там есть).
                    Пока писал, не было, теперь увидел "(DEVICE_ID)". А может распространятся на несколько машин? Ну, скажем, по криворукости.
                    • 0
                      Я год назад написал свою прошивку для Pandora 3000, вместо штатной антенны сделал блок расширения с GPS и GSM на базе STM32. Полгода отладки и теперь как у автора, но только сервисы собственные )

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Интересные публикации