Discovery

Discover LAN devices using mDNS-SD, SSDP or Manufacturer's MAC address (ARP).

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.

Homey Bridge does not support local Wi-Fi connections, and therefore mDNS, SSDP and MAC discovery are not supported on Homey Cloud.

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

You can view a working example of a Homey App that uses Discovery at: https://github.com/athombv/com.plugwise.adam-example

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 on MacOS or Bonjour browser 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 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.

/.homeycompose/discovery/nanoleaf-aurora.json
{
  "type": "mdns-sd",
  "mdns-sd": {
    "name": "nanoleafapi",
    "protocol": "tcp"
  },
  "id": "{{txt.id}}",
  "conditions": [
    [
      {
        "field": "txt.md",
        "match": {
          "type": "string",
          "value": "NL22"
        }
      }
    ]
  ]
}

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.

SSDP

Devices using the 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.

/.homeycompose/discovery/denon-heos.json
{
  "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"
        }
      }
    ]
  ]
}

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.

/.homeycompose/discovery/weinzierl.json
{
  "type": "mac",
  "mac": {
    "manufacturer": [
      [ 0, 36, 109 ],
      [ 0, 36, 110 ]
    ]
  }
}

The MAC address must be specified in decimal numbers, because JSON does not support hexadecimal-notation.

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:

"id": "{{txt.id}}"
"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.

"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
      }
    }
  ]
]

Conditions are matched case-insensitive.

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:

/.homeycompose/discovery/my_discovery.json
{
  "type": "mdns-sd",
  "mdns-sd": {
    "protocol": "tcp",
    "name": "my_service"
  }
}
/drivers/<driver_id>/driver.compose.json
  "discovery": "my_discovery"

In your driver.js, you can call Driver#getDiscoveryStrategy() to get the current strategy.

/drivers/<driver_id>/driver.js
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;

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

/drivers/<driver_id>/device.js
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;

Using discovery standalone

Simply call ManagerDiscovery#getStrategy() with the discovery strategy ID as defined in your App Manifest. You can then call DiscoveryStrategy#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.

/app.js
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;

Last updated