Disable reporting on device when reporting is disabled. https://github.com/Koenkk/zigbee2mqtt/issues/3826

This commit is contained in:
Koen Kanters
2020-08-04 19:11:13 +02:00
parent 35be95115d
commit 6345cb07bf
6 changed files with 112 additions and 11 deletions
+1 -4
View File
@@ -59,6 +59,7 @@ class Controller {
new ExtensionBind(...args),
new ExtensionOnEvent(...args),
new ExtensionOTAUpdate(...args),
new ExtensionReport(...args),
];
if (settings.get().experimental.new_api) {
@@ -69,10 +70,6 @@ class Controller {
this.extensions.push(new ExtensionBridgeLegacy(...args));
}
if (settings.get().advanced.report) {
this.extensions.push(new ExtensionReport(...args));
}
if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(...args));
}
+1
View File
@@ -8,6 +8,7 @@ const allowedEvents = [
'publishEntityState', // Entity state will be published
'stateChange', // Entity changes its state
'groupMembersChanged', // Members of a group has been changed
'reportingDisabled', // Reporting is disabled for a device
];
class EventBus extends events.EventEmitter {
+17
View File
@@ -11,10 +11,27 @@ class Configure extends Extension {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.configuring = new Set();
this.onReportingDisabled = this.onReportingDisabled.bind(this);
this.attempts = {};
this.topic = `${settings.get().mqtt.base_topic}/bridge/request/device/configure`;
this.legacyTopic = `${settings.get().mqtt.base_topic}/bridge/configure`;
this.eventBus.on(`reportingDisabled`, this.onReportingDisabled);
}
onReportingDisabled(data) {
// Disabling reporting unbinds some cluster which could be bound by configure, re-setup.
const device = data.device;
const resolvedEntity = this.zigbee.resolveEntity(device);
if (resolvedEntity.device.meta && resolvedEntity.device.meta.hasOwnProperty('configured')) {
delete device.meta.configured;
device.save();
}
if (this.shouldConfigure(resolvedEntity)) {
this.configure(resolvedEntity);
}
}
shouldConfigure(resolvedEntity) {
+30 -7
View File
@@ -1,5 +1,6 @@
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const logger = require('../util/logger');
const settings = require('../util/settings');
const ZNLDP12LM = zigbeeHerdsmanConverters.devices.find((d) => d.model === 'ZNLDP12LM');
const utils = require('../util/utils');
const Extension = require('./extension');
@@ -106,6 +107,7 @@ class Report extends Extension {
this.queue = new Set();
this.failed = new Set();
this.pollDebouncers = {};
this.enabled = settings.get().advanced.report;
}
shouldIgnoreClusterForDevice(cluster, definition) {
@@ -124,33 +126,46 @@ class Report extends Extension {
if (this.queue.has(device.ieeeAddr) || this.failed.has(device.ieeeAddr)) return;
this.queue.add(device.ieeeAddr);
const term1 = this.enabled ? 'Setup' : 'Disable';
const term2 = this.enabled ? 'setup' : 'disabled';
try {
for (const ep of device.endpoints) {
for (const [cluster, configuration] of Object.entries(clusters)) {
if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, definition)) {
logger.debug(`Setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
logger.debug(`${term1} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
const items = [];
for (const entry of configuration) {
if (!entry.hasOwnProperty('condition') || (await entry.condition(ep))) {
items.push({...entry});
const toAdd = {...entry};
if (!this.enabled) toAdd.maximumReportInterval = 0xFFFF;
items.push(toAdd);
delete items[items.length - 1].condition;
}
}
await ep.bind(cluster, this.coordinatorEndpoint);
this.enabled ?
await ep.bind(cluster, this.coordinatorEndpoint) :
await ep.unbind(cluster, this.coordinatorEndpoint);
await ep.configureReporting(cluster, items);
logger.info(
`Successfully setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`,
`Successfully ${term2} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`,
);
}
}
}
device.meta.reporting = reportKey;
if (this.enabled) {
device.meta.reporting = reportKey;
} else {
delete device.meta.reporting;
this.eventBus.emit('reportingDisabled', {device});
}
} catch (error) {
logger.error(
`Failed to setup reporting for '${device.ieeeAddr}' - ${error.stack}`,
`Failed to ${term1.toLowerCase()} reporting for '${device.ieeeAddr}' - ${error.stack}`,
);
this.failed.add(device.ieeeAddr);
@@ -173,10 +188,18 @@ class Report extends Extension {
if (messageType === 'deviceAnnounce' && utils.isIkeaTradfriDevice(device)) return true;
if (resolvedEntity.device.interviewing === true) return false;
if (device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) return false;
if (device.type !== 'Router' || device.powerSource === 'Battery') return false;
// Gledopto devices don't support reporting.
if (devicesNotSupportingReporting.includes(definition) || definition.vendor === 'Gledopto') return false;
if (this.enabled && device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) {
return false;
}
if (!this.enabled && !device.meta.hasOwnProperty('reporting')) {
return false;
}
return true;
}
+10
View File
@@ -68,6 +68,16 @@ describe('Configure', () => {
expectRemoteConfigured();
});
it('Should reconfigure reporting on reportingDisabled event', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
mockClear(device);
expectRemoteNotConfigured();
controller.eventBus.emit('reportingDisabled', {device})
await flushPromises();
expectRemoteConfigured();
});
it('Should not configure twice', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
+53
View File
@@ -24,6 +24,9 @@ describe('Report', () => {
function expectOnOffBrightnessColorReport(endpoint, colorXY) {
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
const device = endpoint.getDevice();
expect(device.meta.reporting).toBe(1);
expect(endpoint.unbind).toHaveBeenCalledTimes(0);
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint);
expect(endpoint.bind).toHaveBeenCalledWith('genLevelCtrl', coordinatorEndpoint);
@@ -38,12 +41,32 @@ describe('Report', () => {
}
}
function expectOnOffBrightnessColorReportDisabled(endpoint, colorXY) {
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
const device = endpoint.getDevice();
expect(device.meta.reporting).toBe(undefined);
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledTimes(0);
expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint);
expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', coordinatorEndpoint);
expect(endpoint.unbind).toHaveBeenCalledWith('lightingColorCtrl', coordinatorEndpoint);
expect(endpoint.configureReporting).toHaveBeenCalledTimes(3);
expect(endpoint.configureReporting).toHaveBeenCalledWith('genOnOff', [{"attribute": "onOff", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(endpoint.configureReporting).toHaveBeenCalledWith('genLevelCtrl', [{"attribute": "currentLevel", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
if (colorXY) {
expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentX", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentY", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
} else {
expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
}
}
mockClear = (device) => {
for (const endpoint of device.endpoints) {
endpoint.read.mockClear();
endpoint.write.mockClear();
endpoint.configureReporting.mockClear();
endpoint.bind.mockClear();
endpoint.unbind.mockClear();
}
}
@@ -52,6 +75,11 @@ describe('Report', () => {
settings._reRead();
data.writeEmptyState();
settings.set(['advanced', 'report'], true);
for (const device of Object.values(zigbeeHerdsman.devices)) {
mockClear(device);
delete device.meta.reporting;
}
controller = new Controller();
await controller.start();
mocksClear.forEach((m) => m.mockClear());
@@ -64,6 +92,31 @@ describe('Report', () => {
expectOnOffBrightnessColorReport(endpoint, true);
});
it('Should not configure reporting on startup when disabled', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
mockClear(device);
delete device.meta.report;
settings.set(['advanced', 'report'], false);
controller = new Controller();
await controller.start();
await flushPromises();
expect(device.meta.reporting).toBe(undefined);
expect(endpoint.bind).toHaveBeenCalledTimes(0);
});
it('Should disable reporting on startup when enabled earlier', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
device.meta.reporting = 1;
const endpoint = device.getEndpoint(1);
settings.set(['advanced', 'report'], false);
mockClear(device);
controller = new Controller();
await controller.start();
await flushPromises();
expectOnOffBrightnessColorReportDisabled(endpoint, true);
});
it('Should configure reporting when receicing message from device which has not been setup yet', async () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.getEndpoint(1);