UNPKG

homebridge-broadlink-rm-pro

Version:

Broadlink RM plugin (including the mini and pro) for homebridge with AC Pro and TV features

465 lines (388 loc) 13.5 kB
const ServiceManagerTypes = require('../helpers/serviceManagerTypes'); const delayForDuration = require('../helpers/delayForDuration'); const catchDelayCancelError = require('../helpers/catchDelayCancelError'); const ping = require('../helpers/ping'); const arp = require('../helpers/arp'); const BroadlinkRMAccessory = require('./accessory'); class TVAccessory extends BroadlinkRMAccessory { constructor(log, config = {}, serviceManagerType) { super(log, config, serviceManagerType); if (!config.isUnitTest) {this.checkPing(ping);} } setDefaults() { const { config } = this; config.pingFrequency = config.pingFrequency || 1; config.pingGrace = config.pingGrace || 10; config.offDuration = config.offDuration || 60; config.onDuration = config.onDuration || 60; config.subType = config.subType || 'tv'; if ( config.enableAutoOn === undefined && config.disableAutomaticOn === undefined ) { config.enableAutoOn = false; } else if (config.disableAutomaticOn !== undefined) { config.enableAutoOn = !config.disableAutomaticOn; } if ( config.enableAutoOff === undefined && config.disableAutomaticOff === undefined ) { config.enableAutoOff = false; } else if (config.disableAutomaticOff !== undefined) { config.enableAutoOff = !config.disableAutomaticOff; } } reset() { super.reset(); this.stateChangeInProgress = true; // Clear Timeouts if (this.delayTimeoutPromise) { this.delayTimeoutPromise.cancel(); this.delayTimeoutPromise = null; } if (this.autoOffTimeoutPromise) { this.autoOffTimeoutPromise.cancel(); this.autoOffTimeoutPromise = null; } if (this.autoOnTimeoutPromise) { this.autoOnTimeoutPromise.cancel(); this.autoOnTimeoutPromise = null; } if (this.pingGraceTimeout) { this.pingGraceTimeout.cancel(); this.pingGraceTimeout = null; } if (this.serviceManager.getCharacteristic(Characteristic.Active) === undefined) { this.serviceManager.setCharacteristic(Characteristic.Active, false); } } checkAutoOnOff() { this.reset(); this.checkPingGrace(); this.checkAutoOn(); this.checkAutoOff(); } checkPing(ping) { const { config } = this; let { pingIPAddress, pingFrequency, pingUseArp } = config; if (!pingIPAddress) {return;} // Setup Ping/Arp-based State if(!pingUseArp) {ping(pingIPAddress, pingFrequency, this.pingCallback.bind(this))} else {arp(pingIPAddress, pingFrequency, this.pingCallback.bind(this))} } pingCallback(active) { const { config, state, serviceManager } = this; if (this.stateChangeInProgress){ return; } if (config.pingIPAddressStateOnly) { state.switchState = active ? true : false; serviceManager.updateCharacteristic(Characteristic.Active,state.switchState); return; } const value = active ? true : false; serviceManager.setCharacteristic(Characteristic.Active, value); } async setSwitchState(hexData) { const { data, host, log, name, logLevel } = this; this.stateChangeInProgress = true; this.reset(); if (hexData) {await this.performSend(hexData);} this.checkAutoOnOff(); } async checkPingGrace () { await catchDelayCancelError(async () => { const { config, log, name, state, serviceManager } = this; let { pingGrace } = config; if (pingGrace) { this.pingGraceTimeoutPromise = delayForDuration(pingGrace); await this.pingGraceTimeoutPromise; this.stateChangeInProgress = false; } }); } async checkAutoOff() { await catchDelayCancelError(async () => { const { config, log, name, state, serviceManager } = this; let { disableAutomaticOff, enableAutoOff, onDuration } = config; if (state.switchState && enableAutoOff) { log( `${name} setSwitchState: (automatically turn off in ${onDuration} seconds)` ); this.autoOffTimeoutPromise = delayForDuration(onDuration); await this.autoOffTimeoutPromise; serviceManager.setCharacteristic(Characteristic.Active, false); } }); } async checkAutoOn() { await catchDelayCancelError(async () => { const { config, log, name, state, serviceManager } = this; let { disableAutomaticOn, enableAutoOn, offDuration } = config; if (!state.switchState && enableAutoOn) { log( `${name} setSwitchState: (automatically turn on in ${offDuration} seconds)` ); this.autoOnTimeoutPromise = delayForDuration(offDuration); await this.autoOnTimeoutPromise; serviceManager.setCharacteristic(Characteristic.Active, true); } }); } getServices() { const services = this.getInformationServices(); services.push(this.serviceManager.service); services.push(...this.serviceManagers); return services; } setupServiceManager() { const { data, name, config, serviceManagerType, log } = this; const { on, off } = data || {}; this.serviceManagers = []; this.serviceManager = new ServiceManagerTypes[serviceManagerType]( name, Service.Television, log ); this.serviceManager.setCharacteristic(Characteristic.ConfiguredName, name); this.serviceManager.setCharacteristic( Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE ); this.serviceManager.addToggleCharacteristic({ name: 'switchState', type: Characteristic.Active, getMethod: this.getCharacteristicValue, setMethod: this.setCharacteristicValue, bind: this, props: { onData: on || data, offData: off || undefined, setValuePromise: this.setSwitchState.bind(this) } }); this.serviceManager.setCharacteristic(Characteristic.ActiveIdentifier, 1); this.serviceManager .getCharacteristic(Characteristic.ActiveIdentifier) .on('get', (callback) => callback(null, this.state.input || 0)) .on('set', (newValue, callback) => { if ( !data || !data.inputs || !data.inputs[newValue] || !data.inputs[newValue].data ) { log(`${name} Input: No input data found. Ignoring request.`); callback(null); return; } this.state.input = newValue; this.performSend(data.inputs[newValue].data); callback(null); }); this.serviceManager .getCharacteristic(Characteristic.RemoteKey) .on('set', (newValue, callback) => { if (!data || !data.remote) { log(`${name} RemoteKey: No remote keys found. Ignoring request.`); callback(null); return; } let hexData = null; switch (newValue) { case Characteristic.RemoteKey.REWIND: hexData = data.remote.rewind; // not found yet break; case Characteristic.RemoteKey.FAST_FORWARD: hexData = data.remote.fastForward; // not found yet break; case Characteristic.RemoteKey.NEXT_TRACK: hexData = data.remote.nextTrack; // not found yet break; case Characteristic.RemoteKey.PREVIOUS_TRACK: hexData = data.remote.previousTrack; // not found yet break; case Characteristic.RemoteKey.ARROW_UP: hexData = data.remote.arrowUp; break; case Characteristic.RemoteKey.ARROW_DOWN: hexData = data.remote.arrowDown; break; case Characteristic.RemoteKey.ARROW_LEFT: hexData = data.remote.arrowLeft; break; case Characteristic.RemoteKey.ARROW_RIGHT: hexData = data.remote.arrowRight; break; case Characteristic.RemoteKey.SELECT: hexData = data.remote.select; break; case Characteristic.RemoteKey.BACK: hexData = data.remote.back; break; case Characteristic.RemoteKey.EXIT: hexData = data.remote.exit; break; case Characteristic.RemoteKey.PLAY_PAUSE: hexData = data.remote.playPause; break; case Characteristic.RemoteKey.INFORMATION: hexData = data.remote.info; break; } if (!hexData) { log(`${name} RemoteKey: No IR code found for received remote input!`); callback(null); return; } this.performSend(hexData); callback(null); }); this.serviceManager .getCharacteristic(Characteristic.PictureMode) .on('set', function(newValue, callback) { // Not found yet console.log('set PictureMode => setNewValue: ' + newValue); callback(null); }); this.serviceManager .getCharacteristic(Characteristic.PowerModeSelection) .on('set', (newValue, callback) => { if (!data || !data.powerMode) { log( `${name} PowerModeSelection: No settings data found. Ignoring request.` ); callback(null); return; } let hexData = null; switch (newValue) { case Characteristic.PowerModeSelection.SHOW: // TV settings hexData = data.powerMode.show; break; case Characteristic.PowerModeSelection.HIDE: // not found yet hexData = data.powerMode.hide; break; } if (!hexData) { log( `${name} PowerModeSelection: No IR code found for received remote input!` ); callback(null); return; } this.performSend(hexData); callback(null); }); const speakerService = new Service.TelevisionSpeaker('Speaker', 'Speaker'); speakerService.setCharacteristic( Characteristic.Active, Characteristic.Active.ACTIVE ); speakerService.setCharacteristic( Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE ); speakerService .getCharacteristic(Characteristic.VolumeSelector) .on('set', (newValue, callback) => { if (!data || !data.volume) { log( `${name} VolumeSelector: No settings data found. Ignoring request.` ); callback(null); return; } let hexData = null; switch (newValue) { case Characteristic.VolumeSelector.INCREMENT: hexData = data.volume.up; break; case Characteristic.VolumeSelector.DECREMENT: hexData = data.volume.down; break; } if (!hexData) { log( `${name} VolumeSelector: No IR code found for received remote input!` ); callback(null); return; } this.performSend(hexData); callback(null); }); speakerService .getCharacteristic(Characteristic.Mute) .on('set', (newValue, callback) => { if (!data || !data.volume || !data.volume.mute) { log( `${name} VolumeSelector: No mute data found. Ignoring request.` ); callback(null); return; } let hexData = data.volume.mute; if (!hexData) { log( `${name} VolumeSelector: No IR code found for mute!` ); callback(null); return; } this.performSend(hexData); callback(null); }); this.serviceManagers.push(speakerService); if (data.inputs && data.inputs instanceof Array) { for (let i = 0; i < data.inputs.length; i++) { const input = data.inputs[i]; const inputService = new Service.InputSource(`input${i}`, `input${i}`); inputService .setCharacteristic(Characteristic.Identifier, i) .setCharacteristic(Characteristic.ConfiguredName, input.name) .setCharacteristic( Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED ) .setCharacteristic( Characteristic.InputSourceType, getInputType(input.type) ); this.serviceManagers.push(inputService); this.serviceManager.service.addLinkedService(inputService); } } } } function getInputType(type) { if (!type) { return 0; } switch (type.toLowerCase()) { case 'other': return 0; case 'home_screen': return 1; case 'tuner': return 2; case 'hdmi': return 3; case 'composite_video': return 4; case 's_video': return 5; case 'component_video': return 6; case 'dvi': return 7; case 'airplay': return 8; case 'usb': return 9; case 'application': return 10; } } module.exports = TVAccessory;