پیاده‌سازی برنامه اجرای محلی

برای پشتیبانی از اجرای محلی، باید برنامه‌ای بسازید که این اهداف خانه هوشمند را مدیریت کند:

  • IDENTIFY: از شناسایی دستگاه‌های هوشمند قابل‌کنترل به‌صورت محلی پشتیبانی می‌کند. گرداننده هدف داده‌هایی را که دستگاه هوشمندتان درطول کاوش برمی‌گرداند استخراج می‌کند و آن را در پاسخ به Google ارسال می‌کند.
  • ‫EXECUTE: از اجرای فرمان‌ها پشتیبانی می‌کند.
  • ‫QUERY: از پرسمان وضعیت دستگاه پشتیبانی می‌کند.
  • REACHABLE_DEVICES: (اختیاری) از کاوش دستگاه‌های نهایی با قابلیت کنترل محلی که پشت دستگاه مرکز کنترل (یا پل) قرار دارند پشتیبانی می‌کند.

این برنامه در دستگاه‌های Google Home یا Google Nest کاربر اجرا می‌شود و دستگاه هوشمند شما را به «دستیار» متصل می‌کند. می‌توانید برنامه را بااستفاده از TypeScript (ترجیحاً) یا جاوا اسکریپت ایجاد کنید.

TypeScript توصیه می‌شود زیرا می‌توانید از اتصال‌ها استفاده کنید تا به‌طور ایستا مطمئن شوید داده‌هایی که برنامه‌تان برمی‌گرداند با انواع موردانتظار پلاتفرم مطابقت دارد.

برای جزئیات بیشتر درباره میانای برنامه‌سازی کاربردی، مرجع میانای برنامه‌سازی کاربردی کیت توسعه نرم‌افزار Local Home را ببینید.

قطعه‌های زیر نشان می‌دهند که چگونه می‌توانید برنامه اجرای محلی را مقداردهی اولیه کنید و گرداننده‌های خود را پیوست کنید.

مستقل
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");
  });

پروژه‌تان را ایجاد کنید

برای استقرار برنامه اجرای محلی، باید دسته جاوا اسکریپت برای کدتان و همه وابستگی‌های آن بسازید.

از راه‌انداز پروژه برنامه اجرای محلی برای راه‌اندازی ساختار پروژه مناسب با پیکربندی بسته‌بندی ترجیحی خود استفاده کنید.

الگوهای پروژه

برای انتخاب پیکربندی بسته‌بندی‌کننده، فرمان 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

پیکربندی تایپ‌اسکریپت با بسته‌بندی‌کننده 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 به‌صورت محلی ارائه می‌دهد.

پیاده‌سازی گرداننده IDENTIFY

وقتی دستگاه Google Home یا Google Nest بازراه‌اندازی شود و دستگاه‌های محلی تأییدنشده (ازجمله دستگاه‌های نهایی متصل به هاب) را ببیند، گرداننده IDENTIFY راه‌اندازی می‌شود. پلاتفرم Local Home بااستفاده از اطلاعات پیکربندی اسکن که قبلاً مشخص کرده‌اید، دستگاه‌های محلی را اسکن می‌کند و با نتایج اسکن، گرداننده IDENTIFY شما را فراخوانی می‌کند.

IdentifyRequest از پلاتفرم Local Home حاوی داده‌های اسکن نمونه LocalIdentifiedDevice است. فقط یک نمونه device براساس پیکربندی اسکن که دستگاه را شناسایی کرده است، پر می‌شود.

اگر نتایج اسکن با دستگاهتان مطابقت داشته باشد، گرداننده IDENTIFY شما باید شیء IdentifyResponsePayload را برگرداند که شامل شیء device با فراداده خانه هوشمند (مثل نوع، ویژگی، و وضعیت گزارش) است.

اگر verificationId از پاسخ IDENTIFY با یکی از مقادیر otherDeviceIds برگردانده‌شده توسط پاسخ SYNC مطابقت داشته باشد، Google ارتباط دستگاه را برقرار می‌کند.

مثال

قطعه‌های زیر نشان می‌دهند که چگونه می‌توانید به‌ترتیب گرداننده‌های 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 دنبال کنید:

  • اگر پاسخ SYNC شما شناسه‌های دستگاه‌های نهایی محلی متصل به هاب را گزارش می‌کند، isProxy را به‌عنوان true در IdentifyResponsePayload تنظیم کنید.
  • اگر پاسخ SYNC شما دستگاه هاب را گزارش نمی‌کند، isLocalOnly را به‌عنوان true در IdentifyResponsePayload تنظیم کنید.
  • فیلد device.id حاوی شناسه دستگاه محلی برای خود دستگاه هاب است.

پیاده‌سازی کنترل‌کننده REACHABLE_DEVICES (فقط یکپارچه‌سازی‌های مرکز کنترل)

هدف REACHABLE_DEVICES توسط Google ارسال می‌شود تا تأیید کند کدام دستگاه‌های نهایی می‌توانند به‌صورت محلی کنترل شوند. این هدف هر بار که Google اسکن اکتشافی را اجرا می‌کند (تقریباً هر دقیقه یک بار) راه‌اندازی می‌شود، تا زمانی که کانون آنلاین تشخیص داده شود.

گرداننده REACHABLE_DEVICES را به‌طور مشابه با گرداننده IDENTIFY پیاده‌سازی می‌کنید، با این تفاوت که گرداننده شما باید شناسه‌های دستگاه اضافی را که ازطریق دستگاه پراکسی محلی (یعنی هاب) قابل‌دسترسی هستند جمع‌آوری کند. فیلد device.verificationId حاوی شناسه دستگاه محلی برای دستگاه نهایی متصل به کانون است.

ReachableDevicesRequest از پلاتفرم Local Home حاوی نمونه‌ای از LocalIdentifiedDevice است. ازطریق این نمونه، می‌توانید شناسه دستگاه پراکسی و همچنین داده‌های نتایج اسکن را دریافت کنید.

گرداننده 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;
  };

پیاده‌سازی کنترل‌کننده EXECUTE

گرداننده EXECUTE شما در برنامه، فرمان‌های کاربر را پردازش می‌کند و از Local Home SDK برای دسترسی به دستگاه‌های هوشمندتان ازطریق پروتکل موجود استفاده می‌کند.

پلاتفرم Local Home همان بار ورودی را به تابع گرداننده EXECUTE ارسال می‌کند که برای هدف EXECUTE به اجرای ابری شما ارسال می‌شود. به‌همین ترتیب، گرداننده EXECUTE شما داده‌های خروجی را در همان قالبی برمی‌گرداند که از پردازش قصد EXECUTE حاصل می‌شود. برای ساده کردن ایجاد پاسخ، می‌توانید از کلاس Execute.Response.Builder که Local Home SDK ارائه می‌دهد استفاده کنید.

برنامه شما دسترسی مستقیم به نشانی IP دستگاه ندارد. درعوض، از میانای CommandRequest برای ایجاد فرمان‌ها براساس یکی از این پروتکل‌ها استفاده کنید: UDP،‏ TCP، یا HTTP. سپس، تابع deviceManager.send() را برای ارسال فرمان‌ها فراخوانی کنید.

هنگام هدف‌یابی فرمان‌ها برای دستگاه‌ها، از شناسه دستگاه (و پارامترهای فیلد customData، درصورت وجود) از پاسخ SYNC برای برقراری ارتباط با دستگاه استفاده کنید.

مثال

تکه‌کد زیر نشان می‌دهد که چگونه می‌توانید کنترل‌کننده 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

گرداننده QUERY شما در برنامه، درخواست‌های کاربر را پردازش می‌کند و از Local Home SDK برای گزارش وضعیت دستگاه‌های هوشمندتان استفاده می‌کند.

پلاتفرم Local Home همان محموله درخواست را به تابع گرداننده «QUERY» ارسال می‌کند که برای هدف QUERY به اجرای ابری شما ارسال می‌شود. به‌طور مشابه، گرداننده QUERY شما داده‌ها را در همان قالبی برمی‌گرداند که از پردازش قصد QUERY به‌دست می‌آید.

ارسال فرمان به دستگاه‌های پشت هاب

برای کنترل دستگاه‌های نهایی پشت هاب، ممکن است لازم باشد اطلاعات بیشتری در بار داده فرمان مختص پروتکل ارسال‌شده به هاب ارائه دهید تا هاب بتواند تشخیص دهد فرمان برای کدام دستگاه است. در برخی موارد، این موضوع می‌تواند مستقیماً از مقدار device.id استنباط شود، اما وقتی این‌طور نباشد، باید این داده‌های اضافی را به‌عنوان بخشی از فیلد customData اضافه کنید.

اگر برنامه‌تان را بااستفاده از TypeScript ایجاد کرده‌اید، فراموش نکنید که برنامه‌تان را به JavaScript کامپایل کنید. می‌توانید از سیستم واحدی که انتخاب می‌کنید برای نوشتن کدتان استفاده کنید. مطمئن شوید هدف شما توسط مرورگر Chrome پشتیبانی می‌شود.