-
Notifications
You must be signed in to change notification settings - Fork 39
How to support new devices
Warning: To support new devices you need some basic coding skills. Also, take a look at Homebridge API to fully understand the following example.
Fork the repository using the fork button in GitHub (see the help for more information) and create a new branch. Once you are done with your modifications, open a PR on this repo and wait it to be reviewed and merged.
Note: This plugin is based on Herdsman and Herdsman Converters libraries. It uses the former to access the ZigBee protocol and the latter to talk with different devices.
In the following steps we will walk through adding an Ikea TRADFRI SHORTCUT Button. A complete implementation of the button accessory can be found here.
The first thing you should do when dealing with a new device is to verify it is supported be the herdsman
library. It can be done by checking the list of supported devices of zigbee2MQTT project here.
Once you know the device is supported, you can go ahead and pair it using the suggested method in the above page. Remember that in order to let the dongle accept new devices, you need to set it into permit join
state: with this plugin it can be done by using the virtual switch exposed in the Home App on your iPhone/iPad/iWhatever.
When pairing, keep Homebridge logs open, you should see something like this:
[12/28/2020, 18:22:00] [ZigBee] Found ZigBee device: Device {
ID: 14,
_type: 'EndDevice',
_ieeeAddr: '0xec1bbdfffed0a229',
_networkAddress: 26557,
_manufacturerID: 4476,
_endpoints: [
Endpoint {
ID: 1,
profileID: 260,
deviceID: 2080,
inputClusters: [Array],
outputClusters: [Array],
deviceNetworkAddress: 26557,
deviceIeeeAddress: '0xec1bbdfffed0a229',
clusters: [Object],
_binds: [],
meta: {}
}
],
_manufacturerName: 'IKEA of Sweden',
_powerSource: 'Battery',
_modelID: 'TRADFRI SHORTCUT Button',
_applicationVersion: 33,
_stackVersion: 98,
_zclVersion: 3,
_hardwareVersion: 1,
_dateCode: '20190715',
_softwareBuildID: '2.3.015',
_interviewCompleted: true,
_interviewing: false,
meta: {},
_lastSeen: 1609166249871
}
[12/28/2020, 18:22:00] [ZigBee] Unrecognized device: 0xec1bbdfffed0a229 IKEA of Sweden TRADFRI SHORTCUT Button
The log is useful to register the class you need to implement for your new accessory: the important information are _manufacturerName
and _modelID
, write them down somewhere, you will use once you are done with your coding.
Once you identified your model, you need to add a new class for it. The class will be created by the platform once it will recognise the device.
The class represent an HomeKit device for Homebridge and must extend the base abstract class ZigBeeAccessory. Since this class is abstract
you'll need at least to implement the method:
public abstract getAvailableServices(): Service[];
where you have to return an array of HomeKit services exposed by your device (you can use pre-defined builders to build some very simple service if you want).
In our case (TRADFRI SHORTCUT Button) we need to expose a StatelessProgrammableSwitch
HomeKit Service to map the button and let the Home App to show and configure it. Documentation about this can be found here. In general, I would suggest reading some document about writing Homebridge plugins, since you need basic knowledge on its API.
Since most of the devices share the service configuration part, I created some builder you can reuse (or improve) to create your own classes. Take a look at the folder builders in the src
folder to know which builder is already implemented. In the case of a button, we can use the ProgrammableSwitchServiceBuilder
and declare our button.
Let's create our class and implement the requested method:
export class IkeaShurtcutSwitch extends ZigBeeAccessory {
protected switchServiceOn: Service;
getAvailableServices(): Service[] {
const builder = new ProgrammableSwitchServiceBuilder(
this.platform,
this.accessory,
this.client,
this.state
);
// Declare the button service: it will handle press and long press events
const ProgrammableSwitchEvent = this.platform.Characteristic.ProgrammableSwitchEvent;
[this.switchServiceOn] = builder
.withStatelessSwitch('ON', 'on', 1, [
ProgrammableSwitchEvent.SINGLE_PRESS,
ProgrammableSwitchEvent.LONG_PRESS,
])
.build();
return [this.switchServiceOn];
}
}
The final step is the registration of your device: in the index.ts
file you need to register all the models you want to map on your new HomeKit accessory. The information you need for adding your accessory are the _manufacturerName
and _modelID
you found when pairing the device the first time.
For this example, we need to add this line:
import { IkeaShurtcutSwitch } from './accessories/ikea/ikea-shurtcut-switch';
function registerSupportedDevices(): void {
// ...
registerAccessoryClass('IKEA of Sweden' /* _manufacturerName */, ['TRADFRI SHORTCUT Button' /* _modelID */], IkeaShurtcutSwitch);
}
Note that a new HomeKit accessory can map multiple ZigBee devices: in many cases, the code will be the same since the real difference is implemented inside the communication layer (i.e. the converters). Take a look at Philips bulbs implementation for an example of this.
Build your plugin and restart Homebridge. If everything is correct, you will now see the button between your home devices and when (physically) interacting with it you should see something in logs like this:
[12/30/2020, 20:20:18] [ZigBee] Zigbee message from 0xec1bbdfffed0a229 commandOn
[12/30/2020, 20:20:18] [ZigBee] Decoded state from incoming message { action: 'on' }
[12/30/2020, 20:20:18] [ZigBee] Updating state of device TRADFRI shortcut button: { action: 'on' }
[12/30/2020, 20:20:28] [ZigBee] Zigbee message from 0xec1bbdfffed0a229 commandMoveWithOnOff
[12/30/2020, 20:20:28] [ZigBee] Decoded state from incoming message { action: 'brightness_move_up', action_rate: 83 }
[12/30/2020, 20:20:28] [ZigBee] Updating state of device TRADFRI shortcut button: { action: 'brightness_move_up', action_rate: 83 }
[12/30/2020, 20:20:29] [ZigBee] Zigbee message from 0xec1bbdfffed0a229 commandStopWithOnOff
[12/30/2020, 20:20:29] [ZigBee] Decoded state from incoming message { action: 'brightness_stop' }
[12/30/2020, 20:20:29] [ZigBee] Updating state of device TRADFRI shortcut button: { action: 'brightness_stop' }
Good! It works, but nothing is still happening. We need more work.
The above steps are enough to map the device and see it in the Home App, but unless some extreme cases, you need to add some logic to map messages coming from the device into HomeKit device states. This has to be done inside the update(state: DeviceState): void
function of your accessory class.
Let's go ahead and override the base function (and remember to call the super implementation, in order to maintain the state of the device updated everywhere) in our IkeaShurtcutSwitch.ts
file based on the logs we saw before:
// ...
update(state: DeviceState) {
super.update(state); // remember this!
const ProgrammableSwitchEvent = this.platform.Characteristic.ProgrammableSwitchEvent;
switch (state.action) {
case 'brightness_move_up':
case 'brightness_move_down':
this.switchServiceOn
.getCharacteristic(ProgrammableSwitchEvent)
.setValue(ProgrammableSwitchEvent.LONG_PRESS);
break;
case 'on':
this.switchServiceOn
.getCharacteristic(ProgrammableSwitchEvent)
.setValue(ProgrammableSwitchEvent.SINGLE_PRESS);
break;
}
}
// ...
This code is essentially telling to Homebridge/HomeKit to trigger the ProgrammableSwitchEvent.SINGLE_PRESS
when state.action === 'on'
and ProgrammableSwitchEvent.LONG_PRESS
when state.action === 'brightness_move_up' || state.action === 'brightness_move_down'
. This code will finally bring the event from ZigBee device to the HomeKit accessory!
The final class looks like this:
import { ZigBeeAccessory } from '../zig-bee-accessory';
import { Service } from 'homebridge';
import { DeviceState } from '../../zigbee/types';
import { ProgrammableSwitchServiceBuilder } from '../../builders/programmable-switch-service-builder';
export class IkeaShurtcutSwitch extends ZigBeeAccessory {
protected switchServiceOn: Service;
getAvailableServices(): Service[] {
const builder = new ProgrammableSwitchServiceBuilder(
this.platform,
this.accessory,
this.client,
this.state
);
const ProgrammableSwitchEvent = this.platform.Characteristic.ProgrammableSwitchEvent;
[this.switchServiceOn] = builder
.withStatelessSwitch('ON', 'on', 1, [
ProgrammableSwitchEvent.SINGLE_PRESS,
ProgrammableSwitchEvent.LONG_PRESS,
])
.build();
return [this.switchServiceOn];
}
update(state: DeviceState) {
super.update(state);
const ProgrammableSwitchEvent = this.platform.Characteristic.ProgrammableSwitchEvent;
switch (state.action) {
case 'brightness_move_up':
case 'brightness_move_down':
this.switchServiceOn
.getCharacteristic(ProgrammableSwitchEvent)
.setValue(ProgrammableSwitchEvent.LONG_PRESS);
break;
case 'on':
this.switchServiceOn
.getCharacteristic(ProgrammableSwitchEvent)
.setValue(ProgrammableSwitchEvent.SINGLE_PRESS);
break;
}
}
}
A more "sophisticated" implementation (with battery support) can be found here.