Skip to content

Commit

Permalink
Start on new API implementation. #3281
Browse files Browse the repository at this point in the history
  • Loading branch information
Koenkk committed May 4, 2020
1 parent 772f6c0 commit 856ecf4
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
2 changes: 2 additions & 0 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ExtensionHomeAssistant = require('./extension/homeassistant');
const ExtensionConfigure = require('./extension/configure');
const ExtensionDeviceGroupMembership = require('./extension/legacy/deviceGroupMembership');
const ExtensionBridgeLegacy = require('./extension/legacy/bridgeLegacy');
const ExtensionBridge = require('./extension/bridge');
const ExtensionGroups = require('./extension/groups');
const ExtensionAvailability = require('./extension/availability');
const ExtensionBind = require('./extension/bind');
Expand Down Expand Up @@ -48,6 +49,7 @@ class Controller {
new ExtensionBind(...args),
new ExtensionOnEvent(...args),
new ExtensionOTAUpdate(...args),
// new ExtensionBridge(...args),
];

/* istanbul ignore else */
Expand Down
132 changes: 132 additions & 0 deletions lib/extension/bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* istanbul ignore file newApi */

const logger = require('../util/logger');
const utils = require('../util/utils');
const Extension = require('./extension');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const settings = require('../util/settings');

const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);

class BridgeLegacy extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
super(zigbee, mqtt, state, publishEntityState, eventBus);


this.requestLookup = {
permitjoin: this.requestPermitJoin.bind(this),
};
}

async onMQTTConnected() {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/+`);
await this.publishInfo();
await this.publishDevices();
await this.publishGroups();
}

async onMQTTMessage(topic, message) {
const match = topic.match(requestRegex);
if (match && this.requestLookup[match[1].toLowerCase()]) {
message = JSON.parse(message);
const response = await this.requestLookup[match[1].toLowerCase()](message);
await this.mqtt.publish(`bridge/response/${match[1]}`, JSON.stringify(response), {retain: true, qos: 0});
}
}

async requestPermitJoin(message) {
const value = typeof message === 'object' ? message.value : message;
await this.zigbee.permitJoin(value);
await this.publishInfo();
return utils.getResponse(message, {value: value}, null);
}

async onZigbeeEvent(type, data, resolvedEntity) {
if (['deviceJoined', 'deviceLeave', 'deviceInterview'].includes(type)) {
let payload;
const ieeeAddress = data.device ? data.device.ieeeAddr : data.ieeeAddr;
if (type === 'deviceJoined') payload = {friendlyName: resolvedEntity.settings.friendlyName, ieeeAddress};
else if (type === 'deviceInterview') {
payload = {friendlyName: resolvedEntity.settings.friendlyName, status: data.status, ieeeAddress};
if (data.status === 'successful') {
payload.supported = !!resolvedEntity.definition;
payload.definition = resolvedEntity.definition ? {
model: resolvedEntity.definition.model,
vendor: resolvedEntity.definition.vendor,
description: resolvedEntity.definition.description,
supports: resolvedEntity.definition.supports,
} : null;
}
} else payload = {ieeeAddress}; // deviceLeave

await this.mqtt.publish('bridge/event', JSON.stringify({type, data: payload}), {retain: false, qos: 0});
}

if ('deviceLeave' === type || ('deviceInterview' === type && data.status !== 'started')) {
await this.publishDevices();
}
}

async publishInfo() {
const info = await utils.getZigbee2mqttVersion();
const coordinator = await this.zigbee.getCoordinatorVersion();
const payload = {
version: info.version,
commit: info.commitHash,
coordinator,
logLevel: logger.getLevel(),
permitJoin: await this.zigbee.getPermitJoin(),
};

await this.mqtt.publish('bridge/info', JSON.stringify(payload), {retain: true, qos: 0});
}

async publishDevices(topic, message) {
const devices = this.zigbee.getClients().map((device) => {
const definition = zigbeeHerdsmanConverters.findByDevice(device);
const resolved = this.zigbee.resolveEntity(device);
const definitionPayload = definition ? {
model: definition.model,
vendor: definition.vendor,
description: definition.description,
supports: definition.supports,
} : null;

return {
ieeeAddress: device.ieeeAddr,
type: device.type,
networkAddress: device.networkAddress,
supported: !!definition,
friendlyName: resolved.settings.friendlyName,
definition: definitionPayload,
powerSource: device.powerSource,
softwareBuildID: device.softwareBuildID,
dateCode: device.dateCode,
interviewing: device.interviewing,
interviewCompleted: device.interviewCompleted,
};
});

await this.mqtt.publish('bridge/devices', JSON.stringify(devices), {retain: true, qos: 0});
}

async publishGroups(topic, message) {
const groups = this.zigbee.getGroups().map((group) => {
const resolved = this.zigbee.resolveEntity(group);
return {
ID: group.groupID,
friendlyName: resolved.settings.friendlyName,
members: group.members.map((m) => {
return {
ieeeAddress: m.deviceIeeeAddress,
endpoint: m.ID,
};
}),
};
});

await this.mqtt.publish('bridge/groups', JSON.stringify(groups), {retain: true, qos: 0});
}
}

module.exports = BridgeLegacy;
11 changes: 11 additions & 0 deletions lib/util/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ function getObjectsProperty(objects, key, defaultValue) {
return defaultValue;
}

/* istanbul ignore next newApi */
function getResponse(request, data, error) {
const response = {data, status: error ? 'error' : 'ok'};
if (error) response.error = error;
if (typeof request === 'object' && request.hasOwnProperty('transaction')) {
response.transaction = request.transaction;
}
return response;
}

module.exports = {
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
secondsToMilliseconds: (seconds) => seconds * 1000,
Expand All @@ -136,4 +146,5 @@ module.exports = {
isBatteryPowered: (device) => device.powerSource && device.powerSource === 'Battery',
formatDate: (date, type) => formatDate(date, type),
equalsPartial,
getResponse,
};
14 changes: 12 additions & 2 deletions lib/zigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,11 @@ class Zigbee extends events.EventEmitter {
* }
*/
resolveEntity(key) {
/* istanbul ignore next newApi */
assert(
typeof key === 'string' || typeof key === 'number' ||
key.constructor.name === 'Device', `Wrong type '${typeof key}'`,
key.constructor.name === 'Device' || key.constructor.name === 'Group',
`Wrong type '${typeof key}'`,
);

if (typeof key === 'string' || typeof key === 'number') {
Expand Down Expand Up @@ -215,7 +217,7 @@ class Zigbee extends events.EventEmitter {
if (!group) group = this.createGroup(entity.ID);
return {type: 'group', group, settings: entity, name: entity.friendlyName};
}
} else {
} /* istanbul ignore else newApi */ else if (key.constructor.name === 'Device') {
const setting = settings.getEntity(key.ieeeAddr);
return {
type: 'device',
Expand All @@ -225,6 +227,14 @@ class Zigbee extends events.EventEmitter {
name: setting ? setting.friendlyName : (key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr),
definition: zigbeeHerdsmanConverters.findByDevice(key),
};
} else { // Group
const setting = settings.getEntity(key.groupID);
return {
type: 'group',
group: key,
settings: setting,
name: setting.friendlyName,
};
}
}

Expand Down

0 comments on commit 856ecf4

Please sign in to comment.