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

1. Введение

Программа доступа к устройствам предоставляет Smart Device Management API, 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) для настройки интеграции доступа к устройствам. Идентификатор клиента и секрет клиента , созданные в рамках проекта GCP, будут использоваться как часть потока OAuth между приложением разработчика и Google Cloud. Разработчикам также необходимо посетить консоль доступа к устройствам, чтобы создать проект для доступа к API управления интеллектуальными устройствами.

Облачная платформа Google

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

585e926b21994ac9.png

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

14e7eabc422c7fda.png

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

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

5de534212d44fce7.png

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

e6a670da18952f08.png

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

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

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

f8a3f27354bc2625.png

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

db7ba33d8b707148.png

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

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

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

Создать проект Firebase

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

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

dbb02bbacac093f5.png

Установите инструменты Firebase

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

$ npm i -g firebase-tools

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

$ firebase --version

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

$ 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

Вы можете клонировать репозиторий 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

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

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

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

Развертывание примера 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, введите учетные данные партнера и нажмите кнопку ВОЙТИ . Идентификатор клиента и секрет клиента — это учетные данные, которые вы получили от Google Cloud Platform, а идентификатор проекта — от консоли доступа к устройству.

78b48906a2dd7c05.png

Кнопка ВОЙТИ проведет ваших пользователей через поток OAuth для вашего предприятия, начиная с экрана входа в их учетную запись Google. После входа в систему пользователям будет предложено предоставить вашему проекту разрешения на доступ к их устройствам Nest.

e9b7887c4ca420.png

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

b227d510cb1df073.png

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

673a4fd217e24dad.png

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

6. Контроль устройства

Пример приложения Device Access использует вызовы 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 устанавливает для вашего термостата 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 в вашем проекте, а также установить необходимые зависимости. Вы увидите, что папка вашего проекта содержит каталог функций с файлом index.js для определения наших облачных функций, package.json для определения настроек и каталогом node_modules для хранения зависимостей.

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

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

Затем запуск npm install из каталога функций должен установить зависимости для вашего проекта:

$ npm install

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

$ npm install express xmlhttprequest --save

Обновление до плана Blaze

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

c6a5e5a21397bef6.png

Создать экспресс-сервер

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

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

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);

Чтобы направлять запросы на наш сервер, нам нужно настроить перезапись из 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";

Затем добавьте функцию proxyRequestscripts.js , которая имеет ту же сигнатуру, что и функция 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 on Cloud.

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

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

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

461e9bae74227fc1.png

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

3adb01644217578c.png

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

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

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

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

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