import * as Config from "../Models/Config" import { IAccessory } from './IAccessory'; import callbackify from '../Util/Callbackify'; import HarmonyDataProvider from '../DataProviders/HarmonyDataProvider'; let Service: HAPNodeJS.Service; let Characteristic: HAPNodeJS.Characteristic; let Api: any; let homebridge: any; /** * Enum describing remote key presses from homebridge. */ export enum RemoteKey { REWIND = 0, FAST_FORWARD = 1, NEXT_TRACK = 2, PREVIOUS_TRACK = 3, ARROW_UP = 4, ARROW_DOWN = 5, ARROW_LEFT = 6, ARROW_RIGHT = 7, SELECT = 8, BACK = 9, EXIT = 10, PLAY_PAUSE = 11, INFORMATION = 15, } export interface IControlUnitProps { dataProvider: HarmonyDataProvider, displayName: string, activities: Array, api: any, log: any, homebridge: any, } /** * ControlUnit accessory */ export class ControlUnit implements IAccessory { //Required by homebridge name: string = ""; //fields private log: any = {}; private displayName: string = ""; //Service fields private televisionService: HAPNodeJS.Service | undefined; private televisionSpeakerService: HAPNodeJS.Service | undefined; private informationService: HAPNodeJS.Service | undefined; private inputServices: Array = []; //Harmony fields private activities: Array = []; private dataProvider: HarmonyDataProvider; public platformAccessory: any; /** * Constructor * @param props Input properties */ constructor(props: IControlUnitProps) { //Assign class variables this.log = props.log; Api = props.api; Service = props.api.hap.Service; Characteristic = props.api.hap.Characteristic; this.name = props.displayName; this.displayName = props.displayName; this.activities = props.activities; this.dataProvider = props.dataProvider; homebridge = props.homebridge; this.platformAccessory = new homebridge.platformAccessory(this.displayName, this.generateUUID(), homebridge.hap.Accessory.Categories.TELEVISION); //Configure services this.configureTvService(); this.configureTvSpeakerService(); // this.configureAccessoryInformation(); this.configureInputSourceService(); //Configure external services this.getServices().forEach(service => { try { this.platformAccessory.addService(service); } catch (error) { } //@ts-ignore if (service.linked) { //@ts-ignore this.televisionService!.addLinkedService(service); } }); } /************* * * Tv Service * *************/ /** * Configure television service */ private configureTvService(): void { this.televisionService = new Service.Television( this.displayName, 'tvService' ) this.televisionService.setCharacteristic(Characteristic.ConfiguredName, this.displayName); //@ts-ignore this.televisionService.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); this.televisionService.setCharacteristic(Characteristic.ActiveIdentifier, 1); this.televisionService.setCharacteristic(Characteristic.Active, false); //setup listeners this.televisionService.getCharacteristic(Characteristic.Active) //@ts-ignore .on("set", callbackify(this.onSetAccessoryActive)) //@ts-ignore .on("get", callbackify(this.onGetAccessoryActive)); //Set remote characteristics if is external this.televisionService.getCharacteristic(Characteristic.RemoteKey) //@ts-ignore .on("set", callbackify(this.onSetRemoteKey)); this.televisionService.getCharacteristic(Characteristic.ActiveIdentifier) //@ts-ignore .on("set", callbackify(this.onSetActiveIdentifier)) //@ts-ignore .on("get", callbackify(this.onGetActiveIdentifier)); } /** * Event handler for SET active characteristic */ private onSetAccessoryActive = async (value: any) => { switch (value) { case 0: this.dataProvider.powerOff(this.name); break; //Turn on with first activity case 1: this.dataProvider.powerOn(this.name, this.activities[0]); break; } } /** * Event handler for GET active characteristic */ private onGetAccessoryActive = async () => { //@ts-ignore return this.dataProvider.getIsActive(this.name) ? Characteristic.Active.Active : Characteristic.Active.Inactive } /** * Event handler for SET remote key */ private onSetRemoteKey = async (key: any) => { this.dataProvider.sendKeyPress(this.name, key); } /** * Event handler for SET active identifier characteristic */ private onSetActiveIdentifier = async (identifier: any) => { this.dataProvider.startActivity(this.name, this.activities[identifier]); } /** * Event handler for GET active identifier characteristic */ private onGetActiveIdentifier = async () => { let currentActivity: Config.IActivity = this.dataProvider.getIsActive(this.name)!; let identifier: number = 0; if (currentActivity) { identifier = this.activities.findIndex(e => e.DisplayName === currentActivity.DisplayName); } return identifier; } /****************** * * Speaker Service * *****************/ /** * Configure Speaker Service */ private configureTvSpeakerService(): void { this.televisionSpeakerService = new Service.TelevisionSpeaker( this.displayName, 'tvSpeakerService' ); this.televisionSpeakerService.setCharacteristic(Characteristic.Name, this.displayName); //@ts-ignore this.televisionSpeakerService.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE); //@ts-ignore this.televisionSpeakerService.setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE); this.televisionSpeakerService.subtype = this.displayName + "Volume"; if (this.televisionService) { //@ts-ignore this.televisionService.addLinkedService(this.televisionSpeakerService); } //Setup listeners this.televisionSpeakerService.getCharacteristic(Characteristic.VolumeSelector) //@ts-ignore .on("set", callbackify(this.onSetVolumeSelector)); } /** * Event handler for SET volume characteristic */ private onSetVolumeSelector = async (value: any) => { switch (value) { case 0: this.dataProvider.volumeUp(this.name); break; case 1: this.dataProvider.volumeDown(this.name); break; } } /***************** * * Input services * *****************/ /** * Configure input service */ private configureInputSourceService(): void { let inputs: Array = []; this.activities.forEach((activity: Config.IActivity, index: number) => { let inputService = new Service.InputSource(activity.DisplayName, 'activity' + activity.DisplayName); inputService .setCharacteristic(Characteristic.Identifier, index) .setCharacteristic( Characteristic.ConfiguredName, activity.DisplayName) .setCharacteristic( Characteristic.IsConfigured, //@ts-ignore Characteristic.IsConfigured.CONFIGURED ) //@ts-ignore .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI); //@ts-ignore this.televisionService!.addLinkedService(inputService); inputs.push(inputService); }); this.inputServices = inputs; } 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); }); } /** * Called by homebridge to gather services for this accessory. */ getServices(): Array { let services: Array = [this.televisionService!, this.televisionSpeakerService!]; this.inputServices.forEach((service: HAPNodeJS.Service | undefined) => { services.push(service!); }); return (services); } }