Fix various crashes when removing a device. https://github.com/Koenkk/zigbee2mqtt/issues/2004

This commit is contained in:
Koen Kanters
2019-09-26 01:14:58 +02:00
parent 3083655072
commit 9964d8e462
4 changed files with 43 additions and 21 deletions
+21 -18
View File
@@ -169,63 +169,66 @@ class Controller {
async onZigbeeEvent(type, data) {
const entity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
if (!entity.settings && data.device) {
if (data.device && !entity.settings) {
// Only deviceLeave doesn't have a device (not interesting to add to settings)
entity.settings = settings.addDevice(data.device.ieeeAddr);
}
const friendlyName = entity.settings.friendlyName;
const name = entity && entity.settings ? entity.settings.friendlyName : null;
if (type === 'message') {
logger.debug(
`Received Zigbee message from '${entity.settings.friendlyName}' of type '${data.type}' ` +
`Received Zigbee message from '${name}' of type '${data.type}' ` +
`with data '${JSON.stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
(data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``)
);
} else if (type === 'deviceJoined') {
logger.info(`Device '${friendlyName}' joined`);
this.mqtt.log('device_connected', {friendly_name: friendlyName});
logger.info(`Device '${name}' joined`);
this.mqtt.log('device_connected', {friendly_name: name});
} else if (type === 'deviceInterview') {
if (data.status === 'successful') {
logger.info(`Successfully interviewed '${friendlyName}', device has succesfully been paired`);
logger.info(`Successfully interviewed '${name}', device has succesfully been paired`);
if (entity.mapped) {
const {vendor, description, model} = entity.mapped;
logger.info(
`Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`
);
const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
const log = {friendly_name: name, model, vendor, description, supported: true};
this.mqtt.log('pairing', 'interview_successful', log);
} else {
logger.warn(
`Device '${friendlyName}' with Zigbee model '${data.device.modelID}' is NOT supported, ` +
`Device '${name}' with Zigbee model '${data.device.modelID}' is NOT supported, ` +
`please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
);
this.mqtt.log('pairing', 'interview_successful', {friendly_name: friendlyName, supported: false});
this.mqtt.log('pairing', 'interview_successful', {friendly_name: name, supported: false});
}
} else if (data.status === 'failed') {
logger.error(`Failed to interview '${friendlyName}', device has not succesfully been paired`);
this.mqtt.log('pairing', 'interview_failed', {friendly_name: friendlyName});
logger.error(`Failed to interview '${name}', device has not succesfully been paired`);
this.mqtt.log('pairing', 'interview_failed', {friendly_name: name});
} else {
/* istanbul ignore else */
if (data.status === 'started') {
logger.info(`Starting interview of '${friendlyName}'`);
this.mqtt.log('pairing', 'interview_started', {friendly_name: friendlyName});
logger.info(`Starting interview of '${name}'`);
this.mqtt.log('pairing', 'interview_started', {friendly_name: name});
}
}
} else if (type === 'deviceAnnounce') {
logger.debug(`Device '${friendlyName}' announced itself`);
logger.debug(`Device '${name}' announced itself`);
} else {
/* istanbul ignore else */
if (type === 'deviceLeave') {
logger.warn(`Device '${friendlyName}' left the network`);
this.mqtt.log('device_removed', 'left_network', {friendly_name: friendlyName});
logger.warn(`Device '${name || data.ieeeAddr}' left the network`);
this.mqtt.log('device_removed', 'left_network', {friendly_name: name || data.ieeeAddr});
}
}
// Call extensions
this.callExtensionMethod('onZigbeeEvent', [type, data, entity.mapped, entity.settings]);
this.callExtensionMethod(
'onZigbeeEvent',
[type, data, entity ? entity.mapped : null, entity ? entity.settings : null]
);
}
onMQTTMessage(payload) {
+1 -1
View File
@@ -223,7 +223,7 @@ class BridgeConfig extends BaseExtension {
}
async removeOrBan(ban, message) {
const entity = this.zigbee.resolveEntity(message);
const entity = this.zigbee.resolveEntity(message.trim());
const cleanup = () => {
// Remove from configuration.yaml
+4
View File
@@ -154,6 +154,10 @@ class Zigbee extends events.EventEmitter {
return null;
} else if (entity.type === 'device') {
const device = this.getDeviceByIeeeAddr(entity.ID);
if (!device) {
return null;
}
const mapped = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
let isDefaultEndpoint = true;
+17 -2
View File
@@ -20,6 +20,7 @@ describe('Controller', () => {
let controller;
beforeEach(() => {
zigbeeHerdsman.returnDevices.splice(0);
controller = new Controller();
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
@@ -303,13 +304,27 @@ describe('Controller', () => {
expect(logger.debug).toHaveBeenCalledWith(`Device 'bulb' announced itself`);
});
it('On zigbee event device leave', async () => {
it('On zigbee event device leave (removed from database and settings)', async () => {
await controller.start();
zigbeeHerdsman.returnDevices.push('0x00124b00120144ae');
settings.set(['devices'], {})
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.bulb;
const payload = {ieeeAddr: device.ieeeAddr};
await zigbeeHerdsman.events.deviceLeave(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function));
});
it('On zigbee event device leave (removed from database and NOT settings)', async () => {
await controller.start();
zigbeeHerdsman.returnDevices.push('0x00124b00120144ae');
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
const payload = {ieeeAddr: device.ieeeAddr};
await zigbeeHerdsman.events.deviceLeave(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function));
});
it('Publish entity state attribute output', async () => {