286 lines
9.0 KiB
TypeScript
286 lines
9.0 KiB
TypeScript
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<Config.IActivity>,
|
|
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<HAPNodeJS.Service | undefined> = [];
|
|
|
|
//Harmony fields
|
|
private activities: Array<Config.IActivity> = [];
|
|
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<HAPNodeJS.Service> = [];
|
|
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<HAPNodeJS.Service> {
|
|
let services: Array<HAPNodeJS.Service> = [this.televisionService!, this.televisionSpeakerService!];
|
|
this.inputServices.forEach((service: HAPNodeJS.Service | undefined) => {
|
|
services.push(service!);
|
|
});
|
|
return (services);
|
|
}
|
|
} |