Implementar o app de fulfillment local

Para oferecer suporte ao fulfillment local, você precisa criar um app que processe estas intents de casa inteligente:

  • IDENTIFY: oferece suporte à descoberta de dispositivos inteligentes controláveis localmente. O processador de intents extrai os dados que seu dispositivo inteligente retorna durante a descoberta e os envia em uma resposta ao Google.
  • EXECUTE: oferece suporte à execução de comandos.
  • QUERY: oferece suporte a consultas sobre o estado do dispositivo.
  • REACHABLE_DEVICES: (opcional) oferece suporte à descoberta de dispositivos finais controláveis localmente por trás de um hub (ou ponte).

Esse app é executado nos dispositivos Google Home ou Google Nest do usuário e conecta o dispositivo inteligente ao Assistente. É possível criar o app usando TypeScript (preferencial) ou JavaScript.

O TypeScript é recomendado porque você pode usar vinculações para garantir de forma estática que os dados retornados pelo app correspondam aos tipos esperados pela plataforma.

Para mais detalhes sobre a API, consulte a referência da API do SDK Local Home.

Os snippets a seguir mostram como inicializar o app de fulfillment local e anexar seus manipuladores.

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

Criar seu projeto

Para implantar o app de atendimento local, é necessário criar um pacote JavaScript para seu código e todas as dependências dele.

Use o inicializador de projeto do app de atendimento local para inicializar a estrutura de projeto adequada com a configuração do bundler preferido.

Modelos de projeto

Para selecionar a configuração do bundler, execute o comando npm init, conforme mostrado nos exemplos a seguir:

Nenhum

TypeScript sem configuração de bundler:

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

Estrutura do projeto:

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

Substitua project-directory por um novo diretório que vai conter o projeto do app de atendimento local.

Webpack

TypeScript com configuração do agrupador webpack:

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

Estrutura do projeto:

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

Substitua project-directory por um novo diretório que vai conter o projeto do app de atendimento local.

Rollup

TypeScript com configuração do pacote Rollup:

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

Estrutura do projeto:

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

Substitua project-directory por um novo diretório que vai conter o projeto do app de atendimento local.

Lote

TypeScript com configuração do empacotador Parcel:

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

Estrutura do projeto:

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

Substitua project-directory por um novo diretório que vai conter o projeto do app de atendimento local.

Realizar tarefas comuns no nível do projeto

O projeto gerado é compatível com os seguintes scripts npm:

Bundle
cd project-directory/
npm run build

Esse script compila a origem do TypeScript e agrupa o app com as dependências dele para o ambiente de execução do Chrome no subdiretório dist/web e o ambiente de execução do Node.js no subdiretório dist/node.

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

Esse script verifica a sintaxe do seu código TypeScript, o compila sem produzir saída no subdiretório dist/ e executa testes automatizados de test.ts.

Veicular
cd project-directory/
npm run start

Durante o desenvolvimento, esse script veicula os pacotes de apps para os ambientes de execução do Chrome e do Node.js localmente.

Implementar o gerenciador de IDENTIFICAÇÃO

O manipulador IDENTIFY será acionado quando o dispositivo Google Home ou Google Nest for reinicializado e detectar dispositivos locais não verificados (incluindo dispositivos finais conectados a um hub). A plataforma Local Home vai procurar dispositivos locais usando as informações de configuração de verificação especificadas anteriormente e chamar seu manipulador IDENTIFY com os resultados da verificação.

O IdentifyRequest da plataforma Local Home contém os dados de verificação de uma instância LocalIdentifiedDevice. Apenas uma instância de device é preenchida, com base na configuração de verificação que descobriu o dispositivo.

Se os resultados da verificação corresponderem ao seu dispositivo, o manipulador IDENTIFY vai retornar um objeto IdentifyResponsePayload que inclui um objeto device com metadados de casa inteligente (como tipos, características e estado do relatório).

O Google estabelece uma associação de dispositivo se o verificationId da resposta IDENTIFY corresponder a um dos valores otherDeviceIds retornados pela resposta SYNC.

Exemplo

Os snippets a seguir mostram como criar manipuladores de IDENTIFY para integrações de dispositivos autônomos e hubs, respectivamente.

Independente
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;
  };
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 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;
  };

Identificar dispositivos conectados a um hub

Se o Google identificar um dispositivo hub, ele vai tratar o hub como o condutor para os dispositivos finais conectados a ele e tentar verificar esses dispositivos finais.

Para permitir que o Google confirme a presença de um dispositivo hub, siga estas instruções para seu manipulador IDENTIFY:

  • Se a resposta do SYNC informar os IDs dos dispositivos finais locais conectados ao hub, defina isProxy como true no IdentifyResponsePayload.
  • Se a resposta SYNC não informar o dispositivo hub, defina isLocalOnly como true no IdentifyResponsePayload.
  • O campo device.id contém o ID do dispositivo local para o próprio dispositivo hub.

Implementar o manipulador REACHABLE_DEVICES (somente integrações de hub)

O Google envia a intent REACHABLE_DEVICES para confirmar quais dispositivos finais podem ser controlados localmente. Essa intent é acionada sempre que o Google executa uma verificação de descoberta (aproximadamente uma vez por minuto), desde que o hub esteja on-line.

Você implementa o gerenciador REACHABLE_DEVICES de maneira semelhante ao gerenciador IDENTIFY, exceto que ele precisa coletar outros IDs de dispositivos acessíveis pelo dispositivo proxy local (ou seja, o hub). O campo device.verificationId contém o ID do dispositivo local para um dispositivo final conectado ao hub.

O ReachableDevicesRequest da plataforma Local Home contém uma instância de LocalIdentifiedDevice. Com essa instância, é possível receber o ID do dispositivo proxy e os dados dos resultados da verificação.

O manipulador REACHABLE_DEVICES precisa retornar um objeto ReachableDevicesPayload que inclua um objeto devices com uma matriz de valores verificationId que representam os dispositivos finais controlados pelo hub. Os valores de verificationId precisam corresponder a um dos otherDeviceIds da resposta SYNC.

O snippet a seguir mostra como criar seu manipulador 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;
  };

Implementar o gerenciador de EXECUÇÃO

O manipulador EXECUTE no app processa comandos do usuário e usa o SDK Local Home para acessar seus dispositivos inteligentes por um protocolo existente.

A plataforma Local Home transmite o mesmo payload de entrada para a função do gerenciador EXECUTE que a intent EXECUTE para o fulfillment na nuvem. Da mesma forma, o manipulador EXECUTE retorna dados de saída no mesmo formato do processamento da intent EXECUTE. Para simplificar a criação de respostas, use a classe Execute.Response.Builder fornecida pelo SDK local do Google Home.

Seu app não tem acesso direto ao endereço IP do dispositivo. Em vez disso, use a interface CommandRequest para criar comandos com base em um destes protocolos: UDP, TCP ou HTTP. Em seguida, chame a função deviceManager.send() para enviar os comandos.

Ao segmentar comandos para dispositivos, use o ID do dispositivo (e os parâmetros do campo customData, se incluído) da resposta SYNC para se comunicar com o dispositivo.

Exemplo

O snippet de código a seguir mostra como criar seu manipulador EXECUTE.

Independente/Hub
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());
  };

Implementar o gerenciador de QUERY

O manipulador QUERY no app processa solicitações do usuário e usa o SDK local do Google Home para informar o estado dos dispositivos inteligentes.

A plataforma Local Home transmite a mesma carga útil de solicitação para a função de manipulador "QUERY" que a intent QUERY para o fulfillment na nuvem. Da mesma forma, o manipulador QUERY retorna dados no mesmo formato do processamento da intent QUERY.

Como enviar comandos para dispositivos conectados a um hub

Para controlar dispositivos finais atrás de um hub, talvez seja necessário fornecer mais informações no payload de comando específico do protocolo enviado ao hub para que ele identifique a qual dispositivo o comando se destina. Em alguns casos, isso pode ser inferido diretamente do valor device.id, mas, quando não for possível, inclua esses dados extras como parte do campo customData.

Se você criou o app usando TypeScript, não se esqueça de compilá-lo para JavaScript. Você pode usar o sistema de módulos de sua preferência para escrever o código. Verifique se o destino é compatível com o navegador Chrome.