This commit is contained in:
Koen Kanters 2020-04-05 15:41:24 +02:00
parent 3ef7555ca2
commit 4fa9aaa0b2
11 changed files with 105 additions and 107 deletions

View File

@ -172,13 +172,13 @@ class Controller {
}
async onZigbeeEvent(type, data) {
const entity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
if (data.device && !entity.settings) {
const resolvedEntity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
if (data.device && !resolvedEntity.settings) {
// Only deviceLeave doesn't have a device (not interesting to add to settings)
entity.settings = settings.addDevice(data.device.ieeeAddr);
resolvedEntity.settings = settings.addDevice(data.device.ieeeAddr);
}
const name = entity && entity.settings ? entity.settings.friendlyName : null;
const name = resolvedEntity && resolvedEntity.settings ? resolvedEntity.settings.friendlyName : null;
if (type === 'message') {
logger.debug(
@ -192,11 +192,9 @@ class Controller {
if (data.status === 'successful') {
logger.info(`Successfully interviewed '${name}', device has successfully been paired`);
if (entity.definition) {
const {vendor, description, model} = entity.definition;
logger.info(
`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`,
);
if (resolvedEntity.definition) {
const {vendor, description, model} = resolvedEntity.definition;
logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`);
} else {
logger.warn(
`Device '${name}' with Zigbee model '${data.device.modelID}' is NOT supported, ` +
@ -221,10 +219,7 @@ class Controller {
}
// Call extensions
this.callExtensionMethod(
'onZigbeeEvent',
[type, data, entity ? entity.definition : null, entity ? entity.settings : null],
);
this.callExtensionMethod('onZigbeeEvent', [type, data, resolvedEntity]);
}
onMQTTMessage(payload) {

View File

@ -30,10 +30,10 @@ class BaseExtension {
* Is called when a Zigbee message from a device is received.
* @param {string} type Type of the message
* @param {Object} data Data of the message
* @param {Object?} mappedDevice The mapped device
* @param {Object?} resolvedEntity Resolved entity returned from this.zigbee.resolveEntity()
* @param {Object?} settingsDevice Device settings
*/
// onZigbeeEvent(type, data, mappedDevice, settingsDevice) {}
// onZigbeeEvent(type, data, resolvedEntity) {}
/**
* Is called when a MQTT message is received

View File

@ -147,11 +147,11 @@ class BridgeLegacy extends BaseExtension {
};
if (device.type !== 'Coordinator') {
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const friendlyDevice = settings.getDevice(device.ieeeAddr);
payload.model = mappedDevice ? mappedDevice.model : device.modelID;
payload.vendor = mappedDevice ? mappedDevice.vendor : '-';
payload.description = mappedDevice ? mappedDevice.description : '-';
payload.model = definition ? definition.model : device.modelID;
payload.vendor = definition ? definition.vendor : '-';
payload.description = definition ? definition.description : '-';
payload.friendly_name = friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr;
payload.manufacturerID = device.manufacturerID;
payload.manufacturerName = device.manufacturerName;
@ -368,39 +368,38 @@ class BridgeLegacy extends BaseExtension {
await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0});
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (type === 'deviceJoined') {
this.lastJoinedDeviceName = settingsDevice.friendlyName;
onZigbeeEvent(type, data, resolvedEntity) {
if (type === 'deviceJoined' && resolvedEntity) {
this.lastJoinedDeviceName = resolvedEntity.name;
}
const entity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
const name = entity && entity.settings ? entity.settings.friendlyName : null;
if (type === 'deviceJoined') {
this.mqtt.log('device_connected', {friendly_name: name});
this.mqtt.log('device_connected', {friendly_name: resolvedEntity.name});
} else if (type === 'deviceInterview') {
if (data.status === 'successful') {
if (entity.definition) {
const {vendor, description, model} = entity.definition;
const log = {friendly_name: name, model, vendor, description, supported: true};
if (resolvedEntity.definition) {
const {vendor, description, model} = resolvedEntity.definition;
const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true};
this.mqtt.log('pairing', 'interview_successful', log);
} else {
this.mqtt.log('pairing', 'interview_successful', {friendly_name: name, supported: false});
this.mqtt.log('pairing', 'interview_successful',
{friendly_name: resolvedEntity.name, supported: false});
}
} else if (data.status === 'failed') {
this.mqtt.log('pairing', 'interview_failed', {friendly_name: name});
this.mqtt.log('pairing', 'interview_failed', {friendly_name: resolvedEntity.name});
} else {
/* istanbul ignore else */
if (data.status === 'started') {
this.mqtt.log('pairing', 'interview_started', {friendly_name: name});
this.mqtt.log('pairing', 'interview_started', {friendly_name: resolvedEntity.name});
}
}
} else if (type === 'deviceAnnounce') {
this.mqtt.log('device_announced', 'announce', {friendly_name: name});
this.mqtt.log('device_announced', 'announce', {friendly_name: resolvedEntity.name});
} else {
/* istanbul ignore else */
if (type === 'deviceLeave') {
this.mqtt.log('device_removed', 'left_network', {friendly_name: name || data.ieeeAddr});
const name = resolvedEntity ? resolvedEntity.name : data.ieeeAddr;
this.mqtt.log('device_removed', 'left_network', {friendly_name: name});
}
}
}

View File

@ -144,13 +144,13 @@ class DeviceAvailability extends BaseExtension {
async onReconnect(device) {
if (device && device.modelID) {
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
if (mappedDevice) {
if (definition) {
const used = [];
try {
for (const key of toZigbeeCandidates) {
const converter = mappedDevice.toZigbee.find((tz) => tz.key.includes(key));
const converter = definition.toZigbee.find((tz) => tz.key.includes(key));
if (converter && !used.includes(converter)) {
await converter.convertGet(device.endpoints[0], key, {});
used.push(converter);
@ -180,7 +180,7 @@ class DeviceAvailability extends BaseExtension {
}
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
onZigbeeEvent(type, data, resolvedEntity) {
const device = data.device;
if (!device) {
return;

View File

@ -1,6 +1,5 @@
const settings = require('../util/settings');
const logger = require('../util/logger');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const BaseExtension = require('./baseExtension');
class DeviceConfigure extends BaseExtension {
@ -12,18 +11,18 @@ class DeviceConfigure extends BaseExtension {
this.topic = `${settings.get().mqtt.base_topic}/bridge/configure`;
}
shouldConfigure(device, mappedDevice) {
if (!device || !mappedDevice || !mappedDevice.configure) {
shouldConfigure(resolvedEntity) {
if (!resolvedEntity || !resolvedEntity.definition || !resolvedEntity.definition.configure) {
return false;
}
if (device.meta &&
device.meta.hasOwnProperty('configured') &&
device.meta.configured === mappedDevice.meta.configureKey) {
if (resolvedEntity.device.meta &&
resolvedEntity.device.meta.hasOwnProperty('configured') &&
resolvedEntity.device.meta.configured === resolvedEntity.definition.meta.configureKey) {
return false;
}
if (device.interviewing === true) {
if (resolvedEntity.device.interviewing === true) {
return false;
}
@ -39,34 +38,32 @@ class DeviceConfigure extends BaseExtension {
return;
}
const entity = this.zigbee.resolveEntity(message);
if (!entity || entity.type !== 'device') {
const resolvedEntity = this.zigbee.resolveEntity(message);
if (!resolvedEntity || resolvedEntity.type !== 'device') {
logger.error(`Device '${message}' does not exist`);
return;
}
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(entity.device.modelID);
if (!mappedDevice.configure) {
logger.warn(`Skipping configure of '${entity.name}', device does not require this.`);
if (!resolvedEntity.definition || !resolvedEntity.definition.configure) {
logger.warn(`Skipping configure of '${resolvedEntity.name}', device does not require this.`);
return;
}
this.configure(entity.device, mappedDevice, entity.settings, true);
this.configure(resolvedEntity, true);
}
async onZigbeeStarted() {
this.coordinatorEndpoint = this.zigbee.getDevicesByType('Coordinator')[0].getEndpoint(1);
for (const device of this.zigbee.getClients()) {
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const settingsDevice = settings.getDevice(device.ieeeAddr);
if (this.shouldConfigure(device, mappedDevice)) {
await this.configure(device, mappedDevice, settingsDevice);
const resolvedEntity = this.zigbee.resolveEntity(device);
if (this.shouldConfigure(resolvedEntity)) {
await this.configure(resolvedEntity);
}
}
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
onZigbeeEvent(type, data, resolvedEntity) {
const device = data.device;
if (type === 'deviceJoined' && device.meta.hasOwnProperty('configured')) {
@ -74,12 +71,13 @@ class DeviceConfigure extends BaseExtension {
device.save();
}
if (this.shouldConfigure(device, mappedDevice)) {
this.configure(device, mappedDevice, settingsDevice);
if (this.shouldConfigure(resolvedEntity)) {
this.configure(resolvedEntity);
}
}
async configure(device, mappedDevice, settingsDevice, force=false) {
async configure(resolvedEntity, force=false) {
const device = resolvedEntity.device;
if (this.configuring.has(device.ieeeAddr) || (this.attempts[device.ieeeAddr] >= 3 && !force)) {
return false;
}
@ -90,16 +88,15 @@ class DeviceConfigure extends BaseExtension {
this.attempts[device.ieeeAddr] = 0;
}
logger.info(`Configuring '${settingsDevice.friendlyName}'`);
logger.info(`Configuring '${resolvedEntity.name}'`);
try {
await mappedDevice.configure(device, this.coordinatorEndpoint);
logger.info(`Successfully configured '${settingsDevice.friendlyName}'`);
// eslint-disable-next-line
device.meta.configured = mappedDevice.meta.configureKey;
await resolvedEntity.definition.configure(device, this.coordinatorEndpoint);
logger.info(`Successfully configured '${resolvedEntity.name}'`);
device.meta.configured = resolvedEntity.definition.meta.configureKey;
device.save();
} catch (error) {
logger.error(
`Failed to configure '${settingsDevice.friendlyName}', ` +
`Failed to configure '${resolvedEntity.name}', ` +
`attempt ${this.attempts[device.ieeeAddr] + 1} (${error.stack})`,
);
this.attempts[device.ieeeAddr]++;

View File

@ -8,9 +8,9 @@ class DeviceEvent extends BaseExtension {
}
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (data.device) {
this.callOnEvent(data.device, type, data, mappedDevice);
onZigbeeEvent(type, data, resolvedEntity) {
if (data.device && resolvedEntity.definition) {
this.callOnEvent(data.device, type, data, resolvedEntity.definition);
}
}
@ -20,15 +20,15 @@ class DeviceEvent extends BaseExtension {
}
}
callOnEvent(device, type, data, mappedDevice) {
callOnEvent(device, type, data, definition) {
zigbeeHerdsmanConverters.onEvent(type, data, device);
if (!mappedDevice) {
mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
if (!definition) {
definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
}
if (mappedDevice && mappedDevice.onEvent) {
mappedDevice.onEvent(type, data, device);
if (definition && definition.onEvent) {
definition.onEvent(type, data, device);
}
}
}

View File

@ -54,8 +54,8 @@ class DeviceReceive extends BaseExtension {
return result;
}
canHandleEvent(type, data, mappedDevice) {
if (type !== 'message') {
canHandleEvent(type, data, resolvedEntity) {
if (type !== 'message' || !resolvedEntity) {
return false;
}
@ -88,7 +88,7 @@ class DeviceReceive extends BaseExtension {
return false;
}
if (!mappedDevice) {
if (!resolvedEntity.definition) {
logger.warn(`Received message from unsupported device with Zigbee model '${data.device.modelID}'`);
logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html.`);
return false;
@ -97,12 +97,12 @@ class DeviceReceive extends BaseExtension {
return true;
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (!this.canHandleEvent(type, data, mappedDevice)) {
onZigbeeEvent(type, data, resolvedEntity) {
if (!this.canHandleEvent(type, data, resolvedEntity)) {
return;
}
const converters = mappedDevice.fromZigbee.filter((c) => {
const converters = resolvedEntity.definition.fromZigbee.filter((c) => {
const type = Array.isArray(c.type) ? c.type.includes(data.type) : c.type === data.type;
return c.cluster === data.cluster && type;
});
@ -110,14 +110,14 @@ class DeviceReceive extends BaseExtension {
// Check if there is an available converter
if (!converters.length) {
logger.debug(
`No converter available for '${mappedDevice.model}' with cluster '${data.cluster}' ` +
`No converter available for '${resolvedEntity.definition.model}' with cluster '${data.cluster}' ` +
`and type '${data.type}' and data '${JSON.stringify(data.data)}'`,
);
return;
}
const debounce = settingsDevice.debounce || settings.get().device_options.debounce;
const debounceIgnore = settingsDevice.debounce_ignore || settings.get().device_options.debounce_ignore;
const debounce = resolvedEntity.settings.debounce || settings.get().device_options.debounce;
const debounceIgnore = resolvedEntity.settings.debounce_ignore || settings.get().device_options.debounce_ignore;
// Convert this Zigbee message to a MQTT message.
// Get payload for the message.
@ -166,7 +166,7 @@ class DeviceReceive extends BaseExtension {
let payload = {};
converters.forEach((converter) => {
const options = {...settings.get().device_options, ...settings.getDevice(data.device.ieeeAddr)};
const converted = converter.convert(mappedDevice, data, publish, options, meta);
const converted = converter.convert(resolvedEntity.definition, data, publish, options, meta);
if (converted) {
payload = {...payload, ...converted};
}

View File

@ -107,8 +107,8 @@ class DeviceReport extends BaseExtension {
this.pollDebouncers = {};
}
shouldIgnoreClusterForDevice(cluster, mappedDevice) {
if (mappedDevice === ZNLDP12LM && cluster === 'closuresWindowCovering') {
shouldIgnoreClusterForDevice(cluster, definition) {
if (definition === ZNLDP12LM && cluster === 'closuresWindowCovering') {
// Device announces it but doesn't support it
// https://github.com/Koenkk/zigbee2mqtt/issues/2611
return true;
@ -117,14 +117,16 @@ class DeviceReport extends BaseExtension {
return false;
}
async setupReporting(device, mappedDevice) {
async setupReporting(resolvedEntity) {
const {device, definition} = resolvedEntity;
if (this.configuring.has(device.ieeeAddr) || this.failed.has(device.ieeeAddr)) return;
this.configuring.add(device.ieeeAddr);
try {
for (const ep of device.endpoints) {
for (const [cluster, configuration] of Object.entries(clusters)) {
if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, mappedDevice)) {
if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, definition)) {
logger.debug(`Setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
const items = [];
@ -158,9 +160,11 @@ class DeviceReport extends BaseExtension {
this.configuring.delete(device.ieeeAddr);
}
shouldSetupReporting(mappedDevice, device, messageType) {
if (!device || !mappedDevice) return false;
shouldSetupReporting(resolvedEntity, messageType) {
if (!resolvedEntity || !resolvedEntity.device || !resolvedEntity.definition ||
messageType === 'deviceLeave') return false;
const {device, definition} = resolvedEntity;
// Handle messages of type endDeviceAnnce and devIncoming.
// This message is typically send when a device comes online after being powered off
// Ikea TRADFRI tend to forget their reporting after powered off.
@ -171,7 +175,7 @@ class DeviceReport extends BaseExtension {
if (device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) return false;
if (!utils.isRouter(device) || utils.isBatteryPowered(device)) return false;
// Gledopto devices don't support reporting.
if (devicesNotSupportingReporting.includes(mappedDevice) || mappedDevice.vendor === 'Gledopto') return false;
if (devicesNotSupportingReporting.includes(definition) || definition.vendor === 'Gledopto') return false;
return true;
}
@ -179,16 +183,16 @@ class DeviceReport extends BaseExtension {
this.coordinatorEndpoint = this.zigbee.getDevicesByType('Coordinator')[0].getEndpoint(1);
for (const device of this.zigbee.getClients()) {
const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
if (this.shouldSetupReporting(mappedDevice, device, null)) {
this.setupReporting(device, mappedDevice);
const resolvedEntity = this.zigbee.resolveEntity(device);
if (this.shouldSetupReporting(resolvedEntity, null)) {
this.setupReporting(resolvedEntity);
}
}
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (this.shouldSetupReporting(mappedDevice, data.device, type)) {
this.setupReporting(data.device, mappedDevice);
onZigbeeEvent(type, data, resolvedEntity) {
if (this.shouldSetupReporting(resolvedEntity, type)) {
this.setupReporting(resolvedEntity);
}
if (type === 'message') {

View File

@ -1862,10 +1862,10 @@ class HomeAssistant extends BaseExtension {
}
}
onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
onZigbeeEvent(type, data, resolvedEntity) {
const device = data.device;
if (device && mappedDevice) {
this.discover(device, mappedDevice);
if (device && resolvedEntity && resolvedEntity.definition) {
this.discover(device, resolvedEntity.definition);
}
}

View File

@ -17,10 +17,10 @@ class OTAUpdate extends BaseExtension {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/update`);
}
async onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (data.type !== 'commandQueryNextImageRequest') return;
async onZigbeeEvent(type, data, resolvedEntity) {
if (data.type !== 'commandQueryNextImageRequest' || !resolvedEntity || !resolvedEntity.definition) return;
const supportsOTA = mappedDevice && mappedDevice.hasOwnProperty('ota');
const supportsOTA = resolvedEntity.definition.hasOwnProperty('ota');
if (supportsOTA) {
// When a device does a next image request, it will usually do it a few times after each other
// with only 10 - 60 seconds inbetween. It doesn' make sense to check for a new update
@ -30,13 +30,16 @@ class OTAUpdate extends BaseExtension {
if (!check || this.inProgress.has(data.device.ieeeAddr)) return;
this.lastChecked[data.device.ieeeAddr] = Date.now();
const available = await mappedDevice.ota.isUpdateAvailable(data.device, logger, data.data);
const available = await resolvedEntity.definition.ota.isUpdateAvailable(data.device, logger, data.data);
this.publishEntityState(data.device.ieeeAddr, {update_available: available});
if (available) {
const message = `Update available for '${settingsDevice.friendly_name}'`;
const message = `Update available for '${resolvedEntity.settings.friendly_name}'`;
logger.info(message);
this.mqtt.log('ota_update', message, {status: 'available', device: settingsDevice.friendly_name});
this.mqtt.log(
'ota_update', message,
{status: 'available', device: resolvedEntity.settings.friendly_name},
);
}
}

View File

@ -136,10 +136,10 @@ class Zigbee extends events.EventEmitter {
* @return {object} {
* type: device | coordinator
* device|group: zigbee-herdsman entity
* endpoint: selected endpoint
* endpoint: selected endpoint (only if type === device)
* settings: from configuration.yaml
* name: name of the entity
* definition: zigbee-herdsman-converters definition
* definition: zigbee-herdsman-converters definition (only if type === device)
* }
*/
resolveEntity(key) {