Skip to content

Commit

Permalink
Improved device exclusion. Updated README with help on running the de…
Browse files Browse the repository at this point in the history
…velopment version.
  • Loading branch information
itavero committed Jul 5, 2020
1 parent 0e53483 commit 93e60b4
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 26 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ As soon as the project reaches a mature and stable state, the first major versio
and after the project will apply [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased][]
### Added

- Devices can now be excluded using their `friendly_name` as well as their IEEE address.

### Changed

- README now mentions how to run the "development" version.

## [0.0.3][] - 2020-07-01
### Added
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ If you find your issue already exists, make relevant comments and add your [reac
If you cannot find an existing issue that describes your bug or feature, feel free to submit an new issue.
Please try to provide enough (background) information.

### Testing the latest changes
After a bug fix or new feature is pushed to GitHub, you can already try it even before it is released.
There's two ways to install the latest stuff from GitHub on your machine:

1. Use the install command: `npm i -g itavero/homebridge-z2m#master` (in which `master` is the name of the branch)
2. Checkout the repository locally, and run the following commands:
```
cd path/to/git/repository
npm i
npm run build
npm link
```

## Installation
> ⚠️ This plugin is still under active development. Things might break between release.
Expand Down Expand Up @@ -65,7 +78,7 @@ Within the `mqtt` object, you can add pretty much all the configuration options

Please refer to the [zigbee2mqtt documentation](https://www.zigbee2mqtt.io/information/configuration.html) for more information on the MQTT options.

Within `devices.exclude` you can put an array with the IEEE addresses of the Zigbee devices you wish to exclude from this integration.
Within `devices.exclude` you can put an array with the IEEE addresses, or the `friendly_name`, of the Zigbee devices you wish to exclude from this integration.

## How it (should 😉) work
The plugin listens to the [MQTT messages](https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html) published by zigbee2mqtt.
Expand Down
5 changes: 4 additions & 1 deletion src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ export interface Zigbee2mqttDeviceInfo {
powerSource?: string;
modelID?: string;
hardwareVersion?: number;
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isDeviceInfo = (x: any): x is Zigbee2mqttDeviceInfo => (x.ieeeAddr && x.friendly_name);
62 changes: 39 additions & 23 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig,

import { PLATFORM_NAME, PLUGIN_NAME } from './settings';
import { Zigbee2mqttAccessory } from './platformAccessory';
import { Zigbee2mqttDeviceInfo } from './models';
import { Zigbee2mqttDeviceInfo, isDeviceInfo } from './models';

import * as mqtt from 'mqtt';
import * as fs from 'fs';
Expand Down Expand Up @@ -80,8 +80,6 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
// in order to ensure they weren't added to homebridge already. This event can also be used
// to start discovery of new accessories.
this.api.on('didFinishLaunching', () => {
log.debug('Executed didFinishLaunching callback');

// Setup MQTT callbacks and subscription
this.MqttClient.on('message', this.onMessage);
this.MqttClient.subscribe(config.mqtt.base_topic + '/#');
Expand All @@ -108,22 +106,24 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
// Probably a status update from a device
this.handleDeviceUpdate(topic, state);
} else {
this.log.debug(`Received message on '${topic}', but it was not handled`);
this.log.debug(`Unhandled message on topic: ${topic}`);
}
}

private async handleDeviceUpdate(topic: string, state: Record<string, unknown>) {
const accessory = this.accessories.find((acc) => acc.matchesIdentifier(topic));
if (accessory) {
accessory.updateStates(state);
} else {
this.log.debug(`Device '${topic}' not found for update.`);
if (!this.isDeviceExcluded(topic)) {
const accessory = this.accessories.find((acc) => acc.matchesIdentifier(topic));
if (accessory) {
accessory.updateStates(state);
} else {
this.log.debug(`Device '${topic}' not found for update.`);
}
}
}

private handleReceivedDevices(devices: Zigbee2mqttDeviceInfo[]) {
devices.forEach((device) => {
if (this.isDeviceExcluded(device.ieeeAddr)) {
if (this.isDeviceExcluded(device)) {
return;
}
if (device.friendly_name === 'Coordinator' || device.type === 'Coordinator') {
Expand All @@ -138,7 +138,7 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
const staleAccessories: PlatformAccessory[] = [];
for (let i = this.accessories.length - 1; i >= 0; --i) {
const foundIndex = devices.findIndex((d) => d.ieeeAddr === this.accessories[i].ieeeAddress);
if (foundIndex < 0 || this.isDeviceExcluded(this.accessories[i].ieeeAddress)) {
if (foundIndex < 0 || this.isDeviceExcluded(this.accessories[i].accessory.context.device)) {
// Not found or excluded; remove it.
staleAccessories.push(this.accessories[i].accessory);
this.accessories.splice(i, 1);
Expand All @@ -150,34 +150,50 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
}

configureAccessory(accessory: PlatformAccessory) {
this.log.debug('Loading accessory from cache:', accessory.displayName);
this.addAccessory(accessory);
}

private isDeviceExcluded(ieeeAddr : string) : boolean {
if (this.config?.devices?.exclude !== undefined && Array.isArray(this.config.devices.exclude)) {
if (this.config.devices.exclude.includes(ieeeAddr)) {
this.log.debug(`Accessory ${ieeeAddr} is excluded.`);
return true;
private isDeviceExcluded(device: Zigbee2mqttDeviceInfo | string): boolean {
if (Array.isArray(this.config?.devices?.exclude)) {
const identifiers : string[] = [];
if (isDeviceInfo(device)) {
identifiers.push(device.ieeeAddr.toLocaleLowerCase());
identifiers.push(device.friendly_name.toLocaleLowerCase());
} else {
identifiers.push(device.toLocaleLowerCase());
}
for (const key of this.config.devices.exclude) {
if (identifiers.includes(key.toLocaleLowerCase())) {
return true;
}
}
}
return false;
}

private addAccessory(accessory: PlatformAccessory) {
if (this.isDeviceExcluded(accessory.context.device.ieeeAddr)) {
if (this.isDeviceExcluded(accessory.context.device)) {
this.log.warn(`Excluded device found on startup: ${accessory.context.device.friendly_name} (${accessory.context.device.ieeeAddr}).`);
process.nextTick(() => {
try {
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
} catch (error) {
this.log.error('Trying to delete accessory because it is excluded.');
this.log.error(error);
}
});
return;
}
if (this.accessories.findIndex((acc) => acc.UUID === accessory.UUID) < 0) {
// New entry
this.log.info('Adding accessory', accessory.displayName);
this.log.info('Restoring accessory:', accessory.displayName);
const acc = new Zigbee2mqttAccessory(this, accessory);
this.accessories.push(acc);
}
}

private createOrUpdateAccessory(device: Zigbee2mqttDeviceInfo) {
if (this.isDeviceExcluded(device.ieeeAddr)) {
if (this.isDeviceExcluded(device)) {
return;
}
const uuid = this.api.hap.uuid.generate(device.ieeeAddr);
Expand All @@ -186,7 +202,7 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
existingAcc.updateDeviceInformation(device);
} else {
// New entry
this.log.info('Creating accessory', device.friendly_name);
this.log.info('New accessory:', device.friendly_name);
const accessory = new this.api.platformAccessory(device.friendly_name, uuid);
accessory.context.device = device;
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
Expand All @@ -208,11 +224,11 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
options = { qos: 0, retain: false, ...options };
if (!this.isConnected) {
this.log.error('Not connected to MQTT server!');
this.log.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
this.log.error(`Cannot send message to '${topic}': '${payload}`);
return;
}

this.log.info(`MQTT publish: topic '${topic}', payload '${payload}'`);
this.log.info(`Publish to '${topic}': '${payload}'`);

return new Promise((resolve) => {
this.MqttClient.publish(topic, payload, options, () => resolve());
Expand Down
2 changes: 1 addition & 1 deletion src/platformAccessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export class SingleReadOnlyValueServiceWrapper implements ServiceWrapper {
}

get displayName(): string {
return this.service.constructor.name;
return `${this.key} (${this.service.UUID}`;
}

updateValueForKey(key: string, value: unknown): void {
Expand Down

0 comments on commit 93e60b4

Please sign in to comment.