Skip to content

Commit

Permalink
Zigbee-herdsman (Koenkk#1945)
Browse files Browse the repository at this point in the history
* Update zigbee-herdsman and zigbee-shepherd-converters.

* Force Aqara S2 Lock endvices (Koenkk#1764)

* Start on zigbee-herdsman controller refactor.

* More updates.

* Cleanup zapp.

* updates.

* Propagate adapter disconnected event.

* Updates.

* Initial refactor to zigbee-herdsman.

* Refactor deviceReceive to zigbee-herdsman.

* Rename

* Refactor deviceConfigure.

* Finish bridge config.

* Refactor availability.

* Active homeassistant extension and more refactors.

* Refactor groups.

* Enable soft reset.

* Activate group membership

* Start on tests.

* Enable reporting.

* Add more controller tests.

* Add more tests

* Fix linting error.

* Data en deviceReceive tests.

* Move to zigbee-herdsman-converters.

* More device publish tests.

* Cleanup dependencies.

* Bring device publish coverage to 100.

* Bring home assistant test coverage to 100.

* Device configure tests.

* Attempt to fix tests.

* Another attempt.

* Another one.

* Another one.

* Another.

* Add wait.

* Longer wait.

* Debug.

* Update dependencies.

* Another.

* Begin on availability tests.

* Improve availability tests.

* Complete deviceAvailability tests.

* Device bind tests.

* More tests.

* Begin networkmap refactors.

* start on networkmap tests.

* Network map tests.

* Add utils tests.

* Logger tests.

* Settings and logger tests.

* Ignore some stuff for coverage and add todos.

* Add remaining missing tests.

* Enforce 100% test coverage.

* Start on groups test and refactor entityPublish to resolveEntity

* Remove joinPathStorage, not used anymore as group information is stored into zigbee-herdsman database.

* Fix linting issues.

* Improve tests.

* Add groups.

* fix group membership.

* Group: log names.

* Convert MQTT message to string by default.

* Fix group name.

* Updates.

* Revert configuration.yaml.

* Add new line.

* Fixes.

* Updates.

* Fix tests.

* Ignore soft reset extension.
  • Loading branch information
Koenkk authored Sep 9, 2019
1 parent e26ad2a commit d83085e
Show file tree
Hide file tree
Showing 57 changed files with 6,322 additions and 13,423 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/*
node_modules/*
test/
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ install:
- npm install

before_script:
- npm test
- npm run test-with-coverage
- npm run eslint

script:
Expand Down
1 change: 0 additions & 1 deletion docs/_config.yml

This file was deleted.

1 change: 0 additions & 1 deletion docs/index.md

This file was deleted.

2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ let stopping = false;
function handleQuit() {
if (!stopping) {
stopping = true;
controller.stop(() => process.exit());
controller.stop();
}
}
344 changes: 176 additions & 168 deletions lib/controller.js

Large diffs are not rendered by default.

174 changes: 84 additions & 90 deletions lib/extension/bridgeConfig.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const settings = require('../util/settings');
const logger = require('../util/logger');
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const utils = require('../util/utils');
const assert = require('assert');

const configRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/((?:\\w+/get)|(?:\\w+))`);
const allowedLogLevels = ['error', 'warn', 'info', 'debug'];
Expand Down Expand Up @@ -50,18 +51,21 @@ class BridgeConfig {
}

whitelist(topic, message) {
message = message.toString();
const IDByFriendlyName = settings.getIeeeAddrByFriendlyName(message);
const deviceID = IDByFriendlyName ? IDByFriendlyName : message;
const whitelisted = settings.whitelistDevice(deviceID);
whitelisted ? logger.info(`Whitelisted '${deviceID}'`) : logger.error(`Failed to whitelist '${deviceID}'`);
this.mqtt.log('device_whitelisted', deviceID);
try {
const entity = settings.getEntity(message);
assert(entity, `Entity '${message}' does not exist`);
settings.whitelistDevice(entity.ID);
logger.info(`Whitelisted '${entity.friendlyName}'`);
this.mqtt.log('device_whitelisted', entity.friendlyName);
} catch (error) {
logger.error(`Failed to whitelist '${message}' '${error}'`);
}
}

deviceOptions(topic, message) {
let json = null;
try {
json = JSON.parse(message.toString());
json = JSON.parse(message);
} catch (e) {
logger.error('Failed to parse message as JSON');
return;
Expand All @@ -72,34 +76,28 @@ class BridgeConfig {
return;
}

const ieeeAddr = settings.getIeeeAddrByFriendlyName(json.friendly_name);
if (!ieeeAddr) {
logger.error(`Failed to find device '${json.friendly_name}'`);
return;
}

settings.changeDeviceOptions(ieeeAddr, json.options);
const entity = settings.getEntity(json.friendly_name);
assert(entity, `Entity '${json.friendly_name}' does not exist`);
settings.changeDeviceOptions(entity.ID, json.options);
logger.info(`Changed device specific options of '${json.friendly_name}' (${JSON.stringify(json.options)})`);
}

permitJoin(topic, message) {
this.zigbee.permitJoin(message.toString().toLowerCase() === 'true', () => this.publish());
async permitJoin(topic, message) {
await this.zigbee.permitJoin(message.toLowerCase() === 'true');
this.publish();
}

reset(topic, message) {
this.zigbee.softReset((error) => {
if (error) {
logger.error('Soft reset failed');
} else {
logger.info('Soft resetted ZNP');
}
});
async reset(topic, message) {
try {
await this.zigbee.softReset();
logger.info('Soft resetted ZNP');
} catch (error) {
logger.error('Soft reset failed');
}
}

lastSeen(topic, message) {
const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local'];
message = message.toString();

if (!allowed.includes(message)) {
logger.error(`${message} is not an allowed value, possible: ${allowed}`);
return;
Expand All @@ -111,8 +109,6 @@ class BridgeConfig {

elapsed(topic, message) {
const allowed = ['true', 'false'];
message = message.toString();

if (!allowed.includes(message)) {
logger.error(`${message} is not an allowed value, possible: ${allowed}`);
return;
Expand All @@ -123,7 +119,7 @@ class BridgeConfig {
}

logLevel(topic, message) {
const level = message.toString().toLowerCase();
const level = message.toLowerCase();
if (allowedLogLevels.includes(level)) {
logger.info(`Switching log level to '${level}'`);
logger.transports.console.level = level;
Expand All @@ -135,23 +131,23 @@ class BridgeConfig {
this.publish();
}

devices(topic, message) {
const devices = this.zigbee.getDevices().map((device) => {
async devices(topic, message) {
const devices = (await this.zigbee.getDevices({})).map((device) => {
const payload = {
ieeeAddr: device.ieeeAddr,
type: device.type,
};

if (device.type !== 'Coordinator') {
const mappedDevice = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const friendlyDevice = settings.getDevice(device.ieeeAddr);
payload.model = mappedDevice ? mappedDevice.model : device.modelId;
payload.model = mappedDevice ? mappedDevice.model : device.modelID;
payload.friendly_name = friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr;
payload.nwkAddr = device.nwkAddr;
payload.manufId = device.manufId;
payload.manufName = device.manufName;
payload.networkAddress = device.networkAddress;
payload.manufacturerID = device.manufacturerID;
payload.manufacturerName = device.manufacturerName;
payload.powerSource = device.powerSource;
payload.modelId = device.modelId;
payload.modelID = device.modelID;
payload.hwVersion = device.hwVersion;
payload.swBuildId = device.swBuildId;
payload.dateCode = device.dateCode;
Expand All @@ -168,16 +164,19 @@ class BridgeConfig {
}

groups(topic, message) {
this.mqtt.log('groups', settings.getGroups());
this.mqtt.log('groups', settings.getGroups().map((g) => {
const group = {...g};
delete group.friendlyName;
return group;
}));
}

rename(topic, message) {
const invalid = `Invalid rename message format expected {old: 'friendly_name', new: 'new_name} ` +
`got ${message.toString()}`;
const invalid = `Invalid rename message format expected {old: 'friendly_name', new: 'new_name} got ${message}`;

let json = null;
try {
json = JSON.parse(message.toString());
json = JSON.parse(message);
} catch (e) {
logger.error(invalid);
return;
Expand All @@ -189,72 +188,66 @@ class BridgeConfig {
return;
}

if (settings.changeFriendlyName(json.old, json.new)) {
try {
settings.changeFriendlyName(json.old, json.new);
logger.info(`Successfully renamed - ${json.old} to ${json.new} `);
this.mqtt.log('device_renamed', {from: json.old, to: json.new});
} else {
logger.error(`Failed to renamed - ${json.old} to ${json.new}`);
return;
} catch (error) {
logger.error(`Failed to rename - ${json.old} to ${json.new}`);
}
}

addGroup(topic, message) {
const name = message.toString();
const added = settings.addGroup(name);
added ? logger.info(`Added group '${name}'`) : logger.error(`Failed to add group '${name}'`);
async addGroup(topic, message) {
const name = message;
const group = settings.addGroup(name);
await this.zigbee.createGroup(group.ID);
logger.info(`Added group '${name}'`);
}

removeGroup(topic, message) {
const name = message.toString();
const removed = settings.removeGroup(name);
removed ? logger.info(`Removed group '${name}'`) : logger.error(`Failed to remove group '${name}'`);
const name = message;
settings.removeGroup(name);
logger.info(`Removed group '${name}'`);
}

remove(topic, message) {
this.removeOrBan(false, message);
async remove(topic, message) {
await this.removeOrBan(false, message);
}

ban(topic, message) {
this.removeOrBan(true, message);
async ban(topic, message) {
await this.removeOrBan(true, message);
}

removeOrBan(ban, message) {
message = message.toString();
const IDByFriendlyName = settings.getIeeeAddrByFriendlyName(message);
const deviceID = IDByFriendlyName ? IDByFriendlyName : message;
const device = this.zigbee.getDevice(deviceID);
async removeOrBan(ban, message) {
const entity = await this.zigbee.resolveEntity(message);

const cleanup = () => {
// Remove from configuration.yaml
settings.removeDevice(deviceID);
settings.removeDevice(entity.settings.ID);

// Remove from state
this.state.remove(deviceID);
this.state.remove(entity.settings.ID);

logger.info(`Successfully ${ban ? 'banned' : 'removed'} ${deviceID}`);
logger.info(`Successfully ${ban ? 'banned' : 'removed'} ${entity.settings.friendlyName}`);
this.mqtt.log(ban ? 'device_banned' : 'device_removed', message);
};

// Remove from zigbee network.
if (device) {
this.zigbee.removeDevice(deviceID, ban, (error) => {
if (!error) {
cleanup();
} else {
logger.error(`Failed to ${ban ? 'ban' : 'remove'} ${deviceID}`);
}
});
} else {
try {
logger.info(`Removing '${entity.settings.friendlyName}'`);
await entity.device.removeFromNetwork();
cleanup();
} catch (error) {
logger.error(`Failed to ${ban ? 'ban' : 'remove'} ${entity.settings.friendlyName} (${error})`);
}
}

onMQTTConnected() {
async onMQTTConnected() {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/config/+`);
this.publish();
await this.publish();
}

onMQTTMessage(topic, message) {
async onMQTTMessage(topic, message) {
if (!topic.match(configRegex)) {
return false;
}
Expand All @@ -265,24 +258,25 @@ class BridgeConfig {
return false;
}

this.supportedOptions[option](topic, message);
await this.supportedOptions[option](topic, message);

return true;
}

publish() {
utils.getZigbee2mqttVersion((info) => {
const topic = `bridge/config`;
const payload = {
version: info.version,
commit: info.commitHash,
coordinator: this.zigbee.getFirmwareVersion(),
log_level: logger.transports.console.level,
permit_join: this.zigbee.getPermitJoin(),
};
async publish() {
const info = await utils.getZigbee2mqttVersion();
const coordinator = await this.zigbee.getCoordinatorVersion();

const topic = `bridge/config`;
const payload = {
version: info.version,
commit: info.commitHash,
coordinator,
log_level: logger.transports.console.level,
permit_join: await this.zigbee.getPermitJoin(),
};

this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, null);
});
await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0});
}
}

Expand Down
Loading

0 comments on commit d83085e

Please sign in to comment.