From a8dd13dd899f1dae53b43e61e2c97d764ed5b636 Mon Sep 17 00:00:00 2001 From: Patrick Seal Date: Tue, 7 Nov 2023 15:12:34 -0800 Subject: [PATCH] feat(Bulb): add Bulb#blink method (#162) --- src/bulb/index.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++- test/bulb/index.ts | 28 ++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/bulb/index.ts b/src/bulb/index.ts index 372da6d..0dca168 100644 --- a/src/bulb/index.ts +++ b/src/bulb/index.ts @@ -6,7 +6,7 @@ import Device, { isBulbSysinfo } from '../device'; import type { CommonSysinfo, DeviceConstructorOptions } from '../device'; import Cloud from '../shared/cloud'; import Emeter, { RealtimeNormalized } from '../shared/emeter'; -import Lighting, { LightState } from './lighting'; +import Lighting, { LightState, LightStateInput } from './lighting'; import Schedule from './schedule'; import Time from '../shared/time'; @@ -435,6 +435,52 @@ class Bulb extends Device { return !powerState; } + /** + * Blink Bulb. + * + * Sends `system.lighting.set_light_state` command alternating on at full brightness and off number of `times` at `rate`, + * then sets the light state to its pre-blink state. + * @throws {@link ResponseError} + */ + async blink( + times = 5, + rate = 1000, + sendOptions?: SendOptions, + ): Promise { + const delay = (t: number): Promise => { + return new Promise((resolve) => { + setTimeout(resolve, t); + }); + }; + + const origLightState = await this.lighting.getLightState(sendOptions); + let lastBlink: number; + + let isBlinkOn = false; + for (let i = 0; i < times * 2; i += 1) { + isBlinkOn = !isBlinkOn; + lastBlink = Date.now(); + + const lightState: LightStateInput = isBlinkOn + ? { on_off: 1, brightness: 100 } + : { on_off: 0 }; + + // eslint-disable-next-line no-await-in-loop + await this.lighting.setLightState(lightState, sendOptions); + + const timeToWait = rate / 2 - (Date.now() - lastBlink); + if (timeToWait > 0) { + // eslint-disable-next-line no-await-in-loop + await delay(timeToWait); + } + } + const currLightState = await this.lighting.getLightState(sendOptions); + if (currLightState !== origLightState) { + await this.lighting.setLightState(origLightState, sendOptions); + } + return true; + } + private emitEvents(): void { if (!this.emitEventsEnabled) { return; diff --git a/test/bulb/index.ts b/test/bulb/index.ts index 04a850b..63ff5cd 100644 --- a/test/bulb/index.ts +++ b/test/bulb/index.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import sinon from 'sinon'; -import type { Bulb } from '../../src'; +import type { Bulb, LightStateInput } from '../../src'; import { AnyDevice } from '../../src/client'; import { config, expect, retry, testDevices } from '../setup'; @@ -206,6 +206,32 @@ describe('Bulb', function () { expect(spyPowerUpdate).to.be.calledThrice; }); }); + + describe('#blink()', function () { + it('should blink Bulb', async function () { + expect(await bulb.blink(2, 100)).to.be.true; + }); + + ( + [ + { on_off: 0 }, + { on_off: 1 }, + { on_off: 1, brightness: 50 }, + { on_off: 1, brightness: 75 }, + ] as LightStateInput[] + ).forEach((lightState) => { + it(`should restore the previous lightstate after blink: ${JSON.stringify( + lightState, + )}`, async function () { + await bulb.lighting.setLightState(lightState); + + const preLightState = await bulb.lighting.getLightState(); + expect(await bulb.blink(2, 100)).to.be.true; + const postLightState = await bulb.lighting.getLightState(); + expect(postLightState).to.eql(preLightState); + }); + }); + }); }); }); });