From 66892e136918652dcfbbe7a374c420f76d9cd3c1 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:00:19 +0100 Subject: [PATCH 1/5] fix: Optimize permit join logic --- src/controller/controller.ts | 39 +++++++++--------------------------- src/controller/events.ts | 2 +- test/controller.test.ts | 22 ++++++++++---------- 3 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/controller/controller.ts b/src/controller/controller.ts index 1db602faed..3d8cdb220a 100644 --- a/src/controller/controller.ts +++ b/src/controller/controller.ts @@ -79,7 +79,6 @@ class Controller extends events.EventEmitter { private touchlink: Touchlink; private permitJoinTimeoutTimer: NodeJS.Timeout | undefined; - private permitJoinTimeout: number; private backupTimer: NodeJS.Timeout | undefined; private databaseSaveTimer: NodeJS.Timeout | undefined; private stopping: boolean; @@ -99,7 +98,6 @@ class Controller extends events.EventEmitter { this.adapterDisconnected = true; // set false after adapter.start() is successfully called this.options = mixinDeep(JSON.parse(JSON.stringify(DefaultOptions)), options); this.unknownDevices = new Set(); - this.permitJoinTimeout = 0; // Validate options for (const channel of this.options.network.channelList) { @@ -284,9 +282,8 @@ class Controller extends events.EventEmitter { } public async permitJoin(time: number, device?: Device): Promise { - clearInterval(this.permitJoinTimeoutTimer); + clearTimeout(this.permitJoinTimeoutTimer); this.permitJoinTimeoutTimer = undefined; - this.permitJoinTimeout = 0; if (time > 0) { // never permit more than uint8, and never permit 255 that is often equal to "forever" @@ -295,41 +292,25 @@ class Controller extends events.EventEmitter { await this.adapter.permitJoin(time, device?.networkAddress); await this.greenPower.permitJoin(time, device?.networkAddress); - // TODO: should use setTimeout and timer only for open/close emit - // let the other end (frontend) do the sec-by-sec updating (without mqtt publish) - // Also likely creates a gap of a few secs between what Z2M says and what the stack actually has => unreliable timer end - this.permitJoinTimeout = time; - this.permitJoinTimeoutTimer = setInterval(async (): Promise => { - // assumed valid number while in interval - this.permitJoinTimeout--; - - if (this.permitJoinTimeout <= 0) { - clearInterval(this.permitJoinTimeoutTimer); - this.permitJoinTimeoutTimer = undefined; - this.permitJoinTimeout = 0; - - this.emit('permitJoinChanged', {permitted: false, timeout: this.permitJoinTimeout}); - } else { - this.emit('permitJoinChanged', {permitted: true, timeout: this.permitJoinTimeout}); - } - }, 1000); + this.permitJoinTimeoutTimer = setTimeout(async (): Promise => { + this.emit('permitJoinChanged', {permitted: false}); + + this.permitJoinTimeoutTimer = undefined; + }, time); - this.emit('permitJoinChanged', {permitted: true, timeout: this.permitJoinTimeout}); + this.emit('permitJoinChanged', {permitted: true, duration: time}); } else { logger.debug('Disable joining', NS); await this.greenPower.permitJoin(0); await this.adapter.permitJoin(0); - this.emit('permitJoinChanged', {permitted: false, timeout: this.permitJoinTimeout}); + this.emit('permitJoinChanged', {permitted: false}); } } - /** - * @returns Timeout until permit joining expires. [0-254], with 0 being "not permitting joining". - */ - public getPermitJoinTimeout(): number { - return this.permitJoinTimeout; + public getPermitJoin(): boolean { + return this.permitJoinTimeoutTimer !== undefined; } public isStopping(): boolean { diff --git a/src/controller/events.ts b/src/controller/events.ts index 282950f6b6..a955ccb303 100644 --- a/src/controller/events.ts +++ b/src/controller/events.ts @@ -25,7 +25,7 @@ interface DeviceLeavePayload { interface PermitJoinChangedPayload { permitted: boolean; - timeout?: number; + duration?: number; } interface LastSeenChangedPayload { diff --git a/test/controller.test.ts b/test/controller.test.ts index a014acbda1..388b9f6b31 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -2349,7 +2349,7 @@ describe('Controller', () => { expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 254}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(254); + expect(controller.getPermitJoin()).toStrictEqual(true); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2363,7 +2363,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(controller.getPermitJoinTimeout()).toStrictEqual(4); + expect(controller.getPermitJoin()).toStrictEqual(true); // Timer expired await jest.advanceTimersByTimeAsync(10 * 1000); @@ -2371,7 +2371,7 @@ describe('Controller', () => { expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(events.permitJoinChanged.length).toStrictEqual(255); expect(events.permitJoinChanged[254]).toStrictEqual({permitted: false, timeout: 0}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(0); + expect(controller.getPermitJoin()).toStrictEqual(false); // Green power expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); @@ -2384,7 +2384,7 @@ describe('Controller', () => { expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 254}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(254); + expect(controller.getPermitJoin()).toStrictEqual(true); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2399,7 +2399,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(events.permitJoinChanged.length).toStrictEqual(251); - expect(controller.getPermitJoinTimeout()).toStrictEqual(4); + expect(controller.getPermitJoin()).toStrictEqual(true); // Disable await controller.permitJoin(0); @@ -2408,7 +2408,7 @@ describe('Controller', () => { expect(mockAdapterPermitJoin.mock.calls[1][0]).toStrictEqual(0); expect(events.permitJoinChanged.length).toStrictEqual(252); expect(events.permitJoinChanged[251]).toStrictEqual({permitted: false, timeout: 0}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(0); + expect(controller.getPermitJoin()).toStrictEqual(false); // Green power const commissionFrameDisable = Zcl.Frame.create(1, 1, true, undefined, 3, 'commisioningMode', 33, {options: 0x0a, commisioningWindow: 0}, {}); @@ -2427,13 +2427,13 @@ describe('Controller', () => { expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); expect(mockAdapterPermitJoin.mock.calls[0][1]).toStrictEqual(129); - expect(controller.getPermitJoinTimeout()).toStrictEqual(254); + expect(controller.getPermitJoin()).toStrictEqual(true); // Timer expired await jest.advanceTimersByTimeAsync(300 * 1000); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(controller.getPermitJoinTimeout()).toStrictEqual(0); + expect(controller.getPermitJoin()).toStrictEqual(false); }); it('Controller permit joining for specific time', async () => { @@ -2444,13 +2444,13 @@ describe('Controller', () => { expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(10); expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 10}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(10); + expect(controller.getPermitJoin()).toStrictEqual(true); await jest.advanceTimersByTimeAsync(5 * 1000); expect(events.permitJoinChanged.length).toStrictEqual(6); expect(events.permitJoinChanged[5]).toStrictEqual({permitted: true, timeout: 5}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(5); + expect(controller.getPermitJoin()).toStrictEqual(true); // Timer expired await jest.advanceTimersByTimeAsync(7 * 1000); @@ -2458,7 +2458,7 @@ describe('Controller', () => { expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(events.permitJoinChanged.length).toStrictEqual(11); expect(events.permitJoinChanged[10]).toStrictEqual({permitted: false, timeout: 0}); - expect(controller.getPermitJoinTimeout()).toStrictEqual(0); + expect(controller.getPermitJoin()).toStrictEqual(false); }); it('Controller permit joining for too long time throws', async () => { From 90c6e7ebf566b9ed3c26c03348868d2235427ab7 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:58:43 +0100 Subject: [PATCH 2/5] fixes, align naming --- src/controller/controller.ts | 16 +++++------ src/controller/events.ts | 2 +- test/controller.test.ts | 52 +++++++++++++++++++----------------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/controller/controller.ts b/src/controller/controller.ts index 3d8cdb220a..583625db5f 100644 --- a/src/controller/controller.ts +++ b/src/controller/controller.ts @@ -78,7 +78,7 @@ class Controller extends events.EventEmitter { // @ts-expect-error assigned and validated in start() private touchlink: Touchlink; - private permitJoinTimeoutTimer: NodeJS.Timeout | undefined; + private permitJoinTimer: NodeJS.Timeout | undefined; private backupTimer: NodeJS.Timeout | undefined; private databaseSaveTimer: NodeJS.Timeout | undefined; private stopping: boolean; @@ -282,8 +282,8 @@ class Controller extends events.EventEmitter { } public async permitJoin(time: number, device?: Device): Promise { - clearTimeout(this.permitJoinTimeoutTimer); - this.permitJoinTimeoutTimer = undefined; + clearTimeout(this.permitJoinTimer); + this.permitJoinTimer = undefined; if (time > 0) { // never permit more than uint8, and never permit 255 that is often equal to "forever" @@ -292,13 +292,13 @@ class Controller extends events.EventEmitter { await this.adapter.permitJoin(time, device?.networkAddress); await this.greenPower.permitJoin(time, device?.networkAddress); - this.permitJoinTimeoutTimer = setTimeout(async (): Promise => { + this.permitJoinTimer = setTimeout((): void => { this.emit('permitJoinChanged', {permitted: false}); - this.permitJoinTimeoutTimer = undefined; - }, time); + this.permitJoinTimer = undefined; + }, time * 1000); - this.emit('permitJoinChanged', {permitted: true, duration: time}); + this.emit('permitJoinChanged', {permitted: true, time}); } else { logger.debug('Disable joining', NS); @@ -310,7 +310,7 @@ class Controller extends events.EventEmitter { } public getPermitJoin(): boolean { - return this.permitJoinTimeoutTimer !== undefined; + return this.permitJoinTimer !== undefined; } public isStopping(): boolean { diff --git a/src/controller/events.ts b/src/controller/events.ts index a955ccb303..94404a52a8 100644 --- a/src/controller/events.ts +++ b/src/controller/events.ts @@ -25,7 +25,7 @@ interface DeviceLeavePayload { interface PermitJoinChangedPayload { permitted: boolean; - duration?: number; + time?: number; } interface LastSeenChangedPayload { diff --git a/test/controller.test.ts b/test/controller.test.ts index 388b9f6b31..a290bfddf0 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -490,7 +490,7 @@ describe('Controller', () => { let controller: Controller; beforeAll(async () => { - jest.useFakeTimers({doNotFake: ['setTimeout']}); + jest.useFakeTimers(); Date.now = jest.fn().mockReturnValue(150); setLogger(mockLogger); dummyBackup = await Utils.BackupUtils.toUnifiedBackup(mockDummyBackup); @@ -2346,18 +2346,17 @@ describe('Controller', () => { await controller.permitJoin(254); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); + expect(mockAdapterPermitJoin).toHaveBeenNthCalledWith(1, 254, undefined); expect(events.permitJoinChanged.length).toStrictEqual(1); - expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 254}); + expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); - expect(mocksendZclFrameToAll.mock.calls[0][0]).toStrictEqual(ZSpec.GP_ENDPOINT); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(1, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); expect(deepClone(mocksendZclFrameToAll.mock.calls[0][1])).toStrictEqual(deepClone(commisionFrameEnable)); - expect(mocksendZclFrameToAll.mock.calls[0][2]).toStrictEqual(ZSpec.GP_ENDPOINT); await jest.advanceTimersByTimeAsync(250 * 1000); @@ -2369,8 +2368,8 @@ describe('Controller', () => { await jest.advanceTimersByTimeAsync(10 * 1000); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(events.permitJoinChanged.length).toStrictEqual(255); - expect(events.permitJoinChanged[254]).toStrictEqual({permitted: false, timeout: 0}); + expect(events.permitJoinChanged.length).toStrictEqual(2); + expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); // Green power @@ -2380,43 +2379,41 @@ describe('Controller', () => { it('Controller permit joining all, disabled manually', async () => { await controller.start(); await controller.permitJoin(254); + expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); + expect(mockAdapterPermitJoin).toHaveBeenNthCalledWith(1, 254, undefined); expect(events.permitJoinChanged.length).toStrictEqual(1); - expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 254}); + expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); - expect(mocksendZclFrameToAll.mock.calls[0][0]).toStrictEqual(ZSpec.GP_ENDPOINT); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(1, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); expect(deepClone(mocksendZclFrameToAll.mock.calls[0][1])).toStrictEqual(deepClone(commisionFrameEnable)); - expect(mocksendZclFrameToAll.mock.calls[0][2]).toStrictEqual(ZSpec.GP_ENDPOINT); await jest.advanceTimersByTimeAsync(250 * 1000); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(events.permitJoinChanged.length).toStrictEqual(251); expect(controller.getPermitJoin()).toStrictEqual(true); // Disable await controller.permitJoin(0); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(2); - expect(mockAdapterPermitJoin.mock.calls[1][0]).toStrictEqual(0); - expect(events.permitJoinChanged.length).toStrictEqual(252); - expect(events.permitJoinChanged[251]).toStrictEqual({permitted: false, timeout: 0}); + expect(mockAdapterPermitJoin).toHaveBeenNthCalledWith(2, 0); + expect(events.permitJoinChanged.length).toStrictEqual(2); + expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); // Green power const commissionFrameDisable = Zcl.Frame.create(1, 1, true, undefined, 3, 'commisioningMode', 33, {options: 0x0a, commisioningWindow: 0}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(2); - expect(mocksendZclFrameToAll.mock.calls[1][0]).toStrictEqual(ZSpec.GP_ENDPOINT); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(2, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); expect(deepClone(mocksendZclFrameToAll.mock.calls[1][1])).toStrictEqual(deepClone(commissionFrameDisable)); - expect(mocksendZclFrameToAll.mock.calls[1][2]).toStrictEqual(ZSpec.GP_ENDPOINT); }); it('Controller permit joining through specific device', async () => { @@ -2425,14 +2422,21 @@ describe('Controller', () => { await controller.permitJoin(254, controller.getDeviceByIeeeAddr('0x129')); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(254); - expect(mockAdapterPermitJoin.mock.calls[0][1]).toStrictEqual(129); + expect(mockAdapterPermitJoin).toHaveBeenCalledWith(254, 129); + expect(events.permitJoinChanged.length).toStrictEqual(1); + expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); + expect(controller.getPermitJoin()).toStrictEqual(true); + + await jest.advanceTimersByTimeAsync(120 * 1000); + expect(controller.getPermitJoin()).toStrictEqual(true); // Timer expired await jest.advanceTimersByTimeAsync(300 * 1000); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); + expect(events.permitJoinChanged.length).toStrictEqual(2); + expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); }); @@ -2441,23 +2445,21 @@ describe('Controller', () => { await controller.permitJoin(10); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(mockAdapterPermitJoin.mock.calls[0][0]).toStrictEqual(10); + expect(mockAdapterPermitJoin).toHaveBeenCalledWith(10, undefined); expect(events.permitJoinChanged.length).toStrictEqual(1); - expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, timeout: 10}); + expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 10}); expect(controller.getPermitJoin()).toStrictEqual(true); await jest.advanceTimersByTimeAsync(5 * 1000); - expect(events.permitJoinChanged.length).toStrictEqual(6); - expect(events.permitJoinChanged[5]).toStrictEqual({permitted: true, timeout: 5}); expect(controller.getPermitJoin()).toStrictEqual(true); // Timer expired await jest.advanceTimersByTimeAsync(7 * 1000); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); - expect(events.permitJoinChanged.length).toStrictEqual(11); - expect(events.permitJoinChanged[10]).toStrictEqual({permitted: false, timeout: 0}); + expect(events.permitJoinChanged.length).toStrictEqual(2); + expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); }); From 0063ef62c72b30cf5e8108b457faa0fbf7006f4f Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:17:50 +0100 Subject: [PATCH 3/5] prettier --- test/controller.test.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/controller.test.ts b/test/controller.test.ts index a290bfddf0..b1a1971535 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -2355,7 +2355,13 @@ describe('Controller', () => { const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); - expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(1, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith( + 1, + ZSpec.GP_ENDPOINT, + expect.any(Object), + ZSpec.GP_ENDPOINT, + ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE, + ); expect(deepClone(mocksendZclFrameToAll.mock.calls[0][1])).toStrictEqual(deepClone(commisionFrameEnable)); await jest.advanceTimersByTimeAsync(250 * 1000); @@ -2390,7 +2396,13 @@ describe('Controller', () => { const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); - expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(1, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith( + 1, + ZSpec.GP_ENDPOINT, + expect.any(Object), + ZSpec.GP_ENDPOINT, + ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE, + ); expect(deepClone(mocksendZclFrameToAll.mock.calls[0][1])).toStrictEqual(deepClone(commisionFrameEnable)); await jest.advanceTimersByTimeAsync(250 * 1000); @@ -2412,7 +2424,13 @@ describe('Controller', () => { const commissionFrameDisable = Zcl.Frame.create(1, 1, true, undefined, 3, 'commisioningMode', 33, {options: 0x0a, commisioningWindow: 0}, {}); expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(2); - expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith(2, ZSpec.GP_ENDPOINT, expect.any(Object), ZSpec.GP_ENDPOINT, ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE); + expect(mocksendZclFrameToAll).toHaveBeenNthCalledWith( + 2, + ZSpec.GP_ENDPOINT, + expect.any(Object), + ZSpec.GP_ENDPOINT, + ZSpec.BroadcastAddress.RX_ON_WHEN_IDLE, + ); expect(deepClone(mocksendZclFrameToAll.mock.calls[1][1])).toStrictEqual(deepClone(commissionFrameDisable)); }); From 1fcbc85e8e766df1ef416cc2ad50803d5f565e50 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 30 Nov 2024 21:28:37 +0100 Subject: [PATCH 4/5] Update --- src/controller/controller.ts | 11 ++++++++++- test/controller.test.ts | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/controller/controller.ts b/src/controller/controller.ts index 583625db5f..1e8c0b4617 100644 --- a/src/controller/controller.ts +++ b/src/controller/controller.ts @@ -79,6 +79,7 @@ class Controller extends events.EventEmitter { private touchlink: Touchlink; private permitJoinTimer: NodeJS.Timeout | undefined; + private permitJoinEnd?: number; private backupTimer: NodeJS.Timeout | undefined; private databaseSaveTimer: NodeJS.Timeout | undefined; private stopping: boolean; @@ -284,6 +285,7 @@ class Controller extends events.EventEmitter { public async permitJoin(time: number, device?: Device): Promise { clearTimeout(this.permitJoinTimer); this.permitJoinTimer = undefined; + this.permitJoinEnd = undefined; if (time > 0) { // never permit more than uint8, and never permit 255 that is often equal to "forever" @@ -292,11 +294,14 @@ class Controller extends events.EventEmitter { await this.adapter.permitJoin(time, device?.networkAddress); await this.greenPower.permitJoin(time, device?.networkAddress); + const timeMs = time * 1000; + this.permitJoinEnd = Date.now() + timeMs; this.permitJoinTimer = setTimeout((): void => { this.emit('permitJoinChanged', {permitted: false}); this.permitJoinTimer = undefined; - }, time * 1000); + this.permitJoinEnd = undefined; + }, timeMs); this.emit('permitJoinChanged', {permitted: true, time}); } else { @@ -313,6 +318,10 @@ class Controller extends events.EventEmitter { return this.permitJoinTimer !== undefined; } + public getPermitJoinEnd(): number | undefined { + return this.permitJoinEnd; + } + public isStopping(): boolean { return this.stopping; } diff --git a/test/controller.test.ts b/test/controller.test.ts index b1a1971535..ab5534a72e 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -2343,6 +2343,10 @@ describe('Controller', () => { it('Controller permit joining all, disabled automatically', async () => { await controller.start(); + + expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); + await controller.permitJoin(254); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); @@ -2350,6 +2354,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2369,6 +2374,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Timer expired await jest.advanceTimersByTimeAsync(10 * 1000); @@ -2377,6 +2383,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(2); expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); // Green power expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); @@ -2384,6 +2391,10 @@ describe('Controller', () => { it('Controller permit joining all, disabled manually', async () => { await controller.start(); + + expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); + await controller.permitJoin(254); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); @@ -2391,6 +2402,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2410,6 +2422,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Disable await controller.permitJoin(0); @@ -2419,6 +2432,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(2); expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); // Green power const commissionFrameDisable = Zcl.Frame.create(1, 1, true, undefined, 3, 'commisioningMode', 33, {options: 0x0a, commisioningWindow: 0}, {}); @@ -2444,10 +2458,12 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); await jest.advanceTimersByTimeAsync(120 * 1000); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Timer expired await jest.advanceTimersByTimeAsync(300 * 1000); @@ -2456,6 +2472,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(2); expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); }); it('Controller permit joining for specific time', async () => { @@ -2471,6 +2488,7 @@ describe('Controller', () => { await jest.advanceTimersByTimeAsync(5 * 1000); expect(controller.getPermitJoin()).toStrictEqual(true); + expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); // Timer expired await jest.advanceTimersByTimeAsync(7 * 1000); @@ -2479,6 +2497,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(2); expect(events.permitJoinChanged[1]).toStrictEqual({permitted: false}); expect(controller.getPermitJoin()).toStrictEqual(false); + expect(controller.getPermitJoinEnd()).toBeUndefined(); }); it('Controller permit joining for too long time throws', async () => { From cf9f43b84d5654a57eda49da10d9b79e027d4ff5 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 30 Nov 2024 21:49:40 +0100 Subject: [PATCH 5/5] More precise tests --- test/controller.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/controller.test.ts b/test/controller.test.ts index ab5534a72e..e4374c7b09 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -2347,6 +2347,7 @@ describe('Controller', () => { expect(controller.getPermitJoin()).toStrictEqual(false); expect(controller.getPermitJoinEnd()).toBeUndefined(); + (Date.now as ReturnType).mockReturnValueOnce(0); await controller.permitJoin(254); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); @@ -2354,7 +2355,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); - expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); + expect(controller.getPermitJoinEnd()).toStrictEqual(254 * 1000); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2374,7 +2375,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(controller.getPermitJoin()).toStrictEqual(true); - expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); + expect(controller.getPermitJoinEnd()).toStrictEqual(254 * 1000); // Timer expired await jest.advanceTimersByTimeAsync(10 * 1000); @@ -2395,6 +2396,7 @@ describe('Controller', () => { expect(controller.getPermitJoin()).toStrictEqual(false); expect(controller.getPermitJoinEnd()).toBeUndefined(); + (Date.now as ReturnType).mockReturnValueOnce(0); await controller.permitJoin(254); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); @@ -2402,7 +2404,7 @@ describe('Controller', () => { expect(events.permitJoinChanged.length).toStrictEqual(1); expect(events.permitJoinChanged[0]).toStrictEqual({permitted: true, time: 254}); expect(controller.getPermitJoin()).toStrictEqual(true); - expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); + expect(controller.getPermitJoinEnd()).toStrictEqual(254 * 1000); // Green power const commisionFrameEnable = Zcl.Frame.create(1, 1, true, undefined, 2, 'commisioningMode', 33, {options: 0x0b, commisioningWindow: 254}, {}); @@ -2422,7 +2424,7 @@ describe('Controller', () => { expect(mocksendZclFrameToAll).toHaveBeenCalledTimes(1); expect(mockAdapterPermitJoin).toHaveBeenCalledTimes(1); expect(controller.getPermitJoin()).toStrictEqual(true); - expect(controller.getPermitJoinEnd()).toBeGreaterThan(0); + expect(controller.getPermitJoinEnd()).toStrictEqual(254 * 1000); // Disable await controller.permitJoin(0);