建構裝置存取網頁應用程式

1. 簡介

Device Access 計畫提供 Smart Device Management API,開發人員可以運用這個 REST API 透過自己的應用程式控制 Google Nest 裝置。使用者必須同意讓第三方存取自己的 Nest 裝置。

52f77aa38cda13a6.png

要成功整合裝置存取權,必須完成以下三個重要步驟:

  1. 建立專案:在 Google Cloud Platform 中建立專案,並在裝置存取權控制台中註冊為開發人員。
  2. 帳戶連結:透過帳戶連結流程取得使用者,並擷取存取碼。交換存取權杖的代碼。
  3. 裝置控制:透過存取權杖傳送指令,提出 Smart Device Management API 要求控制裝置。

在本程式碼研究室中,我們將深入探討裝置存取權的運作方式,建構處理驗證作業的網路應用程式,並發出 Smart Device Management API 呼叫。我們也會探討如何使用 Node.js 和 Express 部署簡易的 Proxy 伺服器,轉送裝置存取權要求。

在開始之前,建議您複習一下我們在本程式碼研究室中會用到的常見網路技術,例如使用 OAuth 2.0 進行驗證使用 Node.js 建構網路應用程式,不過這些並非必要條件。

需準備的資訊

  • Node.js 8 以上版本
  • 已連結 Nest Thermostat 的 Google 帳戶

課程內容

  • 設定託管靜態網頁和 Cloud 函式的 Firebase 專案
  • 透過以瀏覽器為基礎的網頁應用程式發出裝置存取要求
  • 使用 Node.js 和 Express 建構 Proxy 伺服器,以便將要求轉送

2. 建立專案

開發人員必須先建立 Google Cloud Platform (GCP) 專案,才能設定裝置存取權的整合作業。在 GCP 專案中產生的用戶端 ID用戶端密鑰,會做為開發人員應用程式與 Google Cloud 之間的 OAuth 流程的一部分。開發人員也必須前往裝置存取權控制台,建立專案才能存取 Smart Device Management API。

Google Cloud Platform

前往 Google Cloud Platform。按一下「建立新專案」,然後提供專案名稱。畫面上也會顯示 Google Cloud 的專案 ID [GCP-Project-Id],請記下這個 ID,因為會在設定 Firebase 期間使用。(我們會在本程式碼研究室中將此 ID 稱為 [GCP-Project-Id])。

585e926b21994ac9.png

第一步是為專案啟用必要的 API 程式庫。依序前往「API 和服務」>「程式庫」,然後搜尋 Smart Device Management API。您必須啟用這個 API,才能授權專案向 Device Access API 呼叫發出要求。

14e7eabc422c7fda.png

在繼續建立 OAuth 憑證之前,我們需要為專案設定 OAuth 同意畫面。依序前往「API 和服務」>「OAuth 同意畫面」。在「使用者類型」部分,選擇「外部」。請提供應用程式的名稱和支援電子郵件地址,以及開發人員的聯絡資訊,以便完成第一個畫面。當系統要求您提供「測試使用者」時,請務必在這個步驟提供已連結裝置的電子郵件地址。

設定 OAuth 同意畫面後,請依序前往「API 和服務」>「憑證」,按一下「+ 建立憑證」,然後選取「OAuth 用戶端 ID」。在應用程式類型中,選取「網頁應用程式」

5de534212d44fce7.png

為客戶提供名稱,然後按一下「建立」。我們稍後會新增授權的 JavaScript 來源和授權的重新導向 URI。完成這個程序後,您就會看到與此 OAuth 2.0 用戶端相關聯的 [Client-Id][Client-Secret]

e6a670da18952f08.png

裝置存取權控制台

前往裝置存取權控制台。如果您之前沒有使用過裝置存取權控制台,系統會要求您同意服務條款,並支付 $5 美元的註冊費。

建立新專案並為專案取名。在下一個視窗中,提供您在上一個步驟中從 GCP 取得的 [Client-Id]

f8a3f27354bc2625.png

啟用事件並完成專案建立步驟後,系統會將您帶往專案首頁。您的 [Project-Id] 會列在專案名稱下方。

db7ba33d8b707148.png

請記下你的 [Project-Id],我們會在傳送要求至 Smart Device Management API 時使用這組 ID。

3. Firebase 設定

Firebase 可讓開發人員輕鬆快速地部署網頁應用程式。我們將使用 Firebase 開發用戶端網頁應用程式,整合裝置存取權的功能。

建立 Firebase 專案

前往 Firebase 主控台。按一下「Add Project」,然後選取您在建立專案步驟中建立的專案。這項操作會建立並連結至 GCP 專案 [GCP-Project-Id] 的 Firebase 專案。

成功建立 Firebase 專案後,您應該會看到以下畫面:

dbb02bbacac093f5.png

安裝 Firebase 工具

Firebase 提供一組 CLI 工具,可用於建構及部署應用程式。如要安裝這些工具,請開啟新的終端機視窗,然後執行下列指令。這會在全球安裝 Firebase 工具。

$ npm i -g firebase-tools

如要確認 Firebase 工具是否正確安裝,請檢查版本資訊。

$ firebase --version

您可以使用登入指令,使用自己的 Google 帳戶登入 Firebase CLI 工具。

$ firebase login

初始化託管專案

登入成功後,下一步就是為網頁應用程式初始化代管專案。在終端機中,前往要建立專案的資料夾,然後執行下列指令:

$ firebase init hosting

Firebase 會詢問一連串問題,協助您開始使用託管專案:

  1. 請選取下列選項:使用現有專案
  2. 為這個目錄選取預設的 Firebase 專案:選擇***[GCP-Project-Id]***
  3. 您要使用什麼做為公開目錄?- 公開
  4. 是否要設為單頁應用程式?— 是
  5. 是否要透過 GitHub 設定自動建構及部署作業?— 否

專案初始化後,您可以使用下列指令將專案部署至 Firebase:

$ firebase deploy

Firebase 會掃描您的專案,並將必要檔案部署至雲端代管服務。

fe15cf75e985e9a1.png

在瀏覽器中開啟代管網址時,您應該會看到剛剛部署的網頁:

e40871238c22ebe2.png

您已瞭解使用 Firebase 部署網頁的基本概念,現在讓我們開始部署程式碼研究室的範例!

4. 程式碼研究室範例

您可以使用下列指令,複製 GitHub 上託管的 程式碼研究室存放區

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

這個存放區提供的範例分為兩個資料夾。codelab-start 資料夾內含必要的檔案,可讓您從這個程式碼研究室的目前步驟開始操作。codelab-done 資料夾包含本程式碼研究室的完整版本,包含功能完整的用戶端和 node.js 伺服器。

在本程式碼研究室中,我們會使用 codelab-start 資料夾中的檔案。不過,您隨時可以參考程式碼研究室的程式碼研究室版本。

程式碼研究室範例檔案

codelab-start 資料夾的檔案結構如下:

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

公用資料夾包含應用程式的靜態頁面。firebase.json 負責將網路要求轉送至我們的應用程式。在 codelab-done 版本中,您也會看到 functions 目錄,其中包含要在 Google Cloud 函式中部署的 Proxy 伺服器 (express) 邏輯。

部署程式碼研究室範例

codelab-start 中的檔案複製到專案目錄中。

$ firebase deploy

Firebase 部署完成後,您應該會看到程式碼研究室應用程式:

e84c1049eb4cca92.png

啟動驗證流程需要合作夥伴憑證,我們將在下一節說明。

5. 處理 OAuth

OAuth 是存取委派的網路標準,常見的用途是讓使用者在不需共用密碼的情況下,授權第三方應用程式存取自己的帳戶資訊。我們採用 OAuth 2.0,讓開發人員能夠透過裝置存取權存取使用者裝置。

7ee31f5d9c37f699.png

指定重新導向 URI

OAuth 流程的第一個步驟是將一組參數傳遞至 Google OAuth 2.0 端點。取得使用者同意後,Google OAuth 伺服器會向重新導向 URI 發出含有授權碼的要求。

scripts.js 中,將 SERVER_URI 常數 (第 19 行) 更新為您自己的代管網址:

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

如透過這項變更重新部署應用程式,專案使用的重新導向 URI 也會隨之更新。

$ firebase deploy

啟用重新導向 URI

更新指令碼檔案中的重新導向 URI 後,您必須將其新增至專案中為用戶端 ID 建立的允許重新導向 URI 清單。前往 Google Cloud Platform 中的「Credentials」(憑證) 頁面,畫面上會列出為專案建立的所有憑證:

1a07b624b5e548da.png

在「OAuth 2.0 用戶端 ID」清單下方,選取您在「建立專案」步驟時建立的用戶端 ID。將應用程式的重新導向 URI 新增至專案的授權重新導向 URI 清單。

6d65b298e1f005e2.png

請嘗試登入!

前往您透過 Firebase 設定的代管網址,輸入合作夥伴憑證,然後按一下「登入」按鈕。用戶端 ID 和用戶端密鑰是您從 Google Cloud Platform 取得的憑證,專案 ID 則來自裝置存取權主控台。

78b48906a2dd7c05.png

「登入」按鈕會將使用者導向貴公司的 OAuth 流程,從登入畫面開始進入 Google 帳戶。登入後,系統會要求使用者授予專案存取其 Nest 裝置的權限。

e9b7887c4ca420.png

由於這是模擬應用程式,Google 會在發出重新導向前發出警告!

b227d510cb1df073.png

按一下「進階」,然後選取「前往 web.app (不安全)」才能完成應用程式重新導向

673a4fd217e24dad.png

這項操作會在傳入的 GET 要求中提供 OAuth 代碼,而應用程式接著會交換「存取權杖」和「更新權杖」。

6. 裝置控制

Device Access 範例應用程式使用 Smart Device Management REST API 呼叫控制 Google Nest 裝置。這些呼叫涉及在 GET 或 POST 要求標頭中傳遞存取權杖,以及某些指令所需的酬載。

我們編寫了一般存取要求函式來處理這些呼叫。不過,您必須為此函式提供正確的端點,以及必要時的酬載物件!

function deviceAccessRequest(method, call, localpath, payload = null) {...}
  • 方法 — HTTP 要求類型 (GETPOST)
  • call - 代表 API 呼叫的字串,用於路由回應 (listDevicesthermostatModetemperatureSetpoint)
  • localpath - 要求的端點,包含專案 ID 和裝置 ID (附加在 https://smartdevicemanagement.googleapis.com/v1 後方)
  • 酬載 (*) — API 呼叫所需的額外資料 (例如,代表設定點溫度的數值)

我們將建立 UI 控制項範例 (裝置清單、設定模式、設定溫度),用來控制 Nest Thermostat:

86f8a193aa397421.png

這些 UI 控制項會呼叫 scripts.js 中的對應函式 (listDevices()postThermostatMode()postTemperatureSetpoint())。這些欄位會留空,方便您自行實作!目標是選擇正確的方法/路徑,並將酬載傳遞至 deviceAccessRequest(...) 函式。

可列出裝置

最簡單的裝置存取權呼叫為 listDevices。使用 GET 要求,且不需要酬載。端點必須使用 projectId 進行結構化。依照下列方式完成 listDevices() 函式:

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

儲存變更,然後使用下列指令再次部署 Firebase 專案:

$ firebase deploy

部署新版應用程式後,請嘗試重新載入頁面,然後按一下「列出裝置」。系統應會在「裝置控制」下方的清單中填入溫度控制器的 ID:

b64a198673ed289f.png

從清單中選取裝置後,scripts.js 檔案中的 deviceId 欄位就會更新。對於這兩個控制項,我們需要針對要控制的特定裝置指定 deviceId

溫度控制器控制

在 Smart Device Management API 中,Nest Thermostat 基本操控有兩種特徵。ThermostatModeTemperatureSetpoint。ThermostatMode 會將 Nest Thermostat 的模式設為以下四種模式之一:{Off, Heat, Cool, HeatCool}。接著,我們需要在酬載中提供所選模式。

scripts.js 中的 postThermostatMode() 函式替換為以下內容:

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 Thermostat 的溫度 (以攝氏為單位)。酬載中有 heatCelsiuscoolCelsius 兩個參數可供設定,視所選溫度控制器模式而定。

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 伺服器 (選用)

恭喜!您建立了用戶端網頁應用程式,可透過瀏覽器提出 Smart Device Management API 要求。如果您想在伺服器端進行建構,我們建議您使用 Proxy 伺服器,這樣就能從瀏覽器重新導向要求。

針對這個 Proxy 伺服器,我們將使用 Firebase 雲端函式:Node.js 和 Express。

初始化 Cloud Functions

開啟新的終端機視窗,然後前往專案目錄並執行下列指令:

$ firebase init functions

Firebase 會詢問一組問題,以便初始化 Cloud 函式:

  1. 您希望以哪種語言編寫 Cloud Functions?— JavaScript
  2. 要使用 ESLint 找出可能的錯誤並強制執行樣式嗎?—
  3. 您是否想現在使用 npm 安裝依附元件?— 是

這會在專案中初始化 functions 資料夾,並安裝必要的依附元件。您會看到專案資料夾包含函式目錄,其中含有定義 Cloud 函式的 index.js 檔案、用來定義設定的 package.json,以及用來存放依附元件的 node_modules 目錄。

我們將使用兩個 npm 程式庫來建構伺服器端功能:express 和 xmlhttprequest。您必須在 package.json 檔案的依附元件清單中加入下列項目:

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

接著,從函式目錄執行 npm 安裝作業,應該會為專案安裝依附元件:

$ npm install

如果 npm 下載套件時發生問題,您可以嘗試儲存 xmlhttprequest 並使用下列指令明確表示:

$ npm install express xmlhttprequest --save

升級至 Blaze 方案

使用 firebase deploy 指令時,您必須升級至 Blaze 方案,而這項方案需要在帳戶中新增付款方式。依序前往「專案總覽」>「用量和帳單」,並確認已為專案選取 Blaze 方案。

c6a5e5a21397bef6.png

建構 Express 伺服器

Express 伺服器採用簡單的架構,可回應傳入的 GETPOST 要求。我們已建構了一個可監聽 POST 要求、將要求傳輸至酬載中指定的目的網址,並利用從移轉所接收的回應來回應。

修改 functions 目錄中的 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"
      }
    ]
  }
}

這樣一來,系統就會將開頭為 /proxy 的網址轉送至 Express 伺服器,其餘網址則會繼續轉送至 index.html

Proxy API 呼叫

伺服器準備就緒後,我們要在 scripts.js 中定義 Proxy URI,讓瀏覽器將要求傳送至這個位址:

const PROXY_URI = SERVER_URI + "/proxy";

接著,新增 proxyRequest 函式 scripts.js,其簽章與 deviceAccessRequest(...) 函式相同,用於間接的 Device Access 呼叫。

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

最後一個步驟是在 scripts.js 中的 postThermostatMode()postTemperatureSetpoint() 函式中,將 deviceAccessRequest(...) 呼叫替換為 proxyRequest(...) 函式。

執行 firebase deploy 以更新應用程式。

$ firebase deploy

這樣一來,您現在就能在 Cloud Functions 上使用 Express 執行 Node.js 代理伺服器。

提供 Cloud 函式權限

最後一個步驟是檢查 Cloud 函式的存取權限,並確保用戶端應用程式能夠呼叫這些函式。

在 Google Cloud Platform 的選單中前往「Cloud Functions」分頁,然後選取您的 Cloud 函式:

461e9bae74227fc1.png

依序點選「權限」和「新增成員」。將 allUsers 寫入新成員欄位,並選取「Cloud Functions」>「Cloud Functions 叫用者」做為角色。按一下「儲存」後,系統會顯示警告訊息:

3adb01644217578c.png

選取「允許公開存取」後,您的用戶端應用程式就能使用雲端函式。

恭喜,您已完成所有步驟。您現在可以前往網頁應用程式,並透過 Proxy 伺服器測試裝置控制項!

後續步驟

想進一步瞭解裝置存取權嗎?請參閱特徵說明文件,進一步瞭解如何控制其他 Nest 裝置,並參閱認證程序,瞭解如何逐步向全球推出產品!

歡迎利用 Device Access 網頁應用程式範例應用程式,讓自己的技能更加豐富,並部署可正常運作的網頁應用程式來控制 Nest 攝影機、門鈴和溫度控制器。