Implémenter l'application de traitement en local

Pour prendre en charge le traitement en local, vous devez créer une application pour gérer les intents de maison connectée suivants :

  • IDENTIFY : permet de découvrir les appareils connectés contrôlables localement. Le gestionnaire d'intent extrait les données renvoyées par votre appareil connecté lors de la découverte et les envoie à Google dans une réponse.
  • EXECUTE : permet l'exécution des commandes.
  • QUERY : permet d'interroger l'état de l'appareil.
  • REACHABLE_DEVICES : (facultatif) permet la découverte des appareils finaux contrôlables localement derrière un hub (ou un pont).

Cette application s'exécute sur les appareils Google Home ou Google Nest de l'utilisateur et connecte votre appareil connecté à l'Assistant. Vous pouvez créer l'application à l'aide de TypeScript (recommandé) ou de JavaScript.

TypeScript est recommandé, car vous pouvez utiliser des liaisons pour vous assurer de manière statique que les données renvoyées par votre application correspondent aux types attendus par la plate-forme.

Pour en savoir plus sur l'API, consultez la documentation de référence de l'API Local Home SDK.

Les extraits suivants montrent comment initialiser l'application de traitement local et associer vos gestionnaires.

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

Créer un projet

Pour déployer votre application de traitement des commandes en magasin, vous devez créer un bundle JavaScript pour votre code et toutes ses dépendances.

Utilisez l'initialiseur de projet d'application de traitement en local pour amorcer la structure de projet appropriée avec la configuration de votre outil de regroupement préféré.

Modèles de projet

Pour sélectionner la configuration de votre outil de regroupement, exécutez la commande npm init comme indiqué dans les exemples suivants :

Aucun

TypeScript sans configuration de bundler :

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

Structure du projet :

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

Remplacez project-directory par un nouveau répertoire qui contiendra le projet d'application d'exécution locale.

Webpack

Configuration de TypeScript avec le bundler webpack :

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

Structure du projet :

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

Remplacez project-directory par un nouveau répertoire qui contiendra le projet d'application d'exécution locale.

Regroupement

Configuration de TypeScript avec le bundler Rollup :

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

Structure du projet :

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

Remplacez project-directory par un nouveau répertoire qui contiendra le projet d'application d'exécution locale.

Colis

Configuration de TypeScript avec le bundler Parcel :

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

Structure du projet :

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

Remplacez project-directory par un nouveau répertoire qui contiendra le projet d'application d'exécution locale.

Effectuer des tâches courantes au niveau du projet

Le projet généré est compatible avec les scripts npm suivants :

Bundle
cd project-directory/
npm run build

Ce script compile la source TypeScript et regroupe votre application avec ses dépendances pour l'environnement d'exécution Chrome dans le sous-répertoire dist/web et l'environnement d'exécution Node.js dans le sous-répertoire dist/node.

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

Ce script vérifie la syntaxe de votre code TypeScript, le compile sans produire de résultat dans le sous-répertoire dist/ et exécute les tests automatisés à partir de test.ts.

Diffuser
cd project-directory/
npm run start

Pendant le développement, ce script sert vos packages d'application pour les environnements d'exécution Chrome et Node.js en local.

Implémenter le gestionnaire IDENTIFY

Le gestionnaire IDENTIFY est déclenché lorsque l'appareil Google Home ou Google Nest redémarre et détecte des appareils locaux non validés (y compris les appareils finaux connectés à un hub). La plate-forme Local Home recherchera les appareils locaux à l'aide des informations de configuration de l'analyse que vous avez spécifiées précédemment et appellera votre gestionnaire IDENTIFY avec les résultats de l'analyse.

Le IdentifyRequest de la plate-forme Local Home contient les données d'analyse d'une instance LocalIdentifiedDevice. Une seule instance device est renseignée, en fonction de la configuration d'analyse qui a détecté l'appareil.

Si les résultats de l'analyse correspondent à votre appareil, votre gestionnaire IDENTIFY doit renvoyer un objet IdentifyResponsePayload, qui inclut un objet device avec des métadonnées pour la maison connectée (telles que les types, les traits et l'état du rapport).

Google établit une association d'appareils si le verificationId de la réponse IDENTIFY correspond à l'une des valeurs otherDeviceIds renvoyées par la réponse SYNC.

Exemple

Les extraits suivants montrent comment créer des gestionnaires IDENTIFY pour les intégrations d'appareils autonomes et de hubs, respectivement.

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

Identifier les appareils derrière un hub

Si Google identifie un hub, il le traitera comme un canal vers les appareils finaux connectés au hub et tentera de valider ces appareils finaux.

Pour permettre à Google de confirmer la présence d'un hub, suivez ces instructions pour votre gestionnaire IDENTIFY :

  • Si la réponse de votre SYNC indique les ID des appareils finaux locaux connectés au hub, définissez isProxy sur true dans IdentifyResponsePayload.
  • Si la réponse SYNC ne signale pas votre hub, définissez isLocalOnly sur true dans IdentifyResponsePayload.
  • Le champ device.id contient l'ID de l'appareil local pour le hub lui-même.

Implémenter le gestionnaire REACHABLE_DEVICES (intégrations de hubs uniquement)

L'intention REACHABLE_DEVICES est envoyée par Google pour confirmer les appareils finaux pouvant être contrôlés localement. Cette intention est déclenchée chaque fois que Google exécute une analyse de découverte (environ une fois par minute), à condition que le hub soit détecté comme étant en ligne.

Vous implémentez le gestionnaire REACHABLE_DEVICES de la même manière que le gestionnaire IDENTIFY, sauf que votre gestionnaire doit collecter des ID d'appareils supplémentaires accessibles par l'appareil proxy local (c'est-à-dire le hub). Le champ device.verificationId contient l'ID de l'appareil local pour un appareil final connecté au hub.

Le ReachableDevicesRequest de la plate-forme Local Home contient une instance de LocalIdentifiedDevice. Cette instance vous permet d'obtenir l'ID de l'appareil proxy ainsi que les données des résultats de l'analyse.

Votre gestionnaire REACHABLE_DEVICES doit renvoyer un objet ReachableDevicesPayload qui inclut un objet devices contenant un tableau de valeurs verificationId représentant les appareils finaux contrôlés par le hub. Les valeurs verificationId doivent correspondre à l'une des valeurs otherDeviceIds de la réponse SYNC.

L'extrait suivant montre comment créer votre gestionnaire 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;
  };

Implémenter le gestionnaire EXECUTE

Votre gestionnaire EXECUTE dans l'application traite les commandes des utilisateurs et utilise le SDK Local Home pour accéder à vos appareils connectés via un protocole existant.

La plate-forme Local Home transmet la même charge utile d'entrée à la fonction de gestionnaire EXECUTE que pour l'intent EXECUTE à votre traitement cloud. De même, votre gestionnaire EXECUTE renvoie des données de sortie au même format que celui utilisé pour le traitement de l'intention EXECUTE. Pour simplifier la création de la réponse, vous pouvez utiliser la classe Execute.Response.Builder fournie par le SDK Local Home.

Votre application n'a pas accès directement à l'adresse IP de l'appareil. Utilisez plutôt l'interface CommandRequest pour créer des commandes basées sur l'un de ces protocoles : UDP, TCP ou HTTP. Appelez ensuite la fonction deviceManager.send() pour envoyer les commandes.

Lorsque vous ciblez des commandes sur des appareils, utilisez l'ID de l'appareil (et les paramètres du champ customData, le cas échéant) de la réponse SYNC pour communiquer avec l'appareil.

Exemple

L'extrait de code suivant montre comment créer votre gestionnaire EXECUTE.

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

Implémenter le gestionnaire QUERY

Votre gestionnaire QUERY dans l'application traite les requêtes des utilisateurs et utilise le SDK Local Home pour signaler l'état de vos appareils connectés.

La plate-forme Local Home transmet la même charge utile de requête à la fonction de gestionnaire "QUERY" que pour l'intent QUERY à votre traitement cloud. De même, votre gestionnaire QUERY renvoie des données au même format que celles issues du traitement de l'intention QUERY.

Envoyer des commandes à des appareils situés derrière un hub

Pour contrôler les appareils finaux situés derrière un hub, vous devrez peut-être fournir des informations supplémentaires dans la charge utile de la commande spécifique au protocole envoyée au hub afin que celui-ci puisse identifier l'appareil auquel la commande est destinée. Dans certains cas, cela peut être déduit directement de la valeur device.id, mais lorsque ce n'est pas le cas, vous devez inclure ces données supplémentaires dans le champ customData.

Si vous avez créé votre application à l'aide de TypeScript, n'oubliez pas de la compiler en JavaScript. Vous pouvez utiliser le système de module de votre choix pour écrire votre code. Assurez-vous que votre cible est compatible avec le navigateur Chrome.