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

1. 簡介

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

52f77aa38cda13a6.png

成功整合裝置存取權的步驟如下:

  1. 建立專案:在 Google Cloud Platform 中建立專案,並在裝置存取權控制台中申請成為開發人員。
  2. 帳戶連結:透過帳戶連結流程取得使用者,並擷取存取碼。交換存取權杖的代碼。
  3. Device Control:透過存取權杖傳送指令,發出 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。(在本程式碼研究室中,我們會將此 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」,然後選取您在「Project Creation」步驟中建立的專案。這個動作會建立 Firebase 專案,並連結至您的 GCP 專案 [GCP-Project-Id]

成功建立 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 資料夾中的檔案,但如果您隨時都能使用,也可以參考程式碼研究室完成的版本。

程式碼研究室範例檔案

程式碼研究室-start 資料夾的檔案結構如下:

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

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

部署程式碼研究室範例

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

$ firebase deploy

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

e84c1049eb4cca92.png

如要啟動驗證程序,您必須提供合作夥伴憑證,我們會在下一節說明。

5. 處理 OAuth

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 的「憑證」頁面,系統會列出您為專案建立的所有憑證:

1a07b624b5e548da.png

在「OAuth 2.0 Client Ids」(OAuth 2.0 用戶端 ID) 清單中,選取您在「Project Creation」步驟中建立的用戶端 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))
  • 呼叫 — 代表 API 呼叫的字串,用於轉送回應 (listDevicesthermostatModetemperatureSetpoint)
  • localpath:發出要求的目標端點,包含專案 ID 和裝置 ID (附加在 https://smartdevicemanagement.googleapis.com/v1 後方)
  • 酬載 (*) — API 呼叫所需的其他資料 (例如,代表某設定點溫度的數值)

我們會打造控制 Nest Thermostat 的範例 UI 控制項 (清單裝置、設定模式、設定溫度):

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

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

b64a198673ed289f.png

從清單中選擇裝置後,系統將更新 scripts.js 檔案中的「deviceId」欄位。針對接下來的兩個控制項,我們需要為要控制的特定裝置指定 deviceId

控制溫度控制器

Smart Device Management API 中有兩組 Nest Thermostat 的基本控制功能。ThermostatModeTemperatureSetpoint。ThermostatMode 會將 Nest Thermostat 的模式設為下列任一模式:{關閉、熱能、冷、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 Server (選用)

恭喜!您建構了一個用戶端網頁應用程式,可透過瀏覽器提出 Smart Device Management API 要求。如果你想在伺服器端進行建構,我們也想向你介紹能從瀏覽器重新導向要求的 Proxy 伺服器,幫助你快速上手。

針對這個 Proxy 伺服器,我們會使用 Firebase 的 Cloud 函式、Node.js 和 Express。

初始化 Cloud Functions

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

$ firebase init functions

Firebase 會詢問您一組問題,以便初始化 Cloud Functions:

  1. 您想使用什麼語言編寫 Cloud Functions?- 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 安裝作業,應該會為專案安裝依附元件:

$ npm install

如果 npm 在下載套件時遇到問題,您可以嘗試儲存 xmlhttprequest 並透過下列指令明確表達:

$ npm install express xmlhttprequest --save

升級至 Blaze 方案

你必須升級至 Blaze 方案,才能使用 firebase deploy 指令,必須為帳戶新增付款方式。依序前往「專案總覽 >「用量與計費」,並確實為專案選取 Blaze 方案。

c6a5e5a21397bef6.png

建構 Express 伺服器

Express 伺服器採用簡單的架構來回應傳入的 GETPOST 要求。我們已建立 Ingress 來監聽 POST 要求,將其傳輸到酬載中指定的目的地網址,然後使用移轉所接收的回應來回應。

修改函式目錄中的 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(...) 函式相同的簽章,可用於間接裝置存取權呼叫。

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.jspostThermostatMode()postTemperatureSetpoint() 函式中,將 deviceAccessRequest(...) 呼叫替換為 proxyRequest(...) 函式。

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

$ firebase deploy

完成這些步驟後,您現在就已在 Cloud Functions 建立採用 Express 的 Node.js Proxy 伺服器。

提供 Cloud 函式權限

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

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

461e9bae74227fc1.png

依序按一下「Permissions」(權限) 和「Add Member」(新增成員)。將 allUsers 寫入新成員欄位,然後依序選取「Cloud Functions」>「Cloud Functions 叫用者」角色。按一下 [儲存] 隨即會顯示警告訊息:

3adb01644217578c.png

選取「允許公開存取」即可讓用戶端應用程式使用您的 Cloud 函式。

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

後續步驟

想瞭解如何擴展裝置存取權的專業知識嗎?如要進一步瞭解如何控制其他 Nest 裝置,請參閱特徵說明文件;如要瞭解如何將產品上市,請參閱認證流程

運用裝置存取網頁應用程式範例應用程式,進一步提陞技能。你可以在程式碼研究室中運用此應用程式範例,並部署可正常運作的網頁應用程式,藉此控制 Nest 攝影機、門鈴和溫度控制器。