Implementa l'app di distribuzione locale

Per supportare l'evasione degli ordini locali, devi creare un'app per gestire questi intent per la smart home:

  • IDENTIFY: supporta il rilevamento di smart device controllabili localmente. Il gestore di intent estrae i dati che lo smart device restituisce durante il rilevamento e li invia in una risposta a Google.
  • EXECUTE: supporta l'esecuzione dei comandi.
  • QUERY: supporta l'esecuzione di query sullo stato del dispositivo.
  • REACHABLE_DEVICES: (Facoltativo) supporta il rilevamento di dispositivi finali controllabili localmente dietro un dispositivo hub (o bridge).

Questa app viene eseguita sui dispositivi Google Home o Google Nest dell'utente e connette lo smart device all'assistente. Puoi creare l'app utilizzando TypeScript (opzione preferita) o JavaScript.

TypeScript è consigliato perché puoi sfruttare i bindings per garantire in modo statico che i dati restituiti dall'app corrispondano ai tipi previsti dalla piattaforma.

Per ulteriori dettagli sull'API, consulta il riferimento all'API Local Home SDK.

Gli snippet riportati di seguito mostrano come inizializzare l'app di evasione degli ordini locale e collegare i gestori.

Autonomo
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");
  });

Creazione di un progetto

Per eseguire il deployment della tua app di distribuzione locale, devi creare un bundle JavaScript per il codice e tutte le sue dipendenze.

Utilizza l'inizializzatore del progetto dell'app di evasione degli ordini locale per avviare la struttura del progetto appropriata con la configurazione del bundler che preferisci.

Modelli di progetto

Per selezionare la configurazione del bundler, esegui il comando npm init come mostrato negli esempi seguenti:

Nessuna

TypeScript senza configurazione del bundler:

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

Struttura del progetto:

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

Sostituisci project-directory con una nuova directory che conterrà il progetto dell'app di evasione degli ordini locale.

Webpack

TypeScript con configurazione del bundling di webpack:

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

Struttura del progetto:

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

Sostituisci project-directory con una nuova directory che conterrà il progetto dell'app di evasione degli ordini locale.

Riepilogo

TypeScript con configurazione del compilatore del bundle Rollup:

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

Struttura del progetto:

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

Sostituisci project-directory con una nuova directory che conterrà il progetto dell'app di evasione degli ordini locale.

Pacco

TypeScript con configurazione bundler Parcel:

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

Struttura del progetto:

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

Sostituisci project-directory con una nuova directory che conterrà il progetto dell'app di fulfillment locale.

Eseguire attività comuni a livello di progetto

Il progetto generato supporta i seguenti comandi npm:

Cofanetto
cd project-directory/
npm run build

Questo script compila l'origine TypeScript e raggruppa la tua app con le sue dipendenze per l'ambiente di runtime di Chrome nella sottodirectory dist/web e l'ambiente di runtime Node.js nella sottodirectory dist/node.

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

Questo script verifica la sintassi del codice TypeScript, lo compila senza produrre output nella sottodirectory dist/ ed esegue i test automatici da test.ts.

Pubblica
cd project-directory/
npm run start

Durante lo sviluppo, questo script gestisce localmente gli app bundle per gli ambienti di runtime Chrome e Node.js.

Implementare il gestore IDENTIFY

L'handler IDENTIFY viene attivato quando il dispositivo Google Home o Google Nest si riavvia e rileva dispositivi locali non verificati (inclusi i dispositivi finali collegati a un hub). La piattaforma Home locale analizzerà i dispositivi locali utilizzando le informazioni di configurazione della scansione specificate in precedenza e chiamerà il gestore IDENTIFY con i risultati della scansione.

Il IdentifyRequest della piattaforma Local Home contiene i dati di scansione di un'istanza LocalIdentifiedDevice. Viene compilata una sola istanza device, in base alla configurazione della scansione che ha rilevato il dispositivo.

Se i risultati della ricerca corrispondono al tuo dispositivo, l'handler IDENTIFY deve restituire un oggetto IdentifyResponsePayload che include un oggetto device con metadati per la smart home (ad esempio tipi, tratti e stato del report).

Google stabilisce un'associazione del dispositivo se il valore verificationId della risposta IDENTIFY corrisponde a uno dei valori otherDeviceIds restituiti dalla risposta SYNC.

Esempio

I seguenti snippet mostrano come creare gestori IDENTIFY rispettivamente per le integrazioni di dispositivi e hub autonomi.

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

Identificare i dispositivi dietro un hub

Se Google identifica un hub, lo tratterà come il canale di accesso ai dispositivi di destinazione collegati e tenterà di verificarli.

Per consentire a Google di verificare la presenza di un dispositivo hub, segui queste istruzioni per il tuo IDENTIFY handler:

  • Se la risposta SYNC riporta gli ID dei dispositivi finali locali connessi all'hub, imposta isProxy su true in IdentifyResponsePayload.
  • Se la risposta SYNC non segnala il dispositivo hub, impostaisLocalOnly su true inIdentifyResponsePayload.
  • Il campo device.id contiene l'ID dispositivo locale del dispositivo hub stesso.

Implementare il gestore REACHABLE_DEVICES (solo integrazioni hub)

L'intent REACHABLE_DEVICES viene inviato da Google per confermare quali dispositivi finali possono essere controllati localmente. Questo intento viene attivato ogni volta che Google esegue una scansione di scoperta (circa una volta ogni minuto), a condizione che l'hub sia rilevato come online.

L'handler REACHABLE_DEVICES viene implementato in modo simile all'handler IDENTIFY, tranne per il fatto che deve raccogliere ID dispositivo aggiuntivi raggiungibili dal dispositivo proxy locale (ovvero l'hub). Il campo device.verificationId contiene l'ID dispositivo locale di un dispositivo finale collegato all'hub.

Il ReachableDevicesRequest della piattaforma Local Home contiene un'istanza di LocalIdentifiedDevice. Tramite questa istanza, puoi ottenere l'ID dispositivo proxy e i dati provenienti dai risultati della scansione.

L'handler REACHABLE_DEVICES deve restituire un oggetto ReachableDevicesPayload che include un oggetto devices contenente un array di valori verificationId che rappresentano i dispositivi finali controllati dall'hub. I valori verificationId devono corrispondere a uno dei valori otherDeviceIds della risposta SYNC.

Lo snippet seguente mostra come creare l'handler 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;
  };

Implementazione del gestore EXECUTE

Il gestore EXECUTE nell'app elabora i comandi utente e utilizza l'SDK Local Home per accedere agli smart device tramite un protocollo esistente.

La piattaforma Local Home passa lo stesso payload di input alla funzione di gestore EXECUTE come per l'intent EXECUTE al tuo cloud fulfillment. Analogamente, l'handler EXECUTE restituisce i dati di output nello stesso formato dell'elaborazione dell'intent EXECUTE. Per semplificare la creazione della risposta, puoi utilizzare la classe Execute.Response.Builder fornita dall'SDK Local Home.

La tua app non ha accesso diretto all'indirizzo IP del dispositivo. Utilizza invece l'interfaccia CommandRequest per creare comandi basati su uno di questi protocolli: UDP, TCP o HTTP. Quindi, chiama la funzione deviceManager.send() per inviare i comandi.

Quando scegli il target dei comandi per i dispositivi, utilizza l'ID dispositivo (e i parametri del campo customData, se incluso) della risposta SYNC per comunicare con il dispositivo.

Esempio

Il seguente snippet di codice mostra come creare l'handler EXECUTE.

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

Implementare l'handler QUERY

L'handler QUERY nell'app elabora le richieste degli utenti e utilizza l'SDK Local Home per segnalare lo stato dei tuoi smart device.

La piattaforma Home locale passa lo stesso payload di richiesta alla funzione di gestore "QUERY" dell'intent QUERY al completamento della richiesta cloud. Analogamente, l'handler QUERY restituisce i dati nello stesso formato dell'elaborazione dell'intent QUERY.

Invio di comandi a dispositivi dietro un hub

Per controllare i dispositivi di destinazione dietro un hub, potresti dover fornire informazioni aggiuntive nel payload del comando specifico del protocollo inviato all'hub affinché quest'ultimo possa identificare il dispositivo a cui è rivolto il comando. In alcuni casi, ciò può essere dedotto direttamente dal valore device.id, ma in caso contrario, devi includere questi dati aggiuntivi nel campo customData.

Se hai creato l'app utilizzando TypeScript, ricordati di compilarla in JavaScript. Puoi utilizzare il sistema di moduli di tua scelta per scrivere il codice. Assicurati che la destinazione sia supportata dal browser Chrome.