diff --git a/package-lock.json b/package-lock.json index 69ae266..3789650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/node": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", + "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", diff --git a/package.json b/package.json index 7867a65..e46919a 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,8 @@ "dependencies": { "homebridge": "^0.4.53", "node-hue-api": "^4.0.5" + }, + "devDependencies": { + "@types/node": "^13.11.1" } -} \ No newline at end of file +} diff --git a/src/fluxAccessory.ts b/src/fluxAccessory.ts index 60c72fa..b81161f 100644 --- a/src/fluxAccessory.ts +++ b/src/fluxAccessory.ts @@ -3,6 +3,7 @@ import Api = require("node-hue-api/lib/api/Api"); import Light = require("node-hue-api/lib/model/Light"); import LightState = require("node-hue-api/lib/model/lightstate/LightState"); import { Sleep } from "./sleep"; +import { Scheduler } from "./scheduler"; let Service: HAPNodeJS.Service; let Characteristic: HAPNodeJS.Characteristic; @@ -23,12 +24,12 @@ export class FluxAccessory implements IAccessory { private _switchService: HAPNodeJS.Service; private _infoService: HAPNodeJS.Service; - private _isActive: boolean; + private _isEnabled: boolean; private _hue: Api; private _lights: Array = []; - private _brightness: number = 0; + private _scheduler: Scheduler; constructor(props: IFluxProps) { //Assign class variables @@ -37,8 +38,9 @@ export class FluxAccessory implements IAccessory { Service = props.api.hap.Service; Characteristic = props.api.hap.Characteristic; this._homebridge = props.homebridge; + this._scheduler = new Scheduler(60000, 60000, this._log); - this._isActive = false; + this._isEnabled = false; this._hue = props.hue; this.platformAccessory = new this._homebridge.platformAccessory(this.name, this.generateUUID(), this._homebridge.hap.Accessory.Categories.SWITCH); @@ -55,20 +57,41 @@ export class FluxAccessory implements IAccessory { ) this._switchService.getCharacteristic(Characteristic.On) - //@ts-ignore - // .on("set", this.onPowerSet) - // .on("get", this.onPowerGet); + //@ts-ignore + .on("set", this.onSetEnabled) + .on("get", this.onGetEnabled); } public name: string = "Flux"; public platformAccessory: any; + /** + * Handler for switch set event + * @param callback The callback function to call when complete + */ + private onSetEnabled = async (activeState: boolean, callback: (error?: Error | null | undefined) => void) => { - public getServices(): HAPNodeJS.Service[] { - throw new Error("Method not implemented."); + return callback(); } + /** + * Handler for switch get event + * @param callback The callback function to call when complete + */ + private onGetEnabled = (callback: (error: Error | null, value: boolean) => void) => { + return callback(null, this._isEnabled); + } + + + /** + * Called by homebridge to gather services. + */ + public getServices = (): Array => { + return [this._infoService, this._switchService!]; + } + + /** * Helper function to convert a hex string to rgb * @param hex hex string starting with "#" diff --git a/src/scheduler.ts b/src/scheduler.ts new file mode 100644 index 0000000..af1fbbe --- /dev/null +++ b/src/scheduler.ts @@ -0,0 +1,93 @@ +import { EventEmitter } from 'events'; +import { Sleep } from './sleep'; + +export interface ITask { + delegate: Delegate; + title: string; +} + +export type Delegate = () => Promise; + +export class Scheduler extends EventEmitter { + private _tasks: Array; + private _isStarted: boolean; + private _log: any; + private _delay: number; + private _watchdog: number; + + constructor(delay: number, watchdog: number, log: any) { + super(); + this._tasks = []; + this._isStarted = false; + this._log = log; + this._delay = delay; + this._watchdog = watchdog; + } + + public get IsStarted(): boolean { + return this._isStarted; + } + + public addTask = (task: ITask): Scheduler => { + if (this._isStarted) { + throw new Error("Stop the scheduler before adding a task"); + } + + this._tasks.push(task); + + return this; + } + + public deleteTask = (task: ITask): Scheduler => { + if (this._isStarted) { + throw new Error("Stop the scheduler before removing a task"); + } + + const idx = this._tasks.indexOf(task); + if (idx > -1) { + this._tasks.splice(idx, 1); + } + + return this; + } + + public start = (): void => { + if (this._tasks.length > 0 && !this._isStarted) { + this.begin(); + } + } + + public stop = (): void => { + if (this._isStarted) { + this._isStarted = false; + } + } + + private begin = async (): Promise => { + let current = ""; + + const timeoutId = setTimeout(() => { + this.emit('error', new Error(`Task '${current}' failed to execute within the watchdog timer`)); + }, + this._watchdog); + + timeoutId.unref(); + + this._isStarted = true; + while (this._isStarted) { + for (const task of this._tasks) { + try { + timeoutId.refresh(); + current = task.title; + + await task.delegate(); + } catch (err) { + this.emit('error', err); + continue; + } + } + } + + await Sleep(this._delay); + } +} \ No newline at end of file