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

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

  • IDENTIFY: תמיכה בזיהוי של מכשירים חכמים שניתן לשלוט בהם באופן מקומי. הכלי ל-Intent מחלץ את הנתונים שהמכשיר החכם מחזיר במהלך הגילוי ושולח אותם בתגובה ל-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");
  });
Hub
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 בספרייה חדשה שתכלול את הפרויקט של אפליקציית המילוי המקומי.

Webpack

TypeScript עם הגדרת webpack bundler:

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

Parcel

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

ה-handler של IDENTIFY יופעל כשמכשיר Google Home או Google Nest יופעל מחדש, ויופיעו מכשירים מקומיים לא מאומתים (כולל מכשירי קצה שמחוברים לרכזת). פלטפורמת Local Home תסרוק אחר מכשירים מקומיים באמצעות פרטי תצורת הסריקה שציינתם קודם, ותפעיל את הטיפולר של IDENTIFY עם תוצאות הסריקה.

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

אם תוצאות הסריקה תואמות למכשיר שלכם, הטיפול של 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;
  };
Hub
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 מכיל את מזהה המכשיר המקומי של מכשיר הצומת עצמו.

הטמעה של ה-handler של REACHABLE_מכשירים (שילובים ב-Hub בלבד)

Google שולחת את הכוונה 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

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

דוגמה

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

פלטפורמת Home המקומית מעבירה את אותו עומס בקשה לפונקציית הטיפול (handler) 'QUERY', כמו לכוונה QUERY, למילוי ההזמנה בענן. באופן דומה, הטיפול של ה-handler ב-QUERY מחזיר נתונים באותו פורמט כמו עיבוד הכוונה QUERY.

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

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

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