Join us online for the Google Smart Home Developer Summit on October 21st! Register here to learn what's new, and what's coming up for Google Smart Home.

Seamless Setup with Bluetooth

The Seamless Setup feature simplifies the user’s experience when setting up new smart home devices for Google Assistant. With this feature, Google scans for any unprovisioned nearby devices and indicates to users those that are available for Seamless Setup. Users can then enable and configure their devices from a single location, via the Google Home app, instead of linking from different apps for each smart home device. As a smart home developer, you implement Seamless Setup for your Action by enabling devices for discovery in the Actions console and then creating a local fulfillment app that handles setup intents.

Seamless Setup uses Bluetooth Low Energy (BLE) connectivity to connect and configure devices with Google Assistant. Using this approach eliminates the need for a separate hub to handle cloud fulfillment connections with Assistant, and allows you to connect new devices without modifying code in your existing provider apps.

How it works

Diagram of provisioning flow
Figure 1: Seamless Setup provisioning flow using Bluetooth.

Seamless Setup is a part of the Local Home SDK. This feature allows Google Home and Nest devices to act as a local hub and handle the provisioning and registration of nearby unconnected smart home devices.

The Local Home platform scans for nearby BLE devices. If any are found to match the scan config information you provided for Unprovisioned devices, the platform issues IDENTIFY intents for each matching device. Google then uses the IDENTIFY responses to present users with a list of devices available for seamless setup when they click Setup a new device in the Google Home app.

When the user selects one of the listed devices for setup in the Home app, the platform issues an INDICATE intent. Your local fulfillment app handles this intent in an appropriate way for the device, such as making lights blink, to provide users with a cue to indicate the device they selected. The user is then prompted to confirm that they want to complete setup for that device. If the user chooses to continue setup, the platform sends the PROVISION intent to the local fulfillment app to configure the smart home device for use.

The Local Home platform waits for the provisioning process to complete on the device, scanning for any nearby devices matching the Provisioned scan configuration you provided. Once a provisioned device is identified matching the device ID, the Local Home platform sends the REGISTER intent to the local fulfillment app to complete the setup process and report device metadata to Home Graph.

If an error occurs during the registration process, or the user later decides to remove a provisioned device, the platform sends the UNPROVISION intent to the local fulfillment app.

Add Seamless Setup to your smart home Action

This section walks you through how to implement seamless setup for your existing smart home Action.

Before you begin

Set up the scan configuration for BLE

To specify the discovery information, follow these steps:

  1. Open your smart home project in the Actions console.
  2. In the left navigation, click Actions.
  3. Under Configure local home SDK (optional) > Add capabilities, enable Seamless Setup.
  4. Under Configure local home SDK (optional) > Add device scan configuration, click New scan config.
  5. Select BLE as the scan matching protocol type from the drop-down and enter values for Google to scan.

In the console, you need to configure two distinct BLE scan matchers for device advertisements: Provisioned (not configured for setup) and Unprovisioned (configured after setup). These matchers are used to discover local devices in each state. Scan configurations can include a combination of advertised fields in the payload:

  • 16-bit Service Class UUIDs (0x02, 0x03)
  • 128-bit Service Class UUIDs (0x06, 0x07)
  • Name of device, corresponding to either:
    • Shortened Local Name (0x08)
    • Complete Local Name (0x09)
  • Service Data (0x16)
  • Mesh Beacon (0x2B)
  • Manufacturer-specific data (0xFF)
  • Bluetooth company identifier
  • Bluetooth member UUID
  • Organizationally Unique Identifier (OUI)

Scan configuration example

The following example shows the unprovisioned scan matcher values for a BLE device with manufacturer ID of 0xFFEE and payload data of 0x1234. In the console, you would specify these BLE scan config values:

  • State: Unprovisioned
  • Manufacturer-specific Data: [Ee]{2}[Ff]{2}1234

The corresponding provisioned scan matcher for the same BLE device with payload data of 0x5678 would be specified in the console as follows:

  • State: Provisioned
  • Manufacturer-specific Data: [Ee]{2}[Ff]{2}5678

Add the seamless setup intents

To support seamless setup, you need to build or modify a local fulfillment app to handle the following smart home intents:

  • IDENTIFY: Supports discovery of smart devices available for seamless setup. The intent handler extracts data that your smart device returns during discovery and sends this in a response to Google.
  • INDICATE: Provides users with a cue indicating which device they are setting up.
  • PROVISION: Indicates that the user wants to set up a device and provisions the device on the network.
  • REGISTER: Provides Google with information about supported types and traits for provisioned devices.
  • UNPROVISION: Indicates that the user wants to remove their device from Google Assistant.

This app runs on user’s Google Home or Google Nest devices and connects your smart device to Assistant. You can create the app using TypeScript (preferred) or JavaScript.

TypeScript is recommended because you can leverage bindings to statically ensure that the data your app returns match the types that the platform expects.

Build the IDENTIFY handler

Google scans for matching nearby devices and reports them to your local fulfillment app through an IDENTIFY intent. Your app must return a valid device ID and type. You should also return a valid deviceInfo value.

The following snippet shows how you might implement the IDENTIFY handler.

const deviceInfo: IntentFlow.DeviceInfo = {
  manufacturer: 'Lights, Inc.',
  model: 'Smart Light',
  hwVersion: '1.0.0',
  swVersion: '1.0.0',
};

public identifyHandler = async(
  identifyRequest: smarthome.IntentFlow.IdentifyRequest):
  Promise<smarthome.IntentFlow.IdentifyResponse> => {

    const deviceName = identifyRequest.inputs[0].payload.device.bleData?.name || 'light-device';

    const identifyResponse: smarthome.IntentFlow.IdentifyResponse = {
      requestId: identifyRequest.requestId,
      intent: smarthome.Intents.IDENTIFY,
      payload: {
        device: {
          id: deviceName,
          type: 'action.devices.types.LIGHT',
          deviceInfo: deviceInfo,
          indicationMode: smarthome.IntentFlow.IndicationMode.BLINK,
        },
      },
    };

    return identifyResponse;
  };

Build the INDICATE handler

When the user initially selects a device for seamless setup from the Google Home app, the platform sends the INDICATE intent to provide a cue that the user has chosen the right device. The device should blink, beep, or otherwise physically signal the device selection.

The contents of the response are not read by the platform.

public indicateHandler = async(
  indicateRequest: smarthome.IntentFlow.IndicateRequest):
  Promise<smarthome.IntentFlow.IndicateResponse> => {

    // TODO: Send command to device to blink or make noise

    const indicateResponse: smarthome.IntentFlow.IndicateResponse = {
      requestId: indicateRequest.requestId,
      intent: smarthome.Intents.INDICATE,
      payload: {},
    };

    return indicateResponse;
  };

Build the PROVISION handlers

The Local Home platform sends the PROVISION intent after the user confirms that they want to complete seamless setup on their selected device. On receiving this request, your local fulfillment app should proceed to configure the device. Once configured, the device should advertise itself in the provisioned state. The structureData from the response is persisted in Home Graph and returned with future intent requests.

Similarly, when a device is removed from the user's Home Graph, the Local Home platform sends an UNPROVISION intent to notify that the device should be decommissioned.

public provisionHandler = async(
  provisionRequest: smarthome.IntentFlow.ProvisionRequest):
  Promise<smarthome.IntentFlow.ProvisionResponse> => {

    // Example provisioning payload for the device
    const data = Buffer.from('{provision: true}').toString('hex');

    // Construct a write command to provisioning service on the device
    const command = new DataFlow.BleRequestData();
    command.requestId = provisionRequest.requestId;
    command.deviceId = provisionRequest.inputs[0].payload.device.id || 'light-device';
    command.serviceUuid = serviceUuid;
    command.characteristicUuid = characteristicUuid;
    command.operation = Constants.BleOperation.WRITE_WITHOUT_RESPONSE;
    command.data = data;

    // Send command to the device
    const manager: smarthome.DeviceManager = this.app.getDeviceManager();
    await manager.send(command);

    const provisionResponse: smarthome.IntentFlow.ProvisionResponse = {
      requestId: provisionRequest.requestId,
      intent: smarthome.Intents.PROVISION,
      payload: {
        structureData: {
          foo: 'bar',
        },
      },
    };

    return provisionResponse;
  };

public unprovisionHandler = async(
  unprovisionRequest: smarthome.IntentFlow.UnprovisionRequest):
  Promise<smarthome.IntentFlow.UnprovisionResponse> => {

    // TODO: Send command to provisioning service

    const response: smarthome.IntentFlow.UnprovisionResponse = {
      requestId: unprovisionRequest.requestId,
      intent: smarthome.Intents.UNPROVISION,
      payload: {}
    };
    return response;
  };

Build the REGISTER handler

After the device is discovered by a scan in the provisioned state, Google sends a REGISTER intent. On receiving this request, your local fulfillment app should report the full device metadata in a format similar to a SYNC response. This response persists in Home Graph as the device entry.

Unless avoidAutoconnect is enabled in the device's IDENTIFY response, the platform attempts to make a BLE connection to the device after REGISTER succeeds.

public registerHandler = async(
  registerRequest: smarthome.IntentFlow.RegisterRequest):
  Promise<smarthome.IntentFlow.RegisterResponse> => {

    const deviceId = registerRequest.inputs[0].payload.device.bleData?.name || 'light-device';
    const deviceName = registerRequest.inputs[0].payload.device.name;
    const device: smarthome.IntentFlow.Device = {
      id: deviceId,
      type: 'action.devices.types.LIGHT',
      deviceInfo: deviceInfo,
      name: {
        name: deviceName,
      },
      willReportState: true,
      traits: ['action.devices.traits.OnOff'],
    };

    const registerResponse: smarthome.IntentFlow.RegisterResponse = {
      requestId: registerRequest.requestId,
      intent: smarthome.Intents.REGISTER,
      payload: {
        devices: [device]
      },
    };

    return registerResponse;
  };

Implement the EXECUTE handler

Once seamless setup devices are configured, they handle user commands through the EXECUTE intent. The EXECUTE handler in the app processes user commands and uses the Local Home SDK to access your smart devices through an existing protocol.

The Local Home platform passes the same input payload to the EXECUTE handler function as for the EXECUTE intent to your cloud fulfillment. Likewise, your EXECUTE handler returns output data in the same format as the EXECUTE intent. To simplify the response creation, you can use the Execute.Response.Builder class that the Local Home SDK provides.

Your app does not have direct access to the address of the device. Instead, use the CommandRequest interface to create commands based on the underlying BLE protocol. Then, call the deviceManager.send() function to send the commands.

const serviceUuid = '...';
const characteristicUuid = '...';

executeHandler: IntentFlow.ExecuteHandler = async(request: IntentFlow.ExecuteRequest):
  Promise<IntentFlow.ExecuteResponse> => {
    const executeResponse = new Execute.Response.Builder().setRequestId(request.requestId);
    const promises: Array<Promise<void>> = [];

    // Iterate through all devices and execute commands.
    for (const device of request.inputs[0].payload.commands[0].devices) {
      const deviceId = device.id;

      const execution = request.inputs[0].payload.commands[0].execution[0];
      const params = execution.params as { on: boolean };
      const bytes = params.on ? 'on' : 'off';

      const command = new DataFlow.BleRequestData();
      command.requestId = request.requestId;
      command.deviceId = deviceId || '';
      command.serviceUuid = serviceUuid;
      command.characteristicUuid = characteristicUuid;
      command.operation = Constants.BleOperation.WRITE_WITHOUT_RESPONSE;
      command.data = Buffer.from(bytes).toString('hex');
      const dm: smarthome.DeviceManager = this.app.getDeviceManager();
      const p: Promise<void> =
        dm.send(command)
          .then((response: DataFlow.CommandSuccess) => {
            // TODO: Read state from the device if possible.
            executeResponse.setSuccessState(deviceId, { 'on': params.on });
          })
          .catch((e: IntentFlow.HandlerError) => {
            executeResponse.setErrorState(
              deviceId, e.errorCode || 'invalid_request'
            );
          });

      promises.push(p);
    }

    return Promise.all(promises)
      .then(() => executeResponse.build());
  };

Reporting local device state

Since BLE devices do not have a companion cloud fulfillment implementation, asynchronous state changes must be reported locally through the Local Home SDK.

Implement the connection event handlers

Your local fulfillment app can register a handler for the following events sent by the Local Home platform:

  • AUTOCONNECT: Sent when a provisioned device reconnects to the Google Home or Google Nest device.
  • DISCONNECT: Send when a provisioned device disconnects from the Google Home or Google Nest device.

These handlers provide a good place to report your device's online state to the Local Home platform using the reportState() method of DeviceManager.

public autoConnectEventHandler = async(request: smarthome.IntentFlow.EventRequest):
  Promise<smarthome.IntentFlow.EventResponse> => {

    const device = request.inputs[0].payload.device;

    const manager: smarthome.DeviceManager = this.app.getDeviceManager();
    const normalResponse: smarthome.ReportState.Response.Payload =
        new smarthome.ReportState.Response.Builder()
            .setRequestId(request.requestId)
            .setState(device.id, {online: true})
            .build();
    manager.reportState(normalResponse);

    return {
      requestId: request.requestId,
      intent: smarthome.Intents.EVENT,
      payload: {}
    };
  };

public disconnectEventHandler = async(request: smarthome.IntentFlow.EventRequest):
  Promise<smarthome.IntentFlow.EventResponse> => {

    const device = request.inputs[0].payload.device;

    const manager: smarthome.DeviceManager = this.app.getDeviceManager();
    const normalResponse: smarthome.ReportState.Response.Payload =
        new smarthome.ReportState.Response.Builder()
            .setRequestId(request.requestId)
            .setState(device.id, {online: false})
            .build();
    manager.reportState(normalResponse);

    return {
      requestId: request.requestId,
      intent: smarthome.Intents.EVENT,
      payload: {}
    };
  };

Subscribe to notifications

BLE devices can report data asynchronously using notifications. The Local Home platform forwards notifications received by provisioned devices to your local fulfillment app.

To handle notifications in your app, first send a REGISTER_FOR_NOTIFICATIONS command to the device using the DeviceManager API. A good place to do this is the auto-connect event handler.

const serviceUuid = '...';
const characteristicUuid = '...';

public autoConnectEventHandler = async(request: IntentFlow.EventRequest):
  Promise<IntentFlow.EventResponse> => {

    const device = request.inputs[0].payload.device;

    // Subscribe to notifications
    const command = new DataFlow.BleRequestData();
    command.requestId = request.requestId;
    command.deviceId = device.id || 'light-device';
    command.serviceUuid = serviceUuid;
    command.characteristicUuid = characteristicUuid;
    command.operation = Constants.BleOperation.REGISTER_FOR_NOTIFICATIONS;

    // Send command to the device
    const manager: smarthome.DeviceManager = this.app.getDeviceManager();
    await manager.send(command);

    return {
      requestId: request.requestId,
      intent: smarthome.Intents.EVENT,
      payload: {}
    };
  };

This will enable the Local Home platform to send a PARSE_NOTIFICATION intent to your local fulfillment app each time a notification payload is generated by the BLE device.

Report characteristic state changes

Next, implement the PARSE_NOTIFICATION intent handler. When a notification is received from the device, pass the updated state to the Local Home platform using the reportState() method of DeviceManager.

public parseNotificationHandler = async(
  notificationRequest: smarthome.IntentFlow.ParseNotificationRequest):
  Promise<smarthome.IntentFlow.IndicateResponse> => {

    const deviceId = notificationRequest.inputs[0].payload.device.id;
    const notificationData =
        notificationRequest.inputs[0].payload.device.notificationData.value;

    // TODO: Convert notification data into device state

    const manager: smarthome.DeviceManager = this.app.getDeviceManager();
    const normalResponse: smarthome.ReportState.Response.Payload =
        new smarthome.ReportState.Response.Builder()
            .setRequestId(notificationRequest.requestId)
            .setState(deviceId, deviceState)
            .build();
    manager.reportState(normalResponse);

    const notificationResponse: smarthome.IntentFlow.ParseNotificationResponse = {
      requestId: notificationRequest.requestId,
      intent: smarthome.Intents.PARSE_NOTIFICATION,
      payload: {},
    };

    return notificationResponse;
  };

The new device state will be stored in Home Graph.

Testing and Debugging Seamless Setup

Testing your Seamless Setup implementation is an important step to ensure a high-quality user experience.

For more information about testing or debugging your local fulfillment app, see the Test and debug documentation.

Handling firmware updates

Firmware updates for devices configured using Seamless Setup over Bluetooth are not currently supported through the Actions console. If firmware updates are required for your device, you should not use this feature for setup.

Working with mesh networks

Some additional work is needed if your BLE devices form some type of mesh network. The advantage of using mesh networks is that Google Home or Google Nest devices do not need to maintain direct connection with each device in the mesh network. Mesh networks also extend the effective range of a Google Home or Google Nest devices and make it possible to reach the devices that are further from the originating device.

For the initial setup of a device, it must be reachable directly from a Google Home or Google Nest device. After initial setup, the device can participate in a mesh network. Note that Google Home or Google Nest device itself is not participating in the mesh as a node."

Dealing with BLE Mesh networks requires implementation of two additional intent handlers in your local fulfillment app. Before intent handlers can be triggered by the platform, the IDENTIFY handler needs a few changes to let the platform know about your use of mesh protocols:

  • In the IdentifyResponse field, set {isProxy: true} for each device that can act as a proxy in the mesh.

  • Set {commandedOverProxy: true} in the IdentifyResponse field for each device that can be controlled by the proxy device.

These flags trigger a state machine in the platform for proxy election. The platform sends REACHABLE_DEVICES intent periodically. The response from REACHABLE_DEVICES handler should be a list of all the devices reachable from this particular proxy device. When a proxy is selected, the platform sends PROXY_SELECTED intent to let your local fulfillment app know about the proxy device that are used to communicate with other nodes in the mesh. Once this happens, the connection between all the devices other than the proxy device are dropped.