0

I have built a Google Smart Home Action using the Local Fulfillment SDK as outlined in the following articles:

I am using UDP for device discovery and my Google Nest Hub can successfully scan and detect the virtual device running on my laptop as well as download the JS of the local app.

My Local Home SDK configuration is as follows - Local Home SDK Configuration.

When the Nest Hub executes the IDENTIFY intent of my app handler I am receiving the following error(s):

[smarthome.DeviceManager] Intent handler failed with error: Buffer is not defined

[smarthome.DeviceManager] Got a rejected promise Buffer is not defined 

This appears to be a Node.JS error as opposed to something specific to the local SDK app itself. Below is the code of my local app.

/// <reference types="@google/local-home-sdk" />

import App = smarthome.App;
import Constants = smarthome.Constants;
import DataFlow = smarthome.DataFlow;
import Execute = smarthome.Execute;
import Intents = smarthome.Intents;
import IntentFlow = smarthome.IntentFlow;

const SERVER_PORT = 3388;

interface ILightParams {
  on?: boolean,
  brightness?: number
}

class LocalExecutionApp {

  constructor(private readonly app: App) { }

  identifyHandler(request: IntentFlow.IdentifyRequest):
      Promise<IntentFlow.IdentifyResponse> {
    console.log("IDENTIFY intent: " + JSON.stringify(request, null, 2));

    const scanData = request.inputs[0].payload.device.udpScanData;
    console.log("SCANDATA: " + JSON.stringify(scanData));
    if (!scanData) {
      const err = new IntentFlow.HandlerError(request.requestId, 'invalid_request', 'Invalid scan data');
      return Promise.reject(err);
    }

    const localDeviceId = Buffer.from(scanData.data, 'hex');
    console.log("ScanData Local Device: " + localDeviceId);

    const response: IntentFlow.IdentifyResponse = {
      intent: Intents.IDENTIFY,
      requestId: request.requestId,
      payload: {
        device: {
          // id: localDeviceId.toString(),
          id: 'sample-device',
          verificationId: localDeviceId.toString(),
        }
      }
    };
    console.log("IDENTIFY response: " + JSON.stringify(response, null, 2));

    return Promise.resolve(response);
  }

  executeHandler(request: IntentFlow.ExecuteRequest):
      Promise<IntentFlow.ExecuteResponse> {
    console.log("EXECUTE intent: " + JSON.stringify(request, null, 2));

    const command = request.inputs[0].payload.commands[0];
    const execution = command.execution[0];
    const response = new Execute.Response.Builder()
      .setRequestId(request.requestId);

    const promises: Promise<void>[] = command.devices.map((device) => {
      console.log("Handling EXECUTE intent for device: " + JSON.stringify(device));

      // Convert execution params to a string for the local device
      const params = execution.params as ILightParams;
      const payload = this.getDataForCommand(execution.command, params);

      // Create a command to send over the local network
      const radioCommand = new DataFlow.HttpRequestData();
      radioCommand.requestId = request.requestId;
      radioCommand.deviceId = device.id;
      radioCommand.data = JSON.stringify(payload);
      radioCommand.dataType = 'application/json';
      radioCommand.port = SERVER_PORT;
      radioCommand.method = Constants.HttpOperation.POST;
      radioCommand.isSecure = false;

      console.log("Sending HTTP request to the smart home device:", payload);

      return this.app.getDeviceManager()
        .send(radioCommand)
        .then(() => {
          const state = {online: true};
          response.setSuccessState(device.id, Object.assign(state, params));
          console.log(`Command successfully sent to ${device.id}`);
        })
        .catch((e: IntentFlow.HandlerError) => {
          e.errorCode = e.errorCode || 'invalid_request';
          response.setErrorState(device.id, e.errorCode);
          console.error('An error occurred sending the command', e.errorCode);
        });
    });

    return Promise.all(promises)
      .then(() => {
        return response.build();
      })
      .catch((e) => {
        const err = new IntentFlow.HandlerError(request.requestId, 'invalid_request', e.message);
        return Promise.reject(err);
      });
  }

  /**
   * Convert execution request into a local device command
   */
  getDataForCommand(command: string, params: ILightParams): unknown {
    switch (command) {
      case 'action.devices.commands.OnOff':
        return {
          on: params.on ? true : false
        };
      default:
        console.error('Unknown command', command);
        return {};
    }
  }
}

const localHomeSdk = new App('1.0.0');
const localApp = new LocalExecutionApp(localHomeSdk);
localHomeSdk
  .onIdentify(localApp.identifyHandler.bind(localApp))
  .onExecute(localApp.executeHandler.bind(localApp))
  .listen()
  .then(() => console.log('Ready'))
  .catch((e: Error) => console.error(e));

Any insight on why this error is occurring is greatly appreciated.

Cheers.

1 Answers1

1

Reposting the answer in the comments:

Buffer is not a type that is directly supported in a browser environment. You can see this for yourself by trying to run something like x = new Buffer() in your web browser's dev console.

To support a class like Buffer, you can use a bundling tool like Webpack. In the official sample, you can see an example Webpack configuration. Other bundling tools can also be used, and examples can be found in the official Local Home scaffolding tool or can be called directly with an npm init command.

npm init @google/local-home-app app/ --bundler webpack
npm init @google/local-home-app app/ --bundler rollup
npm init @google/local-home-app app/ --bundler parcel
Nick Felker
  • 11,536
  • 1
  • 21
  • 35