Sau khi tạo xong Google project và cài đặt Firebase CLI tools để tương tác với firebase ở phần 2, phần này mình sẽ giới thiệu đến các bạn những thành phần cơ bản nhất của mô hình Smart home Actions đó là Home Graph và Device Types/Traits. Sau đó chúng ta sẽ tìm hiểu cách viết một chương trình fulfillment bằng ngôn ngữ Node.js để xử lý các intent từ Google Assistant và tương tác với Firebase Realtime Database nhé.
Phần 3: Home Graph, Device Types/Traits và xây dựng Fulfillment
1. Cơ sở dữ liệu Home Graph
Với mô hình Smart home Actions, khi người dùng sử dụng ứng dụng Home trên điện thoại hoặc sử dụng giọng nói tương tác với trợ lý ảo Google để thực hiện một yêu cầu nào đó, chẳng hạn điều khiển hay truy vấn trạng thái của một thiết bị trong hệ thống. Google Assistant phải tiếp nhận và hiểu yêu cầu nhằm đưa ra hành động phù hợp tiếp theo, để làm được điều đó Google Assistant cần tham chiếu một cơ sở dữ liệu đặc biệt có tên là Home Graph.
Home Graph là một cơ sở dữ liệu có chứa các trạng thái và ngữ cảnh của ngôi nhà cùng với các thiết bị bên trong ngôi nhà, nó sẽ cung cấp cho Google Assistant thông tin về toàn bộ kiến trúc của hệ thống, như có bao nhiêu phòng trong nhà, đó là những phòng nào, các thiết bị cụ thể trong mỗi phòng và trạng thái hiện tại của mỗi thiết bị,…
Nhờ cơ sở dữ liệu đặc biệt này mà người dùng mới có thể có được giao diện trực quan từ ứng dụng Home và tương tác với trợ lý ảo Google Assistant một cách tự nhiên nhất. Ví dụ người dùng có thể điều khiển nhiều thiết bị cùng lúc, hoặc điều khiển một thiết bị trong một phòng cụ thể nào đó Google Assistant đều có thể hiểu được nhờ dữ liệu được tổ chức phân cấp và dán nhãn cụ thể trong Home Graph.
2. Device Types và Device Traits:
Khi nói đến nhà thông minh, thành phần cơ bản nhất và rất quan trọng mà chúng ta phải quan tâm đó là “thiết bị”, một mô hình hoặc nền tảng xây dựng nhà thông minh cần phải hỗ trợ tích hợp nhiều loại thiết bị khác nhau như đèn, quạt, máy hút bụi, máy giặc, điều hòa, máy sưởi,… và với mỗi loại thiết bị thì sẽ có các đặc điểm, cách thức điều khiển khác nhau.
Khi người dùng yêu cầu: “Hey Google, change the bedroom light to 50% brightness!” hoặc “Hey Google, pause the vacuum!“, Google sẽ trích xuất các thông tin cần thiết từ yêu cầu, trong đó có 2 loại dữ liệu liên quan đến thiết bị là: “loại thiết bị” (light và vacuum) và “đặc tính của thiết bị” (brightness và pause) để gửi đến server (còn gọi là fulfillment), từ đó server đã lập trình sẵn sẽ thực thi yêu cầu và gửi phản hồi về lại Google. Nếu như các bạn từng theo dõi chuỗi bài viết hướng dẫn xây dựng ứng dụng tích hợp Google Asisstant sử dụng mô hình Conversational Actions của mình trước đây các bạn sẽ thấy một điều là các loại thiết bị cụ thể, các trạng thái thiết bị, các câu thoại tương ứng để tương tác thiết bị trong hệ thống thì chúng ta cần phải tự khai báo thủ công toàn bộ trên Dialogflow. Việc phải thủ công toàn bộ các bước như vậy đôi lúc sẽ khiến nhà phát triển tốn khá nhiều thời gian, thật may mắn là với mô hình Smart home Actions này thì Google đã xây dựng sẵn cho chúng ta ở các bước này rồi, mô hình định nghĩa 2 loại thông số liên quan đến thiết bị để chúng ta có thể lựa chọn sử dụng là:
- Device Types: đại diện cho các loại thiết bị mà mô hình hỗ trợ, hiện hỗ trợ hơn 20 loại thiết bị khác nhau như: light, fan, door, vacuum, washer, camera…
- Device Traits: đại diện cho đặc điểm hay trạng thái của từng loại thiết bị như: OnOff, Brightness, FanSpeed, StartStop,…
Với mỗi Types có thể có một hoặc nhiều Trais, ví dụ với loại thiết bị bóng đèn có type là Light có thể có các traits: OnOff (bật tắt), Brightness (điều chỉnh độ sáng), và ColorSetting (điều chỉnh màu sắc). Và các thông số này sẽ được gửi trong các gói intent request và response dưới định dạng chuỗi json kiểu như thế này:
Vì vậy, công việc của chúng ta khi xây dựng fulfillment sẽ chỉ là nhận các gói request từ Google, trích xuất chuỗi json để thực thi và gửi phải hồi lại theo format đã được định nghĩa sẵn, trong bài này mình sẽ sử dụng ngôn ngữ Node.js để lập trình fulfillment và xử lý các gói tin.
3. Lập trình fulfillment:
Trong chuỗi bài viết này, mình sẽ hướng dẫn các bạn xây dựng ứng dụng điều khiển LED trên NodeMCU bằng trợ lý ảo Google để mô phỏng thiết bị đèn. Như đã phân tích ở phần 1 fulfillment của chúng ta sẽ bao gồm 2 server là auth/token server để xử lý Account linking (quá trình xác thực ban đầu giữa Home app và fulfillment) và smarthome server để xử lý các intent SYNC, EXEC và QUERY.
Chương trình mẫu nằm trong thư mục Fulfillment các bạn có thể tải ở đây, sau khi tải về các bạn copy và paste vào file index.js trong thư mục functions để bước tiếp theo chúng ta sẽ deploy lên Firebase Functions. Trước khi deploy, mình sẽ giải thích tổng quan các chức năng có trong chương trình:
Import module:
1 2 3 4 |
const functions = require('firebase-functions'); const {smarthome} = require('actions-on-google'); const util = require('util'); const admin = require('firebase-admin'); |
Các dòng đầu tiên này có chức năng import các module cần thiết và gán vào các biến để phục vụ các chức năng chúng ta sẽ viết bên dưới, giống như include thư viện trong các ngôn ngữ C, C++ quen thuộc. Trong đó:
- functions chứa module hỗ trợ việc triển khai HTTP endpoint lên firebase function.
- {smarthome} chứa module hỗ trợ việc xử lý các intent SYNC, EXEC và QUERY.
- admin chứa module hỗ trợ việc tương tác với Firebase Realtime Database để lưu trữ và truy vấn dữ liệu của thiết bị.
Xây dựng fake auth/token server:
Auth/token server là một endpoint rất quan trọng trong các ứng dụng thực tế, đây là server để xử lý việc xác thực theo cơ chế OAuth 2.0 giữa người dùng với server của hãng thông qua tài khoản đã đăng ký và quản lý thời gian các phiên sử dụng thông qua các token. Chúng ta có thể xây dựng các hàm yêu cầu người dùng xác thực bằng Gmail hoặc Facebook trước khi sử dụng, tuy nhiên trong khuôn khổ bài viết này chúng ta sẽ cho phép xác thực bằng các giá trị mặc định bằng cách triển khai hai hàm fakeauth và faketoken:
1 2 3 |
exports.fakeauth = functions.https.onRequest((request, response) => { ... } |
1 2 3 |
và exports.faketoken = functions.https.onRequest((request, response) => { ... } |
Xây dựng smarthome server để xử lý các intent:
Đầu tiên tạo đối tượng app:
1 2 3 |
const app = smarthome({ debug: true, }); |
Sau đó ta xây dựng các hàm để xử lý intent từ đối tượng này: app.onSync, app.onQuery và app.onExecute.
Hàm onSync xử lý các yêu cầu đồng bộ danh sách thiết bị của Google, hàm sẽ được gọi trong trường hợp người dùng liên kết ban đầu hoặc kết nối lại thông qua ứng dụng Home, tức là khi nhận được intent action.devices.SYNC. Gói tin phản hồi cần chứa các thông tin : tất cả thiết bị hiện có (type), đặc điểm thiết bị (trait) và các thuộc tính khác của các thiết bị. Dưới đây là chương trình mẫu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
app.onSync((body) => { return { requestId: body.requestId, payload: { agentUserId: '123', devices: [{ id: 'smartlight', //Loại thiết bị: LIGHT type: 'action.devices.types.LIGHT', //Các đặc điểm của thiết bị: OnOff, Brightness và ColorSetting traits: [ 'action.devices.traits.OnOff', 'action.devices.traits.Brightness', 'action.devices.traits.ColorSetting' ], name: { defaultNames: ['TAPIT Smart Lighting'], //Tên của thiết bị, có thể dùng để gọi khi tương tác với Google Assistant name: 'smart light', //Có thể thêm tên phụ của thiết bị nicknames: ['tapit light'] }, ....... }] } }; }); |
Hàm onQuery để xử lý các yêu cầu truy vấn trạng thái hiện tại của thiết bị, hàm được gọi khi nhận được intent action.devices.QUERY, ví dụ khi người dùng hỏi về trạng thái on/off, độ sáng hiện tại của đèn,… Dữ liệu trả về sẽ là trạng thái của các thiết bị phân biệt nhau bởi id của từng thiết bị, đây đoạn là chương trình mẫu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
app.onQuery((body, headers) => { // TODO Get device state return { requestId: body.requestId, payload: { devices: { 123: { on: true, online: true }, 456: { on: true, online: true, brightness: 80, color: { name: "cerulean", spectrumRGB: 31655 } } } } }; }); |
Trong project chúng ta xây dựng, dữ liệu trạng thái thiết bị sẽ được truy xuất từ Firebase Realtime Database. Vì vậy chúng ta cần viết thêm hàm queryFirebase và queryDevice để truy vấn trạng thái:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const queryFirebase = (deviceId) => firebaseRef.child(deviceId).once('value') .then((snapshot) => { const snapshotVal = snapshot.val(); return { on: snapshotVal.OnOff.on, brightness: snapshotVal.Brightness, color: snapshotVal.Color.spectrumRGB, }; }); const queryDevice = (deviceId) => queryFirebase(deviceId).then((data) => ({ online: true, on: data.on, brightness: data.brightness, spectrumRGB: data.color, })); |
sau đó dữ liệu nhận về sẽ được truyền vào payload phản hồi về cho Google Assistant:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
for (const input of body.inputs) { for (const device of input.payload.devices) { const deviceId = device.id; queryPromises.push(queryDevice(deviceId) .then((data) => { // Add response to device payload payload.devices[deviceId] = data; } )); } } // Wait for all promises to resolve return Promise.all(queryPromises).then((values) => ({ requestId: requestId, payload: payload, }) ); |
Hàm onExcute dùng để xử lý các yêu cầu thực thi hành động đối với thiết bị, và hàm sẽ được gọi khi nhận được intent action.devices.EXECUTE, ví dụ khi người dùng yêu cầu điều khiển bật tắt đèn, thay đổi màu sắc, độ sáng,… Dữ liệu trả về sẽ là trạng thái thiết bị sau khi cập nhật và trạng thái của quá trình thực thi như: SUCCESS, ERROR. Đoạn chương trình mẫu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
app.onExecute((body, headers) => { // TODO Send command to device return { requestId: body.requestId, payload: { commands: [{ ids: ["123"], status: "SUCCESS", states: { on: true, online: true } }, { ids: ["456"], status: "ERROR", errorCode: "deviceTurnedOff" }] } }; }); |
Trong project của chúng ta, sau khi thực thi trạng thái của thiết bị sẽ được cập nhật lền Firebase Realtime Database nên sẽ được viết như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
for (const input of body.inputs) { for (const command of input.payload.commands) { for (const device of command.devices) { const deviceId = device.id; payload.commands[0].ids.push(deviceId); for (const execution of command.execution) { const execCommand = execution.command; const {params} = execution; switch (execCommand) { case 'action.devices.commands.OnOff': firebaseRef.child(deviceId).child('OnOff').update({ on: params.on, }); payload.commands[0].states.on = params.on; break; case 'action.devices.commands.BrightnessAbsolute': firebaseRef.child(deviceId).update({ Brightness: params.brightness, }); payload.commands[0].states.brightness = params.brightness; break; case 'action.devices.commands.ColorAbsolute': firebaseRef.child(deviceId).child('Color').update({ name: params.color.name, spectrumRGB: params.color.spectrumRGB, }); // payload.commands[0].states['color']['spectrumRgb'] = params.color.spectrumRGB; break; } } } } } return { requestId: requestId, payload: payload, }; |
Cuối cùng tất cả sẽ được exports thành hàm smarthome:
1 |
exports.smarthome = functions.https.onRequest(app); |
Các thuộc tính cụ thể và các viết các intent các bạn có thể tham khảo thêm ở đây
Bây giờ chúng ta sẽ triển khai fulfillment lên Cloud Functions. Từ thư mục chứa project ở cửa sổ cmd, các bạn thực hiện như sau:
- Di chuyển vào thư mục functons:
1cd functions - Triển khai project:
1firebase deploy
Sau khi deploy xong, để kiểm tra truy cập vào vào đường dẫn ở dòng Project Console sẽ dẫn đến cửa sổ Firebase Console. Chọn mục Functions ở thanh menu sẽ thấy 3 hàm chúng ta vừa deploy xong đang lắng nghe các request.
Như vậy mình đã hướng dẫn các bạn cách viết chương trình fulfillment để xử lý các intent và triển khai các function lên Firebase Cloud Functions. Trong bài viết tiếp theo chúng ta sẽ thực hiện liên kết fulfillment với actions để có thể điều khiển bằng trợ lý ảo Google nhé. Chúc các bạn thành công!
Nhóm TAPIT IoTs
Xem tiếp Phần 4: Liên kết Actions với Fulfillment và lập trình ESP8266
Xem thêm: Video youtube hướng dẫn thực hiện ứng dụng
Xem thêm: Tổng hợp hướng dẫn Internet of Things với NodeMCU ESP8266 và ESP32