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

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
- Familiarize yourself with the basics of creating a smart home Action.
- In the Actions console, make sure you have an existing smart home project.
- Make sure that you are logged in with the same Google account in the Actions console and in Assistant on your test device.
- You'll need a Node.js environment to write your app. For installing Node.js and npm, Node Version Manager is recommended.
- To work with the latest version of the Local Home SDK, you will need to enroll your test devices into the Cast Preview Program.
Set up the scan configuration for BLE
To specify the discovery information, follow these steps:
- Open your smart home project in the Actions console.
- In the left navigation, click Actions.
- Under Configure local home SDK (optional) > Add capabilities, enable Seamless Setup.
- Under Configure local home SDK (optional) > Add device scan configuration, click New scan config.
- 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 theIdentifyResponse
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.