This commit is contained in:
Koen Kanters
2020-07-12 23:50:15 +02:00
parent d81f5575ff
commit ff8f16fedb
4 changed files with 412 additions and 83 deletions
+133 -69
View File
@@ -4,6 +4,8 @@ const Extension = require('./extension');
const utils = require('../util/utils');
const postfixes = utils.getEndpointNames();
const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/(.+)/(remove|add|remove_all)$`);
const legacyTopicRegexRemoveAll = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/remove_all$`);
@@ -26,6 +28,13 @@ class Groups extends Extension {
this.mqtt.subscribe(`${topic}remove_all`);
}
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/add`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/remove`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/remove_all`);
}
}
async onZigbeeStarted() {
@@ -143,20 +152,30 @@ class Groups extends Extension {
let resolvedEntityGroup = null;
let resolvedEntityDevice = null;
let hasEndpointName = null;
let error = null;
let groupKey = null;
let deviceKey = null;
let triggeredViaLegacyApi = false;
/* istanbul ignore else */
if (this.legacyApi) {
const topicMatch = topic.match(legacyTopicRegex);
if (topicMatch) {
resolvedEntityGroup = this.zigbee.resolveEntity(topicMatch[1]);
type = topicMatch[2];
const topicRegexMatch = topic.match(topicRegex);
const legacyTopicRegexRemoveAllMatch = topic.match(legacyTopicRegexRemoveAll);
const legacyTopicRegexMatch = topic.match(legacyTopicRegex);
if (this.legacyApi && (legacyTopicRegexMatch || legacyTopicRegexRemoveAllMatch)) {
triggeredViaLegacyApi = true;
if (legacyTopicRegexMatch) {
resolvedEntityGroup = this.zigbee.resolveEntity(legacyTopicRegexMatch[1]);
type = legacyTopicRegexMatch[2];
if (!resolvedEntityGroup || resolvedEntityGroup.type !== 'group') {
logger.error(`Group '${topicMatch[1]}' does not exist`);
logger.error(`Group '${legacyTopicRegexMatch[1]}' does not exist`);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: message, group: topicMatch[1], error: 'group doesn\'t exists'};
const payload = {
friendly_name: message, group: legacyTopicRegexMatch[1], error: 'group doesn\'t exists',
};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_${type}_failed`, message: payload}),
@@ -165,10 +184,8 @@ class Groups extends Extension {
return {};
}
} else if (topic.match(legacyTopicRegexRemoveAll)) {
type = 'remove_all';
} else {
return {};
type = 'remove_all';
}
resolvedEntityDevice = this.zigbee.resolveEntity(message);
@@ -177,7 +194,9 @@ class Groups extends Extension {
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: message, group: topicMatch[1], error: 'entity doesn\'t exists'};
const payload = {
friendly_name: message, group: legacyTopicRegexMatch[1], error: 'entity doesn\'t exists',
};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_${type}_failed`, message: payload}),
@@ -188,78 +207,123 @@ class Groups extends Extension {
}
hasEndpointName = postfixes.find((p) => message.endsWith(`/${p}`));
} else if (topicRegexMatch) {
type = topicRegexMatch[1];
message = JSON.parse(message);
deviceKey = message.device;
if (type !== 'remove_all') {
groupKey = message.group;
resolvedEntityGroup = this.zigbee.resolveEntity(message.group);
if (!resolvedEntityGroup || resolvedEntityGroup.type !== 'group') {
error = `Group '${message.group}' does not exist`;
}
}
resolvedEntityDevice = this.zigbee.resolveEntity(message.device);
if (!error && (!resolvedEntityDevice || !resolvedEntityDevice.type === 'device')) {
error = `Device '${message.device}' does not exist`;
}
hasEndpointName = postfixes.find((p) => message.device.endsWith(`/${p}`));
}
return {resolvedEntityGroup, resolvedEntityDevice, type, hasEndpointName};
return {
resolvedEntityGroup, resolvedEntityDevice, type, hasEndpointName, error, groupKey, deviceKey,
triggeredViaLegacyApi,
};
}
async onMQTTMessage(topic, message) {
const {resolvedEntityGroup, resolvedEntityDevice, type, hasEndpointName} =
this.parseMQTTMessage(topic, message);
let {
resolvedEntityGroup, resolvedEntityDevice, type, hasEndpointName, error, triggeredViaLegacyApi,
groupKey, deviceKey,
} = this.parseMQTTMessage(topic, message);
if (!type) return;
const keys = [
`${resolvedEntityDevice.device.ieeeAddr}/${resolvedEntityDevice.endpoint.ID}`,
`${resolvedEntityDevice.name}/${resolvedEntityDevice.endpoint.ID}`,
];
const definition = resolvedEntityDevice.definition;
const endpoints = definition && definition.endpoint ? definition.endpoint(resolvedEntityDevice.device) : null;
const endpointName = endpoints ?
Object.entries(endpoints).find((e) => e[1] === resolvedEntityDevice.endpoint.ID)[0] : null;
if (endpointName) {
keys.push(`${resolvedEntityDevice.device.ieeeAddr}/${endpointName}`);
keys.push(`${resolvedEntityDevice.name}/${endpointName}`);
const responseData = {device: deviceKey};
if (groupKey) {
responseData.group = groupKey;
}
if (!hasEndpointName) {
keys.push(resolvedEntityDevice.name);
keys.push(resolvedEntityDevice.device.ieeeAddr);
}
if (!error) {
try {
const keys = [
`${resolvedEntityDevice.device.ieeeAddr}/${resolvedEntityDevice.endpoint.ID}`,
`${resolvedEntityDevice.name}/${resolvedEntityDevice.endpoint.ID}`,
];
if (type === 'add') {
logger.info(`Adding '${resolvedEntityDevice.name}' to '${resolvedEntityGroup.name}'`);
await resolvedEntityDevice.endpoint.addToGroup(resolvedEntityGroup.group);
settings.addDeviceToGroup(resolvedEntityGroup.settings.ID, keys);
const definition = resolvedEntityDevice.definition;
const endpoints = definition && definition.endpoint ?
definition.endpoint(resolvedEntityDevice.device) : null;
const endpointName = endpoints ?
Object.entries(endpoints).find((e) => e[1] === resolvedEntityDevice.endpoint.ID)[0] : null;
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_add`, message: payload}),
);
}
} else if (type === 'remove') {
logger.info(`Removing '${resolvedEntityDevice.name}' from '${resolvedEntityGroup.name}'`);
await resolvedEntityDevice.endpoint.removeFromGroup(resolvedEntityGroup.group);
settings.removeDeviceFromGroup(resolvedEntityGroup.settings.ID, keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove`, message: payload}),
);
}
} else { // remove_all
logger.info(`Removing '${resolvedEntityDevice.name}' from all groups`);
await resolvedEntityDevice.endpoint.removeFromAllGroups();
for (const settingsGroup of settings.getGroups()) {
settings.removeDeviceFromGroup(settingsGroup.ID, keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove_all`, message: payload}),
);
if (endpointName) {
keys.push(`${resolvedEntityDevice.device.ieeeAddr}/${endpointName}`);
keys.push(`${resolvedEntityDevice.name}/${endpointName}`);
}
if (!hasEndpointName) {
keys.push(resolvedEntityDevice.name);
keys.push(resolvedEntityDevice.device.ieeeAddr);
}
if (type === 'add') {
logger.info(`Adding '${resolvedEntityDevice.name}' to '${resolvedEntityGroup.name}'`);
await resolvedEntityDevice.endpoint.addToGroup(resolvedEntityGroup.group);
settings.addDeviceToGroup(resolvedEntityGroup.settings.ID, keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_add`, message: payload}),
);
}
} else if (type === 'remove') {
logger.info(`Removing '${resolvedEntityDevice.name}' from '${resolvedEntityGroup.name}'`);
await resolvedEntityDevice.endpoint.removeFromGroup(resolvedEntityGroup.group);
settings.removeDeviceFromGroup(resolvedEntityGroup.settings.ID, keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove`, message: payload}),
);
}
} else { // remove_all
logger.info(`Removing '${resolvedEntityDevice.name}' from all groups`);
await resolvedEntityDevice.endpoint.removeFromAllGroups();
for (const settingsGroup of settings.getGroups()) {
settings.removeDeviceFromGroup(settingsGroup.ID, keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove_all`, message: payload}),
);
}
}
}
} catch (e) {
error = `Failed to ${type} from group (${e.message})`;
}
}
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/group/members/${type}`, JSON.stringify(response));
}
if (error) {
logger.error(error);
}
}
}
+28
View File
@@ -210,6 +210,34 @@ describe('Bind', () => {
);
});
it('Error bind fails when source not existing', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote_not_existing', to: 'bulb_color'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote_not_existing","to":"bulb_color"},"status":"error","error":"Source device 'remote_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Error bind fails when target not existing', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color_not_existing'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"bulb_color_not_existing"},"status":"error","error":"Target device or group 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Legacy api: Should bind', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
+248 -12
View File
@@ -81,7 +81,7 @@ describe('Groups', () => {
expect(logger.error).toHaveBeenCalledWith("Cannot find 'not_existing_bla' of group 'group_1'");
});
it('Add to group via MQTT', async () => {
it('Legacy api: Add to group via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -96,7 +96,7 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function));
});
it('Add to group with slashes via MQTT', async () => {
it('Legacy api: Add to group with slashes via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups["group/with/slashes"];
@@ -117,7 +117,7 @@ describe('Groups', () => {
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/+/+/+/+/add');
});
it('Add to group via MQTT with postfix', async () => {
it('Legacy api: Add to group via MQTT with postfix', async () => {
const device = zigbeeHerdsman.devices.QBKG03LM;
const endpoint = device.getEndpoint(3);
const group = zigbeeHerdsman.groups.group_1;
@@ -130,7 +130,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
});
it('Add to group via MQTT with postfix shouldnt add it twice', async () => {
it('Legacy api: Add to group via MQTT with postfix shouldnt add it twice', async () => {
const device = zigbeeHerdsman.devices.QBKG03LM;
const endpoint = device.getEndpoint(3);
const group = zigbeeHerdsman.groups.group_1;
@@ -145,7 +145,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
});
it('Remove from group via MQTT', async () => {
it('Legacy api: Remove from group via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -160,7 +160,7 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_remove","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function));
});
it('Remove from group via MQTT when in zigbee but not in settings', async () => {
it('Legacy api: Remove from group via MQTT when in zigbee but not in settings', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -174,7 +174,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual(['dummy']);
});
it('Remove from group via MQTT with postfix variant 1', async () => {
it('Legacy api: Remove from group via MQTT with postfix variant 1', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -188,7 +188,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
});
it('Remove from group via MQTT with postfix variant 2', async () => {
it('Legacy api: Remove from group via MQTT with postfix variant 2', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -202,7 +202,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
});
it('Remove from group via MQTT with postfix variant 3', async () => {
it('Legacy api: Remove from group via MQTT with postfix variant 3', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -216,7 +216,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
});
it('Remove from group all', async () => {
it('Legacy api: Remove from group all', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
@@ -245,7 +245,7 @@ describe('Groups', () => {
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
});
it('Log when adding to non-existing group', async () => {
it('Legacy api: Log when adding to non-existing group', async () => {
await controller.start();
await flushPromises();
logger.error.mockClear();
@@ -254,7 +254,7 @@ describe('Groups', () => {
expect(logger.error).toHaveBeenCalledWith("Group 'group_1_not_existing' does not exist");
});
it('Log when adding to non-existing device', async () => {
it('Legacy api: Log when adding to non-existing device', async () => {
await controller.start();
await flushPromises();
logger.error.mockClear();
@@ -459,4 +459,240 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
});
it('Add to group via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: []}});
expect(group.members.length).toBe(0);
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Add to group via MQTT fails', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: []}});
expect(group.members.length).toBe(0);
await controller.start();
endpoint.addToGroup.mockImplementationOnce(() => {throw new Error('timeout')});
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"error","error":"Failed to add from group (timeout)"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Add to group with slashes via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups["group/with/slashes"];
settings.set(['groups'], {'99': {friendly_name: 'group/with/slashes', retain: false, devices: []}});
expect(group.members.length).toBe(0);
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group/with/slashes', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group/with/slashes').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color","group":"group/with/slashes"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Add to group via MQTT with postfix', async () => {
const device = zigbeeHerdsman.devices.QBKG03LM;
const endpoint = device.getEndpoint(3);
const group = zigbeeHerdsman.groups.group_1;
expect(group.members.length).toBe(0);
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Add to group via MQTT with postfix shouldnt add it twice', async () => {
const device = zigbeeHerdsman.devices.QBKG03LM;
const endpoint = device.getEndpoint(3);
const group = zigbeeHerdsman.groups.group_1;
expect(group.members.length).toBe(0);
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'}));
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [device.ieeeAddr]}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group via MQTT when in zigbee but not in settings', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: ['dummy']}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual(['dummy']);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group via MQTT with postfix variant 1', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [`wall_switch_double/right`]}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"0x0017880104e45542/3","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group via MQTT with postfix variant 2', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [`0x0017880104e45542/right`]}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'wall_switch_double/3'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"wall_switch_double/3","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group via MQTT with postfix variant 3', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [`wall_switch_double/3`]}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/right'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"0x0017880104e45542/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Remove from group all', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [`wall_switch_double/3`]}});
await controller.start();
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove_all', JSON.stringify({device: '0x0017880104e45542/right'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove_all',
JSON.stringify({"data":{"device":"0x0017880104e45542/right"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Error when adding to non-existing group', async () => {
await controller.start();
await flushPromises();
logger.error.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1_not_existing', device: 'bulb_color'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1_not_existing"},"status":"error","error":"Group 'group_1_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Error when adding to non-existing device', async () => {
await controller.start();
await flushPromises();
logger.error.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color_not_existing'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color_not_existing","group":"group_1"},"status":"error","error":"Device 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
});
+3 -2
View File
@@ -48,9 +48,10 @@ class Endpoint {
return this.outputClusters.includes(clusters[cluster]);
}
this.addToGroup = (group) => {
this.addToGroup = jest.fn();
this.addToGroup.mockImplementation((group) => {
if (!group.members.includes(this)) group.members.push(this);
}
})
this.getDevice = () => {
return Object.values(devices).find(d => d.ieeeAddr === deviceIeeeAddress);