تنفيذ تطبيق توصيل الطلبات المحلي

لدعم تلبية الطلبات على المستوى المحلي، تحتاج إلى إنشاء تطبيق للتعامل مع الأهداف المنزلية الذكية التالية:

  • IDENTIFY: إتاحة إمكانية اكتشاف الأجهزة الذكية التي يمكن التحكّم فيها محليًا يستخرج معالج النية البيانات التي يعرضها جهازك الذكي أثناء الاستكشاف ويرسلها كاستجابة إلى Google.
  • EXECUTE: إتاحة تنفيذ الأوامر
  • QUERY: إتاحة الاستعلام عن حالة الجهاز
  • REACHABLE_DEVICES: (اختياري) تتيح هذه الميزة اكتشاف الأجهزة النهائية التي يمكن التحكّم فيها محليًا خلف جهاز موزع (أو جهاز جسر).

يعمل هذا التطبيق على أجهزة Google Home أو Google Nest الخاصة بالمستخدم، كما يربط جهازك الذكي بخدمة "مساعد Google". يمكنك إنشاء التطبيق باستخدام TypeScript (مفضل) أو باستخدام JavaScript.

يوصى باستخدام TypeScript لأنّه يمكنك الاستفادة من عمليات الربط لضمان تطابق البيانات التي يعرضها تطبيقك مع الأنواع التي تتوقّعها المنصّة.

لمزيد من التفاصيل حول واجهة برمجة التطبيقات، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقات لحزمة تطوير البرامج (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 للرمز البرمجي وجميع تبعياته.

استخدِم مهيِّئ المشروع لتطبيق التنفيذ المحلي لتشغيل بنية المشروع المناسبة باستخدام إعداد الحزمة الذي تفضّله.

قوالب المشاريع

لاختيار إعدادات أداة الحِزم، شغِّل الأمر 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 مع إعدادات حزمة تجميع:

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

سيتم تشغيل معالج IDENTIFY عند إعادة تشغيل جهاز Google Home أو Google Nest مع رصد أجهزة محلية لم يتم التحقق منها (بما في ذلك الأجهزة النهائية المتصلة بوحدة تحكُّم). وستبحث منصة Local Home عن الأجهزة المحلية باستخدام معلومات إعدادات الفحص التي حدّدتها سابقًا، وستتصل بمعالج IDENTIFY مع نتائج الفحص.

يحتوي IdentifyRequest من منصة Local Home على بيانات الفحص لمثيل LocalIdentifiedDevice. تتم تعبئة مثيل device واحد فقط استنادًا إلى إعدادات الفحص التي اكتشفت الجهاز.

إذا كانت نتائج الفحص تطابق جهازك، من المفترض أن يعرض معالج IDENTIFY كائن IdentifyResponsePayload يتضمّن عنصر device مع بيانات وصفية للمنزل المزوّد بأجهزة ذكية (مثل الأنواع والسمات وحالة التقرير).

سينشئ محرّك بحث Google عملية ربط للجهاز في حال تطابق verificationId في الاستجابة IDENTIFY مع إحدى قيم otherDeviceIds التي تعرضها الاستجابة SYNC.

مثال

توضّح المقتطفات التالية كيفية إنشاء معالِجات IDENTIFY لعمليات دمج الأجهزة المستقلّة والموزع (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 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 من التأكد من توفّر جهاز الموزع (hub)، يُرجى اتّباع التعليمات التالية الخاصة بمعالج IDENTIFY:

  • إذا كانت استجابة SYNC تشير إلى أرقام تعريف الأجهزة النهائية المحلية المتصلة بالمركز، اضبط isProxy على true في IdentifyResponsePayload.
  • إذا لم تبلّغ استجابة SYNC عن جهاز الإرساء، اضبط isLocalOnly على true في IdentifyResponsePayload.
  • يحتوي الحقل device.id على رقم تعريف الجهاز المحلي لجهاز الموزع نفسه.

تنفيذ المعالج REACHABLE_أجهزتك (عمليات دمج الموزعات فقط)

تُرسل Google آلية إجراء REACHABLE_DEVICES للتأكّد من الأجهزة النهائية التي يمكن التحكّم فيها محليًا. يتم تشغيل هذا الهدف في كل مرة تُجري 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 في التطبيق أوامر المستخدمين ويستخدم حزمة تطوير البرامج (SDK) للمنازل المحلية للوصول إلى أجهزتك الذكية من خلال بروتوكول حالي.

تمرِّر منصة Local Home حمولة الإدخال نفسها إلى دالة معالج EXECUTE وبالنسبة إلى غرض EXECUTE تنفيذ الإجراء على السحابة الإلكترونية. وبالمثل، يعرض معالج EXECUTE بيانات المخرجات بالتنسيق نفسه المستخدَم في معالجة الغرض EXECUTE. لتبسيط عملية إنشاء الردود، يمكنك استخدام الفئة Execute.Response.Builder التي توفّرها حزمة تطوير البرامج (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 في التطبيق طلبات المستخدمين ويستخدم حزمة تطوير البرامج (SDK) للمنازل المحلية للإبلاغ عن حالة أجهزتك الذكية.

تمرِّر منصة Local Home حمولة الطلب نفسها إلى دالة معالج "QUERY" وبالنسبة إلى غرض QUERY إلى تنفيذ السحابة الإلكترونية. وبالمثل، يعرض معالج QUERY البيانات بالتنسيق نفسه الذي يعرضه معالجة الغرض QUERY.

إرسال الأوامر إلى الأجهزة الموجودة خلف وحدة تحكُّم

للتحكُّم في الأجهزة النهائية خلف الموزع، قد تحتاج إلى تقديم معلومات إضافية في حمولة الأوامر الخاصة بالبروتوكول والتي يتم إرسالها إلى الموزع حتى يتمكن الموزع من تحديد الجهاز الذي يستهدفه الأمر. في بعض الحالات، يمكن استنتاج ذلك مباشرةً من القيمة device.id، ولكن إذا لم يكن الأمر كذلك، يجب تضمين هذه البيانات الإضافية كجزء من حقل customData.

إذا أنشأت التطبيق باستخدام TypeScript، فتذكر تجميع التطبيق في JavaScript. ويمكنك استخدام نظام الوحدة الذي تختاره لكتابة التعليمات البرمجية. تأكَّد من أنّ متصفّح Chrome متوافق مع هدفك.