UNPKG

homebridge-philips-hue-sync-box

Version:
417 lines (375 loc) 12.9 kB
import { SyncBoxDevice } from './base.js'; import { type CharacteristicValue, HAPStatus, PlatformAccessory, Service, } from 'homebridge'; import type { HueSyncBoxPlatform } from '../platform.js'; import type { State } from '../state.js'; import { PASSTHROUGH, POWER_SAVE, MODE_VIDEO, MODE_MUSIC, MODE_GAME, MODE_LAST_SYNC, BRIGHTNESS_MAX_SYNCBOX, BRIGHTNESS_STEP_SYNCBOX, BRIGHTNESS_MIN, } from '../lib/constants.js'; import { convertSyncBoxToHomekit } from '../lib/brightness.js'; export abstract class BaseTvDevice extends SyncBoxDevice { protected lightbulbService?: Service; protected inputServices: Service[] = []; protected mainAccessory?: PlatformAccessory; protected readonly intensityToNumber: Map<string, number> = new Map([ ['subtle', 1], ['moderate', 2], ['high', 3], ['intense', 4], ]); protected readonly numberToIntensity: Map<number, string> = new Map([ [1, 'subtle'], [2, 'moderate'], [3, 'high'], [4, 'intense'], ]); protected readonly modeToNumber: Map<string, number> = new Map([ [MODE_VIDEO, 1], [MODE_MUSIC, 2], [MODE_GAME, 3], [PASSTHROUGH, 4], [POWER_SAVE, 5], ]); protected readonly numberToMode: Map<number, string> = new Map([ [1, MODE_VIDEO], [2, MODE_MUSIC], [3, MODE_GAME], [4, PASSTHROUGH], [5, POWER_SAVE], ]); protected constructor( protected readonly platform: HueSyncBoxPlatform, public readonly accessory: PlatformAccessory, protected state: State, protected primaryAccessory?: PlatformAccessory ) { super(platform, accessory, state); this.mainAccessory = primaryAccessory; this.createInputServices(); this.createLightbulbService(); this.service.setCharacteristic( this.platform.api.hap.Characteristic.SleepDiscoveryMode, this.platform.api.hap.Characteristic.SleepDiscoveryMode .ALWAYS_DISCOVERABLE ); this.service .getCharacteristic(this.platform.api.hap.Characteristic.RemoteKey) .onSet(this.handleRemoteButton.bind(this)); let name; if (this.platform.config[this.getConfiguredNamePropertyName()]) { name = this.platform.config[this.getConfiguredNamePropertyName()]; } else if ( this.mainAccessory?.context[this.getConfiguredNamePropertyName()] ) { name = this.mainAccessory?.context[this.getConfiguredNamePropertyName()]; } else { name = this.state.device.name; } this.service .getCharacteristic(this.platform.api.hap.Characteristic.ConfiguredName) .onSet(this.handleConfiguredNameChange.bind(this)); this.service.updateCharacteristic( this.platform.api.hap.Characteristic.ConfiguredName, name ); } protected handleConfiguredNameChange(value: CharacteristicValue) { if (this.platform.config[this.getConfiguredNamePropertyName()]) { this.platform.log.warn( this.getConfiguredNamePropertyName() + ' is set in the config, therefore it cannot be changed by HomeKit.' + ' Please change it in the Homebridge config. Alternatively remove the' + ' config and manually configure in HomeKit (not recommended).' ); return; } this.platform.log.debug( this.getConfiguredNamePropertyName() + ' name changed to ' + value ); if (this.mainAccessory) { this.mainAccessory.context[this.getConfiguredNamePropertyName()] = value; } } protected shouldBeOn(): boolean { if ( this.platform.config.treatPassthroughAsOffForTv && this.state.execution.mode === PASSTHROUGH ) { return false; } return this.state.execution.mode !== POWER_SAVE; } protected abstract createInputServices(): void; protected abstract isLightbulbEnabled(): boolean; protected createLightbulbService(): void { if (!this.isLightbulbEnabled()) { return; } this.lightbulbService = this.accessory.getService(this.platform.api.hap.Service.Lightbulb) || this.accessory.addService(this.platform.api.hap.Service.Lightbulb); // Stores the light bulb service // Subscribes for changes of the on characteristic this.lightbulbService .getCharacteristic(this.platform.api.hap.Characteristic.On) .onSet(this.setOnLightbulb.bind(this)); // Subscribes for changes of the brightness characteristic this.lightbulbService .getCharacteristic(this.platform.api.hap.Characteristic.Brightness) .onSet(this.setBrightness.bind(this)); } setOnLightbulb(value: CharacteristicValue) { if (!this.lightbulbService) { return; } this.platform.log.debug('Set On ->', value); const currentVal = this.lightbulbService.getCharacteristic( this.platform.api.hap.Characteristic.On ).value; return this.updateMode(currentVal, value); } protected getServiceType() { return this.platform.api.hap.Service.Television; } protected handleRemoteButton(value: CharacteristicValue) { this.platform.log.debug('Remote key pressed: ' + value); let mode: string; switch (value) { case this.platform.api.hap.Characteristic.RemoteKey.ARROW_UP: this.platform.log.debug('Increase brightness by 25%'); this.updateExecution({ brightness: Math.min( BRIGHTNESS_MAX_SYNCBOX, this.state.execution.brightness + BRIGHTNESS_STEP_SYNCBOX ), }); break; case this.platform.api.hap.Characteristic.RemoteKey.ARROW_DOWN: this.platform.log.debug('Decrease brightness by 25%'); this.updateExecution({ brightness: Math.max( BRIGHTNESS_MIN, this.state.execution.brightness - BRIGHTNESS_STEP_SYNCBOX ), }); break; case this.platform.api.hap.Characteristic.RemoteKey.ARROW_LEFT: { // Gets the current mode or the last sync mode to set the intensity mode = this.getMode(); this.platform.log.debug('Toggle intensity'); if (!this.state.execution[mode]) { this.platform.log.debug( 'Current mode ' + mode + ' does not have an intensity to update' ); break; } const currentIntensity = this.state.execution[mode].intensity; const nextLowestIntensity = (this.intensityToNumber.get(currentIntensity) ?? 4) - 1; if (nextLowestIntensity < 1) { break; } const nextIntensity = this.numberToIntensity.get(nextLowestIntensity); const body = {}; body[mode] = { intensity: nextIntensity, }; this.updateExecution(body); break; } case this.platform.api.hap.Characteristic.RemoteKey.ARROW_RIGHT: { // Gets the current mode or the last sync mode to set the intensity mode = this.getMode(); this.platform.log.debug('Toggle intensity'); if (!this.state.execution[mode]) { this.platform.log.debug( 'Current mode ' + mode + ' does not have an intensity to update' ); break; } const currentIntensity = this.state.execution[mode].intensity; const nextHighestIntensity = (this.intensityToNumber.get(currentIntensity) ?? 1) + 1; if (nextHighestIntensity > 4) { break; } const nextIntensity = this.numberToIntensity.get(nextHighestIntensity); const body = {}; body[mode] = { intensity: nextIntensity, }; this.updateExecution(body); break; } case this.platform.api.hap.Characteristic.RemoteKey.SELECT: { this.platform.log.debug('Toggle mode'); const currentMode = this.state.execution.mode; const nextMode = ((this.modeToNumber.get(currentMode) ?? 4) % 4) + 1; this.updateExecution({ mode: this.numberToMode.get(nextMode), }); break; } case this.platform.api.hap.Characteristic.RemoteKey.PLAY_PAUSE: this.platform.log.debug('Toggle switch state'); if ( this.state.execution.mode !== POWER_SAVE && this.state.execution.mode !== PASSTHROUGH ) { this.updateExecution({ mode: this.platform.config.defaultOffMode, }); } else { let onMode: string = this.platform.config.defaultOnMode; if (onMode === MODE_LAST_SYNC) { onMode = this?.state?.execution?.lastSyncMode ?? MODE_VIDEO; } this.updateExecution({ mode: onMode, }); } break; case this.platform.api.hap.Characteristic.RemoteKey.INFORMATION: { this.platform.log.debug('Toggle hdmi source'); const hdmiSource = this.state.execution.hdmiSource; const currentSourcePosition = parseInt(hdmiSource.replace('input', '')); const nextSourcePosition = (currentSourcePosition % 4) + 1; this.updateExecution({ hdmiSource: 'input' + nextSourcePosition, }); break; } } } protected updateSources(services: Service[]) { // Handles showing/hiding of sources for (const service of services) { service .getCharacteristic( this.platform.api.hap.Characteristic.TargetVisibilityState ) .onSet(this.setVisibility(service)); } } public update(state: State): void { super.update(state); if (!state) { this.setLightbulbUnresponsive(); return; } this.updateTv(); this.updateLightbulb(); } protected abstract updateTv(): void; private updateLightbulb(): void { if (!this.lightbulbService) { return; } this.platform.log.debug('Updated state to ' + this.state.execution.mode); this.lightbulbService.updateCharacteristic( this.platform.api.hap.Characteristic.On, this.state.execution.mode !== POWER_SAVE && this.state.execution.mode !== PASSTHROUGH ); this.platform.log.debug( 'Updated brightness to ' + this.state.execution.brightness ); this.lightbulbService.updateCharacteristic( this.platform.api.hap.Characteristic.Brightness, convertSyncBoxToHomekit(this.state.execution.brightness) ); } private setLightbulbUnresponsive(): void { if (!this.lightbulbService) { return; } this.platform.log.debug('Updated state to ' + this.state.execution.mode); this.lightbulbService.updateCharacteristic( this.platform.api.hap.Characteristic.On, new this.platform.api.hap.HapStatusError( HAPStatus.SERVICE_COMMUNICATION_FAILURE ) ); } protected getInputService( name: string | undefined, position: string ): Service { if (!name) { throw new Error('Name is required'); } const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1); this.platform.log.debug('Creating input service for ' + capitalizedName); const inputService = this.accessory.getServiceById( this.platform.api.hap.Service.InputSource, position ) || this.accessory.addService( this.platform.api.hap.Service.InputSource, position.toLowerCase().replace(' ', ''), position ); // Sets the TV name inputService .setCharacteristic( this.platform.api.hap.Characteristic.ConfiguredName, capitalizedName ) .setCharacteristic( this.platform.api.hap.Characteristic.IsConfigured, this.platform.api.hap.Characteristic.IsConfigured.CONFIGURED ) .setCharacteristic( this.platform.api.hap.Characteristic.CurrentVisibilityState, this.platform.api.hap.Characteristic.CurrentVisibilityState.SHOWN ) .setCharacteristic( this.platform.api.hap.Characteristic.TargetVisibilityState, this.platform.api.hap.Characteristic.TargetVisibilityState.SHOWN ); inputService .setCharacteristic( this.platform.api.hap.Characteristic.Identifier, position[position.length - 1] ) .setCharacteristic( this.platform.api.hap.Characteristic.InputSourceType, this.platform.api.hap.Characteristic.InputSourceType.HDMI ); return inputService; } protected setVisibility(service: Service) { return (value: CharacteristicValue) => { service.setCharacteristic( this.platform.api.hap.Characteristic.CurrentVisibilityState, value ); }; } protected getMode() { let mode = MODE_VIDEO; if ( this.state.execution.mode !== POWER_SAVE && this.state.execution.mode !== PASSTHROUGH ) { mode = this.state.execution.mode; } else if (this.state.execution.lastSyncMode) { mode = this.state.execution.lastSyncMode; } return mode; } protected abstract getConfiguredNamePropertyName(): string; }