# Discovery

Homey can automatically find devices on the user's Wi-Fi network using mDNS-SD, SSDP and MAC. This provides the best experience for a user, because entering an IP address —which can even change over time— is not a great experience.

{% hint style="info" %}
Homey Bridge does not support local Wi-Fi connections, and therefore mDNS, SSDP and MAC discovery are not supported on Homey Cloud.
{% endhint %}

As a bonus, when using Discovery in conjunction with a Driver, Homey manages the Device's availability state automatically.

{% hint style="info" %}
You can view a working example of a Homey App that uses Discovery at: <https://github.com/athombv/com.plugwise.adam-example>
{% endhint %}

## Choosing a discovery strategy

Devices can be discovered using different strategies but most devices use mDNS-SD. To list mDNS-SD devices in your network you can use [Discovery](https://apps.apple.com/us/app/discovery-dns-sd-browser/id305441017) on MacOS or [Bonjour browser](https://hobbyistsoftware.com/bonjourbrowser) on Windows. If a device is not discoverable with mDNS-SD please refer to your device's documentation to learn what strategy you can use for discovery.

### mDNS-SD

[Multicast-DNS Service Discovery](https://en.wikipedia.org/wiki/Multicast_DNS) is a widely used protocol to find devices on a network. It is also known as Avahi or Bonjour. A device broadcasts its presence under a `name` (as specified by the manufacturer) and a `protocol` (`tcp` or `udp`). For example, Homey broadcasts its presence with the name `homey` using the `tcp` protocol.

A `DiscoveryResultMDNSSD` has a `txt` property that contains the (lowercased) TXT values of the broadcast.

{% code title="/.homeycompose/discovery/nanoleaf-aurora.json" %}

```javascript
{
  "type": "mdns-sd",
  "mdns-sd": {
    "name": "nanoleafapi",
    "protocol": "tcp"
  },
  "id": "{{txt.id}}",
  "conditions": [
    [
      {
        "field": "txt.md",
        "match": {
          "type": "string",
          "value": "NL22"
        }
      }
    ]
  ]
}
```

{% endcode %}

{% hint style="info" %}
The discovery `conditions` are optional but highly recommended, you can use these to pre-filter the discovery results. This way your app only receives the discovery results of devices that can actually be paired using your app.
{% endhint %}

### SSDP

Devices using the [Simple Service Discovery Protocol](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol) can be found by specifying a `search` property.

A `DiscoveryResultSSDP` has a `headers` property that contains the (lowercased) headers of the response.

{% code title="/.homeycompose/discovery/denon-heos.json" %}

```javascript
{
  "type": "ssdp",
  "ssdp": {
    "search": "urn:schemas-denon-com:device:ACT-Denon:1"
  },
  "id": "{{headers.usn}}",
  "conditions": [
    [
      {
        "field": "headers.st",
        "match": {
          "type": "string",
          "value": "urn:schemas-denon-com:device:ACT-Denon:1"
        }
      }
    ]
  ]
}
```

{% endcode %}

### MAC

MAC Address discovery works by specifying the first 3 bytes of a network device's MAC address. These first three bytes are reserved for the manufacturer and can thus be used to find a device on the network using Address Resolution Protocol (ARP).

A `DiscoveryResultMAC` only has an `address` property, which contains the IP address of the device.

For example, to find a device with a MAC address that starts with `00:24:6d` or `00:24:6e`, convert them from hexadecimal to decimal.

{% code title="/.homeycompose/discovery/weinzierl.json" %}

```javascript
{
  "type": "mac",
  "mac": {
    "manufacturer": [
      [ 0, 36, 109 ],
      [ 0, 36, 110 ]
    ]
  }
}
```

{% endcode %}

{% hint style="info" %}
The MAC address must be specified in decimal numbers, because JSON does not support hexadecimal-notation.
{% endhint %}

## Defining your discovery strategy

### The Discovery Result

Depending on your discovery type, some properties are available to match on. For example, an mDNS-SD discovery type has a `txt` object and the SSDP discovery type has an `headers` object. The discovery result provides the address that you can use to connect to the device.

### Discovery Result ID

For the `mdns-sd` and `ssdp` discovery types, the app must define how a discovery result can be identified when it has been found multiple times, regardless if the IP address has changed.

For the `mac` discovery type, the mac address is used as the ID.

Find a unique and consistent property in the discovery result and define it as `id` in the App Manifest. Homey will then be able to match the result to previous results, and notify your app the device has been found again, instead of seeing the device as a new discovery result.

**Examples:**

```javascript
"id": "{{txt.id}}"
```

```javascript
"id": "{{headers.uuid}}"
```

All properties available in the DiscoveryResult are available between double curly braces (`{{` and `}}`).

### Discovery Result Conditions

A discovery strategy can have a set of conditions that must be true before the result is sent to the app.

The `conditions` property is an `Array` with one or more `Arrays` in it. This array contains `Objects` rules. When all rules within an array are true, the result is considered a match. Using multiple arrays behaves as `rulesArray1 OR rulesArray2 OR ...`.

There are two match types available: `string` and `regex`.

```javascript
"conditions": [
  [
    {
      "field": "txt.md",
      "match": {
        "type": "string",
        "value": "NL29"
      }
    },
    // AND:
    {
      "field": "txt.version",
      "match": {
        "type": "string",
        "value": "1"
      }
    }
  ],
  // OR:
  [
    {
      "field": "txt.md",
      "match": {
        "type": "regex",
        "value": "NL\\d\\d" // double slashes because of JSON
      }
    }
  ]
]
```

{% hint style="info" %}
Conditions are matched case-insensitive.
{% endhint %}

## Using discovery with a Driver

The recommended way to use Homey's built-in discovery is to link a discovery strategy to a Driver. If you use a discovery strategy with a Driver Homey will automatically manage the availability of your Devices. You can then use the `onDiscovery*` methods in your Device class to get updated whenever the status of the device changes.

To start using discovery with a driver, add the `discovery` property to your driver's entry in the App Manifest.

For example:

{% code title="/.homeycompose/discovery/my\_discovery.json" %}

```javascript
{
  "type": "mdns-sd",
  "mdns-sd": {
    "protocol": "tcp",
    "name": "my_service"
  }
}
```

{% endcode %}

{% code title="/drivers/\<driver\_id>/driver.compose.json" %}

```javascript
  "discovery": "my_discovery"
```

{% endcode %}

{% tabs %}
{% tab title="JavaScript" %}
In your `driver.js`, you can call [`Driver#getDiscoveryStrategy()`](https://apps-sdk-v3.developer.homey.app/Driver.html#getDiscoveryStrategy) to get the current strategy.

{% code title="/drivers/\<driver\_id>/driver.js" %}

```javascript
const Homey = require('homey');

class Driver extends Homey.Driver {
  async onPairListDevices() {
    const discoveryStrategy = this.getDiscoveryStrategy();
    const discoveryResults = discoveryStrategy.getDiscoveryResults();

    const devices = Object.values(discoveryResults).map(discoveryResult => {
      return {
        name: discoveryResult.txt.name,
        data: {
          id: discoveryResult.id,
        },
      };
    });

    return devices;
  }
}

module.exports = Driver;
```

{% endcode %}

In your `device.js`, overload the methods starting with `onDiscovery`.

{% code title="/drivers/\<driver\_id>/device.js" %}

```javascript
const Homey = require('homey');

class Device extends Homey.Device {
  onDiscoveryResult(discoveryResult) {
    // Return a truthy value here if the discovery result matches your device.
    return discoveryResult.id === this.getData().id;
  }

  async onDiscoveryAvailable(discoveryResult) {
    // This method will be executed once when the device has been found (onDiscoveryResult returned true)
    this.api = new MyDeviceAPI(discoveryResult.address);
    await this.api.connect(); // When this throws, the device will become unavailable.
  }

  onDiscoveryAddressChanged(discoveryResult) {
    // Update your connection details here, reconnect when the device is offline
    this.api.address = discoveryResult.address;
    this.api.reconnect().catch(this.error); 
  }

  onDiscoveryLastSeenChanged(discoveryResult) {
    // When the device is offline, try to reconnect here
    this.api.reconnect().catch(this.error); 
  }
}

module.exports = Device;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
In your `driver.mts`, you can call [`Driver#getDiscoveryStrategy()`](https://apps-sdk-v3.developer.homey.app/Driver.html#getDiscoveryStrategy) to get the current strategy.

{% code title="/drivers/\<driver\_id>/driver.mts" %}

```mts
import Homey, { type DiscoveryResultMDNSSD } from "homey";

export default class Driver extends Homey.Driver {
  async onPairListDevices(): Promise<object[]> {
    const discoveryStrategy = this.getDiscoveryStrategy();
    const dicoveryResults = discoveryStrategy.getDiscoveryResults() as { [id: string]: DiscoveryResultMDNSSD };

    const devices = Object.values(dicoveryResults).map(discoveryResult => ({
      name: discoveryResult.name,
      data: { id: discoveryResult.id },
    }));

    return devices;
  }
}

```

{% endcode %}

In your `device.mts`, overload the methods starting with `onDiscovery`.

{% code title="/drivers/\<driver\_id>/device.mts" %}

```mts
import Homey, { type DiscoveryResultMDNSSD } from "homey";
import MyDeviceApi from "device-api";

export default class Device extends Homey.Device {
  api?: MyDeviceApi;

  onDiscoveryResult(discoveryResult: DiscoveryResultMDNSSD): boolean {
    // Return a truthy value here if the discovery result matches your device.
    return discoveryResult.id === this.getData().id;
  }

  async onDiscoveryAvailable(discoveryResult: DiscoveryResultMDNSSD): Promise<void> {
    // This method is called when a discovery result matching this device is found, in order to set up a connection with the device.
    this.api = new MyDeviceApi(discoveryResult.address);
    await this.api.reconnect(); // Throwing an exception here will make the device unavailable with the exception's message.
  }

  onDiscoveryAddressChanged(discoveryResult: DiscoveryResultMDNSSD): void {
    // This method is called when the device was found again at a different address.
    if (this.api === undefined) return;
    this.api.address = discoveryResult.address;
    // Reconnect in case the device was offline
    this.api.reconnect().catch(this.error);
  }

  onDiscoveryLastSeenChanged(discoveryResult: DiscoveryResultMDNSSD): void {
    // This method is called when the device has been found again.
    if (this.api === undefined) return;
    // Reconnect in case the device was offline
    this.api.reconnect().catch(this.error);
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
In your `driver.py`, you can call [`Driver#get_discovery_strategy()`](https://python-apps-sdk-v3.developer.homey.app/driver.html#homey.driver.Driver.get_discovery_strategy) to get the current strategy.

{% code title="/drivers/\<driver\_id>/driver.py" %}

```python
import typing

from homey import driver
from homey.discovery_result_mdns_sd import DiscoveryResultMDNSSD
from homey.discovery_strategy import DiscoveryStrategy
from homey.driver import ListDeviceProperties


class Driver(driver.Driver):
    async def on_pair_list_devices(self, view_data: dict) -> list[ListDeviceProperties]:
        discovery_strategy = typing.cast(
            DiscoveryStrategy[DiscoveryResultMDNSSD], self.get_discovery_strategy()
        )
        discovery_results = discovery_strategy.get_discovery_results()
        return [
            {
                "name": discovery_result.name or "Device",
                "data": {"id": discovery_result.id},
            }
            for discovery_result in discovery_results.values()
        ]


homey_export = Driver

```

{% endcode %}

In your `device.py`, overload the methods starting with `onDiscovery`.

{% code title="/drivers/\<driver\_id>/device.py" %}

```python
from typing import cast

from device_api import MyDeviceApi
from homey import device
from homey.discovery_result import DiscoveryResult
from homey.discovery_result_mdns_sd import DiscoveryResultMDNSSD


class Device(device.Device):
    api: MyDeviceApi | None

    async def on_discovery_result(self, discovery_result: DiscoveryResult) -> bool:
        # Return a truthy value here if the discovery result matches your device.
        return discovery_result.id == self.get_data().get("id")

    async def on_discovery_available(self, discovery_result: DiscoveryResult) -> None:
        # This method is called when a discovery result matching this device is found, in order to set up a connection with the device.
        address = cast(DiscoveryResultMDNSSD, discovery_result).address
        if address is None:
            return
        self.api = MyDeviceApi(address)
        await self.api.reconnect()

    async def on_discovery_address_changed(
        self, discovery_result: DiscoveryResult
    ) -> None:
        # This method is called when the device was found again at a different address.
        if self.api is None or discovery_result.address is None:
            return
        self.api.address = discovery_result.address
        try:
            # Reconnect in case the device was offline
            await self.api.reconnect()
        except Exception as e:
            self.error(e)

    async def on_discovery_last_seen_changed(self, discovery_result: DiscoveryResult):
        # This method is called when the device has been found again.
        if self.api is None:
            return
        try:
            # Reconnect in case the device was offline
            await self.api.reconnect()
        except Exception as e:
            self.error(e)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

## Using discovery standalone

{% tabs %}
{% tab title="JavaScript" %}
Simply call [`ManagerDiscovery#getStrategy()`](https://apps-sdk-v3.developer.homey.app/ManagerDiscovery.html#getStrategy) with the discovery strategy ID as defined in your App Manifest. You can then call [`DiscoveryStrategy#getDiscoveryResults()`](https://apps-sdk-v3.developer.homey.app/DiscoveryStrategy.html#getDiscoveryResults) to get the devices that already have been discovered and listen to the `result` event on the `DiscoveryStrategy` to react to newly discovered devices while the app is running.

{% code title="/app.js" %}

```javascript
const Homey = require('homey');

class App extends Homey.App {
  async onInit() {
    const discoveryStrategy = this.homey.discovery.getStrategy("my_strategy");

    // Use the discovery results that were already found
    const initialDiscoveryResults = discoveryStrategy.getDiscoveryResults();
    for (const discoveryResult of Object.values(initialDiscoveryResults)) {
      this.handleDiscoveryResult(discoveryResult);
    }

    // And listen to new results while the app is running
    discoveryStrategy.on("result", discoveryResult => {
      this.handleDiscoveryResult(discoveryResult);
    });
  }

  handleDiscoveryResult(discoveryResult) {
    this.log("Got result:", discoveryResult);
  }
}

module.exports = App;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
Simply call [`ManagerDiscovery#getStrategy()`](https://apps-sdk-v3.developer.homey.app/ManagerDiscovery.html#getStrategy) with the discovery strategy ID as defined in your App Manifest. You can then call [`DiscoveryStrategy#getDiscoveryResults()`](https://apps-sdk-v3.developer.homey.app/DiscoveryStrategy.html#getDiscoveryResults) to get the devices that already have been discovered and listen to the `result` event on the `DiscoveryStrategy` to react to newly discovered devices while the app is running.

{% code title="/app.mts" %}

```mts
import Homey, { type DiscoveryResultMAC } from "homey";

export default class App extends Homey.App {
  async onInit(): Promise<void> {
    const discoveryStrategy = this.homey.discovery.getStrategy("my_strategy");

    // Use the discovery results that were already found
    const initialDiscoveryResults = discoveryStrategy.getDiscoveryResults() as { [id: string]: DiscoveryResultMAC };
    for (const discoveryResult of Object.values(initialDiscoveryResults)) {
      this.handleDiscoveryResult(discoveryResult);
    }

    // And listen to new results while the app is running
    discoveryStrategy.on("result", (discoveryResult: DiscoveryResultMAC) => {
      this.handleDiscoveryResult(discoveryResult);
    });
  }

  handleDiscoveryResult(discoveryResult: DiscoveryResultMAC): void {
    this.log("Got result:", discoveryResult);
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
Simply call [`ManagerDiscovery#get_strategy()`](https://python-apps-sdk-v3.developer.homey.app/manager/discovery.html#homey.manager.discovery.ManagerDiscovery.get_strategy) with the discovery strategy ID as defined in your App Manifest. You can then call [`DiscoveryStrategy#get_discovery_results()`](https://python-apps-sdk-v3.developer.homey.app/discovery_strategy.html#homey.discovery_strategy.DiscoveryStrategy.get_discovery_results) to get the devices that already have been discovered and listen to the `result` event on the `DiscoveryStrategy` to react to newly discovered devices while the app is running.

{% code title="/app.py" %}

```python
from typing import cast

from homey import app
from homey.discovery_result_mac import DiscoveryResultMAC


class App(app.App):
    async def on_init(self) -> None:
        discovery_strategy = self.homey.discovery.get_strategy("my_strategy")

        initial_discovery_results = discovery_strategy.get_discovery_results()
        for discovery_result in initial_discovery_results.values():
            self.handle_discovery_result(cast(DiscoveryResultMAC, discovery_result))

        discovery_strategy.on("result", self.handle_discovery_result)

    def handle_discovery_result(self, discovery_result: DiscoveryResultMAC):
        self.log("Got result:", discovery_result)


homey_export = App

```

{% endcode %}
{% endtab %}
{% endtabs %}
