import { IAccessory } from "./models/iAccessory"; import { ISequence } from "./models/iConfig"; 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"; let Service: HAPNodeJS.Service; let Characteristic: HAPNodeJS.Characteristic; export interface IChaseProps { name: string; api: any; log: any; homebridge: any; sequence: ISequence; hue: Api } export class Chase implements IAccessory { private _api: any; private _homebridge: any; private _log: any = {}; //Service fields private _lightbulbService: HAPNodeJS.Service; private _infoService: HAPNodeJS.Service; private _sequence: ISequence; private _isActive: boolean; private _hue: Api; private _lights: Array = []; private _brightness: number = 0; constructor(props: IChaseProps) { //Assign class variables this._log = props.log; this._api = props.api; Service = props.api.hap.Service; Characteristic = props.api.hap.Characteristic; this.name = props.name; this._homebridge = props.homebridge; this._sequence = props.sequence; this._isActive = false; this._hue = props.hue; this.platformAccessory = new this._homebridge.platformAccessory(this.name, this.generateUUID(), this._homebridge.hap.Accessory.Categories.SWITCH); //@ts-ignore this._infoService = new Service.AccessoryInformation(); this._infoService.setCharacteristic(Characteristic.Manufacturer, "Brandon Watson") this._infoService.setCharacteristic(Characteristic.Model, "Hue Chase") this._infoService.setCharacteristic(Characteristic.SerialNumber, "123-456-789"); this._lightbulbService = new Service.Lightbulb( this.name, 'lightbulbService' ) this.getLights(); this._lightbulbService.getCharacteristic(Characteristic.On) //@ts-ignore .on("set", this.onPowerSet) .on("get", this.onPowerGet); this._lightbulbService.getCharacteristic(Characteristic.Brightness) //@ts-ignore .on("set", this.onBrightnessSet) .on("get", this.onBrightnessGet); } /** * Required by homebridge. */ public name: string; public platformAccessory: any; /** * Called by homebridge to gather services. */ public getServices = (): Array => { return [this._infoService, this._lightbulbService!]; } /** * Handler for switch set event * @param callback The callback function to call when complete */ private onPowerSet = async (activeState: boolean, callback: (error?: Error | null | undefined) => void) => { if (this._isActive != activeState) { activeState ? this.chase() : this.stopAndSetOff(); } return callback(); } /** * Handler for switch get event * @param callback The callback function to call when complete */ private onPowerGet = (callback: (error: Error | null, value: boolean) => void) => { return callback(null, this._isActive); } private onBrightnessSet = async (newValue: number, callback: (error?: Error | null | undefined) => void) => { this._brightness = newValue; const lightState = new LightState(); lightState .on(true) .brightness(this._brightness) await this.setLights(lightState); return callback(); } private onBrightnessGet = (callback: (eror: Error | null, value: number) => void) => { return callback(null, this._brightness); } /** * Popuplates internal lights array using the configuration values */ private getLights = async () => { //Get lights const lightPromises: Array> = this._sequence.lights.map(async (value: string) => { //@ts-ignore const light: Light = await this._hue.lights.getLightByName(value) this._lights.push(light); }); await Promise.all(lightPromises); } /** * Execute chase sequence */ private chase = async () => { if (this._lights.length === 0) { await this.getLights(); } const lightState = new LightState(); let idx = 0; this._isActive = true; while (this._isActive) { const rgb = this.hexToRgb(this._sequence.colors[idx]) lightState .on(true) .brightness(this._brightness) .transitionInMillis(this._sequence.transitionTime) .rgb(rgb?.red, rgb?.green, rgb?.blue); await this.setLights(lightState); await Sleep(this._sequence.transitionTime); if (idx == this._sequence.colors.length) { idx = 0; } else { idx++; } } } /** * Stop chase sequence */ private stopAndSetOff = async () => { this._isActive = false; const lightState: LightState = new LightState() .off(); await this.setLights(lightState); } private stop = () => { this._isActive = false; } private setLights = async (state: LightState) => { try { await Promise.all( this._lights.map(async (value: Light) => { await this._hue.lights.setLightState(value.id, state); }) ); } catch (err) { this._log(`Error while setting brightness: ${err}`) } } /** * Helper function to convert a hex string to rgb * @param hex hex string starting with "#" */ private hexToRgb = (hex: string): { red: number, green: number, blue: number } | null => { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { red: parseInt(result[1], 16), green: parseInt(result[2], 16), blue: parseInt(result[3], 16) } : null; } /** * Helper function to generate a UUID */ private generateUUID(): string { // Public Domain/MIT var d = new Date().getTime(); if (typeof performance !== 'undefined' && typeof performance.now === 'function') { d += performance.now(); //use high-precision timer if available } return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); } }