diff --git a/README.md b/README.md index 6a9808e..55a4703 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Existing users of my original fork or gw-wiscon's be sure to update the "platfor # Changelog +* Version 0.8.0 supports more models with a newer version of eiscp.js. Also includes bug and performance fixes. * Version 0.7.5 introduces linter check for JSON files and code quality check using xo. Developers can now use "npm test" before submitting a pull request. * Version 0.7 iOS 12.2+ is now required. This is now a Platform, theoretically supporting multiple receivers. Each receiver is a TV accessory (which is why iOS 12.2+ is required). Input labels can customized with `inputs` in the config. An optional Dimmer service for separate volume control is available, useful for non-iPhone control and more advanced automations (it appears as a dimmable light bulb). To disable the volume dimmer, add `"volume_dimmer": false` to your receiver in config. * Version 0.6 includes support for zone2. Adds a new config parameter called "zone" and use "zone2". Thanks for the contrib mbbeaubi. @@ -58,17 +59,15 @@ Example accessory config (needs to be added to the homebridge config.json): "default_input": "net", "default_volume": "10", "max_volume": "40", - "inputs": [ - { - "input_name": "TV", - "display_name": "TV" - }, - { - "input_name": "AUX", - "display_name": "PS4" - } - ] "map_volume_100": false, + "inputs": [ + {"input_name": "dvd", "display_name": "Blu-ray"}, + {"input_name": "video2", "display_name": "Switch"}, + {"input_name": "video3", "display_name": "Wii U"}, + {"input_name": "video6", "display_name": "Apple TV"}, + {"input_name": "video4", "display_name": "AUX"}, + {"input_name": "cd", "display_name": "TV/CD"} + ], "volume_dimmer": false, "switch_service": false, "filter_inputs": true @@ -86,14 +85,14 @@ Receiver Attributes | ----------------------------|------------ **name** | (required) The name you want to use for control of the Onkyo accessories. **ip_address** | (required) The internal ip address of your Onkyo. -**model** | (required) Must be a valid model listed in node_modules/eiscp/eiscp-commands.json file. If your model is not listed, you can use the TX-NR609 if your model supports the Integra Serial Communication Protocol (ISCP). +**model** | (required) Must be a valid model listed in config.schema.json file. If your model is not listed, you can use the TX-NR609 if your model supports the Integra Serial Communication Protocol (ISCP). **poll_status_interval** | (optional) Poll Status Interval. Defaults to 0 or no polling. **default_input** | (optional) A valid source input. Default will use last known input. See output of 3.js in eiscp/examples for options. **default_volume** | (optional) Initial receiver volume upon powerup. This is the true volume number, not a percentage. Ignored if powerup from device knob or external app (like OnkyoRemote3). **max_volume** | (optional) Receiver volume max setting. This is a true volume number, not a percentage, and intended so there is not accidental setting of volume to 80. Ignored by external apps (like OnkyoRemote3). Defaults to 30. **map_volume_100** | (optional) Will remap the volume percentages that appear in the Home app so that the configured max_volume will appear as 100% in the Home app. For example, if the max_volume is 30, then setting the volume slider to 50% would set the receiver's actual volume to 15. Adjusting the stereo volume knob to 35 will appear as 100% in the Home app. This option could confuse some users to it defaults to off false, but it does give the user finer volume control especially when sliding volume up and down in the Home app. Defaults to False. **zone** | (optional) Defaults to main. Optionally control zone2 where supported. -**inputs** | (optional) List of inputs you want populated for the TV service and what you want them to be labeled. Inputs not listed are omitted. +**inputs** | (optional) List of inputs you want populated for the TV service and what you want them to be displayed as. **filter_inputs** | (optional) Boolean value. Setting this to `true` limits inputs displayed in HomeKit to those you provide in `inputs`. If `false` or not defined, all inputs supported by `model` will be displayed. **volume_dimmer** | (optional) Boolean value. Setting this to `false` disables additional Dimmer accessory for separate volume control. diff --git a/config.schema.json b/config.schema.json index bfdb0a5..700f2ef 100644 --- a/config.schema.json +++ b/config.schema.json @@ -1,114 +1,480 @@ { - "pluginAlias": "Onkyo", - "pluginType": "platform", - "schema": { - "name": { - "title": "Name", - "type": "string", - "required": true, - "default": "Onkyo" - }, - "receivers": { - "type": "array", - "items": { - "title": "Onkyo receiver config", + "pluginAlias": "Onkyo", + "pluginType": "platform", + "singular": true, + "headerDisplay": "Onkyo-like receiver as a HomeKit TV.", + "footerDisplay": "https://github.com/solowalker27/homebridge-onkyo", + "schema": { "type": "object", + "required": [ + "receivers" + ], "properties": { - "name": { - "title": "Name", - "type": "string", - "required": true, - "default": "Onkyo Stereo" - }, - "model": { - "title": "Model", - "type": "string", - "required": true, - "default": "TX-NR609" - }, - "ip_address": { - "title": "IP-Address or hostname", - "type": "string", - "required": true, - "default": "10.0.1.23" - }, - "default_input": { - "title": "Default input (optional)", - "type": "string", - "required": false, - "default": "net" - }, - "zone": { - "title": "Zone", - "type": "string", - "required": true, - "enum": ["main", "zone2", "zone3"], - "default": "main" - }, - "default_volume": { - "title": "Default volume (optional)", - "type": "string", - "required": false, - "default": "10" - }, - "max_volume": { - "title": "Maximum volume (optional)", - "type": "string", - "required": false, - "default": "35" - }, - "poll_status_interval": { - "title": "Polling interval (0 for no polling)", - "type": "string", - "required": false, - "default": "0" - }, - "map_volume_100": { - "title": "Map volume (optional)", - "type": "boolean", - "default": false, - "description": "Will remap the volume percentages that appear in the Home app so that the configured max_volume will appear as 100% in the Home app. For example, if the max_volume is 30, then setting the volume slider to 50% would set the receiver's actual volume to 15. Adjusting the stereo volume knob to 35 will appear as 100% in the Home app. This option could confuse some users to it defaults to off false, but it does give the user finer volume control especially when sliding volume up and down in the Home app." - }, - "volume_dimmer": { - "title": "Enable volume control as dimmer accesory", - "type": "boolean", - "default": false, - "description": "Setting this to false disables additional Dimmer accessory for separate volume control." - }, - "switch_service": { - "title": "Enable switch service", - "type": "boolean", - "default": false, - "description": "Setting this to false disables switch service." - }, - "filter_inputs": { - "title": "Filter inputs", - "type": "boolean", - "default": true, - "description": "Setting this to true limits inputs displayed in HomeKit to those you provide in inputs. If false or not defined, all inputs supported by model will be displayed.
If set to true, you MUST configure INPUTS." - }, - "inputs" : { - "title": "Inputs", - "type": "array", - "items": { - "title": "Input", - "type": "object", - "properties": { - "input_name": { - "title": "Name of input on the receiver", - "type": "string", - "required": true - }, - "display_name": { - "title": "Display name of the input", - "type": "string", - "required": true - } - } - } - } - } - } + "receivers": { + "type": "array", + "title": "Receivers", + "descrpition": "List of receivers and/or receiver Zones to add to HomeKit.", + "items": { + "type": "object", + "title": "Receiver or Receiver Zone", + "description": "Receiver or receiver Zone to add to HomeKit.", + "required": [ + "name", + "ip_address", + "model" + ], + "properties": { + "name": { + "type": "string", + "title": "Device Name", + "default": "Receiver", + "description": "Name of the receiver as it will appear in HomeKit." + }, + "model": { + "type": "string", + "title": "Receiver Model Number", + "default": "TX-NR515", + "description": "Receiver model number (or closest to it).", + "oneOf": [ + { + "title": "Model", + "enum": [ + "/515AE(Ether)", + "/515AE", + "/616AE(Ether)", + "/616AE", + "/818AE", + "DHC-40.1", + "DHC-40.2", + "DHC-60.5", + "DHC-60.7", + "DHC-80.1", + "DHC-80.2", + "DHC-80.3", + "DHC-80.6", + "DHC-9.9", + "DRC-R1", + "DRX-2", + "DRX-2.1", + "DRX-3", + "DRX-3.1", + "DRX-4", + "DRX-4.1", + "DRX-5", + "DRX-5.1", + "DRX-7", + "DRX-R1", + "DTC-7", + "DTC-9.1", + "DTC-9.4", + "DTC-9.8", + "DTM-7", + "DTR-10.5", + "DTR-20.1", + "DTR-20.2", + "DTR-20.3", + "DTR-20.4", + "DTR-20.7", + "DTR-30.1", + "DTR-30.2", + "DTR-30.3", + "DTR-30.4", + "DTR-30.5", + "DTR-30.6", + "DTR-30.7", + "DTR-4.5", + "DTR-4.6", + "DTR-4.9", + "DTR-40.1", + "DTR-40.2", + "DTR-40.3", + "DTR-40.4", + "DTR-40.5", + "DTR-40.6", + "DTR-40.7", + "DTR-5.2", + "DTR-5.3", + "DTR-5.4", + "DTR-5.5", + "DTR-5.6", + "DTR-5.8", + "DTR-5.9", + "DTR-50.1", + "DTR-50.2", + "DTR-50.3", + "DTR-50.4", + "DTR-50.5", + "DTR-50.6", + "DTR-50.7", + "DTR-6.2", + "DTR-6.3", + "DTR-6.4", + "DTR-6.5", + "DTR-6.6", + "DTR-6.8", + "DTR-6.9", + "DTR-60.5", + "DTR-60.6", + "DTR-60.7", + "DTR-7.1", + "DTR-7.2", + "DTR-7.3", + "DTR-7.4", + "DTR-7.6", + "DTR-7.7", + "DTR-7.8", + "DTR-7.9", + "DTR-70.1", + "DTR-70.2", + "DTR-70.3", + "DTR-70.4", + "DTR-70.6", + "DTR-8.2", + "DTR-8.3", + "DTR-8.4", + "DTR-8.8", + "DTR-8.9", + "DTR-80.1", + "DTR-80.2", + "DTR-80.3", + "DTR-9.1", + "DTR-9.9", + "DTX-5.8", + "DTX-5.9", + "DTX-7", + "DTX-7.7", + "DTX-7.8", + "DTX-8.8", + "DTX-8.9", + "DTX-9.9", + "ETX-NA1000", + "HT-R693(Ether)", + "HT-R993(Ether)", + "HT-RC550(Ether)", + "HT-RC560(Ether)", + "HT-R693", + "HT-R993", + "HT-RC550", + "HT-RC560", + "HT-RC660", + "NR-365(Ether)", + "NR-365", + "PR-RZ5100", + "PR-SC5507", + "PR-SC5508", + "PR-SC5509", + "PR-SC5530", + "PR-SC885", + "PR-SC886", + "RDC-7", + "RDC-7(Ver2.0)", + "RDC-7.1", + "TX-8270(Ether)", + "TX-8270", + "TX-DS787", + "TX-DS797", + "TX-DS898", + "TX-DS989", + "TX-NA900", + "TX-NA905", + "TX-NA906", + "TX-NA906X", + "TX-NR1000", + "TX-NR1007", + "TX-NR1008", + "TX-NR1009", + "TX-NR1010", + "TX-NR1030", + "TX-NR3007", + "TX-NR3008", + "TX-NR3009", + "TX-NR3010", + "TX-NR3030", + "TX-NR414(Ether)", + "TX-NR474(Ether)", + "TX-NR414", + "TX-NR474", + "TX-NR5000", + "TX-NR5007", + "TX-NR5008", + "TX-NR5009", + "TX-NR5010", + "TX-NR509(Ether)", + "TX-NR509", + "TX-NR515", + "TX-NR525", + "TX-NR535(Ether)", + "TX-NR545(Ether)", + "TX-NR555(Ether)", + "TX-NR575(Ether)", + "TX-NR575DAB(Ether)", + "TX-NR575E(Ether)", + "TX-NR579(Ether)", + "TX-NR609(Ether)", + "TX-NR535", + "TX-NR545", + "TX-NR555", + "TX-NR575", + "TX-NR575DAB", + "TX-NR575E", + "TX-NR579", + "TX-NR609", + "TX-NR616", + "TX-NR626", + "TX-NR636", + "TX-NR646(Ether)", + "TX-NR656(Ether)", + "TX-NR676(Ether)", + "TX-NR676E(Ether)", + "TX-NR646", + "TX-NR656", + "TX-NR676", + "TX-NR676E", + "TX-NR708", + "TX-NR709", + "TX-NR717(Ether)", + "TX-NR727(Ether)", + "TX-NR737(Ether)", + "TX-NR747(Ether)", + "TX-NR717", + "TX-NR727", + "TX-NR737", + "TX-NR747", + "TX-NR757", + "TX-NR777", + "TX-NR807", + "TX-NR808", + "TX-NR809", + "TX-NR818", + "TX-NR828(Ether)", + "TX-NR838(Ether)", + "TX-NR828", + "TX-NR838", + "TX-NR900", + "TX-NR901", + "TX-NR905", + "TX-NR906", + "TX-NR929", + "TX-RZ1100", + "TX-RZ3100", + "TX-RZ610", + "TX-RZ620", + "TX-RZ710", + "TX-RZ720", + "TX-RZ800", + "TX-RZ810", + "TX-RZ820", + "TX-RZ900", + "TX-SA706", + "TX-SA706X", + "TX-SA805", + "TX-SA806", + "TX-SA806X", + "TX-SA875", + "TX-SA876", + "TX-SR702", + "TX-SR703", + "TX-SR705", + "TX-SR706", + "TX-SR707", + "TX-SR803", + "TX-SR804", + "TX-SR805", + "TX-SR806", + "TX-SR875", + "TX-SR876" + ] + } + ] + }, + "ip_address": { + "type": "string", + "title": "IP Address of Receiver", + "default": "10.0.0.46", + "description": "IP address of receiver. NOTE: Reserved address strongly recommended." + }, + "inputs": { + "type": "array", + "title": "Inputs", + "description": "List of inputs to expose in HomeKit and what they should be called.", + "items": { + "type": "object", + "title": "Inputs", + "description": "'input_name' and 'display_name' pair where 'input_name' is the input according to the receiver and 'display_name' is how it will be listed in HomeKit.", + "required": [ + "input_name", + "display_name" + ], + "properties": { + "input_name": { + "type": "string", + "title": "Receiver Input", + "description": "Input according to the receiver.", + "oneOf": [ + {"enum": [ + "07", + "08", + "09", + "am", + "aux1", + "aux2", + "bd", + "cbl", + "cd", + "dlna", + "dvd", + "dvr", + "fm", + "game", + "hidden1", + "hidden2", + "hidden3", + "internet-radio", + "iradio-favorite", + "multi-ch", + "music-server", + "net", + "network", + "off", + "p4s", + "pc", + "phono", + "sat", + "sirius", + "source", + "tape", + "tape-1", + "tape2", + "tuner", + "tv", + "universal-port", + "usb", + "vcr", + "video1", + "video2", + "video3", + "video4", + "video5", + "video6", + "video7", + "xm" + ]} + ] + }, + "display_name": { + "type": "string", + "title": "Display name for HomeKit", + "description": "How the input will appear in HomeKit." + } + } + } + }, + "default_input": { + "type": "string", + "title": "Default Input", + "description": "Always set to this input when powering on receiver.", + "oneOf": [ + { + "enum": [ + "07", + "08", + "09", + "am", + "aux1", + "aux2", + "bd", + "cbl", + "cd", + "dlna", + "dvd", + "dvr", + "fm", + "game", + "hidden1", + "hidden2", + "hidden3", + "internet-radio", + "iradio-favorite", + "multi-ch", + "music-server", + "net", + "network", + "off", + "p4s", + "pc", + "phono", + "sat", + "sirius", + "source", + "tape", + "tape-1", + "tape2", + "tuner", + "tv", + "universal-port", + "usb", + "vcr", + "video1", + "video2", + "video3", + "video4", + "video5", + "video6", + "video7", + "xm" + ] + } + ] + }, + "filter_inputs": { + "type": "boolean", + "title": "Filter Inputs", + "description": "Whether or not to limit exposed inputs to the provided mapping. If true, no other inputs will be exposed.", + "default": true + }, + "default_volume": { + "type": "integer", + "title": "Default Volume", + "description": "Always set volume to this number when powering on receiver." + }, + "max_volume": { + "type": "integer", + "title": "Max Volume", + "description": "Limit volume of receiver to this number when controlling in HomeKit.", + "default": 30 + }, + "volume_dimmer": { + "type": "boolean", + "title": "Show Volume as a Dimmer", + "description": "Whether or not to show receiver volume as a linked light dimmer. Convenient for quick volume changes but Siri thinks it's a light source.", + "default": false + }, + "map_volume_100": { + "type": "boolean", + "title": "Map Max Volume to 100%", + "description": "Maps volume level to the given max volume value as a percentage with 100% being max volume. Useful in conjunction with the volume dimmer. Example: Max Volume is set to 60 with Map Max Volume to 100% enabled. At 100% 'brightness', volume will be 60, 50% 'brightness' will be 30, etc." + }, + "poll_status_interval": { + "type": "string", + "title": "Poll Status Interval", + "description": "Interval in milliseconds at which to poll for status of receiver." + }, + "zone": { + "type": "string", + "title": "Zone", + "description": "Which receier Zone to control.", + "oneOf": [ + { + "enum": ["main", "zone2", "zone3", "zone4"] + } + ], + "default": "main" + }, + "additionalProperties": false + } + } + } + }, + "additionalProperties": false } - } -} +} \ No newline at end of file diff --git a/index.js b/index.js index 8df4f6c..f7ca2e0 100644 --- a/index.js +++ b/index.js @@ -2,16 +2,10 @@ let Service; let Characteristic; -// let Accessory; let RxInputs; const pollingtoevent = require('polling-to-event'); - -// const accessories = []; const info = require('./package.json'); -// Are we actually using rx-types.js anywhere? -// const RxTypes = require('./rx-types.js'); - class OnkyoPlatform { constructor(log, config, api) { this.api = api; @@ -85,29 +79,7 @@ class OnkyoAccessory { this.log.debug('filter_inputs: %s', this.filter_inputs); } - // - // this.inputs = this.config.inputs; - // - /* eslint no-return-assign: "warn" */ - - const array_input = {}; - if (typeof this.config.inputs === undefined) { - this.log.warn('There are no INPUTS configured in the configuration. This is OK if you have "filter_inputs" disabled.'); - this.filter_inputs = false; - this.log.debug('Disabled filtering INPUTS.'); - } else if (typeof this.config.inputs === 'object') { - this.log.debug('Configuration contains an INPUTS object.'); - this.config.inputs.forEach(x => array_input[x.input_name] = x.display_name); - // results in: inputs: { TV: 'TV', AUX: 'PS4' } - this.inputs = array_input; - this.log.debug('This is the list of INPUTS after converting from an object into an array: %s', this.inputs); - } else { - this.log.warn('Configuration does not contain proper INPUTS configuration.'); - this.log('Continuing without INPUTS filter option.'); - this.filter_inputs = false; - } - - this.log.debug('Input filtering is finally %s', this.filter_inputs); + this.inputs = this.config.inputs; this.cmdMap = new Array(2); this.cmdMap.main = new Array(4); @@ -198,38 +170,47 @@ class OnkyoAccessory { createRxInput() { // Create the RxInput object for later use. - let eiscpData = require('./node_modules/eiscp/eiscp-commands.json'); + const eiscpDataAll = require('eiscp/eiscp-commands.json'); const inSets = []; - -/* eslint guard-for-in: "off" */ let set; - - for (set in eiscpData.modelsets) { - eiscpData.modelsets[set].forEach(model => { +/* eslint guard-for-in: "off" */ + for (set in eiscpDataAll.modelsets) { + eiscpDataAll.modelsets[set].forEach(model => { if (model.includes(this.model)) inSets.push(set); }); } - eiscpData = eiscpData.commands.main.SLI.values; - + // 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.substring(0, hold.indexOf(',')); - if (exkey.includes('“') || exkey.includes('“')) { - /* eslint no-useless-escape: "warn" */ - exkey = exkey.replace(/\“/g, ''); - exkey = exkey.replace(/\”/g, ''); + exkey = exkey.replace(/\“/g, ''); // eslint-disable-line no-useless-escape + exkey = exkey.replace(/\”/g, ''); // eslint-disable-line no-useless-escape } if (exkey.includes('UP') || exkey.includes('DOWN') || exkey.includes('QSTN')) continue; - set = eiscpData[exkey].models; + // 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 @@ -239,34 +220,6 @@ class OnkyoAccessory { // Drop last comma first newobj = newobj.slice(0, -1) + ']}'; RxInputs = JSON.parse(newobj); - if (this.filter_inputs) { - let length = RxInputs.Inputs.length; - while (length--) { - if (this.inputs[RxInputs.Inputs[length].label]) - continue; - else - RxInputs.Inputs.splice(length, 1); - } - } - // Deduplicate any input entries. - // Is this necessary? - // var tempRxInputs = JSON.parse(newobj); - // var deduped = []; - // tempRxInputs['Inputs'].forEach((input, index) => { - // var dupe = false; - // deduped.forEach((input2, index2) => { - // if (input2 == input) { - // dupe = true; - // return - // } - // }) - // if (!dupe) { - // deduped.push(input); - // } - // }) - // tempRxInputs.Inputs = deduped; - // RxInputs = tempRxInputs; - // this.log.info(tempRxInputs); } polling(platform) { @@ -366,9 +319,8 @@ class OnkyoAccessory { 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, null, 'statuspoll'); - // } + 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'); @@ -386,14 +338,13 @@ class OnkyoAccessory { eventInput(response) { if (response) { let input = JSON.stringify(response); - input = input.replace(/[\[\]"]+/g, ''); + input = input.replace(/[\[\]"]+/g, ''); // eslint-disable-line no-useless-escape if (input.includes(',')) input = input.substring(0, input.indexOf(',')); // Convert to i_state input code - /* eslint no-negated-condition: "warn" */ const index = - input !== null ? + input !== null ? // eslint-disable-line no-negated-condition RxInputs.Inputs.findIndex(i => i.label === input) : -1; if (this.i_state !== (index + 1)) @@ -408,7 +359,9 @@ class OnkyoAccessory { this.log.error('eventInput - ERROR - INVALID INPUT - Model does not support selected input.'); } - this.getInputSource.bind(this); + // Communicate status + if (this.tvService) + this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(this.i_state); } eventVolume(response) { @@ -436,7 +389,7 @@ class OnkyoAccessory { // GET AND SET FUNCTIONS /// ///////////////////// setPowerState(powerOn, callback, context) { - // if context is statuspoll, then we need to ensure this we do not set the actual value + // 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); @@ -457,7 +410,7 @@ class OnkyoAccessory { 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, response) => { + 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; @@ -470,7 +423,7 @@ class OnkyoAccessory { 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, response) { + 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); }); @@ -482,21 +435,23 @@ class OnkyoAccessory { // Handle defaultInput being either a custom label or manufacturer label let label = this.defaultInput; if (this.inputs) { - for (const id in this.inputs) { - if (this.inputs[id] === this.defaultInput) - label = id; - } + 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 ? + 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, response) { + 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); }); @@ -505,7 +460,7 @@ class OnkyoAccessory { }); } 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, response) => { + 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; @@ -521,7 +476,7 @@ class OnkyoAccessory { // 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, null, 'statuspoll'); + this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state); } getPowerState(callback, context) { @@ -544,13 +499,13 @@ class OnkyoAccessory { // 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, data) => { + 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); + this.tvService.getCharacteristic(Characteristic.Active).updateValue(this.state); } getVolumeState(callback, context) { @@ -573,12 +528,16 @@ class OnkyoAccessory { // 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, data) => { + 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) { @@ -617,12 +576,16 @@ class OnkyoAccessory { // 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, response) => { + 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) { @@ -646,7 +609,7 @@ class OnkyoAccessory { 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, response) => { + 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); @@ -654,7 +617,7 @@ class OnkyoAccessory { }); } 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, response) => { + 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); @@ -663,6 +626,10 @@ class OnkyoAccessory { } 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) { @@ -685,12 +652,16 @@ class OnkyoAccessory { // 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, data) => { + 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) { @@ -715,7 +686,7 @@ class OnkyoAccessory { 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, response) => { + 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); @@ -723,24 +694,28 @@ class OnkyoAccessory { }); } 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, response) => { + 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') { - // if (this.switchHandling == 'poll') { - // this.log.debug('getInputState - polling mode, return i_state: ', this.i_state); - // callback(null, this.i_state); - // return; - // } - // } + if (!context || context !== 'i_statuspoll') { + if (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.'); @@ -752,23 +727,26 @@ class OnkyoAccessory { // 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, data) => { + 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); - // this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(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 (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.')); @@ -785,10 +763,14 @@ class OnkyoAccessory { // 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, response) => { + 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) { @@ -798,7 +780,7 @@ class OnkyoAccessory { 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, data) => { + this.eiscp.command(this.zone + '.setup=' + press, (error, _) => { if (error) { // this.i_state = 1; this.log.error('remoteKeyPress - INPUT: ERROR pressing button %s', press); @@ -814,40 +796,33 @@ class OnkyoAccessory { 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) { - let length = RxInputs.Inputs.length; - while (length--) { - if (this.inputs[RxInputs.Inputs[length].label]) - continue; - else - RxInputs.Inputs.splice(length, 1); - } + // 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) { - if (this.inputs[i.label]) - inputName = this.inputs[i.label]; + this.inputs.forEach((input, _) => { + if (input.input_name === i.label) + inputName = input.display_name; + }); } - // var dupe = false; - // inputs.forEach((y, z) => { - // if (y['subtype'] == i['code']) { - // dupe = true; - // } - // }) - - // if (dupe) return + const input = this.setupInput(i.code, inputName, hapId, service); return input; }); diff --git a/package.json b/package.json index 7cd9007..7ab9944 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-onkyo", - "version": "0.7.17", + "version": "0.8.0", "description": "Homebridge plugin for Onkyo Receivers", "main": "index.js", "scripts": { @@ -12,12 +12,12 @@ "url": "git+https://github.com/ToddGreenfield/homebridge-onkyo.git" }, "dependencies": { - "eiscp": "git://github.com/tillbaks/node-eiscp.git", + "eiscp": "git://github.com/untitledlt/eiscp.js.git", "polling-to-event": "^2.1.0" }, "devDependencies": { "eslint": "^6.3.0", - "eslint-plugin-json": "^1.4.0", + "eslint-plugin-json": "^2.1.1", "xo": "^0.24.0" }, "keywords": [ diff --git a/rx-types.js b/rx-types.js deleted file mode 100644 index d9fe114..0000000 --- a/rx-types.js +++ /dev/null @@ -1,42 +0,0 @@ -const inherits = require('util').inherits; - -let Service; -let Characteristic; - -module.exports = function (homebridge) { - Service = homebridge.hap.Service; - Characteristic = homebridge.hap.Characteristic; - const UUID = homebridge.hap.uuid; - const RxTypes = {}; - -// Custom homekit characteristic for InputSource. -RxTypes.InputSource = function () { - const serviceUUID = UUID.generate('RxTypes:usagedevice:InputSource'); - Characteristic.call(this, 'Input Source', serviceUUID); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 36, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - - inherits(RxTypes.InputSource, Characteristic); - -// Custom homekit characteristic for InputLabel. -RxTypes.InputLabel = function () { - const serviceUUID = UUID.generate('RxTypes:usagedevice:InputLabel'); - Characteristic.call(this, 'Input Label', serviceUUID); - this.setProps({ - format: Characteristic.Formats.String, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - - inherits(RxTypes.InputLabel, Characteristic); - - return RxTypes; -};