From 76ad483a5faf754ad6cd992dea229904e9200fde Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 28 Jul 2015 07:05:35 +0200 Subject: [PATCH 1/2] WIP - Initial attempt at a Yamaha AVR controll module, like Sonos. Not working quite yet, but looks close. --- platforms/YamahaAVR.js | 174 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 platforms/YamahaAVR.js diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js new file mode 100644 index 0000000..eb7ddde --- /dev/null +++ b/platforms/YamahaAVR.js @@ -0,0 +1,174 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var Yamaha = require('yamaha-nodejs'); +var mdns = require('mdns'); + +function YamahaAVRPlatform(log, config){ + this.log = log; + this.config = config; + this.playVolume = config["play_volume"]; + this.browser = mdns.createBrowser(mdns.tcp('http')); +} + +YamahaAVRPlatform.prototype = { + accessories: function(callback) { + this.log("Getting Yamaha AVR devices."); + var that = this; + + var browser = this.browser; + browser.stop(); + browser.removeAllListeners('serviceUp'); // cleanup listeners + + browser.on('serviceUp', function(service){ + var name = service.name; + //console.log('Found HTTP service "' + name + '"'); + // We can't tell just from mdns if this is an AVR... + if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. + var yamaha = new Yamaha(service.host); + yamaha.getSystemConfig().then(function(sysConfig){ + var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; + var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; + that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); + var accessory = new YamahaAVRAccessory(that.log, that.config, service, sysConfig); + callback([accessory]); + }, function(err){ + return; + }) + }); + browser.start(); + } +}; + +function YamahaAVRAccessory(log, config, mdnsService, sysConfig) { + this.log = log; + this.config = config; + this.mdnsService = mdnsService; + this.sysConfig = sysConfig; + + this.name = service.name; + this.serviceName = service.name + " Speakers"; + this.playVolume = this.config["play_volume"]; +} + +YamahaAVRAccessory.prototype = { + + setPlaying: function(playing) { + + if (!this.device) { + this.log("No device found (yet?)"); + return; + } + + var that = this; + + if (playing) { + /* + this.device.play(function(err, success) { + that.log("Playback attempt with success: " + success); + }); + + if (this.playVolume) { + this.device.setVolume(this.playVolume, function(err, success) { + if (!err) { + that.log("Set volume to " + that.playVolume); + } + else { + that.log("Problem setting volume: " + err); + } + }); + } + */ + } + else { + /* + this.device.stop(function(err, success) { + that.log("Stop attempt with success: " + success); + }); + */ + } + }, + + getServices: function() { +console.log('getServices called on "' + this.name + '"...'); + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Yamaha", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0], + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_Id[0], + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.SWITCH_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.serviceName, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { that.setPlaying(value); }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the playback state of the Yamaha AV Receiver", + designedMaxLength: 1 + }] + }]; + } +}; + +module.exports.accessory = YamahaAVRAccessory; +module.exports.platform = YamahaAVRPlatform; From b5f778051e42f5a36e7f4ad7b061b2de3a122604 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Wed, 29 Jul 2015 20:34:38 +0200 Subject: [PATCH 2/2] First working version, probably could be merged if no one sees anything glaringly wrong. Config can set the play volume (in decibels per the AVR display) and the default input. If "AirPlay" is the default input, then the play command will be sent to the AVR. --- config-sample.json | 5 ++++ package.json | 2 ++ platforms/YamahaAVR.js | 55 ++++++++++++++++++------------------------ 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/config-sample.json b/config-sample.json index d4760e0..bd81c12 100644 --- a/config-sample.json +++ b/config-sample.json @@ -57,6 +57,11 @@ "platform": "Sonos", "name": "Sonos", "play_volume": 25 + }, + { + "platform": "YamahaAVR", + "play_volume": -35, + "setMainInputTo": "AirPlay" } ], diff --git a/package.json b/package.json index 48916e3..ae1d196 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", + "mdns": "^2.2.4", "node-hue-api": "^1.0.5", "node-milight-promise": "0.0.2", "node-persist": "0.0.x", @@ -28,6 +29,7 @@ "wink-js": "0.0.5", "xml2js": "0.4.x", "xmldoc": "0.1.x", + "yamaha-nodejs": "0.4.x", "teslams": "1.0.1" } } diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index eb7ddde..f0d10c1 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -6,6 +6,7 @@ function YamahaAVRPlatform(log, config){ this.log = log; this.config = config; this.playVolume = config["play_volume"]; + this.setMainInputTo = config["setMainInputTo"]; this.browser = mdns.createBrowser(mdns.tcp('http')); } @@ -28,7 +29,7 @@ YamahaAVRPlatform.prototype = { var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); - var accessory = new YamahaAVRAccessory(that.log, that.config, service, sysConfig); + var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); callback([accessory]); }, function(err){ return; @@ -38,57 +39,47 @@ YamahaAVRPlatform.prototype = { } }; -function YamahaAVRAccessory(log, config, mdnsService, sysConfig) { +function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { this.log = log; this.config = config; this.mdnsService = mdnsService; + this.yamaha = yamaha; this.sysConfig = sysConfig; - this.name = service.name; - this.serviceName = service.name + " Speakers"; + this.name = mdnsService.name; + this.serviceName = mdnsService.name + " Speakers"; + this.setMainInputTo = config["setMainInputTo"]; this.playVolume = this.config["play_volume"]; } YamahaAVRAccessory.prototype = { setPlaying: function(playing) { - - if (!this.device) { - this.log("No device found (yet?)"); - return; - } - var that = this; + var yamaha = this.yamaha; if (playing) { - /* - this.device.play(function(err, success) { - that.log("Playback attempt with success: " + success); + + yamaha.powerOn().then(function(){ + if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); + else return { then: function(f, r){ f(); } }; + }).then(function(){ + if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); + else return { then: function(f, r){ f(); } }; + }).then(function(){ + if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( + 'Play' + ); + else return { then: function(f, r){ f(); } }; + //else return Promise.fulfilled(undefined); }); - - if (this.playVolume) { - this.device.setVolume(this.playVolume, function(err, success) { - if (!err) { - that.log("Set volume to " + that.playVolume); - } - else { - that.log("Problem setting volume: " + err); - } - }); - } - */ } else { - /* - this.device.stop(function(err, success) { - that.log("Stop attempt with success: " + success); - }); - */ + yamaha.powerOff(); } }, getServices: function() { -console.log('getServices called on "' + this.name + '"...'); var that = this; return [{ sType: types.ACCESSORY_INFORMATION_STYPE, @@ -127,7 +118,7 @@ console.log('getServices called on "' + this.name + '"...'); onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_Id[0], + initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0], supportEvents: false, supportBonjour: false, manfDescription: "SN",