הטמעה של האפליקציה המקומית למילוי הזמנות

כדי לתמוך במימוש הזמנות בחנויות מקומיות, צריך ליצור אפליקציה שתטפל בכוונות האלה של בית חכם:

  • 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 ללא הגדרת bundler:

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 בספרייה חדשה שתכיל את פרויקט האפליקציה המקומית לביצוע הזמנות.

Webpack

‫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 תסרוק את המכשירים המקומיים באמצעות פרטי ההגדרה של הסריקה שציינתם קודם, ותפעיל את הפונקציה IDENTIFY handler עם תוצאות הסריקה.

IdentifyRequest מפלטפורמת Local Home מכיל את נתוני הסריקה של מופע LocalIdentifiedDevice. רק מופע אחד של device מאוכלס, על סמך הגדרת הסריקה שגילתה את המכשיר.

אם תוצאות הסריקה תואמות למכשיר, רכיב ה-IDENTIFY handler צריך להחזיר אובייקט 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 לוודא שקיים מכשיר רכזת, פועלים לפי ההוראות הבאות עבור IDENTIFYה-handler:

  • אם בתגובה של SYNC מופיעים מזהים של מכשירי קצה מקומיים שמחוברים ל-hub, צריך להגדיר את isProxy כ-trueב-IdentifyResponsePayload.
  • אם בתגובה של SYNC לא מופיע המכשיר שלכם, צריך להגדיר את isLocalOnly כ-true ב-IdentifyResponsePayload.
  • השדה device.id מכיל את מזהה המכשיר המקומי של מכשיר ה-Hub עצמו.

הטמעה של ה-handler‏ REACHABLE_DEVICES (רק בשילובים עם רכזות)

‫Google שולחת את REACHABLE_DEVICES כדי לאשר אילו מכשירי קצה אפשר לשלוט בהם באופן מקומי. הכוונה הזו מופעלת בכל פעם ש-Google מריצה סריקה לגילוי (פעם בדקה בערך), כל עוד המרכז מזוהה כמחובר לאינטרנט.

מטמיעים את ה-handler‏ REACHABLE_DEVICES באופן דומה ל-handler‏ IDENTIFY, אבל ה-handler צריך לאסוף מזהי מכשירים נוספים שאפשר להגיע אליהם באמצעות ה-proxy המקומי (כלומר, המרכז). בשדה device.verificationId מופיע מזהה המכשיר המקומי של מכשיר קצה שמחובר ל-Hub.

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 למילוי ההזמנות בענן. באופן דומה, הפונקציה לטיפול בבקשות EXECUTE מחזירה נתוני פלט באותו פורמט כמו נתוני הפלט שמתקבלים מעיבוד כוונת המשתמש EXECUTE. כדי לפשט את יצירת התגובה, אפשר להשתמש במחלקה Execute.Response.Builder ש-Local Home SDK מספק.

לאפליקציה שלכם אין גישה ישירה לכתובת ה-IP של המכשיר. במקום זאת, משתמשים בממשק CommandRequest כדי ליצור פקודות שמבוססות על אחד מהפרוטוקולים הבאים: UDP,‏ TCP או HTTP. לאחר מכן, קוראים לפונקציה deviceManager.send() כדי לשלוח את הפקודות.

כשמפנים פקודות למכשירים, משתמשים במזהה המכשיר (ובפרמטרים מהשדה customData, אם הם כלולים) מהתשובה SYNC כדי לתקשר עם המכשיר.

דוגמה

בקטע הקוד הבא אפשר לראות איך יוצרים את רכיב ה-handler של EXECUTE.

Standalone/Hub
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) של בקשה לפונקציית הטיפול QUERY כמו ל-intent‏ QUERY למילוי ההזמנה בענן. באופן דומה, הפונקציה QUERY handler מחזירה נתונים באותו פורמט כמו העיבוד של כוונת המשתמש QUERY.

שליחת פקודות למכשירים שמוגדרים מאחורי רכזת

כדי לשלוט במכשירי קצה שנמצאים מאחורי רכזת, יכול להיות שתצטרכו לספק מידע נוסף במטען הייעודי (payload) של הפקודה הספציפית לפרוטוקול שנשלחת לרכזת, כדי שהרכזת תוכל לזהות לאיזה מכשיר מיועדת הפקודה. במקרים מסוימים, אפשר להסיק את הנתונים האלה ישירות מהערך של device.id, אבל אם זה לא המצב, צריך לכלול את הנתונים הנוספים האלה כחלק מהשדה customData.

אם יצרתם את האפליקציה באמצעות TypeScript, חשוב לזכור לקמפל את האפליקציה ל-JavaScript. אתם יכולים להשתמש במערכת המודולים שתבחרו כדי לכתוב את הקוד. מוודאים שהיעד נתמך בדפדפן Chrome.