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

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

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

Это приложение работает на устройствах Google Home или Google Nest и подключает ваше смарт-устройство к Ассистенту. Вы можете создать приложение с помощью 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");
  });
Центр
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 с конфигурацией сборщика 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 bundler:

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