To support local fulfillment, you need to build an app to handle these
smart home intents:

- `IDENTIFY`: Supports discovery of locally-controllable smart devices. The intent handler extracts data that your smart device returns during discovery and sends this in a response to Google.
- `EXECUTE`: Supports execution of commands.
- `QUERY`: Supports querying device state.
- `REACHABLE_DEVICES`: (Optional) Supports discovery of locally-controllable end devices behind a hub (or bridge) device.

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](https://github.com/google-home/local-home-sdk)
to statically ensure that the data your app returns match the types that
the platform expects.

For more details about the API, see the
[Local Home SDK API reference](https://developers.home.google.com/reference/local-home.md.txt).

The following snippets show how you might initialize the local fulfillment app and
attach your handlers.
Standalone

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

## Create your project

In order to deploy your local fulfillment app, you need to build a JavaScript bundle
for your code and all its dependencies.

Use the local fulfillment app [project
initializer](https://github.com/actions-on-google/create-local-home-app)
to bootstrap the appropriate project structure with your preferred bundler
configuration.

### Project templates

To select your bundler configuration, run the `npm init` command as shown in the
following examples:
None

TypeScript with no bundler configuration:

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

Project structure:

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


Replace <var translate="no">project-directory</var> with a new directory that will contain the
local fulfillment app project.
Webpack

TypeScript with
[webpack](https://webpack.js.org/) bundler
configuration:

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

Project structure:

```
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
```


Replace <var translate="no">project-directory</var> with a new directory that will contain the
local fulfillment app project.
Rollup

TypeScript with [Rollup](https://rollupjs.org/)
bundler configuration:

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

Project structure:

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


Replace <var translate="no">project-directory</var> with a new directory that will contain the
local fulfillment app project.
Parcel

TypeScript with [Parcel](https://parceljs.org/)
bundler configuration:

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

Project structure:

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


Replace <var translate="no">project-directory</var> with a new directory that will contain the
local fulfillment app project.

### Perform common project-level tasks

The generated project supports the following [npm
scripts](https://docs.npmjs.com/misc/scripts):
Bundle

```
cd project-directory/
npm run build
```


This script compiles TypeScript source, and bundles your app with its dependencies for the Chrome runtime environment in the `dist/web` subdirectory and the Node.js runtime environment in the `dist/node` subdirectory.
Verify

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


This script verifies the syntax of your TypeScript code, compiles it without producing any output in the `dist/` subdirectory, and runs automated tests from `test.ts`.
Serve

```
cd project-directory/
npm run start
```


During development, this script serves your app bundles for the Chrome and Node.js runtime environments locally.

## Implement the IDENTIFY handler

The `IDENTIFY` handler will be triggered when the Google Home or Google Nest device reboots and
sees unverified local devices (including end devices connected to a hub). The
Local Home platform will scan for local devices using the scan config information
you specified earlier and call your `IDENTIFY` handler with the scan results.

The
[`IdentifyRequest`](https://developers.home.google.com/reference/local-home/modules/smarthome.intentflow.md.txt#identifyrequest)
from the Local Home platform contains the scan data of a
[`LocalIdentifiedDevice`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.localidentifieddevice.md.txt)
instance. Only one `device` instance is populated, based on the scan config
that discovered the device.

If the scan results match your device, your `IDENTIFY` handler should return an
[`IdentifyResponsePayload`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.identifyresponsepayload.md.txt)
object, that includes a `device` object with
smart home metadata (such as the types, traits, and report state).

Google establishes a device association if
the `verificationId` from the `IDENTIFY` response matches one of the
`otherDeviceIds` values returned by the `SYNC` response.

> [!NOTE]
> **Note:** The `verificationId` must match one of the `otherDeviceIds` in your `SYNC` response, in order for the Local Home platform to know it's the same device (this process is called *deduplication*).

### Example

The following snippets show how you might create `IDENTIFY` handlers for
standalone device and hub integrations, respectively.
Standalone

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

### Identify devices behind a hub

If Google identifies a hub device, it will treat the hub as the conduit
to the hub's connected end devices and attempt to verify those end devices.

To enable Google to confirm that a hub device is present, follow these
instructions for your `IDENTIFY` handler:

- If your `SYNC` response reports the IDs of local end devices connected to the hub, set `isProxy` as `true`in the [`IdentifyResponsePayload`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.identifyresponsepayload.md.txt).
- If your `SYNC` response does not report your hub device, set `isLocalOnly` as `true` in the [`IdentifyResponsePayload`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.identifyresponsepayload.md.txt).
- The `device.id` field contains the local device ID for the hub device itself.

## Implement the REACHABLE_DEVICES handler (hub integrations only)

The `REACHABLE_DEVICES` intent is sent by Google to confirm which end devices
can be locally controlled. This intent is triggered every time Google runs a
discovery scan (roughly once every minute), as long as the hub is detected to
be online.

You implement the `REACHABLE_DEVICES` handler similarly to the `IDENTIFY`
handler, except that your handler needs to gather additional device IDs
reachable by the local proxy (that is, the hub) device. The
`device.verificationId` field contains the local device ID for an end device
that is connected to the hub.

The
[`ReachableDevicesRequest`](https://developers.home.google.com/reference/local-home/modules/smarthome.intentflow.md.txt#reachabledevicesrequest)
from the Local Home platform contains an instance of
[`LocalIdentifiedDevice`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.localidentifieddevice.md.txt).
Through this instance, you can get the proxy device ID as well as data from
the scan results.

Your `REACHABLE_DEVICES` handler should return a
[`ReachableDevicesPayload`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.intentflow.reachabledevicespayload.md.txt)
object that includes a `devices` object that contains an array of
`verificationId` values representing the end devices that the hub controls. The
`verificationId` values must match one of the `otherDeviceIds` from the
`SYNC` response.

The following snippet shows how you might create your `REACHABLE_DEVICES`
handler.
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;
  };
```

## Implement the EXECUTE handler

Your `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`](https://developers.home.google.com/cloud-to-cloud/intents/execute.md.txt)
intent to your cloud fulfillment. Likewise, your `EXECUTE` handler returns
output data in the same format as from processing the `EXECUTE` intent.
To simplify the response creation, you can use the
[`Execute.Response.Builder`](https://developers.home.google.com/reference/local-home/classes/smarthome.execute.response.builder.md.txt)
class that the Local Home SDK provides.

Your app does not have direct access to the IP address of the device. Instead,
use the
[`CommandRequest`](https://developers.home.google.com/reference/local-home/modules/smarthome.dataflow.md.txt#commandrequest)
interface to create commands based on one of these protocols: UDP, TCP, or HTTP. Then, call the
[`deviceManager.send()`](https://developers.home.google.com/reference/local-home/interfaces/smarthome.devicemanager.md.txt#send)
function to send the commands.

When targeting commands to devices, use the device ID (and parameters from the
`customData` field, if included) from the `SYNC` response to communicate
with the device.

### Example

The following code snippet shows how you might create your `EXECUTE` handler.
Standalone/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());
  };
```

## Implement the QUERY handler

Your `QUERY` handler in the app processes user requests and uses the
Local Home SDK to report the state of your smart devices.

The Local Home platform passes the same request payload to the 'QUERY' handler
function as for the [`QUERY`](https://developers.home.google.com/cloud-to-cloud/intents/query.md.txt)
intent to your cloud fulfillment. Similarly, your `QUERY` handler returns data
in the same format as from processing the `QUERY` intent.

### Sending commands to devices behind a hub

To control end devices behind a hub, you may need to provide extra information
in the protocol-specific command payload sent to the hub in order for the hub
to identify which device the command is aimed for. In some cases, this can be
directly inferred from the `device.id` value, but when this is not the case,
you should include this extra data as part of the `customData` field.

If you created your app using TypeScript, remember to compile your app to
JavaScript. You can use the module system of your choice to write your code.
Make sure your target is supported by the Chrome browser.