כדי לתמוך בביצוע פעולות באופן מקומי, צריך ליצור אפליקציה שתטפל בכוונות האלה של הבית החכם:
-
IDENTIFY: תמיכה בגילוי מכשירים חכמים שניתן לשלוט בהם באופן מקומי. ה-intent handler מחלץ נתונים שהמכשיר החכם מחזיר במהלך הגילוי ושולח אותם בתגובה ל-Google. -
EXECUTE: תמיכה בהרצת פקודות. -
QUERY: תמיכה בשליחת שאילתות לגבי מצב המכשיר. REACHABLE_DEVICES: (אופציונלי) תומך בגילוי של מכשירי קצה שניתן לשלוט בהם באופן מקומי, שנמצאים מאחורי מכשיר רכזת (או גשר).
האפליקציה הזו פועלת במכשירי Google Home או Google Nest של המשתמשים ומחברת את המכשיר החכם ל-Assistant. אתם יכולים ליצור את האפליקציה באמצעות TypeScript (מומלץ) או JavaScript.
מומלץ להשתמש ב-TypeScript כי אפשר להשתמש בקישורים כדי לוודא באופן סטטי שהנתונים שהאפליקציה מחזירה תואמים לסוגים שהפלטפורמה מצפה להם.
לפרטים נוספים על ה-API, קראו את מאמרי העזרה של ה-API של Local Home SDK.
קטעי הקוד הבאים מראים איך אפשר להפעיל את האפליקציה למילוי מקומי ולצרף את ה-handlers.
import App = smarthome.App; const localHomeApp: App = new App("1.0.0"); localHomeApp .onIdentify(identifyHandler) .onExecute(executeHandler) .listen() .then(() => { console.log("Ready"); });
import App = smarthome.App; const localHomeApp: App = new App("1.0.0"); localHomeApp .onIdentify(identifyHandler) .onReachableDevices(reachableDevicesHandler) .onExecute(executeHandler) .listen() .then(() => { console.log("Ready"); });
יצירת הפרויקט
כדי לפרוס את האפליקציה למילוי בקשות מקומי, צריך ליצור חבילת JavaScript לקוד ולכל יחסי התלות שלו.
משתמשים במאתחל הפרויקט של אפליקציית מילוי ההזמנות המקומית כדי לאתחל את מבנה הפרויקט המתאים עם הגדרת ה-bundler המועדפת.
תבניות לפרויקטים
כדי לבחור את הגדרת ה-bundler, מריצים את הפקודה npm init כמו בדוגמאות הבאות:
TypeScript בלי הגדרת חבילה:
npm init @google/local-home-app project-directory/ --bundler none
מבנה הפרויקט:
project-directory/ ├── node_modules/ ├── package.json ├── .gitignore ├── index.ts ├── test.ts ├── tsconfig.json ├── tslint.json └── serve.js
מחליפים את project-directory בספרייה חדשה שתכיל את פרויקט האפליקציה למילוי בקשות מקומי.
הגדרת TypeScript עם חבילת webpack:
npm init @google/local-home-app project-directory/ --bundler webpack
מבנה הפרויקט:
project-directory/ ├── node_modules/ ├── package.json ├── .gitignore ├── index.ts ├── test.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.web.js ├── webpack.config.node.js └── serve.js
מחליפים את project-directory בספרייה חדשה שתכיל את פרויקט האפליקציה למילוי בקשות מקומי.
הגדרת TypeScript עם חבילת Rollup:
npm init @google/local-home-app project-directory/ --bundler rollup
מבנה הפרויקט:
project-directory/ ├── node_modules/ ├── package.json ├── .gitignore ├── index.ts ├── test.ts ├── tsconfig.json ├── tslint.json ├── rollup.config.js └── serve.js
מחליפים את project-directory בספרייה חדשה שתכיל את הפרויקט של אפליקציית המילוי המקומית.
הגדרת חבילה של TypeScript עם Parcel:
npm init @google/local-home-app project-directory/ --bundler parcel
מבנה הפרויקט:
project-directory/ ├── node_modules/ ├── package.json ├── .gitignore ├── index.ts ├── test.ts ├── tsconfig.json ├── tslint.json └── serve.js
מחליפים את project-directory בספרייה חדשה שתכיל את הפרויקט של אפליקציית המילוי המקומית.
ביצוע משימות נפוצות ברמת הפרויקט
הפרויקט שנוצר תומך בסקריפטים הבאים של npm:
cd project-directory/ npm run build
הסקריפט הזה מקמפל את קוד המקור של TypeScript, ומאגד את האפליקציה עם יחסי התלות שלה לסביבת זמן הריצה של Chrome בספריית המשנה dist/web ולסביבת זמן הריצה של Node.js בספריית המשנה dist/node.
cd project-directory/ npm run lint npm run compile npm test
הסקריפט הזה מאמת את התחביר של קוד ה-TypeScript, מקמפל אותו בלי ליצור פלט בספריית המשנה dist/ ומריץ בדיקות אוטומטיות מ-test.ts.
cd project-directory/ npm run start
במהלך הפיתוח, הסקריפט הזה מציג את חבילות האפליקציה לסביבות זמן הריצה של Chrome ו-Node.js באופן מקומי.
הטמעת ה-handler של IDENTIFY
ה-handler של IDENTIFY יופעל כשמכשיר Google Home או מכשיר Google Nest יופעל מחדש ויזהה מכשירים מקומיים לא מאומתים (כולל מכשירי קצה שמחוברים לרכזת). פלטפורמת Local Home תסרוק מכשירים מקומיים באמצעות פרטי הגדרת הסריקה שציינתם קודם, ותפעיל את ה-handler של IDENTIFY עם תוצאות הסריקה.
השדה IdentifyRequest מפלטפורמת Local Home מכיל את נתוני הסריקה של מופע LocalIdentifiedDevice. רק מופע אחד של device מאוכלס, על סמך הגדרת הסריקה שזיהתה את המכשיר.
אם תוצאות הסריקה תואמות למכשיר, ה-handler של IDENTIFY אמור להחזיר אובייקט IdentifyResponsePayload, שכולל אובייקט device עם מטא-נתונים של בית חכם (כמו הסוגים, התכונות ומצב הדוח).
Google יוצרת שיוך למכשיר אם הערך של verificationId מהתגובה של IDENTIFY תואם לאחד מהערכים של otherDeviceIds שהוחזרו בתגובה של SYNC.
דוגמה
בקטעי הקוד הבאים אפשר לראות איך יוצרים פונקציות handler של IDENTIFY לשילובים של מכשירים עצמאיים ורכזות, בהתאמה.
const identifyHandler = (request: IntentFlow.IdentifyRequest): IntentFlow.IdentifyResponse => { // Obtain scan data from protocol defined in your scan config const device = request.inputs[0].payload.device; if (device.udpScanData === undefined) { throw Error("Missing discovery response"); } const scanData = device.udpScanData.data; // Decode scan data to obtain metadata about local device const verificationId = "local-device-id"; // Return a response const response: IntentFlow.IdentifyResponse = { intent: Intents.IDENTIFY, requestId: request.requestId, payload: { device: { id: device.id || "", verificationId, // Must match otherDeviceIds in SYNC response }, }, }; return response; };
const identifyHandler = (request: IntentFlow.IdentifyRequest): IntentFlow.IdentifyResponse => { // Obtain scan data from protocol defined in your scan config const device = request.inputs[0].payload.device; if (device.udpScanData === undefined) { throw Error("Missing discovery response"); } const scanData = device.udpScanData.data; // Decode scan data to obtain metadata about local device const proxyDeviceId = "local-hub-id"; // Return a response const response: IntentFlow.IdentifyResponse = { intent: Intents.IDENTIFY, requestId: request.requestId, payload: { device: { id: proxyDeviceId, isProxy: true, // Device can control other local devices isLocalOnly: true, // Device not present in `SYNC` response }, }, }; return response; };
זיהוי מכשירים מאחורי רכזת
אם Google תזהה מכשיר רכזת, היא תתייחס אליו כאל צינור למכשירי הקצה שמחוברים אליו, ותנסה לאמת את מכשירי הקצה האלה.
כדי לאפשר ל-Google לאשר שקיים מכשיר רכזת, פועלים לפי ההוראות הבאות ל-handler של IDENTIFY:
- אם התגובה של
SYNCמדווחת על המזהים של מכשירי הקצה המקומיים שמחוברים לרכזת, צריך להגדיר אתisProxyכ-trueב-IdentifyResponsePayload. - אם התגובה של
SYNCלא מדווחת על מכשיר הרכזת, צריך להגדיר אתisLocalOnlyכ-trueב-IdentifyResponsePayload. - השדה
device.idמכיל את מזהה המכשיר המקומי של הרכזת עצמה.
הטמעת ה-handler של REACHABLE_DEVICES (שילובים של רכזות בלבד)
Google שולחת את ה-intent REACHABLE_DEVICES כדי לאשר אילו מכשירי קצה יכולים להיות נשלטים באופן מקומי. ה-intent הזה מופעל בכל פעם ש-Google מפעילה סריקת גילוי (בערך פעם בדקה), כל עוד הרכזת מזוהה כמחוברת לאינטרנט.
מטמיעים את ה-handler של REACHABLE_DEVICES באופן דומה ל-handler של IDENTIFY, אבל ה-handler צריך לאסוף מזהי מכשירים נוספים שאפשר להגיע אליהם דרך מכשיר ה-proxy המקומי (כלומר, הרכזת). השדה device.verificationId מכיל את מזהה המכשיר המקומי של מכשיר קצה שמחובר לרכזת.
ה-ReachableDevicesRequest מפלטפורמת Local Home מכיל מופע של LocalIdentifiedDevice.
באמצעות המופע הזה, אתם יכולים לקבל את מזהה המכשיר של שרת ה-Proxy וגם נתונים מתוצאות הסריקה.
ה-handler של REACHABLE_DEVICES צריך להחזיר אובייקט ReachableDevicesPayload שכולל אובייקט devices שמכיל מערך של ערכי verificationId שמייצגים את מכשירי הקצה שהרכזת שולטת בהם. הערכים של verificationId חייבים להתאים לאחד מהערכים של otherDeviceIds מהתגובה של SYNC.
בקטע הקוד הבא אפשר לראות איך יוצרים handler של REACHABLE_DEVICES.
const reachableDevicesHandler = (request: IntentFlow.ReachableDevicesRequest): IntentFlow.ReachableDevicesResponse => { // Reference to the local proxy device const proxyDeviceId = request.inputs[0].payload.device.id; // Gather additional device ids reachable by local proxy device // ... const reachableDevices = [ // Each verificationId must match one of the otherDeviceIds // in the SYNC response { verificationId: "local-device-id-1" }, { verificationId: "local-device-id-2" }, ]; // Return a response const response: IntentFlow.ReachableDevicesResponse = { intent: Intents.REACHABLE_DEVICES, requestId: request.requestId, payload: { devices: reachableDevices, }, }; return response; };
הטמעת ה-handler של EXECUTE
ה-handler של EXECUTE באפליקציה מעבד את הפקודות של המשתמשים ומשתמש ב-Local Home SDK כדי לגשת למכשירים החכמים באמצעות פרוטוקול קיים.
פלטפורמת Local Home מעבירה את אותו מטען ייעודי (payload) של קלט לפונקציית ה-handler של EXECUTE, כמו ל-intent של EXECUTE למילוי הבקשה בענן. באופן דומה, ה-handler של EXECUTE מחזיר נתוני פלט באותו פורמט כמו עיבוד ה-intent של EXECUTE.
כדי לפשט את יצירת התגובה, אתם יכולים להשתמש במחלקה Execute.Response.Builder ש-Local Home SDK מספק.
לאפליקציה אין גישה ישירה לכתובת ה-IP של המכשיר. במקום זאת, משתמשים בממשק CommandRequest כדי ליצור פקודות שמבוססות על אחד מהפרוטוקולים הבאים: UDP, TCP או HTTP. לאחר מכן, קוראים לפונקציה deviceManager.send() כדי לשלוח את הפקודות.
כשמטרגטים פקודות למכשירים, משתמשים במזהה המכשיר (ובפרמטרים מהשדה customData, אם הם נכללים) מהתגובה של SYNC כדי לתקשר עם המכשיר.
דוגמה
בקטע הקוד הבא אפשר לראות איך יוצרים handler של EXECUTE.
const executeHandler = (request: IntentFlow.ExecuteRequest): Promise<IntentFlow.ExecuteResponse> => { // Extract command(s) and device target(s) from request const command = request.inputs[0].payload.commands[0]; const execution = command.execution[0]; const response = new Execute.Response.Builder() .setRequestId(request.requestId); const result = command.devices.map((device) => { // Target id of the device provided in the SYNC response const deviceId = device.id; // Metadata for the device provided in the SYNC response // Use customData to provide additional required execution parameters const customData: any = device.customData; // Convert execution command into payload for local device let devicePayload: string; // ... // Construct a local device command over TCP const deviceCommand = new DataFlow.TcpRequestData(); deviceCommand.requestId = request.requestId; deviceCommand.deviceId = deviceId; deviceCommand.data = devicePayload; deviceCommand.port = customData.port; deviceCommand.operation = Constants.TcpOperation.WRITE; // Send command to the local device return localHomeApp.getDeviceManager() .send(deviceCommand) .then((result) => { response.setSuccessState(result.deviceId, state); }) .catch((err: IntentFlow.HandlerError) => { err.errorCode = err.errorCode || IntentFlow.ErrorCode.INVALID_REQUEST; response.setErrorState(device.id, err.errorCode); }); }); // Respond once all commands complete return Promise.all(result) .then(() => response.build()); };
הטמעת ה-handler של QUERY
ה-handler של QUERY באפליקציה מעבד את בקשות המשתמשים ומשתמש ב-Local Home SDK כדי לדווח על המצב של המכשירים החכמים.
פלטפורמת Local Home מעבירה את אותו מטען ייעודי (payload) של בקשה לפונקציית ה-handler של QUERY, כמו ב-intent QUERY למילוי הבקשה בענן. באופן דומה, ה-handler של QUERY מחזיר נתונים באותו פורמט כמו עיבוד ה-Intent של QUERY.
שליחת פקודות למכשירים שמחוברים לרכזת
כדי לשלוט במכשירי קצה שמאחורי רכזת, יכול להיות שתצטרכו לספק מידע נוסף במטען הייעודי (payload) של הפקודה הספציפית לפרוטוקול שנשלחת לרכזת, כדי שהרכזת תוכל לזהות לאיזה מכשיר הפקודה מיועדת. במקרים מסוימים, אפשר להסיק את זה ישירות מהערך של device.id, אבל אם זה לא המצב, צריך לכלול את הנתונים הנוספים האלה כחלק מהשדה customData.
אם יצרתם את האפליקציה באמצעות TypeScript, אל תשכחו להמיר את האפליקציה ל-JavaScript. אתם יכולים להשתמש במערכת המודולים שאתם מעדיפים כדי לכתוב את הקוד. מוודאים שהיעד נתמך בדפדפן Chrome.