Skip to content

Commit

Permalink
Send only 1 MQTT message when last_seen is enabled and Zigbee message…
Browse files Browse the repository at this point in the history
… is received. #9519
  • Loading branch information
Koenkk committed Nov 27, 2021
1 parent a170f30 commit 0a7a477
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 9 deletions.
6 changes: 2 additions & 4 deletions lib/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,8 @@ class Controller {
}
}

if (settings.get().advanced.last_seen && settings.get().advanced.last_seen !== 'disable') {
this.eventBus.onLastSeenChanged(this, (data) =>
this.publishEntityState(data.device, {}, 'lastSeenChanged'));
}
this.eventBus.onLastSeenChanged(this,
(data) => utils.publishLastSeen(data, settings.get(), false, this.publishEntityState));
}

@bind async enableDisableExtension(enable: boolean, name: string): Promise<void> {
Expand Down
10 changes: 9 additions & 1 deletion lib/extension/receive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import debounce from 'debounce';
import Extension from './extension';
import stringify from 'json-stable-stringify-without-jsonify';
import bind from 'bind-decorator';
import utils from '../util/utils';

type DebounceFunction = (() => void) & { clear(): void; } & { flush(): void; };

Expand Down Expand Up @@ -107,7 +108,11 @@ export default class Receive extends Extension {
data = {...data, device: this.zigbee.deviceByNetworkAddress(data.groupID)};
}

if (!this.shouldProcess(data)) return;
if (!this.shouldProcess(data)) {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
return;
}

const converters = data.device.definition.fromZigbee.filter((c) => {
const type = Array.isArray(c.type) ? c.type.includes(data.type) : c.type === data.type;
Expand Down Expand Up @@ -163,6 +168,9 @@ export default class Receive extends Extension {

if (Object.keys(payload).length) {
publish(payload);
} else {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
}
}
}
3 changes: 2 additions & 1 deletion lib/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ declare global {
type StateChange = {
entity: Device | Group, from: KeyValue, to: KeyValue, reason: string | null, update: KeyValue };
type PermitJoinChanged = ZHEvents.PermitJoinChangedPayload;
type LastSeenChanged = { device: Device };
type LastSeenChanged = { device: Device,
reason: 'deviceAnnounce' | 'networkAddress' | 'deviceJoined' | 'messageEmitted' | 'messageNonEmitted'; };
type DeviceNetworkAddressChanged = { device: Device };
type DeviceAnnounce = { device: Device };
type DeviceInterview = { device: Device, status: 'started' | 'successful' | 'failed' };
Expand Down
17 changes: 16 additions & 1 deletion lib/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,26 @@ const hours = (hours: number): number => 1000 * 60 * 60 * hours;
const minutes = (minutes: number): number => 1000 * 60 * minutes;
const seconds = (seconds: number): number => 1000 * seconds;

function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, allowMessageEmitted: boolean,
publishEntityState: PublishEntityState): void {
/**
* Prevent 2 MQTT publishes when 1 message event is received;
* - In case reason == messageEmitted, receive.ts will only call this when it did not publish a
* message based on the received zigbee message. In this case allowMessageEmitted has to be true.
* - In case reason !== messageEmitted, controller.ts will call this based on the zigbee-herdsman
* lastSeenChanged event.
*/
const allow = data.reason !== 'messageEmitted' || (data.reason === 'messageEmitted' && allowMessageEmitted);
if (settings.advanced.last_seen && settings.advanced.last_seen !== 'disable' && allow) {
publishEntityState(data.device, {}, 'lastSeenChanged');
}
}


export default {
endpointNames, capitalize, getZigbee2MQTTVersion, getDependencyVersion, formatDate, objectHasProperties,
equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile,
getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep,
sanitizeImageParameter, isAvailabilityEnabledForDevice,
sanitizeImageParameter, isAvailabilityEnabledForDevice, publishLastSeen,
};
2 changes: 1 addition & 1 deletion lib/zigbee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default class Zigbee {

this.herdsman.on('adapterDisconnected', () => this.eventBus.emitAdapterDisconnected());
this.herdsman.on('lastSeenChanged', (data: ZHEvents.LastSeenChangedPayload) => {
this.eventBus.emitLastSeenChanged({device: this.resolveDevice(data.device.ieeeAddr)});
this.eventBus.emitLastSeenChanged({device: this.resolveDevice(data.device.ieeeAddr), reason: data.reason});
});
this.herdsman.on('permitJoinChanged', (data: ZHEvents.PermitJoinChangedPayload) => {
this.eventBus.emitPermitJoinChanged(data);
Expand Down
12 changes: 11 additions & 1 deletion test/controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,9 +644,19 @@ describe('Controller', () => {
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.remote;
await zigbeeHerdsman.events.lastSeenChanged({device});
await zigbeeHerdsman.events.lastSeenChanged({device, reason: 'deviceAnnounce'});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote', stringify({"brightness":255,"last_seen":1000}), { qos: 0, retain: true }, expect.any(Function));
});

it('Should not publish last seen changes when reason is messageEmitted', async () => {
settings.set(['advanced', 'last_seen'], 'epoch');
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.remote;
await zigbeeHerdsman.events.lastSeenChanged({device, reason: 'messageEmitted'});
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
});

0 comments on commit 0a7a477

Please sign in to comment.