This commit is contained in:
Koen Kanters
2020-06-13 17:22:00 +02:00
parent 1eed937c43
commit 16f5812f6c
3 changed files with 178 additions and 57 deletions
+63 -57
View File
@@ -13,10 +13,8 @@ class Bridge extends Extension {
this.requestLookup = {
'permitjoin': this.permitJoin.bind(this),
// 'device/remove': this.deviceRemove.bind(this),
// 'device/forceremove': this.deviceForceRemove.bind(this),
// 'device/ban': this.deviceBan.bind(this),
// 'group/remove': this.groupRemove.bind(this),
'device/remove': this.deviceRemove.bind(this),
'group/remove': this.groupRemove.bind(this),
};
}
@@ -33,7 +31,7 @@ class Bridge extends Extension {
async onMQTTMessage(topic, message) {
const match = topic.match(requestRegex);
if (match && this.requestLookup[match[1].toLowerCase()]) {
message = JSON.parse(message);
try {message = JSON.parse(message);} catch {}
try {
const response = await this.requestLookup[match[1].toLowerCase()](message);
await this.mqtt.publish(`bridge/response/${match[1]}`, JSON.stringify(response));
@@ -75,21 +73,13 @@ class Bridge extends Extension {
* Requests
*/
// async deviceRemove(message) {
// return this.removeForceRemoveOrBanEntity('remove', 'device', message);
// }
async deviceRemove(message) {
return this.removeEntity('device', message);
}
// async deviceForceRemove(message) {
// return this.removeForceRemoveOrBanEntity('force_remove', 'device', message);
// }
// async deviceBan(message) {
// return this.removeForceRemoveOrBanEntity('ban', 'device', message);
// }
// async groupRemove(message) {
// return this.removeForceRemoveOrBanEntity('remove', 'group', message);
// }
async groupRemove(message) {
return this.removeEntity('group', message);
}
async permitJoin(message) {
const value = typeof message === 'object' ? message.value : message;
@@ -102,51 +92,67 @@ class Bridge extends Extension {
* Utils
*/
// async removeForceRemoveOrBanEntity(action, entityType, message) {
// const ID = typeof message === 'object' ? message.ID : message.trim();
// const entity = this.zigbee.resolveEntity(ID);
// if (!entity || entity.type !== entityType) {
// throw new Error(`${ID} is not a ${entityType}`);
// }
async removeEntity(entityType, message) {
const ID = typeof message === 'object' ? message.ID : message.trim();
const entity = this.zigbee.resolveEntity(ID);
if (!entity || entity.type !== entityType) {
throw new Error(`${utils.capitalize(entityType)} '${ID}' does not exist`);
}
// const lookup = {
// ban: ['banned', 'Banning', 'ban'],
// force_remove: ['force_removed', 'Force removing', 'force remove'],
// remove: ['removed', 'Removing', 'remove'],
// };
let ban = false;
let force = false;
let banForceLog = '';
// try {
// logger.info(`${lookup[action][1]} '${entity.settings.friendlyName}'`);
// if (entity.type === 'device') {
// if (action === 'ban') {
// settings.banDevice(entity.settings.ID);
// }
if (entityType === 'device' && typeof message === 'object') {
ban = !!message.ban;
force = !!message.force;
banForceLog = ` (ban: ${ban}, force: ${force})`;
}
// action === 'force_remove' ?
// await entity.device.removeFromDatabase() : await entity.device.removeFromNetwork();
// } else {
// await entity.group.removeFromDatabase();
// }
try {
logger.info(`Removing ${entity.type} '${entity.settings.friendlyName}'${banForceLog}`);
if (entity.type === 'device') {
if (ban) {
settings.banDevice(entity.settings.ID);
}
// // Fire event
// if (entity.type === 'device') {
// this.eventBus.emit('deviceRemoved', {device: entity.device});
// }
if (force) {
await entity.device.removeFromDatabase()
} else {
await entity.device.removeFromNetwork();
}
} else {
await entity.group.removeFromDatabase();
}
// // Remove from configuration.yaml
// entity.type === 'device' ?
// settings.removeDevice(entity.settings.ID) : settings.removeGroup(entity.settings.ID);
// Fire event
if (entity.type === 'device') {
this.eventBus.emit('deviceRemoved', {device: entity.device});
}
// // Remove from state
// this.state.remove(entity.settings.ID);
// Remove from configuration.yaml
if (entity.type === 'device') {
settings.removeDevice(entity.settings.ID);
} else {
settings.removeGroup(entity.settings.ID);
}
// logger.info(`Successfully ${lookup[action][0]} ${entity.settings.friendlyName}`);
// entity.type === 'device' ? this.publishDevices() : this.publishGroups();
// return utils.getResponse(message, {ID}, null);
// } catch (error) {
// throw new Error(`Failed to ${lookup[action][2]} ${entity.settings.friendlyName} (${error})`);
// }
// }
// Remove from state
this.state.remove(entity.settings.ID);
logger.info(`Successfully removed ${entity.type} '${entity.settings.friendlyName}'${banForceLog}`);
if (entity.type === 'device') {
this.publishDevices();
return utils.getResponse(message, {ID, ban: ban, force: force}, null);
} else {
this.publishGroups();
return utils.getResponse(message, {ID}, null);
}
} catch (error) {
throw new Error(`Failed to remove ${entity.type} '${entity.settings.friendlyName}'${banForceLog} (${error})`);
}
}
async publishInfo() {
const payload = {
+5
View File
@@ -44,6 +44,10 @@ function flatten(arr) {
}, []);
}
function capitalize(s) {
return s[0].toUpperCase() + s.slice(1);
}
const forceEndDevice = flatten(
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
.map((model) => zigbeeHerdsmanConverters.devices.find((d) => d.model === model))
@@ -150,4 +154,5 @@ module.exports = {
formatDate: (date, type) => formatDate(date, type),
equalsPartial,
getResponse,
capitalize,
};
+110
View File
@@ -24,6 +24,9 @@ describe('Bridge', () => {
logger.info.mockClear();
logger.warn.mockClear();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.bulb;
device.removeFromDatabase.mockClear();
device.removeFromNetwork.mockClear();
controller = new Controller();
await controller.start();
await flushPromises();
@@ -205,4 +208,111 @@ describe('Bridge', () => {
await zigbeeHerdsman.events.message({data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await flushPromises();
});
it('Should allow to remove device by string', async () => {
const device = zigbeeHerdsman.devices.bulb;
controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}};
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', 'bulb');
await flushPromises();
expect(controller.state[device.ieeeAddr]).toBeUndefined();
expect(device.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(device.removeFromDatabase).not.toHaveBeenCalled();
expect(settings.getDevice('bulb')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{"ID": "bulb","ban":false,"force":false},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(settings.get().ban).toStrictEqual([]);
});
it('Should allow to remove device by object ID', async () => {
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({ID: "bulb"}));
await flushPromises();
expect(device.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(device.removeFromDatabase).not.toHaveBeenCalled();
expect(settings.getDevice('bulb')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{"ID": "bulb","ban":false,"force":false},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should allow to force remove device', async () => {
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({ID: "bulb", force: true}));
await flushPromises();
expect(device.removeFromDatabase).toHaveBeenCalledTimes(1);
expect(device.removeFromNetwork).not.toHaveBeenCalled();
expect(settings.getDevice('bulb')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{"ID": "bulb","ban":false,"force":true},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should allow to ban device', async () => {
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({ID: "bulb", ban: true, force: true}));
await flushPromises();
expect(device.removeFromDatabase).toHaveBeenCalledTimes(1);
expect(settings.getDevice('bulb')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{"ID": "bulb","ban":true,"force":true},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(settings.get().ban).toStrictEqual(["0x000b57fffec6a5b2"]);
});
it('Should allow to remove group', async () => {
const group = zigbeeHerdsman.groups.group_1;
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/remove', 'group_1');
await flushPromises();
expect(group.removeFromDatabase).toHaveBeenCalledTimes(1);
expect(settings.getGroup('group_1')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/remove',
JSON.stringify({"data":{"ID": "group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should throw error on removing non-existing device', async () => {
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({ID: "non-existing-device"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{},"status":"error","error":"Device 'non-existing-device' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should throw error when remove device fails', async () => {
const device = zigbeeHerdsman.devices.bulb;
MQTT.publish.mockClear();
device.removeFromNetwork.mockImplementationOnce(() => {throw new Error('device timeout')})
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({ID: "bulb"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/remove',
JSON.stringify({"data":{},"status":"error","error":"Failed to remove device 'bulb' (ban: false, force: false) (Error: device timeout)"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
});