כדי לתמוך באפשרות של שרשור פעולות (fulfillment) מקומי, צריך ליצור אפליקציה שתטפל בכוונות הבאות של הבית החכם:
IDENTIFY
: תמיכה בזיהוי של מכשירים חכמים שניתן לשלוט בהם באופן מקומי. מערכת עיבוד הכוונה מחלצת נתונים שהמכשיר החכם מחזיר במהלך תהליך הגילוי, ושולחת אותם בתגובה ל-Google.EXECUTE
: תומכת בביצוע פקודות.QUERY
: תמיכה בשאילתות לגבי מצב המכשיר.REACHABLE_DEVICES
: (אופציונלי) תמיכה בגילוי של מכשירי קצה שניתן לשלוט בהם באופן מקומי מאחורי מכשיר רכזת (או גשר).
האפליקציה הזו פועלת במכשירי Google Home או Google Nest של המשתמש ומחברת את המכשיר החכם ל-Assistant. אפשר ליצור את האפליקציה באמצעות TypeScript (מועדף) או JavaScript.
מומלץ להשתמש ב-TypeScript כי אפשר להשתמש בקישורים כדי לוודא באופן סטטי שהנתונים שהאפליקציה מחזירה תואמים לסוגי הנתונים שהפלטפורמה מצפה להם.
פרטים נוספים על ה-API זמינים במאמר הפניית API של Local Home SDK.
בקטעים הבאים מוסבר איך אפשר לאתחל את אפליקציית המילוי המקומית ולצרף את הטיפולים.
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 לקוד ולכל יחסי התלות שלו.
משתמשים באפליקציית הביצוע המקומית project initializer כדי להפעיל את מבנה הפרויקט המתאים עם הגדרת ה-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 עם הגדרת Bundler של 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 עם הגדרת ה-bundler של 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
במהלך הפיתוח, הסקריפט הזה מציג את חבילות ה-App Bundle באופן מקומי לסביבות זמן הריצה של Chrome ו-Node.js.
הטמעת ה-handler של IDENTIFY
הטיפול IDENTIFY
יופעל כשמכשיר Google Home או Google Nest יופעל מחדש ויזהה מכשירים מקומיים לא מאומתים (כולל מכשירי קצה שמחוברים לרכזת). פלטפורמת Local Home תסרוק אחר מכשירים מקומיים באמצעות פרטי תצורת הסריקה שציינתם קודם, ותפעיל את הטיפולר של IDENTIFY
עם תוצאות הסריקה.
השדה IdentifyRequest
מפלטפורמת Home המקומית מכיל את נתוני הסריקה של מכונה מסוג LocalIdentifiedDevice
. רק מכונה אחת של device
מאוכלסת, על סמך הגדרת הסריקה שגילתה את המכשיר.
אם תוצאות הסריקה תואמות למכשיר שלכם, הטיפול של IDENTIFY
צריך להחזיר אובייקט IdentifyResponsePayload
שכולל אובייקט device
עם מטא-נתונים של בית חכם (כמו הסוגים, המאפיינים וסטטוס הדוח).
Google יוצרת שיוך של המכשיר אם הערך verificationId
בתגובה IDENTIFY
תואם לאחד מהערכים otherDeviceIds
שמוחזרים בתגובה SYNC
.
דוגמה
בקטעי הקוד הבאים מוסבר איך ליצור מנהלים של 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 מזהה מכשיר רכזת, היא תתייחס לרכזת כצינור הזרימה של מכשירי הקצה המחוברים של ה-Hub, ותנסה לאמת את מכשירי הקצה האלה.
כדי לאפשר ל-Google לאמת שמכשיר רכזת נמצא בסביבה, פועלים לפי ההוראות הבאות ב-handler של IDENTIFY
:
- אם התשובה
SYNC
מדווחת על המזהים של מכשירי קצה מקומיים שמחוברים לרכז, מגדירים אתisProxy
כ-true
בקטעIdentifyResponsePayload
. - אם התגובה של
SYNC
לא כוללת את מכשיר ה-hub, צריך להגדיר אתisLocalOnly
כ-true
בקובץIdentifyResponsePayload
. - השדה
device.id
מכיל את מזהה המכשיר המקומי של מכשיר הצומת עצמו.
הטמעת הטיפול באירוע REACHABLE_DEVICES (בשילובים עם מרכזי נתונים בלבד)
Google שולחת את ה-Intent REACHABLE_DEVICES
כדי לאשר באילו מכשירי קצה אפשר לשלוט באופן מקומי. הכוונה הזו מופעלת בכל פעם ש-Google מפעילה סריקת גילוי (בערך פעם בדקה), כל עוד הרכז מזוהה כמחובר לאינטרנט.
מטמיעים את הטיפול ב-REACHABLE_DEVICES
באופן דומה לטיפול ב-IDENTIFY
, אלא שהטיפול צריך לאסוף מזהי מכשירים נוספים שניתנים לגישה על ידי שרת ה-proxy המקומי (כלומר, ה-hub). השדה device.verificationId
מכיל את מזהה המכשיר המקומי של מכשיר קצה שמחובר למרכז.
הערך ReachableDevicesRequest
מפלטפורמת Local Home מכיל מופע של LocalIdentifiedDevice
.
דרך המכונה הזו אפשר לקבל את מזהה המכשיר של שרת ה-proxy וגם נתונים מתוצאות הסריקה.
הטיפול (handler) של REACHABLE_DEVICES
צריך להחזיר אובייקט ReachableDevicesPayload
שכולל אובייקט devices
שמכיל מערך של ערכים מסוג verificationId
שמייצגים את מכשירי הקצה שהרכז שולט בהם. הערכים של verificationId
צריכים להתאים לאחד מה-otherDeviceIds
מהתשובה SYNC
.
קטע הקוד הבא מראה איך אפשר ליצור את הטיפול ב-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 מעבירה את אותו עומס נתונים להזנה לפונקציית הטיפול EXECUTE
, כמו לכוונה 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()); };
הטמעת הטיפול ב-QUERY
ה-handler של QUERY
באפליקציה מעבד את בקשות המשתמשים ומשתמש ב-Local Home SDK כדי לדווח על המצב של המכשירים החכמים.
פלטפורמת Home המקומית מעבירה את אותו עומס בקשה לפונקציית הטיפול (handler) 'QUERY', כמו לכוונה QUERY
, למילוי ההזמנה בענן. באופן דומה, הטיפול של ה-handler ב-QUERY
מחזיר נתונים באותו פורמט כמו עיבוד הכוונה QUERY
.
שליחת פקודות למכשירים שמחוברים לרכז
כדי לשלוט במכשירי קצה שמחוברים לרכזת, יכול להיות שתצטרכו לספק מידע נוסף במטען הייעודי של הפקודה שנשלחת לרכזת, כדי שהרכזת תזהה לאיזה מכשיר הפקודה מיועדת. במקרים מסוימים אפשר להסיק זאת ישירות מהערך של device.id
, אבל אם לא, צריך לכלול את הנתונים הנוספים האלה כחלק מהשדה customData
.
אם יצרתם את האפליקציה באמצעות TypeScript, חשוב לזכור לקמפל את האפליקציה ל-JavaScript. אתם יכולים להשתמש במערכת המודולים שבחרתם כדי לכתוב את הקוד. מוודאים שדפדפן Chrome תומך ביעד.