Создание веб-приложения доступа к устройствам

1. Введение

Программа Device Access предоставляет API Smart Device Management, REST API, позволяющий разработчикам управлять устройствами Google Nest из своих приложений. Пользователям необходимо дать согласие на доступ третьих лиц к своим устройствам Nest.

52f77aa38cda13a6.png

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

  1. Создание проекта — Создайте проект в Google Cloud Platform и зарегистрируйтесь в качестве разработчика в консоли доступа к устройствам.
  2. Привязка учетных записей — Проведите пользователей через процесс привязки учетных записей и получите код доступа. Обменяйте код на токен доступа.
  3. Управление устройствами — отправляйте запросы к API интеллектуального управления устройствами, используя токен доступа для управления устройствами.

В этом практическом занятии мы подробно разберем принцип работы доступа к устройствам, создав веб-приложение, обрабатывающее аутентификацию и выполняющее вызовы API управления интеллектуальными устройствами. Мы также рассмотрим развертывание простого прокси-сервера с использованием Node.js и Express для маршрутизации запросов доступа к устройствам.

Прежде чем начать, было бы неплохо освежить в памяти основные веб-технологии, которые мы будем использовать в этом практическом занятии, такие как аутентификация с помощью OAuth 2.0 или создание веб-приложения с использованием Node.js , хотя это и не является обязательным условием.

Что вам понадобится

  • Node.js 8 или выше
  • Учетная запись Google с привязанным термостатом Nest

Что вы узнаете

  • Настройка проекта Firebase для размещения статических веб-страниц и облачных функций.
  • Отправка запросов на доступ к устройству через веб-приложение на основе браузера.
  • Создание прокси-сервера с использованием Node.js и Express для маршрутизации ваших запросов.

2. Создание проекта

Разработчикам необходимо создать проект Google Cloud Platform (GCP) для настройки интеграции с Device Access. Идентификатор клиента (Client Id) и секретный ключ клиента (Client Secret) , сгенерированные в проекте GCP, будут использоваться в процессе аутентификации OAuth между приложением разработчика и Google Cloud. Разработчикам также необходимо перейти в консоль Device Access, чтобы создать проект для доступа к API управления интеллектуальными устройствами (Smart Device Management API).

Платформа Google Cloud

Перейдите на Google Cloud Platform . Нажмите «Создать новый проект» и укажите имя проекта. Также отобразится идентификатор проекта [GCP-Project-Id] для Google Cloud; пожалуйста, запишите его, так как мы будем использовать его при настройке Firebase. (В дальнейшем в этом руководстве мы будем называть этот идентификатор [GCP-Project-Id] .)

585e926b21994ac9.png

Первый шаг — включить необходимую библиотеку API в нашем проекте. Перейдите в раздел API и сервисы > Библиотека и найдите Smart Device Management API. Вам необходимо включить этот API, чтобы авторизовать ваш проект для отправки запросов к вызовам Device Access API.

14e7eabc422c7fda.png

Прежде чем переходить к созданию учетных данных OAuth, нам необходимо настроить экран согласия OAuth для нашего проекта. Перейдите в раздел API и сервисы > Экран согласия OAuth . В поле «Тип пользователя» выберите «Внешний» . Укажите имя и адрес электронной почты службы поддержки вашего приложения, а также контактную информацию разработчика, чтобы заполнить первый экран. Когда вас попросят указать тестовых пользователей , обязательно укажите адрес электронной почты с привязанными устройствами на этом шаге.

После настройки экрана согласия OAuth перейдите в раздел API и сервисы > Учетные данные . Нажмите +Создать учетные данные и выберите идентификатор клиента OAuth . В качестве типа приложения выберите веб-приложение .

5de534212d44fce7.png

Укажите имя для вашего клиента и нажмите «СОЗДАТЬ» . Позже мы добавим авторизованный источник JavaScript и авторизованный URI перенаправления. После завершения этого процесса отобразятся [Client-Id] и [Client-Secret], связанные с этим клиентом OAuth 2.0.

e6a670da18952f08.png

Консоль доступа к устройству

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

Создайте новый проект и дайте ему имя. В следующем окне укажите [Client-Id] , полученный от GCP на предыдущем шаге.

f8a3f27354bc2625.png

Включение событий и завершение этапов создания проекта переведут вас на главную страницу вашего проекта. Ваш [Project-Id] будет указан под именем, которое вы дали своему проекту.

db7ba33d8b707148.png

Пожалуйста, запишите ваш [Project-Id] , так как мы будем использовать его при отправке запросов к API управления интеллектуальными устройствами.

3. Настройка Firebase

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

Создайте проект Firebase.

Перейдите в консоль Firebase . Нажмите «Добавить проект» , затем выберите проект, который вы создали на этапе создания проекта . Это создаст проект Firebase, который будет связан с вашим проектом GCP [идентификатор проекта GCP] .

После успешного создания проекта Firebase вы должны увидеть следующий экран:

dbb02bbacac093f5.png

Установите Firebase Tools.

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

$ npm i -g firebase-tools

Чтобы убедиться в правильности установки инструментов Firebase, проверьте информацию о версии.

$ firebase --version

Вы можете войти в инструменты Firebase CLI, используя свою учетную запись Google, с помощью команды login.

$ firebase login

Инициализация проекта хостинга

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

$ firebase init hosting

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

  1. Пожалуйста, выберите вариант — Использовать существующий проект
  2. Выберите проект Firebase по умолчанию для этой директории — Выберите ***[GCP-Project-Id]***
  3. Что вы хотите использовать в качестве общедоступного каталога? — Общедоступный
  4. Настроить как одностраничное приложение? — Да
  5. Настроить автоматическую сборку и развертывание с помощью GitHub? — Нет

После инициализации проекта вы можете развернуть его в Firebase с помощью следующей команды:

$ firebase deploy

Firebase просканирует ваш проект и развернет необходимые файлы на облачном хостинге.

fe15cf75e985e9a1.png

Когда вы откроете URL-адрес хостинга в браузере, вы должны увидеть страницу, которую только что развернули:

e40871238c22ebe2.png

Теперь, когда вы знаете основы развертывания веб-страницы с помощью Firebase, давайте перейдем к развертыванию нашего примера из Codelab!

4. Пример кода для лабораторной работы

Вы можете клонировать репозиторий codelab, размещенный на GitHub, используя приведенную ниже команду:

$ git clone https://github.com/google/device-access-codelab-web-app.git

В этом репозитории мы предоставляем примеры в двух отдельных папках. Папка codelab-start содержит необходимые файлы, чтобы вы могли начать работу с текущего момента в этом Codelab. Папка codelab-done содержит полную версию этого Codelab с полностью функциональным клиентом и сервером Node.js.

В ходе выполнения этого практического задания мы будем использовать файлы из папки codelab-start , однако, если у вас возникнут трудности, вы также можете обратиться к версии из папки codelab-done.

Примеры файлов Codelab

Структура файлов папки codelab-start выглядит следующим образом:

public
├───index.html
├───scripts.js
├───style.css
firebase.json

В папке Public находятся статические страницы нашего приложения. firebase.json отвечает за маршрутизацию веб-запросов к нашему приложению. В версии codelab-done , вы также увидите каталог functions , содержащий логику для нашего прокси-сервера (Express), который будет развернут в Google Cloud Functions.

Развернуть пример из Codelab

Скопируйте файлы из codelab-start в каталог вашего проекта.

$ firebase deploy

После завершения развертывания Firebase вы сможете увидеть приложение Codelab:

e84c1049eb4cca92.png

Для запуска процесса аутентификации требуются учетные данные партнера, которые мы рассмотрим в следующем разделе.

5. Обработка OAuth

OAuth — это веб-стандарт для делегирования доступа, обычно используемый пользователями для предоставления сторонним приложениям доступа к информации своей учетной записи без передачи паролей. Мы используем OAuth 2.0, чтобы разработчики могли получать доступ к устройствам пользователей через функцию «Доступ к устройствам».

7ee31f5d9c37f699.png

Укажите URI перенаправления

Первый шаг в процессе аутентификации OAuth включает передачу набора параметров на конечную точку Google OAuth 2.0. После получения согласия пользователя серверы Google OAuth отправят запрос с кодом авторизации на ваш URI перенаправления.

Обновите константу SERVER_URI (строка 19), указав свой собственный URL-адрес хостинга в scripts.js :

const SERVER_URI = "https://[GCP-Project-Id].web.app";

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

$ firebase deploy

Включить URI перенаправления

После обновления URI перенаправления в файле скриптов необходимо также добавить его в список разрешенных URI перенаправления для идентификатора клиента, созданного для вашего проекта. Перейдите на страницу учетных данных в Google Cloud Platform, где будут перечислены все учетные данные, созданные для вашего проекта:

1a07b624b5e548da.png

В списке идентификаторов клиентов OAuth 2.0 выберите идентификатор клиента, созданный вами на этапе создания проекта . Добавьте URI перенаправления вашего приложения в список авторизованных URI перенаправления для вашего проекта.

6d65b298e1f005e2.png

Попробуйте войти!

Перейдите по URL-адресу хостинга, который вы настроили в Firebase, введите учетные данные партнера и нажмите кнопку «ВОЙТИ» . Идентификатор клиента (Client Id) и секретный ключ (Client Secret) — это учетные данные, полученные от Google Cloud Platform, а идентификатор проекта (Project Id) — из консоли доступа к устройствам (Device Access Console).

78b48906a2dd7c05.png

Кнопка «Вход» проведет пользователей через процесс аутентификации OAuth для вашей компании, начиная с экрана входа в их учетную запись Google. После входа в систему пользователям будет предложено предоставить вашему проекту разрешения на доступ к их устройствам Nest.

e9b7887c4ca420.png

Поскольку это фиктивное приложение, Google выдаст предупреждение перед перенаправлением!

b227d510cb1df073.png

Нажмите «Дополнительно», затем выберите «Перейти к web.app (небезопасно)», чтобы завершить перенаправление на ваше приложение.

673a4fd217e24dad.png

Это позволит получить код OAuth в составе входящего GET-запроса, который приложение затем обменяет на токен доступа и токен обновления.

6. Управление устройством

В демонстрационном приложении «Доступ к устройству» используются вызовы REST API Smart Device Management для управления устройствами Google Nest. Эти вызовы включают передачу токена доступа в заголовке запроса GET или POST, а также полезную нагрузку, необходимую для выполнения определенных команд.

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

function deviceAccessRequest(method, call, localpath, payload = null) {...}
  • метод — Тип HTTP-запроса ( GET или POST)
  • call — строка, представляющая вызов нашего API, используемая для маршрутизации ответов ( listDevices , thermostatMode , temperatureSetpoint ).
  • localpath — конечная точка, к которой направляется запрос, содержащая идентификатор проекта и идентификатор устройства (добавляется после https://smartdevicemanagement.googleapis.com/v1 ).
  • полезная нагрузка (*) — Дополнительные данные, необходимые для вызова API (например, числовое значение, представляющее температуру для заданного значения)

Мы создадим примеры элементов управления пользовательского интерфейса (Список устройств, Установка режима, Установка температуры) для управления термостатом Nest:

86f8a193aa397421.png

Эти элементы управления пользовательского интерфейса будут вызывать соответствующие функции ( listDevices() , postThermostatMode() , postTemperatureSetpoint() ) из scripts.js . Они оставлены пустыми, чтобы вы могли реализовать их самостоятельно! Цель состоит в том, чтобы выбрать правильный метод/путь и передать полезную нагрузку в функцию deviceAccessRequest(...) .

Список устройств

Простейший вызов функции доступа к устройству — это listDevices . Он использует GET запрос и не требует полезной нагрузки. Конечная точка должна быть структурирована с использованием ` projectId . Дополните функцию ` listDevices() следующим образом:

function listDevices() {
  var endpoint = "/enterprises/" + projectId + "/devices";
  deviceAccessRequest('GET', 'listDevices', endpoint);
}

Сохраните изменения и повторно разверните свой проект Firebase с помощью следующей команды:

$ firebase deploy

После установки новой версии приложения попробуйте перезагрузить страницу и нажмите «СПИСОК УСТРОЙСТВ» . В разделе «Управление устройствами» должен отобразиться список, в котором вы увидите идентификатор вашего термостата:

b64a198673ed289f.png

Выбор устройств из списка обновит поле deviceId в файле scripts.js . Для следующих двух элементов управления нам потребуется указать deviceId для конкретного устройства, которым мы хотим управлять.

Управление термостатом

В API управления смарт-устройствами есть два параметра для базового управления термостатом Nest: ThermostatMode и TemperatureSetpoint . Параметр ThermostatMode устанавливает режим работы термостата Nest на один из четырех возможных: {Выкл., Обогрев, Охлаждение, ОбогревОхлаждение}. Затем необходимо указать выбранный режим в составе полезной нагрузки.

Замените функцию postThermostatMode() в scripts.js следующим кодом:

function postThermostatMode() {
  var endpoint = "/enterprises/" + projectId + "/devices/" + deviceId + ":executeCommand";
  var tempMode = id("tempMode").value;
  var payload = {
    "command": "sdm.devices.commands.ThermostatMode.SetMode",
    "params": {
      "mode": tempMode
    }
  };
  deviceAccessRequest('POST', 'thermostatMode', endpoint, payload);
}

Следующая функция, postTemperatureSetpoint() , отвечает за установку температуры (в градусах Цельсия) для вашего термостата Nest. В полезной нагрузке можно задать два параметра: heatCelsius и coolCelsius , в зависимости от выбранного режима работы термостата.

function postTemperatureSetpoint() {
  var endpoint = "/enterprises/" + projectId + "/devices/" + deviceId + ":executeCommand";
  var heatCelsius = parseFloat(id("heatCelsius").value);
  var coolCelsius = parseFloat(id("coolCelsius").value);

  var payload = {
    "command": "",
    "params": {}
  };
  
  if ("HEAT" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat";
    payload.params["heatCelsius"] = heatCelsius;
  }
  else if ("COOL" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetCool";
    payload.params["coolCelsius"] = coolCelsius;
  }
  else if ("HEATCOOL" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetRange";
    payload.params["heatCelsius"] = heatCelsius;
    payload.params["coolCelsius"] = coolCelsius;
  } else {
    console.log("Off and Eco mode don't allow this function");
    return;
  }
  deviceAccessRequest('POST', 'temperatureSetpoint', endpoint, payload);
}

7. Сервер Node.js (необязательно)

Поздравляем! Вы создали веб-приложение на стороне клиента, способное отправлять запросы к API управления интеллектуальными устройствами из браузера. Тем, кто хочет разработать приложение на стороне сервера, мы предлагаем воспользоваться прокси-сервером, который будет перенаправлять ваши запросы из браузера.

Для этого прокси-сервера мы будем использовать облачные функции Firebase, Node.js и Express.

Инициализация облачных функций

Откройте новое окно терминала, перейдите в каталог вашего проекта и выполните следующую команду:

$ firebase init functions

Для инициализации облачных функций Firebase задаст вам ряд вопросов:

  1. Какой язык программирования вы бы предпочли использовать для написания облачных функций? — JavaScript
  2. Вы хотите использовать ESLint для выявления вероятных ошибок и обеспечения соблюдения стиля? — Нет
  3. Вы хотите установить зависимости с помощью npm прямо сейчас? — Да

Это инициализирует папку functions в вашем проекте, а также установит необходимые зависимости. Вы увидите, что папка вашего проекта содержит каталог functions, файл index.js для определения наших облачных функций, файл package.json для определения настроек и каталог node_modules для хранения зависимостей.

Для реализации серверной части мы будем использовать две библиотеки npm : express и xmlhttprequest. Вам потребуется добавить следующие записи в список зависимостей в файле package.json:

"xmlhttprequest": "^1.8.0",
"express": "^4.17.0"

Затем, выполнив команду `npm install` из каталога `functions`, вы установите зависимости для своего проекта:

$ npm install

Если у npm возникнут проблемы с загрузкой пакетов, вы можете попробовать сохранить xmlhttprequest и явно указать это с помощью следующей команды:

$ npm install express xmlhttprequest --save

Перейти на тарифный план Blaze

Для использования команды firebase deploy вам потребуется перейти на тарифный план Blaze, что, в свою очередь, потребует добавления способа оплаты в вашу учетную запись. Перейдите в раздел «Обзор проекта» > «Использование и выставление счетов» и убедитесь, что для вашего проекта выбран тарифный план Blaze.

c6a5e5a21397bef6.png

Создать сервер Express

Сервер Express использует простую структуру для обработки входящих GET и POST запросов. Мы создали сервлет, который прослушивает POST запросы, отправляет их на целевой URL, указанный в полезной нагрузке, и отвечает полученным ответом.

Измените файл index.js в каталоге functions следующим образом:

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const functions = require('firebase-functions');
const express = require('express');
const http = require('http');

const app = express();
app.use(express.json());


//***** Device Access - Proxy Server *****//

// Serving Get Requests (Not used) 
app.get('*', (request, response) => {
  response.status(200).send("Hello World!");
});
// Serving Post Requests
app.post('*', (request, response) => {
  
  setTimeout(() => {
    // Read the destination address from payload:
    var destination = request.body.address;
    
    // Create a new proxy post request:
    var xhr = new XMLHttpRequest();
    xhr.open('POST', destination);
    
    // Add original headers to proxy request:
    for (var key in request.headers) {
            var value = request.headers[key];
      xhr.setRequestHeader(key, value);
    }
    
    // Add command/parameters to proxy request:
    var newBody = {};
    newBody.command = request.body.command;
    newBody.params = request.body.params;
    
    // Respond to original request with the response coming
    // back from proxy request (to Device Access Endpoint)
    xhr.onload = function () {
      response.status(200).send(xhr.responseText);
    };
    
    // Send the proxy request!
    xhr.send(JSON.stringify(newBody));
  }, 1000);
});

// Export our app to firebase functions:
exports.app = functions.https.onRequest(app);

Для переадресации запросов на наш сервер нам необходимо внести следующие изменения в параметры перезаписи (rewrites) в файле firebase.json :

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [{
        "source": "/proxy**",
        "function": "app"
      },{
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Это позволит перенаправлять URL-адреса, начинающиеся с /proxy на наш сервер Express, а остальные будут продолжать перенаправляться на наш index.html .

Вызовы прокси-API

Теперь, когда наш сервер готов, давайте определим URI прокси в scripts.js , чтобы наш браузер отправлял запросы на этот адрес:

const PROXY_URI = SERVER_URI + "/proxy";

Затем добавьте в файл scripts.js функцию proxyRequest , которая имеет ту же сигнатуру, что и функция deviceAccessRequest(...) , для косвенных вызовов доступа к устройству.

function proxyRequest(method, call, localpath, payload = null) {
    var xhr = new XMLHttpRequest();
    
    // We are doing our post request to our proxy server:
    xhr.open(method, PROXY_URI);
    xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
    xhr.onload = function () {
      // Response is passed to deviceAccessResponse function:
      deviceAccessResponse(call, xhr.response);
    };
    
    // We are passing the device access endpoint in address field of the payload:
    payload.address = "https://smartdevicemanagement.googleapis.com/v1" + localpath;
    if ('POST' === method && payload)
        xhr.send(JSON.stringify(payload));
    else
        xhr.send();
}

Последний шаг — заменить вызовы deviceAccessRequest(...) на функцию proxyRequest(...) в функциях postThermostatMode() и postTemperatureSetpoint() в scripts.js .

Для обновления приложения выполните firebase deploy .

$ firebase deploy

Таким образом, теперь у вас есть работающий прокси-сервер Node.js, использующий Express в Cloud Functions.

Предоставьте разрешения для облачных функций.

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

В Google Cloud Platform перейдите во вкладку «Облачные функции» в меню, затем выберите свою облачную функцию:

461e9bae74227fc1.png

Нажмите «Разрешения» , затем «Добавить участника» . В поле «Новый участник» введите allUsers и выберите роль «Cloud Functions» > «Cloud Functions Invoker». При нажатии кнопки «Сохранить» отобразится предупреждение:

3adb01644217578c.png

Выбор параметра «Разрешить публичный доступ» позволит вашему клиентскому приложению использовать облачные функции.

Поздравляем – вы успешно выполнили все шаги. Теперь вы можете перейти в свое веб-приложение и попробовать управлять устройством через прокси-сервер!

Следующие шаги

Ищете способы расширить свои знания в области управления устройствами? Ознакомьтесь с документацией по характеристикам , чтобы узнать больше об управлении другими устройствами Nest, а также о процессе сертификации , который позволит вам освоить шаги по выводу вашего продукта на мировой рынок!

Расширьте свои навыки с помощью примера веб-приложения Device Access, где вы сможете развить свой опыт, полученный в Codelab, и развернуть работающее веб-приложение для управления камерами Nest, дверными звонками и термостатами.