Implementa la app de entrega local

Para admitir la entrega local, debes compilar una app que controle estos intents de casa inteligente:

  • IDENTIFY: Admite el descubrimiento de dispositivos inteligentes que se pueden controlar de forma local. El controlador de intents extrae los datos que devuelve tu dispositivo inteligente durante el descubrimiento y los envía en una respuesta a Google.
  • EXECUTE: Admite la ejecución de comandos.
  • QUERY: Admite la consulta del estado del dispositivo.
  • REACHABLE_DEVICES: (Opcional) Admite el descubrimiento de dispositivos finales controlables de forma local detrás de un dispositivo concentrador (o puente).

Esta app se ejecuta en los dispositivos Google Home o dispositivo Google Nest del usuario y conecta tu dispositivo inteligente con Asistente. Puedes crear la app con TypeScript (preferido) o JavaScript.

Se recomienda TypeScript porque puedes aprovechar las vinculaciones para garantizar de forma estática que los datos que devuelve tu app coincidan con los tipos que espera la plataforma.

Para obtener más detalles sobre la API, consulta la referencia de la API del SDK de Local Home.

En los siguientes fragmentos, se muestra cómo inicializar la app de entrega local y adjuntar tus controladores.

Independiente
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });
Concentrador
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onReachableDevices(reachableDevicesHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });

Crea tu proyecto

Para implementar tu app de entrega local, debes compilar un paquete de JavaScript para tu código y todas sus dependencias.

Usa el inicializador de proyectos de la app de entrega local para iniciar la estructura de proyecto adecuada con la configuración de empaquetador que prefieras.

Plantillas de proyectos

Para seleccionar la configuración del empaquetador, ejecuta el comando npm init como se muestra en los siguientes ejemplos:

Ninguno

TypeScript sin configuración de empaquetador:

npm init @google/local-home-app project-directory/ --bundler none

Estructura del proyecto:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

Reemplaza project-directory por un directorio nuevo que contendrá el proyecto de la app de entrega local.

Webpack

TypeScript con la configuración del empaquetador webpack:

npm init @google/local-home-app project-directory/ --bundler webpack

Estructura del proyecto:

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

Reemplaza project-directory por un directorio nuevo que contendrá el proyecto de la app de entrega local.

Rollup

Configuración del empaquetador de TypeScript con Rollup:

npm init @google/local-home-app project-directory/ --bundler rollup

Estructura del proyecto:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
├── rollup.config.js
└── serve.js

Reemplaza project-directory por un directorio nuevo que contendrá el proyecto de la app de entrega local.

Parcel

Configuración del empaquetador de TypeScript con Parcel:

npm init @google/local-home-app project-directory/ --bundler parcel

Estructura del proyecto:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

Reemplaza project-directory por un directorio nuevo que contendrá el proyecto de la app de entrega local.

Realiza tareas comunes a nivel de proyecto

El proyecto generado admite las siguientes secuencias de comandos de npm:

Paquete
cd project-directory/
npm run build

Esta secuencia de comandos compila el código fuente de TypeScript y empaqueta tu app con sus dependencias para el entorno de ejecución de Chrome en el subdirectorio dist/web y el entorno de ejecución de Node.js en el subdirectorio dist/node.

Verificar
cd project-directory/
npm run lint
npm run compile
npm test

Esta secuencia de comandos verifica la sintaxis de tu código de TypeScript, lo compila sin producir ningún resultado en el subdirectorio dist/ y ejecuta pruebas automatizadas desde test.ts.

Entregar
cd project-directory/
npm run start

Durante el desarrollo, esta secuencia de comandos entrega los paquetes de tu app para los entornos de ejecución de Chrome y Node.js de forma local.

Implementa el controlador IDENTIFY

El controlador IDENTIFY se activará cuando el dispositivo Google Home o Google Nest se reinicie y detecte dispositivos locales no verificados (incluidos los dispositivos finales conectados a una unidad central). La plataforma de Local Home buscará dispositivos locales con la información de configuración de análisis que especificaste antes y llamará a tu controlador IDENTIFY con los resultados del análisis.

El objeto IdentifyRequest de la plataforma de Local Home contiene los datos de análisis de una instancia de LocalIdentifiedDevice. Solo se propaga una instancia de device, según la configuración de análisis que descubrió el dispositivo.

Si los resultados del análisis coinciden con tu dispositivo, el controlador IDENTIFY debería devolver un objeto IdentifyResponsePayload, que incluye un objeto device con metadatos de la casa inteligente (como los tipos, los atributos y el estado del informe).

Google establece una asociación de dispositivos si el verificationId de la respuesta IDENTIFY coincide con uno de los valores otherDeviceIds que devuelve la respuesta SYNC.

Ejemplo

En los siguientes fragmentos, se muestra cómo puedes crear controladores de IDENTIFY para integraciones de dispositivos independientes y de concentradores, respectivamente.

Independiente
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;
  };
Concentrador
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;
  };

Identifica los dispositivos detrás de un concentrador

Si Google identifica un dispositivo concentrador, lo tratará como el canal hacia los dispositivos finales conectados al concentrador y, luego, intentará verificar esos dispositivos finales.

Para permitir que Google confirme que hay un dispositivo concentrador presente, sigue estas instrucciones para tu controlador IDENTIFY:

  • Si tu respuesta de SYNC informa los IDs de los dispositivos finales locales conectados al concentrador, establece isProxy como true en el IdentifyResponsePayload.
  • Si tu respuesta SYNC no informa tu dispositivo concentrador, establece isLocalOnly como true en IdentifyResponsePayload.
  • El campo device.id contiene el ID de dispositivo local del dispositivo concentrador.

Implementa el controlador REACHABLE_DEVICES (solo para integraciones de concentradores)

Google envía el intent REACHABLE_DEVICES para confirmar qué dispositivos finales se pueden controlar de forma local. Este intent se activa cada vez que Google ejecuta un análisis de descubrimiento (aproximadamente una vez por minuto), siempre que se detecte que el concentrador está en línea.

Implementas el controlador REACHABLE_DEVICES de manera similar al controlador IDENTIFY, excepto que tu controlador debe recopilar IDs de dispositivo adicionales a los que puede acceder el dispositivo proxy local (es decir, el concentrador). El campo device.verificationId contiene el ID de dispositivo local para un dispositivo final que está conectado al concentrador.

El objeto ReachableDevicesRequest de la plataforma de Local Home contiene una instancia de LocalIdentifiedDevice. A través de esta instancia, puedes obtener el ID del dispositivo proxy y los datos de los resultados del análisis.

Tu controlador REACHABLE_DEVICES debería devolver un objeto ReachableDevicesPayload que incluya un objeto devices que contenga un array de valores verificationId que representen los dispositivos finales que controla el concentrador. Los valores de verificationId deben coincidir con uno de los otherDeviceIds de la respuesta de SYNC.

En el siguiente fragmento, se muestra cómo puedes crear tu controlador de 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;
  };

Implementa el controlador EXECUTE

El controlador EXECUTE de la app procesa los comandos del usuario y usa el SDK de Local Home para acceder a tus dispositivos inteligentes a través de un protocolo existente.

La plataforma de Local Home pasa la misma carga útil de entrada a la función del controlador EXECUTE que para el intent EXECUTE a tu entrega en la nube. Del mismo modo, tu controlador EXECUTE devuelve datos de salida en el mismo formato que el procesamiento del intent EXECUTE. Para simplificar la creación de la respuesta, puedes usar la clase Execute.Response.Builder que proporciona el SDK de Local Home.

Tu app no tiene acceso directo a la dirección IP del dispositivo. En su lugar, usa la interfaz CommandRequest para crear comandos basados en uno de estos protocolos: UDP, TCP o HTTP. Luego, llama a la función deviceManager.send() para enviar los comandos.

Cuando segmentes comandos para dispositivos, usa el ID de dispositivo (y los parámetros del campo customData, si se incluyen) de la respuesta SYNC para comunicarte con el dispositivo.

Ejemplo

En el siguiente fragmento de código, se muestra cómo puedes crear tu controlador de EXECUTE.

Independiente/concentrador
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());
  };

Implementa el controlador de QUERY

El controlador QUERY de la app procesa las solicitudes de los usuarios y usa el SDK de Local Home para informar el estado de tus dispositivos inteligentes.

La plataforma de Local Home pasa la misma carga útil de solicitud a la función del controlador "QUERY" que para el intent QUERY a tu entrega en la nube. Del mismo modo, tu controlador QUERY devuelve datos en el mismo formato que el procesamiento del intent QUERY.

Envía comandos a dispositivos detrás de un concentrador

Para controlar los dispositivos finales detrás de una unidad central, es posible que debas proporcionar información adicional en la carga útil del comando específico del protocolo que se envía a la unidad central para que esta identifique a qué dispositivo se dirige el comando. En algunos casos, esto se puede inferir directamente del valor device.id, pero, cuando no es así, debes incluir estos datos adicionales como parte del campo customData.

Si creaste tu app con TypeScript, recuerda compilarla en JavaScript. Puedes usar el sistema de módulos que prefieras para escribir tu código. Asegúrate de que el navegador Chrome admita tu destino.