1. 簡介
Device Access 計畫提供 Smart Device Management API,開發人員可以運用這個 REST API 透過自己的應用程式控制 Google Nest 裝置。使用者必須同意讓第三方存取自己的 Nest 裝置。
要成功整合裝置存取權,必須完成以下三個重要步驟:
- 建立專案:在 Google Cloud Platform 建立專案,然後在「裝置存取權」控制台中申請成為開發人員。
- 帳戶連結:透過帳戶連結流程取得使用者存取權,並擷取存取碼。交換存取權杖的代碼。
- 裝置控制:透過存取權杖傳送指令,提出 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])。
第一步是在專案中啟用必要的 API 程式庫。前往 API 與服務 >程式庫,然後搜尋 Smart Device Management API。您必須啟用這個 API,才能授權專案向 Device Access API 呼叫發出要求。
在繼續建立 OAuth 憑證之前,我們必須先為專案設定 OAuth 同意畫面。前往 API 與服務 >OAuth 同意畫面。在「User Type」(使用者類型) 部分,選擇 [external] (外部)。請提供應用程式的名稱和支援電子郵件地址,以及開發人員聯絡資訊,以完成第一個畫面。當系統要求您提供「測試使用者」時,請務必在這個步驟提供已連結裝置的電子郵件地址,
設定 OAuth 同意畫面後,請前往「API 與」服務 >憑證。按一下「+建立憑證」,然後選取「OAuth 用戶端 ID」。在應用程式類型部分,選取「Web application」(網頁應用程式)。
為用戶端命名,然後按一下「建立」。我們稍後會新增授權的 JavaScript 來源和授權的重新導向 URI。完成這個程序後,您就會看到與此 OAuth 2.0 用戶端相關聯的 [Client-Id] 和 [Client-Secret]。
裝置存取權控制台
前往裝置存取權控制台。如果您從未使用過裝置存取控制台,將會收到《服務條款》協議和 $5 美元的註冊費。
建立新專案並為專案命名。在下一個視窗中,提供您在上一個步驟中從 GCP 取得的 [Client-Id]。
啟用事件並完成專案建立步驟後,系統會將你導向專案首頁。您的 [Project-Id] 會列在您為專案的名稱下方。
請記下您的 [Project-Id],因為在傳送要求給 Smart Device Management API 時,我們會使用這項資訊。
3. Firebase 設定
Firebase 可讓開發人員輕鬆快速地部署網頁應用程式。我們將使用 Firebase 開發用戶端網頁應用程式,以便整合裝置存取權。
建立 Firebase 專案
前往 Firebase 控制台。按一下「Add Project」,然後選取您在「Project Creation」步驟建立的專案。這項操作會建立並連結至 GCP 專案 [GCP-Project-Id] 的 Firebase 專案。
成功建立 Firebase 專案後,您應該會看到以下畫面:
安裝 Firebase 工具
Firebase 提供一組 CLI 工具,可用於建構及部署應用程式。如要安裝這些工具,請開啟新的終端機視窗並執行下列指令。這會在全球安裝 Firebase 工具。
$ npm i -g firebase-tools
如要確認 Firebase 工具已正確安裝,請檢查版本資訊。
$ firebase --version
您可以使用登入指令,使用自己的 Google 帳戶登入 Firebase CLI 工具。
$ firebase login
初始化託管專案
能夠登入後,下一步就是初始化網頁應用程式的託管專案。在終端機中,前往要建立專案的資料夾,然後執行下列指令:
$ firebase init hosting
Firebase 會詢問您一組問題,協助您著手建立託管專案:
- 請選取下列選項:使用現有專案
- 為這個目錄選取預設的 Firebase 專案:選擇***[GCP-Project-Id]***
- 您要使用什麼做為公開目錄?- 公開
- 要設為單頁應用程式嗎?— 是
- 要使用 GitHub 設定自動建構與部署作業嗎?— 否
專案初始化後,您可以使用以下指令將專案部署至 Firebase:
$ firebase deploy
Firebase 會掃描專案,並將必要檔案部署至雲端託管。
在瀏覽器中開啟代管網址時,您應該會看到剛剛部署的網頁:
您已瞭解使用 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 伺服器 (express) 邏輯。
部署程式碼研究室範例
將 codelab-start
中的檔案複製到專案的目錄。
$ firebase deploy
Firebase 部署完成後,您應該能看到程式碼研究室應用程式:
啟動驗證流程需要合作夥伴憑證,我們將在下一節說明。
5. 處理 OAuth
OAuth 是存取委派的網路標準,常見的用途是讓使用者在不需共用密碼的情況下,授權第三方應用程式存取自己的帳戶資訊。我們採用 OAuth 2.0,讓開發人員能夠透過裝置存取權存取使用者裝置。
指定重新導向 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」(憑證) 頁面,畫面上會列出為專案建立的所有憑證:
在「OAuth 2.0 用戶端 ID」清單下方,選取您在「建立專案」步驟時建立的用戶端 ID。將應用程式的重新導向 URI 新增至專案的授權重新導向 URI 清單。
請嘗試登入!
前往透過 Firebase 設定的代管網址,輸入合作夥伴憑證,然後按一下「登入」按鈕。用戶端 ID 和用戶端密鑰是您從 Google Cloud Platform 取得的憑證,專案 ID 是取自裝置存取控制台。
「登入」按鈕會將使用者導向貴公司的 OAuth 流程,從登入畫面開始進入 Google 帳戶。登入後,系統會要求使用者授予專案存取 Nest 裝置的權限。
由於這是模擬應用程式,因此 Google 會在發出重新導向前發出警告!
按一下「進階」,然後選取「前往 web.app (不安全)」才能完成應用程式重新導向
這項操作會在傳入的 GET 要求中提供 OAuth 代碼,而應用程式接著會交換「存取權杖」和「更新權杖」。
6. 裝置控制
Device Access 範例應用程式使用 Smart Device Management REST API 呼叫控制 Google Nest 裝置。這些呼叫涉及在 GET 或 POST 要求標頭中傳遞存取權杖,以及某些指令所需的酬載。
我們編寫了一般存取要求函式來處理這些呼叫。不過,您需要為這個函式提供正確的端點和酬載物件!
function deviceAccessRequest(method, call, localpath, payload = null) {...}
- 方法 — HTTP 要求類型 (
GET
或POST)
- 呼叫 — 代表 API 呼叫的字串,用於轉送回應 (
listDevices
、thermostatMode
、temperatureSetpoint
) - localpath — 接收要求的端點,包含專案 ID 和裝置 ID (附加於
https://smartdevicemanagement.googleapis.com/v1
之後) - 酬載 (*) — API 呼叫所需的額外資料 (例如,代表設定點溫度的數值)
我們會建構 UI 控制項範例 (列出裝置、設定模式、設定溫度),以控制 Nest Thermostat:
這些 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:
從清單中挑選裝置將會更新 scripts.js
檔案中的 deviceId
欄位。對於這兩個控制項,我們需要針對要控制的特定裝置指定 deviceId
。
溫度控制器控制
在 Smart Device Management API 中,Nest Thermostat 基本操控有兩種特徵。ThermostatMode 和 TemperatureSetpoint。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 的溫度 (以攝氏為單位)。酬載中有 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 伺服器 (選用)
恭喜!您建立了用戶端網頁應用程式,可透過瀏覽器提出 Smart Device Management API 要求。如果您想要在伺服器端執行建構作業,就很適合透過 Proxy 伺服器,從瀏覽器重新導向您的要求,讓您事半功倍。
針對這個 Proxy 伺服器,我們將使用 Firebase 雲端函式:Node.js 和 Express。
初始化 Cloud Functions
開啟新的終端機視窗,然後前往專案目錄並執行下列指令:
$ firebase init functions
Firebase 會詢問一組問題,以便初始化 Cloud 函式:
- 您希望以哪種語言編寫 Cloud Functions?— JavaScript
- 要使用 ESLint 找出可能的錯誤並強制執行樣式嗎?- 否
- 要立即使用 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 方案。
建構 Express 伺服器
Express 伺服器採用簡單的架構,可回應傳入的 GET
和 POST
要求。我們已建構了一個可監聽 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.js
的 postThermostatMode()
和 postTemperatureSetpoint()
函式中,將 deviceAccessRequest(...)
呼叫替換為 proxyRequest(...)
函式。
執行 firebase deploy
以更新應用程式。
$ firebase deploy
因此,您現在可以在 Cloud Functions 中使用 Express 執行 Node.js Proxy 伺服器。
提供 Cloud 函式權限
最後一個步驟是檢查 Cloud 函式的存取權限,並確保用戶端應用程式能夠呼叫這些函式。
在 Google Cloud Platform 的選單中前往「Cloud Functions」分頁,然後選取您的 Cloud 函式:
依序點選「權限」和「新增成員」。將 allUsers 寫入新成員欄位,然後選取「Cloud Functions」>Cloud Functions 叫用者做為角色。按一下「儲存」後,系統會顯示警告訊息:
選取「允許公開存取」後,用戶端應用程式即可使用 Cloud 函式。
恭喜!您已完成所有步驟。你現在可以前往網頁應用程式,啟用透過 Proxy 伺服器轉送的裝置控制選項!
後續步驟
想進一步掌握裝置存取權相關知識嗎?請參閱特徵說明文件,進一步瞭解如何控制其他 Nest 裝置,並參閱認證程序,瞭解如何逐步向全球推出產品!
歡迎利用 Device Access 網頁應用程式範例應用程式,讓自己的技能更加豐富,並部署可正常運作的網頁應用程式來控制 Nest 攝影機、門鈴和溫度控制器。