├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── availableCommands.txt ├── chainedCommands.js ├── mochatest.js ├── package-lock.json ├── package.json ├── simpleCommands.js ├── test.js └── yamaha.js /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | extends: 'eslint:recommended' 5 | rules: 6 | indent: 7 | - error 8 | - 4 9 | linebreak-style: 10 | - error 11 | - unix 12 | no-unused-vars: 13 | - warn 14 | semi: off 15 | no-console: 0 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | .idea 31 | /npm-debug.log 32 | /tmp/*.zip 33 | /log/*.log 34 | preset* 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | Gruntfile.js 2 | tasks 3 | data 4 | conf/iobroker.json 5 | node_modules 6 | .idea 7 | .git 8 | *.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yamaha-nodejs 2 | ================== 3 | [![NPM Downloads](https://img.shields.io/npm/dm/yamaha-nodejs.svg?style=flat)](https://npmjs.org/package/yamaha-nodejs) 4 | [![Dependency Status](https://david-dm.org/PSeitz/yamaha-nodejs.svg?style=flat)](https://david-dm.org/PSeitz/yamaha-nodejs) 5 | 6 | 7 | A node module to control your yamaha receiver. Tested with RX-V775, should work with all yamaha receivers with a network interface. 8 | 9 | ### Install 10 | npm install yamaha-nodejs 11 | 12 | ## Example 13 | ```javascript 14 | var YamahaAPI = require("yamaha-nodejs"); 15 | var yamaha = new YamahaAPI("192.168.0.100"); 16 | yamaha.powerOn().then(function(){ 17 | console.log("powerOn"); 18 | yamaha.setMainInputTo("NET RADIO").then( function(){ 19 | console.log("Switched to Net Radio"); 20 | yamaha.selectWebRadioListItem(1).then(function(){ 21 | console.log("Selected Favorites"); 22 | yamaha.selectWebRadioListItem(1).then(function(){}); 23 | }); 24 | 25 | }); 26 | }); 27 | ``` 28 | ## Prerequisites 29 | * To power on the yamaha, network standby has to be enabled 30 | * The Yamaha reveiver is stateful. Some commands only work work if the receiver is in the right state. E.g. to get web radio channels, the "NET RADIO" input has to be selected. 31 | 32 | ## Methods 33 | ```javascript 34 | var yamaha = new Yamaha("192.168.0.100") 35 | var yamaha = new Yamaha() // Auto-Discovery 36 | yamaha.powerOff(zone) // or "System" for sytem power 37 | yamaha.powerOn(zone) // or "System" for sytem power 38 | yamaha.isOn(zone) 39 | yamaha.isOff(zone) 40 | 41 | //Volume 42 | yamaha.setVolumeTo(-500, zone) // Value must be divisble by 5 or value will be rejected 43 | yamaha.volumeUp(50, zone) 44 | yamaha.volumeDown(50, zone) 45 | yamaha.muteOn(zone) 46 | yamaha.muteOff(zone) 47 | 48 | //Extended Volume Settings 49 | yamaha.setBassTo(60) //-60 to 60 (may depend on model) 50 | yamaha.setTrebleTo(60) //-60 to 60 (may depend on model) 51 | yamaha.setSubwooferTrimTo(60) //-60 to 60 (may depend on model) 52 | yamaha.setDialogLiftTo(5) //0 to 5 (may depend on model) 53 | yamaha.setDialogLevelTo(3) //0 to 3 (may depend on model) 54 | yamaha.YPAOVolumeOn() 55 | yamaha.YPAOVolumeOff() 56 | yamaha.extraBassOn() 57 | yamaha.extraBassOff() 58 | yamaha.adaptiveDRCOn() 59 | yamaha.adaptiveDRCOff() 60 | 61 | //Playback 62 | yamaha.stop(zone) 63 | yamaha.pause(zone) 64 | yamaha.play(zone) 65 | yamaha.skip(zone) 66 | yamaha.rewind(zone) 67 | 68 | //Remote (Case Sensitive Values) 69 | yamaha.remoteCursor(command) // 'Up', 'Down', 'Left', 'Right', 'Return', 'Sel' 70 | yamaha.remoteMenu(command) // 'Option', 'Display' 71 | 72 | //Switch Input 73 | yamaha.setInputTo("USB", 2) 74 | yamaha.setMainInputTo("NET RADIO") 75 | 76 | //Party Mode 77 | yamaha.partyModeOn() 78 | yamaha.partyModeOff() 79 | yamaha.partyModeUp() 80 | yamaha.partyModeDown() 81 | 82 | //Basic 83 | yamaha.SendXMLToReceiver() 84 | 85 | //Get Info 86 | yamaha.getBasicInfo(zone).done(function(basicInfo){ 87 | basicInfo.getVolume(); 88 | basicInfo.isMuted(); 89 | basicInfo.isOn(); 90 | basicInfo.isOff(); 91 | basicInfo.getCurrentInput(); 92 | basicInfo.isPartyModeEnabled(); 93 | basicInfo.isPureDirectEnabled(); 94 | basicInfo.getBass(); 95 | basicInfo.getTreble(); 96 | basicInfo.getSubwooferTrim(); 97 | basicInfo.getDialogueLift(); 98 | basicInfo.getDialogueLevel(); 99 | basicInfo.getZone(); 100 | basicInfo.isYPAOVolumeEnabled(); 101 | basicInfo.isExtraBassEnabled(); 102 | basicInfo.isAdaptiveDRCEnabled(); 103 | }) 104 | 105 | yamaha.isHeadphoneConnected() 106 | yamaha.getSystemConfig() 107 | yamaha.getAvailableInputs() 108 | yamaha.isMenuReady("NET_RADIO") 109 | 110 | // FM Tuner 111 | yamaha.getTunerInfo() 112 | yamaha.getTunerPresetList() 113 | yamaha.selectTunerPreset(1) 114 | yamaha.selectTunerFrequency(band, frequency) 115 | 116 | //Select Menu Items 117 | yamaha.selectUSBListItem(1) 118 | yamaha.selectWebRadioListItem(1) 119 | 120 | // Single Commands, receiver has to be in the right state 121 | yamaha.getWebRadioList() 122 | yamaha.selectWebRadioListItem(1) 123 | 124 | // Chained Commands, they ensure the receiver is in the right state 125 | yamaha.switchToFavoriteNumber() 126 | yamaha.gotoFolder('/NET_RADIO/Radio/Favorites', 'NET_RADIO') 127 | 128 | // Find the index of a list item to select using the name of the item, 129 | // returns -1 if not found. Will move the list page down so index will always 130 | // between 1 and 8. 131 | yamaha.getIndexOfMenuItem( 132 | await yamaha.getWebRadioList(), 133 | 'BBC Radio 1', 134 | 'NET_RADIO' 135 | ) 136 | 137 | // Zone Commands 138 | yamaha.getAvailableZones() 139 | yamaha.getZoneConfig(zone) 140 | 141 | ``` 142 | 143 | #### Zones 144 | The zone parameter is optional, you can pass a number or a string 145 | 146 | #### Promises 147 | All these methods return a promise: 148 | ```javascript 149 | yamaha.isOn().then(function(result){ 150 | console.log("Receiver is:"+result); 151 | }) 152 | ``` 153 | #### Execute Tests 154 | ```javascript 155 | mocha mochatest.js --ip 192.168.0.25 156 | or with autodiscovery 157 | mocha mochatest.js 158 | ``` 159 | 160 | #### Discovery 161 | If the IP is omitted in the constructor, the module will try to discover the yamaha ip via a SSDP call. 162 | Thanks @soef @mwittig 163 | 164 | #### Changelog 165 | 0.8: 166 | - FIX Remove get request delays 167 | -------------------------------------------------------------------------------- /availableCommands.txt: -------------------------------------------------------------------------------- 1 | $CMD_SetZoneAOn = ' On'; 2 | $CMD_SetZoneAOff = ' Off'; 3 | $CMD_SetZoneBOn = ' On'; 4 | $CMD_SetZoneBOff = ' Off'; 5 | $CMD_SetMuteOn = 'On'; 6 | $CMD_SetMuteOff = 'Off'; 7 | $CMD_SetMuteBOn = 'On'; 8 | $CMD_SetMuteBOff = 'Off'; 9 | $CMD_SetVolumeDown = 'Down 1 dB'; 10 | $CMD_SetVolumeDown5 = 'Down 5 dB'; 11 | $CMD_SetVolumeUp = 'Up 1 dB'; 12 | $CMD_SetVolumeUp5 = 'Up 5 dB'; 13 | $CMD_SetVolumeBDown = 'Down 1 dB'; 14 | $CMD_SetVolumeBDown5 = 'Down 5 dB'; 15 | $CMD_SetVolumeBUp = 'Up 1 dB'; 16 | $CMD_SetVolumeBUp5 = 'Up 5 dB'; 17 | $CMD_SetServer = 'SERVER'; 18 | $CMD_SetTuner = 'TUNER'; 19 | $CMD_SetUSB = 'USB'; 20 | $CMD_SetPowerOn = 'On'; 21 | $CMD_SetPowerStandby = 'Standby'; 22 | $CMD_SetShuffleOn = '<'.$YamahaSource.'>On'; 23 | $CMD_SetShuffleOff = '<'.$YamahaSource.'>Off'; 24 | $CMD_SetRepeatOff = '<'.$YamahaSource.'>Off'; 25 | $CMD_SetRepeatOne = '<'.$YamahaSource.'>One'; 26 | $CMD_SetRepeatAll = '<'.$YamahaSource.'>All'; 27 | $CMD_SetStopItem = '<'.$YamahaSource.'>Stop'; 28 | $CMD_SetRevItem = '<'.$YamahaSource.'>Skip Rev'; 29 | $CMD_SetFwdItem = '<'.$YamahaSource.'>Skip Fwd'; 30 | $CMD_SetPageUp = '<'.$YamahaSource.'>Up'; 31 | $CMD_SetPageDown = '<'.$YamahaSource.'>Down'; 32 | $CMD_SetCursorBack = '<'.$YamahaSource.'>Return'; -------------------------------------------------------------------------------- /chainedCommands.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var xml2js = Promise.promisifyAll(require("xml2js")); 3 | 4 | const LIST_SIZE = 8; 5 | 6 | function topOfCurrentListCheck(currentLine) { 7 | return (currentLine-1)%LIST_SIZE === 0; 8 | } 9 | 10 | function splitPathToArray(path) { 11 | const pathArray = path.split('/'); 12 | let menuArray = []; 13 | for (const menuName of pathArray) { 14 | if (menuName) menuArray.push(menuName); 15 | } 16 | return menuArray; 17 | } 18 | 19 | function Yamaha() 20 | { 21 | } 22 | 23 | // Navigates and selects the #number of the webradio favorites 24 | Yamaha.prototype.switchToFavoriteNumber = function(favoritelistname, number){ 25 | var self = this; 26 | return self.powerOn().then(function(){ 27 | self.setMainInputTo("NET RADIO").then( function(){ 28 | self.selectWebRadioListItem(1).then(function(){ 29 | self.whenMenuReady("NET_RADIO").then(function(){ 30 | return self.selectWebRadioListItem(number); 31 | }); 32 | }); 33 | }); 34 | }); 35 | }; 36 | 37 | // unfinished - not working 38 | Yamaha.prototype.switchToWebRadioWithName = function(name){ 39 | var self = this; 40 | self.setMainInputTo("NET RADIO").then(function(){ 41 | 42 | self.getWebRadioList().then(xml2js.parseStringAsync).then(function(result){ 43 | console.log(result); 44 | }, function (err) { 45 | console.log("err "+err); 46 | }); 47 | 48 | }); 49 | 50 | }; 51 | 52 | Yamaha.prototype.getIndexOfMenuItem = async function(item, listName) { 53 | await this.jumpListItem(listName, 1); 54 | list = await this.getWebRadioList() 55 | while (true) { 56 | const Current_List = list.YAMAHA_AV[listName][0].List_Info[0].Current_List[0]; 57 | let index = 1; 58 | for (const key in Current_List) { 59 | const itemName = Current_List[key][0]['Txt'][0]; 60 | if (itemName === item) { 61 | return index; 62 | } 63 | index++ 64 | } 65 | const Cursor_Position = list.YAMAHA_AV[listName][0].List_Info[0].Cursor_Position[0]; 66 | const currentLine = Number(Cursor_Position.Current_Line[0]); 67 | if (!topOfCurrentListCheck) { 68 | throw new Error('top of viewable list not under cursor'); 69 | } 70 | const maxLine = Number(Cursor_Position.Max_Line[0]); 71 | const nextCurrentLine = currentLine + LIST_SIZE; 72 | if (nextCurrentLine > maxLine) { 73 | break; 74 | } 75 | await this.jumpListItem(listName, nextCurrentLine); 76 | list = await this.getWebRadioList(); 77 | } 78 | 79 | return -1; 80 | } 81 | 82 | Yamaha.prototype.gotoFolder = async function(path, listName) { 83 | const menuOrder = splitPathToArray(path); 84 | await this.setMainInputTo(listName.replace('_', ' ')); 85 | let list = await this.getList(listName); 86 | 87 | let menuName = list.getMenuName(); 88 | 89 | while (menuOrder.indexOf(menuName) < 0) { 90 | if (list.getMenuLayer() <= 1) { 91 | throw new Error('cannot gotoFolder, reached top of tree without finding known folder'); 92 | } 93 | await this.remoteCursor('Return'); 94 | list = await this.getWebRadioList(); 95 | await this.jumpListItem(listName, 1); 96 | menuName = list.getMenuName(); 97 | } 98 | 99 | maxIters = 100; 100 | iters = 0; 101 | while(menuName !== menuOrder[menuOrder.length - 1]) { 102 | iters++ 103 | if (iters > maxIters) { 104 | throw new Error(`gotoFolder decend has run over 100 iterations`) 105 | } 106 | const currentLayer = menuOrder.indexOf(menuName); 107 | const nextMenuItemName = menuOrder[currentLayer + 1]; 108 | list = await this.getWebRadioList(); 109 | const index = await this.getIndexOfMenuItem(nextMenuItemName, listName); 110 | if (index < 0) { 111 | throw new Error(`cannot find menu item "${nextMenuItemName}" in "${menuName}"`) 112 | } 113 | await this.selectListItem(listName, index); 114 | list = await this.getWebRadioList(); 115 | menuName = list.getMenuName(); 116 | } 117 | } 118 | 119 | 120 | module.exports = Yamaha; 121 | -------------------------------------------------------------------------------- /mochatest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 3 | var chai = require('chai'); 4 | var chaiAsPromised = require("chai-as-promised"); 5 | chai.use(chaiAsPromised); 6 | var expect = require('chai').expect; 7 | var Yamaha = require("./yamaha.js"); 8 | 9 | 10 | var yamaha_ip= process.argv[4]; 11 | 12 | // Tests For Yamaha RV-775 13 | describe('Yamaha-API', function() { 14 | this.timeout(5000); 15 | 16 | var yamaha 17 | it('should create a yamaha object', function() { 18 | yamaha = new Yamaha(yamaha_ip, 0.5); 19 | }); 20 | 21 | it('should be turned on', function() { 22 | return expect(yamaha.powerOn().then(function(){ 23 | return yamaha.isOn(); 24 | })).to.eventually.be.true; 25 | }); 26 | 27 | it('should be double turned on', function() { 28 | return expect(yamaha.powerOn().then(function(){ 29 | return yamaha.isOn(); 30 | })).to.eventually.be.true; 31 | }); 32 | 33 | it('should return 16 Inputs', function() { 34 | return expect(yamaha.getAvailableInputs()).to.eventually.have.length(16); 35 | }); 36 | 37 | it('should return 2 zones', function() { 38 | return expect(yamaha.getAvailableZones()).to.eventually.have.length(2); 39 | }); 40 | 41 | it('should set volume to -600', function() { 42 | return expect(yamaha.setVolume(-600).then(function(on){ 43 | return yamaha.getVolume(); 44 | })).to.eventually.equal(-600); 45 | }); 46 | 47 | it('should increase volume by 100', function() { 48 | return expect(yamaha.volumeUp(100).then(function(on){ 49 | return yamaha.getVolume(); 50 | })).to.eventually.equal(-500); 51 | }); 52 | 53 | 54 | // it('should switch to the webradio favorites using the chained command', function() { 55 | 56 | // yamaha.switchToFavoriteNumber(1).then(function(result){ 57 | // yamaha.getCurrentInput().then(function(result){ 58 | // expect(result).to.equal("NET RADIO"); 59 | // }); 60 | // }.to.eventually.be.false); 61 | 62 | // }); 63 | 64 | 65 | it('should switch to HDMI2', function() { 66 | return expect(yamaha.setMainInputTo("HDMI2").then(function() { 67 | return yamaha.getCurrentInput(); 68 | })).to.eventually.equal("HDMI2"); 69 | 70 | }); 71 | 72 | it('should switch to NET RADIO', function() { 73 | return expect(yamaha.setMainInputTo("NET RADIO").then(function() { 74 | return yamaha.getCurrentInput(); 75 | })).to.eventually.equal("NET RADIO"); 76 | 77 | }); 78 | 79 | it('should switch to the webradio favorites and wait to be ready', function() { 80 | this.timeout(7000); 81 | return expect(yamaha.whenMenuReady("NET_RADIO").then(function(result){ 82 | return yamaha.selectWebRadioListItem(1).then(function(inputs){ 83 | return yamaha.whenMenuReady("NET_RADIO"); 84 | }); 85 | })).to.eventually.be.true; 86 | 87 | }); 88 | 89 | it('should switch to partey mode on', function() { 90 | return expect(yamaha.partyModeOn().then(function(on){ 91 | return yamaha.isPartyModeEnabled(); 92 | })).to.eventually.be.true; 93 | }); 94 | 95 | it('should switch to partey mode off', function() { 96 | return expect(yamaha.partyModeOff().then(function(on){ 97 | return yamaha.isPartyModeEnabled(); 98 | })).to.eventually.be.false; 99 | }); 100 | 101 | it('should discover the yamaha and mute main_zone', function() { 102 | var yamaha = new Yamaha(); 103 | return expect(yamaha.muteOn().then(function(on){ 104 | return yamaha.isMuted(); 105 | })).to.eventually.be.true; 106 | }); 107 | 108 | it('should discover the yamaha and unmute main_zone', function() { 109 | var yamaha = new Yamaha(); 110 | return expect(yamaha.muteOff().then(function(on){ 111 | return yamaha.isMuted(); 112 | })).to.eventually.be.false; 113 | }); 114 | 115 | 116 | it('should turn on zone 2', function() { 117 | return expect(yamaha.powerOn(2).then(function(){ 118 | return yamaha.isOn(2); 119 | })).to.eventually.be.true; 120 | 121 | }); 122 | 123 | it('should mute zone 2', function() { 124 | return expect(yamaha.muteOn(2).then(function(on){ 125 | return yamaha.isMuted(2); 126 | })).to.eventually.be.true; 127 | }); 128 | 129 | it('should still have main zone unmuted', function() { 130 | return expect(yamaha.isMuted()).to.eventually.be.false; 131 | }); 132 | 133 | it('should unmute zone 2', function() { 134 | return expect(yamaha.muteOff(2).then(function(on){ 135 | return yamaha.isMuted(2); 136 | })).to.eventually.be.false; 137 | }); 138 | 139 | it('should be turn off zone 2', function() { 140 | return expect(yamaha.powerOff(2).then(function(on){ 141 | return yamaha.isOn(2); 142 | })).to.eventually.be.false; 143 | }); 144 | 145 | it('should be turned off', function() { 146 | return expect(yamaha.powerOff().then(function(on){ 147 | return yamaha.isOn(); 148 | })).to.eventually.be.false; 149 | }); 150 | 151 | 152 | // it('should list the webradio favorites list info', function() { 153 | // var yamaha = new Yamaha(yamaha_ip, 0.5); 154 | 155 | // return yamaha.whenMenuReady("NET_RADIO").then(function(result){ 156 | // return yamaha.selectWebRadioListItem(1).then(function(inputs){ 157 | // return yamaha.whenMenuReady("NET_RADIO").then(function(result){ 158 | // expect(result).to.be.true; 159 | // }); 160 | // }.to.eventually.be.false); 161 | 162 | // }); 163 | 164 | // }); 165 | 166 | 167 | 168 | 169 | 170 | 171 | }); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yamaha-nodejs", 3 | "version": "0.9.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.10.0", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 10 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.4", 20 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 21 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 22 | "requires": { 23 | "safer-buffer": "~2.1.0" 24 | } 25 | }, 26 | "assert-plus": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 29 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 30 | }, 31 | "assertion-error": { 32 | "version": "1.1.0", 33 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 34 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 35 | "dev": true 36 | }, 37 | "asynckit": { 38 | "version": "0.4.0", 39 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 40 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 41 | }, 42 | "aws-sign2": { 43 | "version": "0.7.0", 44 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 45 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 46 | }, 47 | "aws4": { 48 | "version": "1.8.0", 49 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 50 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 51 | }, 52 | "bcrypt-pbkdf": { 53 | "version": "1.0.2", 54 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 55 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 56 | "requires": { 57 | "tweetnacl": "^0.14.3" 58 | } 59 | }, 60 | "bluebird": { 61 | "version": "3.5.3", 62 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", 63 | "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" 64 | }, 65 | "caseless": { 66 | "version": "0.12.0", 67 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 68 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 69 | }, 70 | "chai": { 71 | "version": "3.5.0", 72 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 73 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 74 | "dev": true, 75 | "requires": { 76 | "assertion-error": "^1.0.1", 77 | "deep-eql": "^0.1.3", 78 | "type-detect": "^1.0.0" 79 | } 80 | }, 81 | "chai-as-promised": { 82 | "version": "5.3.0", 83 | "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-5.3.0.tgz", 84 | "integrity": "sha1-CdekApCKpw39vq1T5YU/x50+8hw=", 85 | "dev": true 86 | }, 87 | "combined-stream": { 88 | "version": "1.0.7", 89 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 90 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 91 | "requires": { 92 | "delayed-stream": "~1.0.0" 93 | } 94 | }, 95 | "core-util-is": { 96 | "version": "1.0.2", 97 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 98 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 99 | }, 100 | "dashdash": { 101 | "version": "1.14.1", 102 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 103 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 104 | "requires": { 105 | "assert-plus": "^1.0.0" 106 | } 107 | }, 108 | "debug": { 109 | "version": "2.6.9", 110 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 111 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 112 | "requires": { 113 | "ms": "2.0.0" 114 | } 115 | }, 116 | "deep-eql": { 117 | "version": "0.1.3", 118 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 119 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 120 | "dev": true, 121 | "requires": { 122 | "type-detect": "0.1.1" 123 | }, 124 | "dependencies": { 125 | "type-detect": { 126 | "version": "0.1.1", 127 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 128 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 129 | "dev": true 130 | } 131 | } 132 | }, 133 | "delayed-stream": { 134 | "version": "1.0.0", 135 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 136 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 137 | }, 138 | "ecc-jsbn": { 139 | "version": "0.1.2", 140 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 141 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 142 | "requires": { 143 | "jsbn": "~0.1.0", 144 | "safer-buffer": "^2.1.0" 145 | } 146 | }, 147 | "extend": { 148 | "version": "3.0.2", 149 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 150 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 151 | }, 152 | "extsprintf": { 153 | "version": "1.3.0", 154 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 155 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 156 | }, 157 | "fast-deep-equal": { 158 | "version": "2.0.1", 159 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 160 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 161 | }, 162 | "fast-json-stable-stringify": { 163 | "version": "2.0.0", 164 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 165 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 166 | }, 167 | "forever-agent": { 168 | "version": "0.6.1", 169 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 170 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 171 | }, 172 | "form-data": { 173 | "version": "2.3.3", 174 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 175 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 176 | "requires": { 177 | "asynckit": "^0.4.0", 178 | "combined-stream": "^1.0.6", 179 | "mime-types": "^2.1.12" 180 | } 181 | }, 182 | "getpass": { 183 | "version": "0.1.7", 184 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 185 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 186 | "requires": { 187 | "assert-plus": "^1.0.0" 188 | } 189 | }, 190 | "har-schema": { 191 | "version": "2.0.0", 192 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 193 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 194 | }, 195 | "har-validator": { 196 | "version": "5.1.3", 197 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 198 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 199 | "requires": { 200 | "ajv": "^6.5.5", 201 | "har-schema": "^2.0.0" 202 | } 203 | }, 204 | "http-signature": { 205 | "version": "1.2.0", 206 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 207 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 208 | "requires": { 209 | "assert-plus": "^1.0.0", 210 | "jsprim": "^1.2.2", 211 | "sshpk": "^1.7.0" 212 | } 213 | }, 214 | "is-typedarray": { 215 | "version": "1.0.0", 216 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 217 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 218 | }, 219 | "isstream": { 220 | "version": "0.1.2", 221 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 222 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 223 | }, 224 | "jsbn": { 225 | "version": "0.1.1", 226 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 227 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 228 | }, 229 | "json-schema": { 230 | "version": "0.2.3", 231 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 232 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 233 | }, 234 | "json-schema-traverse": { 235 | "version": "0.4.1", 236 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 237 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 238 | }, 239 | "json-stringify-safe": { 240 | "version": "5.0.1", 241 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 242 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 243 | }, 244 | "jsprim": { 245 | "version": "1.4.1", 246 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 247 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 248 | "requires": { 249 | "assert-plus": "1.0.0", 250 | "extsprintf": "1.3.0", 251 | "json-schema": "0.2.3", 252 | "verror": "1.10.0" 253 | } 254 | }, 255 | "mime-db": { 256 | "version": "1.38.0", 257 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", 258 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" 259 | }, 260 | "mime-types": { 261 | "version": "2.1.22", 262 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", 263 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", 264 | "requires": { 265 | "mime-db": "~1.38.0" 266 | } 267 | }, 268 | "ms": { 269 | "version": "2.0.0", 270 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 271 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 272 | }, 273 | "oauth-sign": { 274 | "version": "0.9.0", 275 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 276 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 277 | }, 278 | "peer-ssdp": { 279 | "version": "0.0.5", 280 | "resolved": "https://registry.npmjs.org/peer-ssdp/-/peer-ssdp-0.0.5.tgz", 281 | "integrity": "sha1-G5FVuolGXszE3iy6LAOOSf3iooE=" 282 | }, 283 | "performance-now": { 284 | "version": "2.1.0", 285 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 286 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 287 | }, 288 | "psl": { 289 | "version": "1.1.31", 290 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", 291 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" 292 | }, 293 | "punycode": { 294 | "version": "2.1.1", 295 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 296 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 297 | }, 298 | "qs": { 299 | "version": "6.5.2", 300 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 301 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 302 | }, 303 | "request": { 304 | "version": "2.88.0", 305 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 306 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 307 | "requires": { 308 | "aws-sign2": "~0.7.0", 309 | "aws4": "^1.8.0", 310 | "caseless": "~0.12.0", 311 | "combined-stream": "~1.0.6", 312 | "extend": "~3.0.2", 313 | "forever-agent": "~0.6.1", 314 | "form-data": "~2.3.2", 315 | "har-validator": "~5.1.0", 316 | "http-signature": "~1.2.0", 317 | "is-typedarray": "~1.0.0", 318 | "isstream": "~0.1.2", 319 | "json-stringify-safe": "~5.0.1", 320 | "mime-types": "~2.1.19", 321 | "oauth-sign": "~0.9.0", 322 | "performance-now": "^2.1.0", 323 | "qs": "~6.5.2", 324 | "safe-buffer": "^5.1.2", 325 | "tough-cookie": "~2.4.3", 326 | "tunnel-agent": "^0.6.0", 327 | "uuid": "^3.3.2" 328 | } 329 | }, 330 | "safe-buffer": { 331 | "version": "5.1.2", 332 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 333 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 334 | }, 335 | "safer-buffer": { 336 | "version": "2.1.2", 337 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 338 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 339 | }, 340 | "sax": { 341 | "version": "1.2.4", 342 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 343 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 344 | }, 345 | "sshpk": { 346 | "version": "1.16.1", 347 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 348 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 349 | "requires": { 350 | "asn1": "~0.2.3", 351 | "assert-plus": "^1.0.0", 352 | "bcrypt-pbkdf": "^1.0.0", 353 | "dashdash": "^1.12.0", 354 | "ecc-jsbn": "~0.1.1", 355 | "getpass": "^0.1.1", 356 | "jsbn": "~0.1.0", 357 | "safer-buffer": "^2.0.2", 358 | "tweetnacl": "~0.14.0" 359 | } 360 | }, 361 | "tough-cookie": { 362 | "version": "2.4.3", 363 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 364 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 365 | "requires": { 366 | "psl": "^1.1.24", 367 | "punycode": "^1.4.1" 368 | }, 369 | "dependencies": { 370 | "punycode": { 371 | "version": "1.4.1", 372 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 373 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 374 | } 375 | } 376 | }, 377 | "tunnel-agent": { 378 | "version": "0.6.0", 379 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 380 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 381 | "requires": { 382 | "safe-buffer": "^5.0.1" 383 | } 384 | }, 385 | "tweetnacl": { 386 | "version": "0.14.5", 387 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 388 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 389 | }, 390 | "type-detect": { 391 | "version": "1.0.0", 392 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 393 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 394 | "dev": true 395 | }, 396 | "uri-js": { 397 | "version": "4.2.2", 398 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 399 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 400 | "requires": { 401 | "punycode": "^2.1.0" 402 | } 403 | }, 404 | "uuid": { 405 | "version": "3.3.2", 406 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 407 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 408 | }, 409 | "verror": { 410 | "version": "1.10.0", 411 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 412 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 413 | "requires": { 414 | "assert-plus": "^1.0.0", 415 | "core-util-is": "1.0.2", 416 | "extsprintf": "^1.2.0" 417 | } 418 | }, 419 | "xml2js": { 420 | "version": "0.4.19", 421 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 422 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 423 | "requires": { 424 | "sax": ">=0.6.0", 425 | "xmlbuilder": "~9.0.1" 426 | } 427 | }, 428 | "xmlbuilder": { 429 | "version": "9.0.7", 430 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 431 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yamaha-nodejs", 3 | "version": "0.9.6", 4 | "description": "An API to control your YAMAHA Receiver written in nodejs", 5 | "main": "yamaha.js", 6 | "dependencies": { 7 | "bluebird": "^3.3.4", 8 | "request": "^2.88.0", 9 | "xml2js": "^0.4.16", 10 | "debug": "^2.2.0", 11 | "peer-ssdp": "0.0.5" 12 | }, 13 | "devDependencies": { 14 | "chai": "^3.5.0", 15 | "chai-as-promised": "^5.2.0" 16 | }, 17 | "scripts": { 18 | "test": "mocha mochatest" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PSeitz/yamaha-nodejs.git" 23 | }, 24 | "keywords": [ 25 | "yamaha", 26 | "api", 27 | "nodejs", 28 | "node", 29 | "receiver", 30 | "network" 31 | ], 32 | "author": "Pascal Seitz", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/PSeitz/Yamaha-Network-API/issues" 36 | }, 37 | "homepage": "https://github.com/PSeitz/Yamaha-Network-API" 38 | } 39 | -------------------------------------------------------------------------------- /simpleCommands.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var debug = require('debug')('Yamaha-nodejs'); 3 | var xml2js = Promise.promisifyAll(require("xml2js")); 4 | 5 | var request = Promise.promisify(require("request")); 6 | Promise.promisifyAll(request); 7 | 8 | //GetParam 9 | //GetParam 10 | 11 | function Yamaha() {} 12 | 13 | Yamaha.prototype.SendXMLToReceiver = function(xml) { 14 | var self = this; 15 | return this.getOrDiscoverIP().then(ip => { 16 | var isPutCommand = xml.indexOf("cmd=\"PUT\"") >= 0 ; 17 | var delay = isPutCommand ? this.responseDelay * 1000 : 0; 18 | var req = { 19 | method: 'POST', 20 | uri: 'http://' + ip + '/YamahaRemoteControl/ctrl', 21 | body: xml 22 | }; 23 | if (this.requestTimeout) req.timeout = this.requestTimeout; 24 | 25 | var prom = request.postAsync(req).delay(delay).then(response => response.body) 26 | if (self.catchRequestErrors === true) prom.catch(console.log.bind(console)); 27 | 28 | return prom 29 | 30 | }) 31 | 32 | }; 33 | 34 | var reYamahaManufacturer = /.*yamaha.*<\/manufacturer>/i; 35 | Yamaha.prototype.discover = function(timeout) { 36 | return new Promise(function(resolve, reject) { 37 | var ssdp = require("peer-ssdp"); 38 | var peer = ssdp.createPeer(); 39 | var timer = setTimeout(notFound, timeout || 5000); 40 | 41 | function notFound() { 42 | if (peer) peer.close(); 43 | reject(new Error('Yamaha Receiver not found')) 44 | } 45 | 46 | peer.on("ready", function() { 47 | peer.search({ 48 | ST: 'urn:schemas-upnp-org:device:MediaRenderer:1' 49 | }); 50 | }).on("found", function(headers, address) { 51 | if (headers.LOCATION) { 52 | request(headers.LOCATION, function(error, response, body) { 53 | if (!error && response.statusCode == 200 && reYamahaManufacturer.test(body)) { 54 | clearTimeout(timer); 55 | peer.close() 56 | resolve(address.address) 57 | } 58 | }); 59 | } 60 | }).start(); 61 | }) 62 | 63 | }; 64 | 65 | function getZone(zone) { 66 | if (!zone) return "Main_Zone"; 67 | 68 | // replace numbers with zones, eg type "2" and it will change to Zone 2 69 | if (zone.length == 1) { 70 | zone = zone.replace("/^1", "Main_Zone"); 71 | zone = zone.replace("/^2", "Zone_2"); 72 | zone = zone.replace("/^3", "Zone_3"); 73 | zone = zone.replace("/^4", "Zone_4"); 74 | } 75 | 76 | switch (zone) { 77 | case 1: 78 | zone = "Main_Zone"; 79 | break; 80 | case 2: 81 | case 3: 82 | case 4: 83 | zone = "Zone_" + zone; 84 | } 85 | return zone; 86 | } 87 | 88 | Yamaha.prototype.muteOn = function(zone) { 89 | var command = '<' + getZone(zone) + '>On'; 90 | return this.SendXMLToReceiver(command); 91 | }; 92 | 93 | Yamaha.prototype.muteOff = function(zone) { 94 | var command = '<' + getZone(zone) + '>Off'; 95 | return this.SendXMLToReceiver(command); 96 | }; 97 | 98 | Yamaha.prototype.stop = function(zone) { 99 | var command = '<' + getZone(zone) + '>Stop'; 100 | return this.SendXMLToReceiver(command); 101 | }; 102 | 103 | Yamaha.prototype.pause = function(zone) { 104 | var command = '<' + getZone(zone) + '>Pause'; 105 | return this.SendXMLToReceiver(command); 106 | }; 107 | 108 | Yamaha.prototype.play = function(zone) { 109 | var command = '<' + getZone(zone) + '>Play'; 110 | return this.SendXMLToReceiver(command); 111 | }; 112 | 113 | Yamaha.prototype.skip = function(zone) { 114 | var command = '<' + getZone(zone) + '>Skip Fwd'; 115 | return this.SendXMLToReceiver(command); 116 | }; 117 | 118 | Yamaha.prototype.rewind = function(zone) { 119 | var command = '<' + getZone(zone) + '>Skip Rev'; 120 | return this.SendXMLToReceiver(command); 121 | }; 122 | 123 | Yamaha.prototype.powerOn = function(zone) { 124 | var command = '<' + getZone(zone) + '>On' 125 | return this.SendXMLToReceiver(command); 126 | }; 127 | 128 | Yamaha.prototype.powerOff = function(zone) { 129 | var command = '<' + getZone(zone) + '>Standby'; 130 | return this.SendXMLToReceiver(command); 131 | }; 132 | 133 | Yamaha.prototype.setVolumeTo = function(to, zone) { 134 | var command = '<' + getZone(zone) + '>' + to + '1dB'; 135 | return this.SendXMLToReceiver(command); 136 | }; 137 | Yamaha.prototype.setVolume = Yamaha.prototype.setVolumeTo; 138 | 139 | Yamaha.prototype.volumeUp = function(by, zone) { 140 | return this.adjustVolumeBy(by, zone); 141 | }; 142 | 143 | Yamaha.prototype.volumeDown = function(by, zone) { 144 | return this.adjustVolumeBy(-by, zone); 145 | }; 146 | 147 | Yamaha.prototype.adjustVolumeBy = function(by, zone) { 148 | if (typeof by == 'string' || by instanceof String) by = parseInt(by); 149 | var self = this; 150 | return self.getBasicInfo(zone).then(function(basicInfo) { 151 | return self.setVolumeTo(basicInfo.getVolume() + by, zone); 152 | }); 153 | }; 154 | 155 | Yamaha.prototype.partyModeOn = function() { 156 | var command = 'On'; 157 | return this.SendXMLToReceiver(command); 158 | }; 159 | 160 | Yamaha.prototype.partyModeOff = function() { 161 | var command = 'Off'; 162 | return this.SendXMLToReceiver(command); 163 | }; 164 | 165 | Yamaha.prototype.partyModeUp = function() { 166 | // Increments all zones up equally 167 | var command = 'Up<'; 168 | return this.SendXMLToReceiver(command); 169 | }; 170 | 171 | Yamaha.prototype.partyModeDown = function() { 172 | // Increments all zones down equally 173 | var command = 'Down<'; 174 | return this.SendXMLToReceiver(command); 175 | }; 176 | 177 | Yamaha.prototype.setMainInputTo = function(to) { 178 | return this.setInputTo(to, "Main_Zone"); 179 | }; 180 | 181 | Yamaha.prototype.setInputTo = function(to, zone) { 182 | var command = '<' + getZone(zone) + '>' + to + ''; 183 | return this.SendXMLToReceiver(command); 184 | }; 185 | 186 | Yamaha.prototype.setSceneTo = function(to, zone) { 187 | var command = '<' + getZone(zone) + '>Scene ' + to + ''; 188 | return this.SendXMLToReceiver(command); 189 | } 190 | 191 | Yamaha.prototype.remoteCursor = function(cursorKey) { 192 | // Valid Cursor Commands (case-sensitve): 193 | // Up, Down, Right, Left, Return, Sel 194 | 195 | var command = '' + cursorKey + ''; 196 | return this.SendXMLToReceiver(command); 197 | }; 198 | 199 | Yamaha.prototype.remoteMenu = function(menuKey) { 200 | // Valid Menu Commands (case-sensitve): 201 | // Option, Display 202 | 203 | var command = '' + menuKey + ''; 204 | return this.SendXMLToReceiver(command); 205 | }; 206 | 207 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | 209 | 210 | Yamaha.prototype.sendRcCode = function(code) { // 7C80 = Power on/off 211 | if (typeof code == 'number') { 212 | code = code.toString(16); 213 | } 214 | //DSZ-Z7: *** 215 | //RX-Vx7x: *** 216 | //var command = '' + code + ''; 217 | //var command = '' + code + ''; 218 | //var command = '' + code + ''; 219 | 220 | var command = '' + code + ''; 221 | return this.SendXMLToReceiver(command); 222 | }; 223 | 224 | Yamaha.prototype.setPureDirect = function(on) { 225 | return this.sendPutCommand('' + (on ? 'On' : 'Off') + ''); 226 | }; 227 | Yamaha.prototype.setHDMIOutput = function(hdmi_num, on) { 228 | return this.sendPutCommand('' + (on ? 'On' : 'Off') + '', 'System'); 229 | }; 230 | 231 | Yamaha.prototype.sleep = function(val, zone) { 232 | if (val < 30) val = 'Off'; 233 | else if (val < 60) val = '30 min'; 234 | else if (val < 90) val = '60 min'; 235 | else if (val < 120) val = '90 min'; 236 | else val = '120 min'; 237 | return this.sendPutCommand('' + val + '', zone); 238 | }; 239 | 240 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 241 | 242 | 243 | Yamaha.prototype.setBassTo = function(to) { 244 | var zone = getZone(); //only available in Main Zone 245 | var command = '<' + zone + '>' + to + '1dB'; 246 | return this.SendXMLToReceiver(command); 247 | }; 248 | 249 | Yamaha.prototype.setTrebleTo = function(to) { 250 | var zone = getZone(); //only available in Main Zone 251 | var command = '<' + zone + '>' + to + '1dB'; 252 | return this.SendXMLToReceiver(command); 253 | }; 254 | 255 | Yamaha.prototype.setSubwooferTrimTo = function(to) { 256 | var zone = getZone(); //only available in Main Zone 257 | var command = '<' + zone + '>' + to + '1dB'; 258 | return this.SendXMLToReceiver(command); 259 | }; 260 | 261 | Yamaha.prototype.setDialogLiftTo = function(to) { 262 | var zone = getZone(); //only available in Main Zone 263 | var command = '<' + zone + '>' + to + ''; 264 | return this.SendXMLToReceiver(command); 265 | }; 266 | 267 | Yamaha.prototype.setDialogLevelTo = function(to) { 268 | var zone = getZone(); //only available in Main Zone 269 | var command = '<' + zone + '>' + to + ''; 270 | return this.SendXMLToReceiver(command); 271 | }; 272 | 273 | Yamaha.prototype.YPAOVolumeOn = function() { 274 | var zone = getZone(); //only available in Main Zone 275 | var command = '<' + zone + '>Auto'; 276 | return this.SendXMLToReceiver(command); 277 | }; 278 | 279 | Yamaha.prototype.YPAOVolumeOff = function() { 280 | var zone = getZone(); //only available in Main Zone 281 | var command = '<' + zone + '>Off'; 282 | return this.SendXMLToReceiver(command); 283 | }; 284 | 285 | Yamaha.prototype.extraBassOn = function() { 286 | var zone = getZone(); //only available in Main Zone 287 | var command = '<' + zone + '>Auto'; 288 | return this.SendXMLToReceiver(command); 289 | }; 290 | 291 | Yamaha.prototype.extraBassOff = function() { 292 | var zone = getZone(); //only available in Main Zone 293 | var command = '<' + zone + '>Off'; 294 | return this.SendXMLToReceiver(command); 295 | }; 296 | 297 | Yamaha.prototype.adaptiveDRCOn = function() { 298 | var zone = getZone(); //only available in Main Zone 299 | var command = '<' + zone + '>Auto'; 300 | return this.SendXMLToReceiver(command); 301 | }; 302 | 303 | Yamaha.prototype.adaptiveDRCOff = function() { 304 | var zone = getZone(); //only available in Main Zone 305 | var command = '<' + zone + '>Off'; 306 | return this.SendXMLToReceiver(command); 307 | }; 308 | 309 | 310 | Yamaha.prototype.getOrDiscoverIP = function() { 311 | if (this.ip) return Promise.resolve(this.ip) 312 | if (!this.discoverPromise) { 313 | this.discoverPromise = this.discover().tap(function(ip) { 314 | this.ip = ip 315 | }) 316 | } 317 | return this.discoverPromise 318 | } 319 | 320 | Yamaha.prototype.sendPutCommand = function(command, zone) { 321 | zone = getZone(zone); 322 | command = '<' + zone + '>' + command + ''; 323 | return this.SendXMLToReceiver(command); 324 | }; 325 | 326 | Yamaha.prototype.getColor = function() { 327 | return "The receiver is blue"; 328 | }; 329 | 330 | Yamaha.prototype.isHeadphoneConnected = function() { 331 | //checks if a Headphone is connected, returns "Connected" or "Not Connected" 332 | //is not available via getBasicInfo, that's why an additional request is needed 333 | //only available in Zone 1, this setting is readonly 334 | 335 | var zone = getZone(1); 336 | var command = '<' + zone + '>GetParam'; 337 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync).then(function(info) { 338 | try { 339 | return info.YAMAHA_AV[zone][0].Sound_Video[0].Headphone[0]; 340 | } catch (e) { 341 | return "Not Available"; //if the Receiver has no Headphone Connector 342 | } 343 | }); 344 | } 345 | 346 | Yamaha.prototype.getBasicInfo = function(zone) { 347 | 348 | var command = '<' + getZone(zone) + '>GetParam'; 349 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync).then(function(info) { 350 | return enrichBasicStatus(info, zone); 351 | }); 352 | 353 | }; 354 | 355 | function enrichBasicStatus(basicStatus, zone) { 356 | zone = getZone(zone); 357 | debug("getBasicInfo",zone,JSON.stringify(basicStatus, null, 2)); 358 | 359 | basicStatus.getVolume = function() { 360 | try { 361 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Volume[0].Lvl[0].Val[0]); 362 | } catch (e) { 363 | return -999; 364 | } 365 | }; 366 | 367 | basicStatus.getZone = function() { 368 | return zone; 369 | } 370 | 371 | basicStatus.isMuted = function() { 372 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Volume[0].Mute[0] !== "Off"; 373 | }; 374 | 375 | basicStatus.isOn = function() { 376 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Power_Control[0].Power[0] === "On"; 377 | }; 378 | 379 | basicStatus.isOff = function() { 380 | return !basicStatus.isOn(); 381 | }; 382 | 383 | basicStatus.getCurrentInput = function() { 384 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Input[0].Input_Sel[0]; 385 | }; 386 | 387 | basicStatus.isPartyModeEnabled = function() { 388 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Party_Info[0] === "On"; 389 | }; 390 | 391 | //the following properties are only available in Main Zone 392 | basicStatus.isPureDirectEnabled = function() { 393 | try { 394 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Pure_Direct[0].Mode[0] === "On"; 395 | } catch (e) { 396 | return 'Not Available'; 397 | } 398 | }; 399 | 400 | basicStatus.getBass = function() { 401 | try { 402 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Tone[0].Bass[0].Val[0]); 403 | } catch (e) { 404 | return 0; 405 | } 406 | }; 407 | 408 | basicStatus.getTreble = function() { 409 | try { 410 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Tone[0].Treble[0].Val[0]); 411 | } catch (e) { 412 | return 0; 413 | } 414 | }; 415 | 416 | basicStatus.getSubwooferTrim = function() { 417 | try { 418 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Volume[0].Subwoofer_Trim[0].Val[0]); 419 | } catch (e) { 420 | return 0; 421 | } 422 | }; 423 | 424 | basicStatus.getDialogueLift = function() { 425 | try { 426 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Dialogue_Adjust[0].Dialogue_Lift[0]); 427 | } catch (e) { 428 | return 0; 429 | } 430 | }; 431 | 432 | basicStatus.getDialogueLevel = function() { 433 | try { 434 | return parseInt(basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Dialogue_Adjust[0].Dialogue_Lvl[0]); 435 | } catch (e) { 436 | return 0; 437 | } 438 | }; 439 | 440 | basicStatus.isYPAOVolumeEnabled = function() { 441 | //values 'Off' or 'Auto' 442 | try { 443 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].YPAO_Volume[0] !== 'Off'; 444 | } catch (e) { 445 | return false; 446 | } 447 | }; 448 | 449 | basicStatus.isExtraBassEnabled = function() { 450 | //values 'Off' or 'Auto' 451 | try { 452 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Extra_Bass[0] !== 'Off'; 453 | } catch (e) { 454 | return false; 455 | } 456 | }; 457 | 458 | basicStatus.isAdaptiveDRCEnabled = function() { 459 | //values 'Off' or 'Auto' 460 | try { 461 | return basicStatus.YAMAHA_AV[zone][0].Basic_Status[0].Sound_Video[0].Adaptive_DRC[0] !== 'Off'; 462 | } catch (e) { 463 | return false; 464 | } 465 | }; 466 | return basicStatus; 467 | } 468 | 469 | 470 | // Add direct functions for basic info 471 | function addBasicInfoWrapper(basicInfo) { 472 | Yamaha.prototype[basicInfo] = function(zone) { 473 | return this.getBasicInfo(zone).then(function(result) { 474 | return result[basicInfo](); 475 | }); 476 | }; 477 | } 478 | //TODO: no list, take properties of basicStatus object 479 | var basicInfos = ["getVolume", "isMuted", "isOn", "isOff", "getCurrentInput", "isPartyModeEnabled", "isPureDirectEnabled", "getBass", "getTreble", "getSubwooferTrim", "getDialogueLift", "getDialogueLevel", "isYPAOVolumeEnabled", "isExtraBassEnabled", "isAdaptiveDRCEnabled"]; 480 | for (var i = 0; i < basicInfos.length; i++) { 481 | var basicInfo = basicInfos[i]; 482 | addBasicInfoWrapper(basicInfo); 483 | } 484 | 485 | Yamaha.prototype.getSystemConfig = function() { 486 | var command = 'GetParam'; 487 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync); 488 | }; 489 | 490 | Yamaha.prototype.getZoneConfig = function(zone) { 491 | var command = '<' + getZone(zone) + '>GetParam'; 492 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync); 493 | }; 494 | 495 | Yamaha.prototype.getAvailableZones = function() { 496 | return this.getSystemConfig().then(function(info) { 497 | var zones = []; 498 | var zonesXML = info.YAMAHA_AV.System[0].Config[0].Feature_Existence[0]; 499 | debug("getAvailableZones",JSON.stringify(info, null, 2)); 500 | for (var prop in zonesXML) { 501 | // Only return zones that the receiver supports 502 | if (prop.includes('one') && zonesXML[prop].includes('1')) { 503 | zones.push(prop); 504 | } 505 | } 506 | return zones; 507 | }); 508 | }; 509 | 510 | Yamaha.prototype.getAvailableInputs = function() { 511 | return this.getSystemConfig().then(function(info) { 512 | var inputs = []; 513 | var inputsXML = info.YAMAHA_AV.System[0].Config[0].Name[0].Input[0]; 514 | for (var prop in inputsXML) { 515 | inputs.push(inputsXML[prop][0]); 516 | } 517 | return inputs; 518 | }); 519 | }; 520 | 521 | Yamaha.prototype.getAvailableInputsWithNames = function() { 522 | return this.getSystemConfig().then(function(info) { 523 | var inputs = []; 524 | var inputsXML = info.YAMAHA_AV.System[0].Config[0].Name[0]; 525 | for (var prop in inputsXML) { 526 | inputs.push(inputsXML[prop][0]); 527 | } 528 | return inputs; 529 | }); 530 | }; 531 | Yamaha.prototype.selectListItem = function(listname, number) { 532 | var command = '<' + listname + '>Line_' + number + ''; 533 | return this.SendXMLToReceiver(command); 534 | }; 535 | 536 | Yamaha.prototype.jumpListItem = function(listname, lineNumber) { 537 | var command = '<' + listname + '>}>' + lineNumber + ''; 538 | return this.SendXMLToReceiver(command); 539 | }; 540 | 541 | Yamaha.prototype.getList = function(name) { 542 | var command = '<' + name + '>GetParam'; 543 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync).then(function(info) { 544 | enrichListInfo(info, name); 545 | return info; 546 | }); 547 | }; 548 | 549 | function enrichListInfo(listInfo, listname) { 550 | 551 | listInfo.hasSelectableItems = function() { 552 | return listInfo.YAMAHA_AV[listname][0].List_Info[0].Current_List[0].Line_1[0].Attribute[0] !== "Unselectable"; 553 | }; 554 | 555 | listInfo.isReady = function() { 556 | 557 | return !listInfo.isBusy() && listInfo.hasSelectableItems(); 558 | }; 559 | 560 | listInfo.isBusy = function() { 561 | return listInfo.YAMAHA_AV[listname][0].List_Info[0].Menu_Status[0] === "Busy"; 562 | }; 563 | 564 | listInfo.getMenuLayer = function() { 565 | return listInfo.YAMAHA_AV[listname][0].List_Info[0].Menu_Layer[0]; 566 | }; 567 | 568 | listInfo.getMenuName = function() { 569 | return listInfo.YAMAHA_AV[listname][0].List_Info[0].Menu_Name[0]; 570 | }; 571 | 572 | } 573 | 574 | 575 | Yamaha.prototype.isMenuReady = function(name) { 576 | var self = this; 577 | return self.getList(name).then(function(result) { 578 | return result.isReady(); 579 | }); 580 | }; 581 | 582 | Yamaha.prototype.whenMenuReady = function(name) { 583 | var self = this; 584 | return self.when("isMenuReady", name, true); 585 | }; 586 | 587 | Yamaha.prototype.when = function(YamahaCall, parameter, expectedReturnValue, tries) { 588 | var self = this; 589 | tries = tries || 0; 590 | return this[YamahaCall](parameter).then(function(result) { 591 | console.log('Polling...'); 592 | if (result == expectedReturnValue) { 593 | return true; 594 | } else if (tries > 40) { 595 | return Promise.reject("Timeout"); 596 | } else { 597 | return Promise.delay(self.pollingDelay).then(function() { 598 | return self.when(YamahaCall, parameter, expectedReturnValue, tries + 1); 599 | }); 600 | } 601 | }); 602 | }; 603 | 604 | Yamaha.prototype.selectUSBListItem = function(number) { 605 | return this.selectListItem("USB", number); 606 | }; 607 | 608 | Yamaha.prototype.jumpUSBListItem = function(number) { 609 | return this.jumpListItem("USB", number); 610 | }; 611 | 612 | Yamaha.prototype.selectWebRadioListItem = function(number) { 613 | return this.selectListItem("NET_RADIO", number); 614 | }; 615 | 616 | Yamaha.prototype.jumpWebRadioListItem = function(number) { 617 | return this.jumpListItem("NET_RADIO", number); 618 | }; 619 | 620 | Yamaha.prototype.selectTunerPreset = function(number) { 621 | var command = '' + number + ''; 622 | return this.SendXMLToReceiver(command); 623 | }; 624 | 625 | Yamaha.prototype.getTunerPresetList = function() { 626 | var command = 'GetParam'; 627 | 628 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync).then(function(info) { 629 | var presets = {}; 630 | for (var presetNum in info.YAMAHA_AV.Tuner[0].Play_Control[0].Preset[0].Data[0]) { 631 | var preset = info.YAMAHA_AV.Tuner[0].Play_Control[0].Preset[0].Data[0][presetNum][0]; 632 | if (preset.Status[0] === 'Exist') { 633 | var number = presetNum.substring(7); 634 | var band = preset.Band[0]; 635 | if (band == 'AM') { 636 | var freq = preset.Freq[0].AM[0].Val[0] / Math.pow(10, preset.Freq[0].AM[0].Exp[0]); 637 | } else { 638 | var freq = preset.Freq[0].FM[0].Val[0] / Math.pow(10, preset.Freq[0].FM[0].Exp[0]); 639 | } 640 | presets[number] = { 641 | "preset": number, 642 | "band": band, 643 | "value": freq 644 | } 645 | } 646 | } 647 | return (presets); 648 | }); 649 | }; 650 | 651 | Yamaha.prototype.getTunerInfo = function() { 652 | var command = 'GetParam'; 653 | 654 | return this.SendXMLToReceiver(command).then(xml2js.parseStringAsync).then(function(info) { 655 | var presets = info.YAMAHA_AV.Tuner[0]; 656 | return (presets); 657 | }); 658 | }; 659 | 660 | Yamaha.prototype.selectTunerFrequency = function(band, frequency) { 661 | var unit = band == "FM" ? "MHz" : "KHz", 662 | exp = band == "FM" ? 2 : 0, 663 | command = '' + band + '<' + band + '>' + frequency + '' + exp + '' + unit + ''; 664 | return this.SendXMLToReceiver(command); 665 | }; 666 | 667 | Yamaha.prototype.getNetRadioPlayInfo = function() { 668 | var command = 'GetParam'; 669 | return this.SendXMLToReceiver(command).then( 670 | function (xml) { 671 | return xml2js.parseStringAsync(xml, { explicitArray: false }) 672 | } 673 | ).then( 674 | function (resp) { 675 | return resp.YAMAHA_AV.NET_RADIO.Play_Info; 676 | } 677 | ); 678 | } 679 | 680 | //TODO: More XML CONVERT 681 | Yamaha.prototype.getWebRadioList = function() { 682 | return this.getList("NET_RADIO"); 683 | }; 684 | Yamaha.prototype.getUSBList = function() { 685 | return this.getList("USB"); 686 | }; 687 | 688 | 689 | module.exports = Yamaha; 690 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var Yamaha = require("./yamaha.js"); 2 | var yamaha = new Yamaha(); 3 | 4 | yamaha.isOn().done(function(result){ 5 | console.log(result); 6 | }); 7 | 8 | 9 | // yamaha.discover().then(function(result){ 10 | // console.log(result); 11 | // }); 12 | -------------------------------------------------------------------------------- /yamaha.js: -------------------------------------------------------------------------------- 1 | var simpleCommands = require('./simpleCommands'); 2 | var chainedCommands = require('./chainedCommands'); 3 | var Promise = require("bluebird"); 4 | 5 | /** 6 | * The Yamaha Module Constructor. 7 | * @constructor 8 | * @param {string} ip - The ip of the yamaha receiver. 9 | * @param {number} responseDelay - The delay of the response for put commands, in seconds - defaults to 1. The receiver needs some time to process the changes PUT methods. Easier than polling... 10 | * @param {number} requestTimeout - The requestTimeout for each request send to the receiver 11 | * 12 | */ 13 | function Yamaha(ip, responseDelay, requestTimeout) 14 | { 15 | if (typeof responseDelay == 'string' || responseDelay instanceof String) responseDelay = parseInt(responseDelay); 16 | if (!responseDelay) responseDelay = 1; 17 | this.ip = ip; 18 | this.responseDelay = responseDelay; 19 | this.pollingDelay = 500; // used for menu ready check, webradio e.g. 20 | this.requestTimeout = requestTimeout; 21 | this.catchRequestErrors = true 22 | } 23 | 24 | extend(Yamaha.prototype, simpleCommands.prototype); 25 | extend(Yamaha.prototype, chainedCommands.prototype); 26 | 27 | Yamaha.prototype.waitForNotify = function (ip, callback) { 28 | var ssdp = require("peer-ssdp"); 29 | var peer = ssdp.createPeer(); 30 | peer.on("ready", function () { 31 | }).on("notify", function (headers, address) { 32 | if (address.adress === ip) { 33 | callback(true); 34 | peer.close(); 35 | } 36 | }).start(); 37 | return peer; 38 | }; 39 | 40 | function extend(destination , source) { 41 | for (var k in source) { 42 | destination[k] = source[k]; 43 | } 44 | } 45 | 46 | module.exports = Yamaha; --------------------------------------------------------------------------------