homebridge-onkyo
Version:
Homebridge plugin for Onkyo Receivers
960 lines (835 loc) • 34.4 kB
JavaScript
/* eslint arrow-body-style: ["off", "never"] */
let Service;
let Characteristic;
let RxInputs;
const pollingtoevent = require('polling-to-event');
const info = require('./package.json');
class OnkyoPlatform {
constructor(log, config, api) {
this.api = api;
this.config = config;
this.log = log;
this.receivers = this.config.receivers;
this.receiverAccessories = [];
this.connections = {};
if (this.receivers === undefined) {
this.log.error('ERROR: your configuration is incorrect. Configuration changed with version 0.7.x');
this.receivers = '';
}
this.createAccessories(this, this.receivers);
}
createAccessories(platform, receivers) {
platform.numberReceivers = platform.receivers.length;
platform.log.debug('Creating %s receivers...', platform.numberReceivers);
if (platform.numberReceivers === 0) return;
receivers.forEach(receiver => { // eslint-disable-line unicorn/no-array-for-each
if (!this.connections[receiver.ip_address]) {
platform.log.debug('Creating new connection for ip %s', receiver.ip_address);
this.connections[receiver.ip_address] = require('./eiscp/eiscp.js');
this.connections[receiver.ip_address].connect({host: receiver.ip_address, reconnect: true, model: receiver.model});
}
const accessory = new OnkyoAccessory(platform, receiver); // eslint-disable-line no-unused-vars
});
}
}
class OnkyoAccessory {
constructor(platform, receiver) {
this.platform = platform;
this.log = platform.log;
this.log.info('**************************************************************');
this.log.info(' homebridge-onkyo version ' + info.version);
this.log.info(' GitHub: https://github.com/ToddGreenfield/homebridge-onkyo ');
this.log.info('**************************************************************');
this.log.info('start success...');
this.log.debug('Debug mode enabled');
this.eiscp = platform.connections[receiver.ip_address];
this.setAttempt = 0;
this.config = receiver;
this.name = this.config.name;
this.log.debug('name %s', this.name);
this.ip_address = this.config.ip_address;
this.log.debug('IP %s', this.ip_address);
this.model = this.config.model;
this.log.debug('Model %s', this.model);
this.zone = (this.config.zone || 'main').toLowerCase();
this.log.debug('Zone %s', this.zone);
if (this.config.volume_type === undefined) {
this.log.error('ERROR: Your configuration is missing the parameter "volume_type". Assuming "none".');
this.volume_type = 'none';
} else {
this.volume_type = this.config.volume_type;
this.log.debug('volume_type: %s', this.volume_type);
}
if (this.config.filter_inputs === undefined) {
this.log.error('ERROR: Your configuration is missing the parameter "filter_inputs". Assuming "false".');
this.filter_inputs = false;
} else {
this.filter_inputs = this.config.filter_inputs;
this.log.debug('filter_inputs: %s', this.filter_inputs);
}
this.inputs = this.config.inputs;
this.cmdMap = new Array(2);
this.cmdMap.main = new Array(4);
this.cmdMap.main.power = 'system-power';
this.cmdMap.main.volume = 'master-volume';
this.cmdMap.main.muting = 'audio-muting';
this.cmdMap.main.input = 'input-selector';
this.cmdMap.zone2 = new Array(4);
this.cmdMap.zone2.power = 'power';
this.cmdMap.zone2.volume = 'volume';
this.cmdMap.zone2.muting = 'muting';
this.cmdMap.zone2.input = 'selector';
this.poll_status_interval = this.config.poll_status_interval || '0';
this.log.debug('poll_status_interval: %s', this.poll_status_interval);
this.defaultInput = this.config.default_input;
this.log.debug('defaultInput: %s', this.defaultInput);
this.defaultVolume = this.config.default_volume;
this.log.debug('defaultVolume: %s', this.defaultVolume);
this.maxVolume = this.config.max_volume || 60;
this.log.debug('maxVolume: %s', this.maxVolume);
this.mapVolume100 = this.config.map_volume_100 || true;
this.log.debug('mapVolume100: %s', this.mapVolume100);
this.buttons = {
[Characteristic.RemoteKey.REWIND]: 'rew',
[Characteristic.RemoteKey.FAST_FORWARD]: 'ff',
[Characteristic.RemoteKey.NEXT_TRACK]: 'skip-f',
[Characteristic.RemoteKey.PREVIOUS_TRACK]: 'skip-r',
[Characteristic.RemoteKey.ARROW_UP]: 'up', // 4
[Characteristic.RemoteKey.ARROW_DOWN]: 'down', // 5
[Characteristic.RemoteKey.ARROW_LEFT]: 'left', // 6
[Characteristic.RemoteKey.ARROW_RIGHT]: 'right', // 7
[Characteristic.RemoteKey.SELECT]: 'enter', // 8
[Characteristic.RemoteKey.BACK]: 'exit', // 9
[Characteristic.RemoteKey.EXIT]: 'exit', // 10
[Characteristic.RemoteKey.PLAY_PAUSE]: 'play', // 11
[Characteristic.RemoteKey.INFORMATION]: 'home', // 15
};
this.state = false;
this.m_state = false;
this.v_state = 0;
this.i_state = null;
this.interval = Number.parseInt(this.poll_status_interval, 10);
this.avrManufacturer = 'Onkyo';
this.avrSerial = this.config.serial || this.ip_address;
this.log.debug('avrSerial: %s', this.avrSerial);
this.switchHandling = 'check';
if (this.interval > 10 && this.interval < 100_000)
this.switchHandling = 'poll';
this.eiscp.on('debug', this.eventDebug.bind(this));
this.eiscp.on('error', this.eventError.bind(this));
this.eiscp.on('connect', this.eventConnect.bind(this));
this.eiscp.on('close', this.eventClose.bind(this));
this.eiscp.on(this.cmdMap[this.zone].power, this.eventSystemPower.bind(this));
this.eiscp.on(this.cmdMap[this.zone].volume, this.eventVolume.bind(this));
this.eiscp.on(this.cmdMap[this.zone].muting, this.eventAudioMuting.bind(this));
this.eiscp.on(this.cmdMap[this.zone].input, this.eventInput.bind(this));
this.setUp();
}
setUp() {
this.createRxInput();
this.polling(this);
this.UUID = this.platform.api.hap.uuid.generate('homebridge:homebridge-onkyo' + this.name);
this.accessory = new this.platform.api.platformAccessory(this.name, this.UUID, this.platform.api.hap.Accessory.Categories.AUDIO_RECEIVER); // eslint-disable-line new-cap
this.createAccessoryInformationService(this.accessory);
this.tvService = this.createTvService(this.accessory);
this.createTvSpeakerService(this.tvService);
this.addSources(this.tvService);
if (this.volume_type) {
this.log.debug('Creating Dimmer service linked to TV for receiver %s', this.name);
this.createVolumeType(this.tvService);
}
this.platform.api.publishExternalAccessories('homebridge-onkyo', [this.accessory]);
}
createRxInput() {
// Create the RxInput object for later use.
const eiscpDataAll = require('./eiscp/eiscp-commands.json');
const inSets = [];
let set;
/* eslint guard-for-in: "off" */
for (set in eiscpDataAll.modelsets) {
eiscpDataAll.modelsets[set].forEach(model => {
if (model.includes(this.model))
inSets.push(set);
});
}
// Get list of commands from eiscpData
const eiscpData = eiscpDataAll.commands.main.SLI.values;
// Create a JSON object for inputs from the eiscpData
let newobj = '{ "Inputs" : [';
let exkey;
for (exkey in eiscpData) {
let hold = eiscpData[exkey].name.toString();
if (hold.includes(','))
hold = hold.slice(0, hold.indexOf(','));
if (exkey.includes('“') || exkey.includes('”')) {
exkey = exkey.replace(/“/g, '');
exkey = exkey.replace(/”/g, '');
}
if (exkey.includes('UP') || exkey.includes('DOWN') || exkey.includes('QSTN'))
continue;
// Work around specific bug for “26”
if (exkey === '“26”')
exkey = '26';
if (exkey in eiscpData) {
if ('models' in eiscpData[exkey])
set = eiscpData[exkey].models;
else
continue;
} else {
continue;
}
if (inSets.includes(set))
newobj = newobj + '{ "code":"' + exkey + '" , "label":"' + hold + '" },';
else
continue;
}
// Drop last comma first
newobj = newobj.slice(0, -1) + ']}';
RxInputs = JSON.parse(newobj);
}
polling(platform) {
const that = platform;
// Status Polling
if (that.switchHandling === 'poll') {
// somebody instroduced powerurl but we are never using it.
// const powerurl = that.status_url;
that.log.debug('start long poller..');
// PWR Polling
const statusemitter = pollingtoevent(done => {
that.log.debug('start PWR polling..');
that.getPowerState((error, response) => {
// pass also the setAttempt, to force a homekit update if needed
done(error, response, that.setAttempt);
}, 'statuspoll');
}, {longpolling: true, interval: that.interval * 1000, longpollEventName: 'statuspoll'});
statusemitter.on('statuspoll', data => {
that.state = data;
that.log.debug('event - PWR status poller - new state: ', that.state);
// if (that.tvService ) {
// that.tvService.getCharacteristic(Characteristic.Active).updateValue(that.state, null, 'statuspoll');
// }
});
// Audio-Input Polling
const i_statusemitter = pollingtoevent(done => {
that.log.debug('start INPUT polling..');
that.getInputSource((error, response) => {
// pass also the setAttempt, to force a homekit update if needed
done(error, response, that.setAttempt);
}, 'i_statuspoll');
}, {longpolling: true, interval: that.interval * 1000, longpollEventName: 'i_statuspoll'});
i_statusemitter.on('i_statuspoll', data => {
that.i_state = data;
that.log.debug('event - INPUT status poller - new i_state: ', that.i_state);
// if (that.tvService ) {
// that.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(that.i_state, null, 'i_statuspoll');
// }
});
// Audio-Muting Polling
const m_statusemitter = pollingtoevent(done => {
that.log.debug('start MUTE polling..');
that.getMuteState((error, response) => {
// pass also the setAttempt, to force a homekit update if needed
done(error, response, that.setAttempt);
}, 'm_statuspoll');
}, {longpolling: true, interval: that.interval * 1000, longpollEventName: 'm_statuspoll'});
m_statusemitter.on('m_statuspoll', data => {
that.m_state = data;
that.log.debug('event - MUTE status poller - new m_state: ', that.m_state);
// if (that.tvService ) {
// that.tvService.getCharacteristic(Characteristic.Mute).updateValue(that.m_state, null, 'm_statuspoll');
// }
});
// Volume Polling
const v_statusemitter = pollingtoevent(done => {
that.log.debug('start VOLUME polling..');
that.getVolumeState((error, response) => {
// pass also the setAttempt, to force a homekit update if needed
done(error, response, that.setAttempt);
}, 'v_statuspoll');
}, {longpolling: true, interval: that.interval * 1000, longpollEventName: 'v_statuspoll'});
v_statusemitter.on('v_statuspoll', data => {
that.v_state = data;
that.log.debug('event - VOLUME status poller - new v_state: ', that.v_state);
// if (that.tvService ) {
// that.tvService.getCharacteristic(Characteristic.Volume).updateValue(that.v_state, null, 'v_statuspoll');
// }
});
}
}
/// ////////////////
// EVENT FUNCTIONS
/// ////////////////
eventDebug(response) {
this.log.debug('eventDebug: %s', response);
}
eventError(response) {
this.log.error('eventError: %s', response);
}
eventConnect(response) {
this.log.debug('eventConnect: %s', response);
this.reachable = true;
}
eventSystemPower(response) {
if (this.state !== (response === 'on'))
this.log.info('Event - System Power changed: %s', response);
this.state = (response === 'on');
this.log.debug('eventSystemPower - message: %s, new state %s', response, this.state);
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state);
// if (this.volume_dimmer) {
// this.m_state = !(response == 'on');
// this.dimmer.getCharacteristic(Characteristic.On).updateValue((response == 'on'), null, 'power event m_status');
// }
}
eventAudioMuting(response) {
this.m_state = (response === 'on');
this.log.debug('eventAudioMuting - message: %s, new m_state %s', response, this.m_state);
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.Mute).updateValue(this.m_state, null, 'm_statuspoll');
}
eventInput(response) {
if (response) {
let input = JSON.stringify(response);
input = input.replace(/[[\]"]+/g, '');
if (input.includes(','))
input = input.slice(0, input.indexOf(','));
// Convert to i_state input code
const index
= input !== null // eslint-disable-line no-negated-condition
? RxInputs.Inputs.findIndex(i => i.label === input)
: -1;
if (this.i_state !== (index + 1))
this.log.info('Event - Input changed: %s', input);
this.i_state = index + 1;
this.log.debug('eventInput - message: %s - new i_state: %s - input: %s', response, this.i_state, input);
// this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(this.i_state);
} else {
// Then invalid Input chosen
this.log.error('eventInput - ERROR - INVALID INPUT - Model does not support selected input.');
}
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(this.i_state);
}
eventVolume(response) {
if (this.mapVolume100) {
const volumeMultiplier = this.maxVolume / 100;
const newVolume = response / volumeMultiplier;
this.v_state = Math.round(newVolume);
this.log.debug('eventVolume - message: %s, new v_state %s PERCENT', response, this.v_state);
} else {
this.v_state = response;
this.log.debug('eventVolume - message: %s, new v_state %s ACTUAL', response, this.v_state);
}
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.Volume).updateValue(this.v_state, null, 'v_statuspoll');
}
eventClose(response) {
this.log.debug('eventClose: %s', response);
this.reachable = false;
}
/// /////////////////////
// GET AND SET FUNCTIONS
/// /////////////////////
setPowerState(powerOn, callback, context) {
// if context is statuspoll, then we need to ensure that we do not set the actual value
if (context && context === 'statuspoll') {
this.log.debug('setPowerState - polling mode, ignore, state: %s', this.state);
callback(null, this.state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
this.setAttempt++;
// do the callback immediately, to free homekit
// have the event later on execute changes
this.state = powerOn;
callback(null, this.state);
if (powerOn) {
this.log.debug('setPowerState - actual mode, power state: %s, switching to ON', this.state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].power + '=on', (error, _) => {
// this.log.debug( 'PWR ON: %s - %s -- current state: %s', error, response, this.state);
if (error) {
this.state = false;
this.log.error('setPowerState - PWR ON: ERROR - current state: %s', this.state);
// if (this.tvService ) {
// this.tvService.getCharacteristic(Characteristic.Active).updateValue(powerOn, null, 'statuspoll');
// }
} else {
// If the AVR has just been turned on, apply the default volume
this.log.debug('Attempting to set the default volume to ' + this.defaultVolume);
if (powerOn && this.defaultVolume) {
this.log.info('Setting default volume to ' + this.defaultVolume);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].volume + ':' + this.defaultVolume, function (error, _) {
if (error)
this.log.error('Error while setting default volume: %s', error);
});
}
// If the AVR has just been turned on, apply the Input default
this.log.debug('Attempting to set the default input selector to ' + this.defaultInput);
// Handle defaultInput being either a custom label or manufacturer label
let label = this.defaultInput;
if (this.inputs) {
this.inputs.forEach((input, _) => {
if (input.input_name === this.default)
label = input.input_name;
else if (input.display_name === this.defaultInput)
label = input.display_name;
});
}
const index
= label !== null // eslint-disable-line no-negated-condition
? RxInputs.Inputs.findIndex(i => i.label === label)
: -1;
this.i_state = index + 1;
if (powerOn && label) {
this.log.info('Setting default input selector to ' + label);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].input + '=' + label, function (error, _) {
if (error)
this.log.error('Error while setting default input: %s', error);
});
}
}
});
} else {
this.log.debug('setPowerState - actual mode, power state: %s, switching to OFF', this.state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].power + '=standby', (error, _) => {
// this.log.debug( 'PWR OFF: %s - %s -- current state: %s', error, response, this.state);
if (error) {
this.state = false;
this.log.error('setPowerState - PWR OFF: ERROR - current state: %s', this.state);
// if (this.tvService ) {
// this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state, null, 'statuspoll');
// }
}
});
}
// if (this.volume_dimmer) {
// this.m_state = !(powerOn == 'on');
// this.dimmer.getCharacteristic(Characteristic.On).updateValue((powerOn == 'on'), null, 'power event m_status');
// }
this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state);
}
getPowerState(callback, context) {
// if context is statuspoll, then we need to request the actual value
if ((!context || context !== 'statuspoll') && this.switchHandling === 'poll') {
this.log.debug('getPowerState - polling mode, return state: ', this.state);
callback(null, this.state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.state);
this.log.debug('getPowerState - actual mode, return state: ', this.state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].power + '=query', (error, _) => {
if (error) {
this.state = false;
this.log.debug('getPowerState - PWR QRY: ERROR - current state: %s', this.state);
}
});
this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state);
}
getVolumeState(callback, context) {
// if context is v_statuspoll, then we need to request the actual value
if ((!context || context !== 'v_statuspoll') && this.switchHandling === 'poll') {
this.log.debug('getVolumeState - polling mode, return v_state: ', this.v_state);
callback(null, this.v_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.v_state);
this.log.debug('getVolumeState - actual mode, return v_state: ', this.v_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].volume + '=query', (error, _) => {
if (error) {
this.v_state = 0;
this.log.debug('getVolumeState - VOLUME QRY: ERROR - current v_state: %s', this.v_state);
}
});
// Communicate status
if (this.tvService)
this.tvSpeakerService.getCharacteristic(Characteristic.Volume).updateValue(this.v_state);
}
setVolumeState(volumeLvl, callback, context) {
// if context is v_statuspoll, then we need to ensure this we do not set the actual value
if (context && context === 'v_statuspoll') {
this.log.debug('setVolumeState - polling mode, ignore, v_state: %s', this.v_state);
callback(null, this.v_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
this.setAttempt++;
// Are we mapping volume to 100%?
if (this.mapVolume100) {
const volumeMultiplier = this.maxVolume / 100;
const newVolume = volumeMultiplier * volumeLvl;
this.v_state = Math.round(newVolume);
this.log.debug('setVolumeState - actual mode, PERCENT, volume v_state: %s', this.v_state);
} else if (volumeLvl > this.maxVolume) {
// Determin if maxVolume threshold breached, if so set to max.
this.v_state = this.maxVolume;
this.log.debug('setVolumeState - VOLUME LEVEL of: %s exceeds maxVolume: %s. Resetting to max.', volumeLvl, this.maxVolume);
} else {
// Must be using actual volume number
this.v_state = volumeLvl;
this.log.debug('setVolumeState - actual mode, ACTUAL volume v_state: %s', this.v_state);
}
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.v_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].volume + ':' + this.v_state, (error, _) => {
if (error) {
this.v_state = 0;
this.log.debug('setVolumeState - VOLUME : ERROR - current v_state: %s', this.v_state);
}
});
// Communicate status
if (this.tvService)
this.tvSpeakerService.getCharacteristic(Characteristic.Volume).updateValue(this.v_state);
}
setVolumeRelative(volumeDirection, callback, context) {
// if context is v_statuspoll, then we need to ensure this we do not set the actual value
if (context && context === 'v_statuspoll') {
this.log.debug('setVolumeRelative - polling mode, ignore, v_state: %s', this.v_state);
callback(null, this.v_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
this.setAttempt++;
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.v_state);
if (volumeDirection === Characteristic.VolumeSelector.INCREMENT) {
this.log.debug('setVolumeRelative - VOLUME : level-up');
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].volume + ':level-up', (error, _) => {
if (error) {
this.v_state = 0;
this.log.error('setVolumeRelative - VOLUME : ERROR - current v_state: %s', this.v_state);
}
});
} else if (volumeDirection === Characteristic.VolumeSelector.DECREMENT) {
this.log.debug('setVolumeRelative - VOLUME : level-down');
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].volume + ':level-down', (error, _) => {
if (error) {
this.v_state = 0;
this.log.error('setVolumeRelative - VOLUME : ERROR - current v_state: %s', this.v_state);
}
});
} else {
this.log.error('setVolumeRelative - VOLUME : ERROR - unknown direction sent');
}
// Communicate status
if (this.tvService)
this.tvSpeakerService.getCharacteristic(Characteristic.Volume).updateValue(this.v_state);
}
getMuteState(callback, context) {
// if context is m_statuspoll, then we need to request the actual value
if ((!context || context !== 'm_statuspoll') && this.switchHandling === 'poll') {
this.log.debug('getMuteState - polling mode, return m_state: ', this.m_state);
callback(null, this.m_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.m_state);
this.log.debug('getMuteState - actual mode, return m_state: ', this.m_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].muting + '=query', (error, _) => {
if (error) {
this.m_state = false;
this.log.debug('getMuteState - MUTE QRY: ERROR - current m_state: %s', this.m_state);
}
});
// Communicate status
if (this.tvService)
this.tvSpeakerService.getCharacteristic(Characteristic.Mute).updateValue(this.m_state);
}
setMuteState(muteOn, callback, context) {
// if context is m_statuspoll, then we need to ensure this we do not set the actual value
if (context && context === 'm_statuspoll') {
this.log.debug('setMuteState - polling mode, ignore, m_state: %s', this.m_state);
callback(null, this.m_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
this.setAttempt++;
// do the callback immediately, to free homekit
// have the event later on execute changes
this.m_state = muteOn;
callback(null, this.m_state);
if (this.m_state) {
this.log.debug('setMuteState - actual mode, mute m_state: %s, switching to ON', this.m_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].muting + '=on', (error, _) => {
if (error) {
this.m_state = false;
this.log.error('setMuteState - MUTE ON: ERROR - current m_state: %s', this.m_state);
}
});
} else {
this.log.debug('setMuteState - actual mode, mute m_state: %s, switching to OFF', this.m_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].muting + '=off', (error, _) => {
if (error) {
this.m_state = false;
this.log.error('setMuteState - MUTE OFF: ERROR - current m_state: %s', this.m_state);
}
});
}
// Communicate status
if (this.tvService)
this.tvSpeakerService.getCharacteristic(Characteristic.Mute).updateValue(this.m_state);
}
getInputSource(callback, context) {
// if context is i_statuspoll, then we need to request the actual value
if ((!context || context !== 'i_statuspoll') && this.switchHandling === 'poll') {
this.log.debug('getInputState - polling mode, return i_state: ', this.i_state);
callback(null, this.i_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
// do the callback immediately, to free homekit
// have the event later on execute changes
this.log.debug('getInputState - actual mode, return i_state: ', this.i_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].input + '=query', (error, _) => {
if (error) {
this.i_state = 1;
this.log.error('getInputState - INPUT QRY: ERROR - current i_state: %s', this.i_state);
}
});
callback(null, this.i_state);
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(this.i_state);
}
setInputSource(source, callback, context) {
// if context is i_statuspoll, then we need to ensure this we do not set the actual value
if (context && context === 'i_statuspoll') {
this.log.info('setInputState - polling mode, ignore, i_state: %s', this.i_state);
callback(null, this.i_state);
return;
}
if (!this.ip_address) {
this.log.error('Ignoring request; No ip_address defined.');
callback(new Error('No ip_address defined.'));
return;
}
this.setAttempt++;
this.i_state = source;
const label = RxInputs.Inputs[this.i_state - 1].label;
this.log.debug('setInputState - actual mode, ACTUAL input i_state: %s - label: %s', this.i_state, label);
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, this.i_state);
this.eiscp.command(this.zone + '.' + this.cmdMap[this.zone].input + ':' + label, (error, _) => {
if (error)
this.log.error('setInputState - INPUT : ERROR - current i_state:%s - Source:%s', this.i_state, source.toString());
});
// Communicate status
if (this.tvService)
this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(this.i_state);
}
remoteKeyPress(button, callback) {
// do the callback immediately, to free homekit
// have the event later on execute changes
callback(null, button);
if (this.buttons[button]) {
const press = this.buttons[button];
this.log.debug('remoteKeyPress - INPUT: pressing key %s', press);
this.eiscp.command(this.zone + '.setup=' + press, (error, _) => {
if (error) {
// this.i_state = 1;
this.log.error('remoteKeyPress - INPUT: ERROR pressing button %s', press);
}
});
} else {
this.log.error('Remote button %d not supported.', button);
}
}
identify(callback) {
this.log.info('Identify requested! %s', this.ip_address);
callback(); // success
}
/// /////////////////////
// TV SERVICE FUNCTIONS
/// /////////////////////
addSources(service) {
// If input name mappings are provided, use them.
// Option to only configure specified inputs with filter_inputs
if (this.filter_inputs) {
// Check the RxInputs.Inputs items to see if each exists in this.inputs. Return new array of those that do.
RxInputs.Inputs = RxInputs.Inputs.filter(rxinput => {
return this.inputs.some(input => {
return input.input_name === rxinput.label;
});
});
}
this.log.debug(RxInputs.Inputs);
// Create final array of inputs, using any labels defined in the config's inputs to override the default labels
const inputs = RxInputs.Inputs.map((i, index) => {
const hapId = index + 1;
let inputName = i.label;
if (this.inputs) {
this.inputs.forEach((input, _) => {
if (input.input_name === i.label)
inputName = input.display_name;
});
}
const input = this.setupInput(i.code, inputName, hapId, service);
return input;
});
return inputs;
}
setupInput(inputCode, name, hapId, television) {
const input = this.accessory.addService(Service.InputSource, `${this.name} ${name}`, inputCode);
const inputSourceType = Characteristic.InputSourceType.HDMI;
input
.setCharacteristic(Characteristic.Identifier, hapId)
.setCharacteristic(Characteristic.ConfiguredName, name)
.setCharacteristic(
Characteristic.IsConfigured,
Characteristic.IsConfigured.CONFIGURED,
)
.setCharacteristic(Characteristic.InputSourceType, inputSourceType);
input.getCharacteristic(Characteristic.ConfiguredName).setProps({
perms: [Characteristic.Perms.READ],
});
television.addLinkedService(input);
return input;
}
createAccessoryInformationService(accessory) {
let informationService = accessory.getService(Service.AccessoryInformation);
if (!informationService)
informationService = accessory.addService(Service.AccessoryInformation);
informationService
.setCharacteristic(Characteristic.Manufacturer, this.avrManufacturer)
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.avrSerial)
.setCharacteristic(Characteristic.FirmwareRevision, info.version)
.setCharacteristic(Characteristic.Name, this.name);
return informationService;
}
createVolumeType(service) {
if (this.volume_type === 'dimmer') {
this.dimmer = this.accessory.addService(Service.Lightbulb, this.name + ' Volume', 'dimmer');
this.dimmer
.getCharacteristic(Characteristic.On)
// Inverted logic taken from https://github.com/langovoi/homebridge-upnp
.on('get', callback => {
this.getMuteState((error, value) => {
if (error) {
callback(error);
return;
}
callback(null, !value);
});
})
.on('set', (value, callback) => this.setMuteState(!value, callback));
this.dimmer
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getVolumeState.bind(this))
.on('set', this.setVolumeState.bind(this));
service.addLinkedService(this.dimmer);
} else if (this.volume_type === 'speed') {
this.speed = this.accessory.addService(Service.Fan, this.name + ' Volume', 'speed');
this.speed
.getCharacteristic(Characteristic.On)
// Inverted logic taken from https://github.com/langovoi/homebridge-upnp
.on('get', callback => {
this.getMuteState((error, value) => {
if (error) {
callback(error);
return;
}
callback(null, !value);
});
})
.on('set', (value, callback) => this.setMuteState(!value, callback));
this.speed
.addCharacteristic(Characteristic.RotationSpeed)
.on('get', this.getVolumeState.bind(this))
.on('set', this.setVolumeState.bind(this));
service.addLinkedService(this.speed);
}
}
createTvService(accessory) {
this.log.debug('Creating TV service for receiver %s', this.name);
const tvService = accessory.addService(Service.Television, this.name);
tvService
.getCharacteristic(Characteristic.ConfiguredName)
.setValue(this.name)
.setProps({
perms: [Characteristic.Perms.READ],
});
tvService
.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE);
tvService
.getCharacteristic(Characteristic.Active)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
tvService
.getCharacteristic(Characteristic.ActiveIdentifier)
.on('set', this.setInputSource.bind(this))
.on('get', this.getInputSource.bind(this));
tvService
.getCharacteristic(Characteristic.RemoteKey)
.on('set', this.remoteKeyPress.bind(this));
return tvService;
}
createTvSpeakerService(tvService) {
this.tvSpeakerService = this.accessory.addService(Service.TelevisionSpeaker, this.name + ' Volume', 'tvSpeakerService');
this.tvSpeakerService
.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE)
.setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE);
this.tvSpeakerService
.getCharacteristic(Characteristic.VolumeSelector)
.on('set', this.setVolumeRelative.bind(this));
this.tvSpeakerService
.getCharacteristic(Characteristic.Mute)
.on('get', this.getMuteState.bind(this))
.on('set', this.setMuteState.bind(this));
this.tvSpeakerService
.addCharacteristic(Characteristic.Volume)
.on('get', this.getVolumeState.bind(this))
.on('set', this.setVolumeState.bind(this));
tvService.addLinkedService(this.tvSpeakerService);
}
}
module.exports = homebridge => {
({Service, Characteristic} = homebridge.hap);
homebridge.registerPlatform('homebridge-onkyo', 'Onkyo', OnkyoPlatform);
};