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

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

  • 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 وظهور أجهزة محلية لم يتم التحقق منها (بما في ذلك الأجهزة النهائية المتصلة بوحدة تحكُّم). سيبحث النظام الأساسي للمنزل المحلي عن الأجهزة المحلية باستخدام معلومات ضبط المسح التي حدّدتها سابقًا وسيتصل بمعالج IDENTIFY لعرض نتائج الفحص.

يحتوي IdentifyRequest من النظام الأساسي Local 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)، سيتعامل مع المركز على أنّه وصلة إلى الأجهزة الطرفية المتصلة بالموزع (Hub) ويحاول التحقق من هذه الأجهزة.

لتمكين Google من التأكد من توفّر جهاز الموزع (Hub)، يُرجى اتّباع التعليمات التالية المتعلّقة بمعالج IDENTIFY:

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

تنفيذ المعالج REACHABLE_device (عمليات دمج المركز فقط)

الغرض من إرسال الطلب "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 في التطبيق أوامر المستخدم ويستخدم حزمة تطوير البرامج (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 متوافق مع هدفك.