Skip to content

Commit

Permalink
Add group off_state option (#12868)
Browse files Browse the repository at this point in the history
  • Loading branch information
Koenkk authored Jun 18, 2022
1 parent ecb18a6 commit f2e953c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 9 deletions.
23 changes: 14 additions & 9 deletions lib/extension/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,10 @@ export default class Groups extends Extension {
if (entity instanceof Device) {
for (const group of groups) {
if (group.zh.hasMember(entity.endpoint(endpointName)) &&
!equals(this.lastOptimisticState[group.ID], payload)) {
if (!payload || payload.state !== 'OFF' || this.areAllMembersOff(group)) {
this.lastOptimisticState[group.ID] = payload;
await this.publishEntityState(group, payload, reason);
}
!equals(this.lastOptimisticState[group.ID], payload) &&
this.shouldPublishPayloadForGroup(group, payload)) {
this.lastOptimisticState[group.ID] = payload;
await this.publishEntityState(group, payload, reason);
}
}
} else {
Expand Down Expand Up @@ -161,10 +160,9 @@ export default class Groups extends Extension {

await this.publishEntityState(device, memberPayload, reason);
for (const zigbeeGroup of groups) {
if (zigbeeGroup.zh.hasMember(member)) {
if (!payload || payload.state !== 'OFF' || this.areAllMembersOff(zigbeeGroup)) {
groupsToPublish.add(zigbeeGroup);
}
if (zigbeeGroup.zh.hasMember(member) &&
this.shouldPublishPayloadForGroup(zigbeeGroup, payload)) {
groupsToPublish.add(zigbeeGroup);
}
}
}
Expand All @@ -176,6 +174,13 @@ export default class Groups extends Extension {
}
}

private shouldPublishPayloadForGroup(group: Group, payload: KeyValue): boolean {
if (group.options.off_state === 'last_member_state') return true;
if (!payload || payload.state !== 'OFF') return true;
if (this.areAllMembersOff(group)) return true;
return false;
}

private areAllMembersOff(group: Group): boolean {
for (const member of group.zh.members) {
const device = this.zigbee.resolveEntity(member.getDevice());
Expand Down
1 change: 1 addition & 0 deletions lib/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ declare global {
devices?: string[],
ID?: number,
optimistic?: boolean,
off_state?: 'all_members_off' | 'last_member_state'
filtered_optimistic?: string[],
retrieve_state?: boolean,
homeassistant?: KeyValue,
Expand Down
8 changes: 8 additions & 0 deletions lib/util/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,14 @@
"qos": {
"type": "number"
},
"off_state": {
"type": ["string"],
"enum": ["all_members_off", "last_member_state"],
"title": "Group off state",
"default": "auto",
"requiresRestart": true,
"description": "Control when to publish state OFF for a group. 'all_members_off': only publish state OFF when all group memebers are in state OFF, 'last_member_state': publish state OFF whenever one of its members changes to OFF"
},
"filtered_attributes": {
"type": "array",
"items": {
Expand Down
24 changes: 24 additions & 0 deletions test/group.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,30 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({state:"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});

it('Should publish state change off if any lights within are still on when changed via device when off_state: last_member_state is used', async () => {
const device_1 = zigbeeHerdsman.devices.bulb_color;
const device_2 = zigbeeHerdsman.devices.bulb;
const endpoint_1 = device_1.getEndpoint(1);
const endpoint_2 = device_2.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint_1);
group.members.push(endpoint_2);
settings.set(['groups'], {
'1': {friendly_name: 'group_1', devices: [device_1.ieeeAddr, device_2.ieeeAddr], retain: false, off_state: 'last_member_state'}
});
await resetExtension();

await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
MQTT.publish.mockClear();

await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenNthCalledWith(1, "zigbee2mqtt/group_1", stringify({state:"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(2, "zigbee2mqtt/bulb_color", stringify({state:"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});

it('Should not publish state change off if any lights within are still on when changed via shared group', async () => {
const device_1 = zigbeeHerdsman.devices.bulb_color;
const device_2 = zigbeeHerdsman.devices.bulb;
Expand Down

0 comments on commit f2e953c

Please sign in to comment.