Pairing

Pairing allows users to add new devices to Homey.

Pairing is started when the user selects the device they want to add from the Homey app. The pair property of the driver defines a list of views, which the user navigates through. These views are called pairing templates. Homey includes a set of system templates that implement consistent pairing steps for most devices.

Basic pairing example

This example is a basic way to enable pairing in your driver. To add pairing to your driver, add the following to your driver.compose.json:

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"class": "socket",
"capabilities": ["onoff"],
"pair": [
{
"id": "list_devices",
// we use a system template here, for consistency, and less work for us!
"template": "list_devices",
// show pair view with id 'add_my_devices' when clicked 'Next'
"navigation": { "next": "add_my_devices" }
},
{
"id": "add_my_devices",
// again, use a template
"template": "add_devices"
}
]
}
/drivers/<driver_id>/driver.js
const Homey = require('homey');
class MyDriver extends Homey.Driver {
async onPairListDevices() {
const devices = [
{
name: "My Device",
data: {
id: "abcd",
},
},
];
return devices;
}
}
module.exports = MyDriver;

System templates

The pairing templates are built with HTML, CSS and JavaScript. Most drivers will suffice using the system templates that are provided by Homey.

Devices List

"template": "list_devices"

This view will show a list of selectable devices to the user, and allows the user to rename the device as well.

Devices are automatically filtered and updated, based on their data property.

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"pair": [
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "add_devices" },
"options": { "singular": true }
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}

Options

Key

Type

Default

Description

singular

boolean

false

Only allow a single device to be selected

/drivers/<driver_id>/driver.js
const Homey = require("homey");
class MyDriver extends Homey.Driver {
async onPair(session) {
const devices = [
{
// The name of the device that will be displayed
name: "My Device",
// The data object is required and should contain only unique properties for the device.
// So a MAC address is good, but an IP address is bad (can change over time)
data: {
id: "abcd",
},
// Optional: The store is dynamic and persistent storage for your device
store: {
// For example store the IP address of your device
address: "127.0.0.1",
},
// Optional: Initial device settings that can be changed by the user afterwards
settings: {
pincode: "1234",
},
// Optional: These overwrite properties overwrite those specified in the app manifest:
icon: "/my_icon.svg", // relative to: /drivers/<driver_id>/assets/
capabilities: ["onoff", "target_temperature"],
capabilitiesOptions: {
target_temperature: {
min: 5,
max: 35,
},
},
},
];
session.setHandler("list_devices", async function () {
// emit when devices are still being searched
session.emit("list_devices", devices);
// return devices when searching is done
return devices;
// when no devices are found, return an empty array
// return [];
// or throw an Error to show that instead
// throw new Error('Something bad has occured!');
});
}
}
module.exports = MyDriver;

Because the list_device template is very common the Driver#onPairListDevices() method exists which you can implement instead of the Driver#onPair() method. You can return the list of available devices directly from this method.

Add Devices

"template": "add_devices"

This view will simply add the devices as selected by devices_list, and finish the pairing session.

OAuth2 Login

"template": "login_oauth2"

This view can be used for devices that need OAuth2 authorization. When it's successful, it will automatically proceed to the next view.

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"pair": [
{
"id": "login_oauth2",
"template": "login_oauth2",
"options": {
"hint": "Login with your credentials",
"button": "Log-in"
}
},
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "add_devices" }
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}

This pair template works best with the homey-oauth2app module.

Options

Key

Type

Default

Description

hint

translation object

""

button

translation object

""

When either hint or button are set to a value, a button will appear and wait for the user to click it before opening the popup.

/drivers/<driver_id>/driver.js
const Homey = require("homey");
const API_URL = "https://api.myservice.com/oauth2/authorise?response_type=code";
const CALLBACK_URL = "https://callback.athom.com/oauth2/callback/";
const CLIENT_ID = Homey.env.CLIENT_ID;
const OAUTH_URL = `${API_URL}&client_id=${CLIENT_ID}&redirect_uri=${CALLBACK_URL}`;
class MyDriver extends Homey.Driver {
async onPair(session) {
const myOAuth2Callback = await this.homey.cloud.createOAuth2Callback(OAUTH_URL);
myOAuth2Callback
.on("url", (url) => {
// dend the URL to the front-end to open a popup
session.emit("url", url);
})
.on("code", (code) => {
// ... swap your code here for an access token
// tell the front-end we're done
session.emit("authorized");
});
}
}
module.exports = MyDriver;

Credentials Login

"template": "login_credentials"

This pair template shows a username & password view where the user can enter credentials.

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"pair": [
{
"id": "login_credentials",
"template": "login_credentials",
"options": {
"logo": "logo.png",
"usernameLabel": { "en": "E-mail address" },
"usernamePlaceholder": { "en": "[email protected]" },
"passwordLabel": { "en": "Password" },
"passwordPlaceholder": { "en": "Password" }
}
},
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "add_devices" }
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}

Options

Key

Type

Default

Description

logo

string

""

A path to an image for a logo

usernameLabel

translation object

"E-mail address"

usernamePlaceholder

translation object

"[email protected]"

passwordLabel

translation object

"Password"

passwordPlaceholder

translation object

"Password"

/drivers/<driver_id>/driver.js
const Homey = require("homey");
const DeviceAPI = require("device-api");
class MyDriver extends Homey.Driver {
async onPair(session) {
let username = "";
let password = "";
session.setHandler("login", async (data) => {
username = data.username;
password = data.password;
const credentialsAreValid = await DeviceAPI.testCredentials({
username,
password,
});
// return true to continue adding the device if the login succeeded
// return false to indicate to the user the login attempt failed
// thrown errors will also be shown to the user
return credentialsAreValid;
});
session.setHandler("list_devices", async () => {
const api = await DeviceAPI.login({ username, password });
const myDevices = await api.getDevices();
const devices = myDevices.map((myDevice) => {
return {
name: myDevice.name,
data: {
id: myDevice.id,
},
settings: {
// Store username & password in settings
// so the user can change them later
username,
password,
},
};
});
return devices;
});
}
}
module.exports = MyDriver;

Pincode

"template": "pincode"

This pair template shows a pincode input. When the pincode is correct, it will proceed to the next view.

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"pair": [
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "pincode" }
},
{
"id": "pincode",
"template": "pincode",
"options": {
"title": "Enter pincode:",
"hint": "Enter the device's pincode",
"type": "number",
"length": 4
}
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}

Options

Key

Type

Default

Description

type

string

"number"

Either number or text

length

number

4

The number of characters

hint

translation object

""

title

translation object

"Enter pincode:"

/drivers/<driver_id>/driver.js
const Homey = require("homey");
class MyDriver extends Homey.Driver {
onPair(session) {
session.setHandler("pincode", async (pincode) => {
return pincode === "1234";
});
}
}
module.exports = MyDriver;

Loading

"template": "loading"

This view will show a loading indicator. It's usually useful to show this view when an asynchronous operation needs to be made.

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"pair": [
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "loading" }
},
{
"id": "loading",
"template": "loading"
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}
/drivers/<driver_id>/driver.js
const Homey = require("homey");
const DeviceAPI = require("device-api");
class MyDriver extends Homey.Driver {
onPair(session) {
session.setHandler("list_devices", async () => {
return [
{
name: "My Device",
data: {
id: "abcd",
},
},
];
});
session.setHandler('showView', async (view) => {
if (view === 'loading') {
await DeviceAPI.connect();
await session.nextView();
}
});
}
}
module.exports = MyDriver;

Done

"template": "done"

This view will automatically close the pair session.

Custom Views

Most drivers will suffice using the provided templates. In certain cases you may want, or need, to create pairing screens that are more suited to your driver. For these cases it is possible to create custom pairing views, to learn more read the custom pairing view guide.

Repairing

To ensure users with a great experience, your app's devices should always stay available without user interaction.

However, sometimes when a device explicitly needs user interaction to be fixed (for example an OAuth2 token has been revoked and the user needs to authenticate again), the user can initiate a repair process.

To enable repairing, you must add support for this to your driver by adding repair to your App Manifest:

/drivers/<driver_id>/driver.compose.json
{
"name": { "en": "My Driver" },
"images": {
"small": "/drivers/my_driver/assets/images/small.png",
"large": "/drivers/my_driver/assets/images/large.png"
},
"repair": [
{
"id": "login_oauth2",
"template": "login_oauth2"
}
]
}

It is also possible to use custom pairing views for repairing. To learn more about custom pairing templates read the custom pairing view guide.

Note that when repairing the Homey.createDevice() method is not available in the custom view and you can add a onRepair method to your driver, which is similar to the onPair method.

/drivers/<driver_id>/driver.js
const Homey = require("homey");
class MyDriver extends Homey.Driver {
onRepair(session, device) {
// Argument session is a PairSocket, similar to Driver.onPair
// Argument device is a Homey.Device that's being repaired
session.setHandler("my_event", (data) => {
// Your code
});
session.setHandler("disconnect", () => {
// Cleanup
});
}
}
module.exports = MyDriver;