Как настроить приложение для самовывоза

Чтобы поддерживать локальное выполнение, вам нужно создать приложение, которое будет обрабатывать следующие намерения умного дома:

  • IDENTIFY – поддерживает обнаружение умных устройств, которыми можно управлять локально. Обработчик намерений извлекает данные, которые ваше умное устройство возвращает во время обнаружения, и отправляет их в ответе Google.
  • EXECUTE: поддерживает выполнение команд.
  • QUERY: поддерживает запросы о состоянии устройства.
  • REACHABLE_DEVICES (необязательно) – позволяет обнаруживать конечные устройства, которыми можно управлять локально через контроллер (или мост).

Это приложение работает на устройствах Google Home или Google Nest и подключает ваше умное устройство к Ассистенту. Вы можете создать приложение с помощью TypeScript (предпочтительно) или JavaScript.

Рекомендуется использовать TypeScript, поскольку вы можете использовать привязки, чтобы статически гарантировать, что данные, возвращаемые вашим приложением, соответствуют типам, ожидаемым платформой.

Дополнительную информацию об API можно найти в справочнике по Local Home SDK API.

В приведенных ниже фрагментах кода показано, как инициализировать приложение для локального выполнения и подключить обработчики.

Отдельные
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 новым каталогом, который будет содержать проект приложения для локального выполнения.

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 новым каталогом, который будет содержать проект приложения для локального выполнения.

Parcel

Конфигурация сборщика 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 для интеграции отдельных устройств и концентраторов.

Отдельные
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 попытается проверить эти конечные устройства.

Чтобы 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.

Hub
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.