Skip to content

Commit

Permalink
Use throttle key instead of device address for throttler
Browse files Browse the repository at this point in the history
  • Loading branch information
gracianodias3 committed Jan 15, 2025
1 parent 9be98ab commit 1449cab
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 3 deletions.
7 changes: 4 additions & 3 deletions lib/extension/receive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ export default class Receive extends Extension {
}

async publishThrottle(device: Device, payload: KeyValue, time: number): Promise<void> {
if (!this.throttlers[device.ieeeAddr]) {
this.throttlers[device.ieeeAddr] = {
const throttleKey = device.ieeeAddr + device.options.throttle;
if (!this.throttlers[throttleKey]) {
this.throttlers[throttleKey] = {
publish: throttle(this.publishEntityState, time * 1000),
};
}
Expand All @@ -81,7 +82,7 @@ export default class Receive extends Extension {
// By updating cache we make sure that state cache is always up-to-date.
this.state.set(device, payload);

await this.throttlers[device.ieeeAddr].publish(device, payload, 'publishThrottle');
await this.throttlers[throttleKey].publish(device, payload, 'publishThrottle');
}

// if debounce_ignore are specified (Array of strings)
Expand Down
94 changes: 94 additions & 0 deletions test/extensions/receive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,100 @@ describe('Extension: Receive', () => {
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
});

it('Should handle the throttle value changing without restart', async () => {
const device = devices.SPAMMER2;
const throttle_for_testing = 10;
settings.set(['device_options', 'throttle'], throttle_for_testing);
settings.set(['device_options', 'retain'], true);
settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'spammer1');
const data1 = {measuredValue: 1};
const payload1 = {
data: data1,
cluster: 'msTemperatureMeasurement',
device,
endpoint: device.getEndpoint(1),
type: 'attributeReport',
linkquality: 10,
};
await mockZHEvents.message(payload1);
const data2 = {measuredValue: 2};
const payload2 = {
data: data2,
cluster: 'msTemperatureMeasurement',
device,
endpoint: device.getEndpoint(1),
type: 'attributeReport',
linkquality: 10,
};
await mockZHEvents.message(payload2);
const data3 = {measuredValue: 3};
const payload3 = {
data: data3,
cluster: 'msTemperatureMeasurement',
device,
endpoint: device.getEndpoint(1),
type: 'attributeReport',
linkquality: 10,
};
await mockZHEvents.message(payload3);
await flushPromises();

expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.01});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: true});

// Now we try after elapsed time to see if it publishes next message
const timeshift = throttle_for_testing * 2000;
vi.advanceTimersByTime(timeshift);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
await flushPromises();

expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.03});
expect(mockMQTTPublishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: true});

// Set throttle to 1s, without any reloading
settings.set(['device_options', 'throttle'], throttle_for_testing / 10);

const data4 = {measuredValue: 4};
const payload4 = {
data: data4,
cluster: 'msTemperatureMeasurement',
device,
endpoint: device.getEndpoint(1),
type: 'attributeReport',
linkquality: 10,
};
await mockZHEvents.message(payload4);
// Advance clock by 2s, not enough time for previous throttle value to pass
vi.advanceTimersByTime((throttle_for_testing * 2000) / 10);

const data5 = {measuredValue: 5};
const payload5 = {
data: data5,
cluster: 'msTemperatureMeasurement',
device,
endpoint: device.getEndpoint(1),
type: 'attributeReport',
linkquality: 10,
};
await mockZHEvents.message(payload5);
await flushPromises();

// Check both messages were published straight away
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(4);
expect(mockMQTTPublishAsync.mock.calls[2][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.04});
expect(mockMQTTPublishAsync.mock.calls[2][2]).toStrictEqual({qos: 0, retain: true});

expect(mockMQTTPublishAsync.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[3][1])).toStrictEqual({temperature: 0.05});
expect(mockMQTTPublishAsync.mock.calls[3][2]).toStrictEqual({qos: 0, retain: true});
});

it('Should throttle multiple messages from spamming devices', async () => {
const device = devices.SPAMMER;
const throttle_for_testing = 1;
Expand Down
10 changes: 10 additions & 0 deletions test/mocks/zigbeeHerdsman.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,16 @@ export const devices = {
'Battery',
'lumi.weather',
),
SPAMMER2: new Device(
'EndDevice',
'0x0017880104e455ab',
6539,
4151,
[new Endpoint(1, [0], [], '0x0017880104e455ab')],
true,
'Battery',
'lumi.weather',
),
RTCGQ11LM: new Device(
'EndDevice',
'0x0017880104e45523',
Expand Down

0 comments on commit 1449cab

Please sign in to comment.