From 8bec3f33642cf22b0992c3253bfdd1f34bf81918 Mon Sep 17 00:00:00 2001 From: Sam D Date: Sat, 15 Nov 2014 19:11:39 -0500 Subject: [PATCH 01/15] fixed typo useful not usefull :stuck_out_tongue_winking_eye: --- API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API.md b/API.md index 509e432..74e65fb 100644 --- a/API.md +++ b/API.md @@ -17,7 +17,7 @@ This module exports 3 items: s.currentTrack(console.log); // sonos.Services - wrappers arounds all UPNP services provided by sonsos - // These aren't used internally by the module at all but may be usefull + // These aren't used internally by the module at all but may be useful // for more complex projects. ###var Sonos = new sonos.Sonos(host, port)### From 476c300b69c9e2cf506c48d1dbbb476666852857 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Fri, 5 Dec 2014 22:21:35 -0600 Subject: [PATCH 02/15] Fix error emitter for search --- lib/sonos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sonos.js b/lib/sonos.js index 1ac2b84..189332d 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -740,7 +740,7 @@ var Search = function Search() { } }); - this.on('error', function(err) { + this.socket.on('error', function(err) { _this.emit('error', err); }); From 128fe69fb24805dbb4c9feebba3c079825c8eb62 Mon Sep 17 00:00:00 2001 From: Ben Evans Date: Mon, 8 Dec 2014 04:43:53 +0000 Subject: [PATCH 03/15] 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7dc4a85..81ac85e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos", - "version": "0.6.1", + "version": "0.7.0", "description": "Node.js Sonos Interface", "main": "index.js", "scripts": { From e3741c93f866c4904fe5732d8cc8e4c8a768fe99 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Mon, 24 Nov 2014 01:25:14 -0600 Subject: [PATCH 04/15] First pass for logical device support --- lib/events/listener.js | 19 +++++++++++----- lib/logicalDevice.js | 51 ++++++++++++++++++++++++++++++++++++++++++ lib/sonos.js | 1 + package.json | 7 +++--- 4 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 lib/logicalDevice.js diff --git a/lib/events/listener.js b/lib/events/listener.js index b62c60c..63fcc65 100644 --- a/lib/events/listener.js +++ b/lib/events/listener.js @@ -10,6 +10,7 @@ var Listener = function(device) { this.device = device; this.parser = new xml2js.Parser(); this.services = {}; + this.serviceEndpoints = {}; }; util.inherits(Listener, events.EventEmitter); @@ -63,7 +64,9 @@ Listener.prototype._messageHandler = function(req, res) { Listener.prototype.addService = function(serviceEndpoint, callback) { if (!this.server) { - throw 'Service endpoints can only be added after listen() is called'; + callback(new Error('Service endpoints can only be added after listen() is finished')); + } else if (this.serviceEndpoints[serviceEndpoint]) { + callback(new Error('Service endpoint already added (' + serviceEndpoint + ')')); } else { var opt = { @@ -81,12 +84,16 @@ Listener.prototype.addService = function(serviceEndpoint, callback) { console.log(response.message || response.statusCode); callback(err || response.statusMessage); } else { - callback(null, response.headers.sid); + var sid = response.headers.sid; - this.services[response.headers.sid] = { + this.services[sid] = { endpoint: serviceEndpoint, data: {} }; + + this.serviceEndpoints[serviceEndpoint] = sid; + + callback(null, sid); } }.bind(this)); @@ -98,15 +105,15 @@ Listener.prototype.listen = function(callback) { if (!this.server) { this._startInternalServer(callback); } else { - throw 'Service listener is already listening'; + callback(new Error('Service listener is already listening')); } }; Listener.prototype.removeService = function(sid, callback) { if (!this.server) { - throw 'Service endpoints can only be modified after listen() is called'; + callback(new Error('Service endpoints can only be modified after listen() is called')); } else if (!this.services[sid]) { - throw 'Service with sid ' + sid + ' is not registered'; + callback(new Error('Service with sid ' + sid + ' is not registered')); } else { var opt = { diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js new file mode 100644 index 0000000..59b1111 --- /dev/null +++ b/lib/logicalDevice.js @@ -0,0 +1,51 @@ +var async = require('async'), + Sonos = require('./sonos').Sonos, + util = require('util'); + +function LogicalDevice(devices) { + if (devices.length === 0) { + throw new Error('Logical device must be initialized with at least one device (' + devices.length + ' given)'); + } + + Sonos.call(this, devices[0].host, devices[0].port); + var sonosDevices = devices.map(function(device) { + return new Sonos(device.host, device.post); + }); + + this.devices = sonosDevices; +} + +util.inherits(LogicalDevice, Sonos); + +LogicalDevice.prototype.setVolume = function(volume, cb) { + this.getVolume(function(oldVolume) { + var diff = volume - oldVolume; + async.forEach(this.devices, function(device, done) { + device.getVolume(function(err, oldDeviceVolume) { + var newDeviceVolume = oldDeviceVolume + diff; + newDeviceVolume = Math.max(newDeviceVolume, 0); + newDeviceVolume = Math.min(newDeviceVolume, 100); + device.setVolume(newDeviceVolume, done); + }); + }, cb); + }.bind(this)); +}; + +LogicalDevice.prototype.getVolume = function(cb) { + var results = []; + + async.forEach(this.devices, function(device, done) { + device.getVolume(function(err, vol) { + results.push(vol); + done(); + }); + }, function(err) { + if (err) cb(err); + else { + var sum = results.reduce(function(vol, acc) { return vol + acc; }); + cb(sum / results.length); + } + }); +}; + +module.exports = LogicalDevice; diff --git a/lib/sonos.js b/lib/sonos.js index 1ac2b84..2073fab 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -774,5 +774,6 @@ var search = function(listener) { */ module.exports.Sonos = Sonos; +module.exports.LogicalDevice = require('./logicalDevice'); module.exports.search = search; module.exports.Services = Services; diff --git a/package.json b/package.json index 7dc4a85..9b64460 100755 --- a/package.json +++ b/package.json @@ -30,12 +30,13 @@ ], "license": "MIT", "dependencies": { + "async": "^0.9.0", "debug": "~0.7.2", + "ip": "~0.3.0", + "request": "~2.27.0", "underscore": "~1.5.1", "upnp-client": "0.0.1", - "xml2js": "~0.2.8", - "request": "~2.27.0", - "ip": "~0.3.0" + "xml2js": "~0.2.8" }, "bugs": { "url": "http://github.com/bencevans/node-sonos/issues" From 6c0f6e24740974c4112b46bbeb783a047e36739c Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Fri, 5 Dec 2014 20:56:48 -0600 Subject: [PATCH 05/15] working volume listener impl --- examples/volumeWatcher.js | 9 +++++++++ lib/events/listener.js | 5 +++-- lib/events/volumeListener.js | 19 +++++++++++++++++++ lib/sonos.js | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 examples/volumeWatcher.js create mode 100644 lib/events/volumeListener.js diff --git a/examples/volumeWatcher.js b/examples/volumeWatcher.js new file mode 100644 index 0000000..c7993c7 --- /dev/null +++ b/examples/volumeWatcher.js @@ -0,0 +1,9 @@ +var Sonos = require('../index').Sonos; +var Listener = require('../lib/events/listener'); + +var device = new Sonos(process.env.SONOS_HOST || '192.168.2.11'); +device.startListeners(function() { + device.on('volumeChange', function(volume) { + console.log(volume); + }); +}); diff --git a/lib/events/listener.js b/lib/events/listener.js index 63fcc65..ffe10de 100644 --- a/lib/events/listener.js +++ b/lib/events/listener.js @@ -11,6 +11,7 @@ var Listener = function(device) { this.parser = new xml2js.Parser(); this.services = {}; this.serviceEndpoints = {}; + this.serviceHandlers = {}; }; util.inherits(Listener, events.EventEmitter); @@ -81,7 +82,6 @@ Listener.prototype.addService = function(serviceEndpoint, callback) { request(opt, function(err, response) { if (err || response.statusCode !== 200) { - console.log(response.message || response.statusCode); callback(err || response.statusMessage); } else { var sid = response.headers.sid; @@ -93,7 +93,8 @@ Listener.prototype.addService = function(serviceEndpoint, callback) { this.serviceEndpoints[serviceEndpoint] = sid; - callback(null, sid); + if (callback) + callback(null, sid); } }.bind(this)); diff --git a/lib/events/volumeListener.js b/lib/events/volumeListener.js new file mode 100644 index 0000000..f78544b --- /dev/null +++ b/lib/events/volumeListener.js @@ -0,0 +1,19 @@ +var Sonos = require('../sonos').Sonos; + +var SERVICE_ENDPOINT = '/MediaRenderer/RenderingControl/Event'; + +var initVolumeListener = function(baseListener, cb) { + + baseListener.on('serviceEvent', function(endpoint, sid, data) { + if (endpoint == SERVICE_ENDPOINT) { + baseListener.parser.parseString(data.LastChange, function(err, result) { + baseListener.device.state.volume = parseInt(result.Event['InstanceID'][0]['Volume'][0]['$']['val']); + baseListener.device.emit('volumeChange', baseListener.device.state.volume); + }); + } + }); + + baseListener.addService(SERVICE_ENDPOINT, cb); +}; + +module.exports = initVolumeListener; \ No newline at end of file diff --git a/lib/sonos.js b/lib/sonos.js index 2073fab..e3e703f 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -18,7 +18,9 @@ var util = require('util'), xml2js = require('xml2js'), debug = require('debug')('sonos'), fs = require('fs'), - _ = require('underscore'); + _ = require('underscore'), + Listener = require('./events/listener'), + async = require('async'); /** * Services @@ -64,6 +66,38 @@ var htmlEntities = function (str) { var Sonos = function Sonos(host, port) { this.host = host; this.port = port || 1400; + this.state = {}; +}; + +util.inherits(Sonos, EventEmitter); + +var serviceListeners = [ + require('./events/volumeListener') +]; + +/** + * Start service event listeners + */ +Sonos.prototype.startListeners = function(cb) { + + this.listener = new Listener(this); + + this.listener.listen(function(err) { + + async.parallel(serviceListeners.map(function(listenerInit) { + return function(callback) { + listenerInit(this.listener, callback); + }.bind(this); + }.bind(this)), cb); + + }.bind(this)); +}; + +/** + * Stop service event listeners + */ +Sonos.prototype.stopListeners = function() { + // meh }; /** From 6036ba64c652d4af84ddf72d11096962b48722e8 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Fri, 5 Dec 2014 21:38:57 -0600 Subject: [PATCH 06/15] Refactor listener class / add docs Completed start/stop on base sonos class --- lib/events/listener.js | 65 ++++++++++++++++++++++++++++-------- lib/events/volumeListener.js | 17 +++++----- lib/sonos.js | 6 ++-- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/events/listener.js b/lib/events/listener.js index ffe10de..e2a201f 100644 --- a/lib/events/listener.js +++ b/lib/events/listener.js @@ -1,3 +1,4 @@ +var async = require('async'); var request = require('request'); var http = require('http'); var ip = require('ip'); @@ -6,12 +7,15 @@ var util = require('util'); var _ = require('underscore'); var events = require('events'); -var Listener = function(device) { +/** + * Listener "Class" + * @param {Sonos} device Corresponding Sonos device + */ +function Listener(device) { this.device = device; this.parser = new xml2js.Parser(); this.services = {}; this.serviceEndpoints = {}; - this.serviceHandlers = {}; }; util.inherits(Listener, events.EventEmitter); @@ -39,6 +43,40 @@ Listener.prototype._startInternalServer = function(callback) { }; +/** + * Add event handler to the endpoint + * @param {String} serviceEndpoint Endpoint to subscribe to + * @param {Function} handler handler to call for events + * @param {Function} callback {err, sonosServiceId} + */ +Listener.prototype.addHandler = function(serviceEndpoint, handler, callback) { + + this.on('serviceEvent', function(endpoint, sid, data) { + if (endpoint == serviceEndpoint) { + handler(data); + } + }); + + if (!this.serviceEndpoints[serviceEndpoint]) + this._addService(serviceEndpoint, callback); +}; + +/** + * Remove all handlers from endpoint + * @param {Function} callback {err, results} + */ +Listener.prototype.removeAllHandlers = function(callback) { + + this.removeAllListeners('serviceEvent'); + + async.parallel(Object.keys(this.services).map(function(sid) { + return function(cb) { + this._removeService(sid, cb); + }.bind(this); + }.bind(this)), callback); + +}; + Listener.prototype._messageHandler = function(req, res) { if (req.method.toUpperCase() === 'NOTIFY' && req.url.toLowerCase() === '/notify') { @@ -63,7 +101,7 @@ Listener.prototype._messageHandler = function(req, res) { } }; -Listener.prototype.addService = function(serviceEndpoint, callback) { +Listener.prototype._addService = function(serviceEndpoint, callback) { if (!this.server) { callback(new Error('Service endpoints can only be added after listen() is finished')); } else if (this.serviceEndpoints[serviceEndpoint]) { @@ -101,16 +139,8 @@ Listener.prototype.addService = function(serviceEndpoint, callback) { } }; -Listener.prototype.listen = function(callback) { - - if (!this.server) { - this._startInternalServer(callback); - } else { - callback(new Error('Service listener is already listening')); - } -}; +Listener.prototype._removeService = function(sid, callback) { -Listener.prototype.removeService = function(sid, callback) { if (!this.server) { callback(new Error('Service endpoints can only be modified after listen() is called')); } else if (!this.services[sid]) { @@ -133,8 +163,17 @@ Listener.prototype.removeService = function(sid, callback) { callback(null, true); } }); + } + +}; + +Listener.prototype.listen = function(callback) { + if (!this.server) { + this._startInternalServer(callback); + } else { + callback(new Error('Service listener is already listening')); } }; -module.exports = Listener; \ No newline at end of file +module.exports = Listener; diff --git a/lib/events/volumeListener.js b/lib/events/volumeListener.js index f78544b..64cde15 100644 --- a/lib/events/volumeListener.js +++ b/lib/events/volumeListener.js @@ -4,16 +4,15 @@ var SERVICE_ENDPOINT = '/MediaRenderer/RenderingControl/Event'; var initVolumeListener = function(baseListener, cb) { - baseListener.on('serviceEvent', function(endpoint, sid, data) { - if (endpoint == SERVICE_ENDPOINT) { - baseListener.parser.parseString(data.LastChange, function(err, result) { - baseListener.device.state.volume = parseInt(result.Event['InstanceID'][0]['Volume'][0]['$']['val']); - baseListener.device.emit('volumeChange', baseListener.device.state.volume); - }); - } - }); + baseListener.addHandler(SERVICE_ENDPOINT, function(data) { + + baseListener.parser.parseString(data.LastChange, function(err, result) { + baseListener.device.state.volume = parseInt(result.Event['InstanceID'][0]['Volume'][0]['$']['val']); + baseListener.device.emit('volumeChange', baseListener.device.state.volume); + }); + + }, cb); - baseListener.addService(SERVICE_ENDPOINT, cb); }; module.exports = initVolumeListener; \ No newline at end of file diff --git a/lib/sonos.js b/lib/sonos.js index e3e703f..e8f996d 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -77,6 +77,7 @@ var serviceListeners = [ /** * Start service event listeners + * @param {Function} callback {err, results} */ Sonos.prototype.startListeners = function(cb) { @@ -95,9 +96,10 @@ Sonos.prototype.startListeners = function(cb) { /** * Stop service event listeners + * @param {Function} callback {err, results} */ -Sonos.prototype.stopListeners = function() { - // meh +Sonos.prototype.stopListeners = function(cb) { + this.listener.removeAllHandlers(cb); }; /** From d9ca0f73b2854bafd52a20204d4ad81ad6ea7499 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Fri, 5 Dec 2014 22:13:35 -0600 Subject: [PATCH 07/15] Changed logical device to use local volume setting / changed init to wait for initial data --- examples/volumeWatcher.js | 1 - lib/events/listener.js | 4 +-- lib/events/volumeListener.js | 18 ++++++++++---- lib/logicalDevice.js | 47 ++++++++++++++++++++++-------------- lib/sonos.js | 20 +++++++++------ package.json | 5 ++-- 6 files changed, 59 insertions(+), 36 deletions(-) diff --git a/examples/volumeWatcher.js b/examples/volumeWatcher.js index c7993c7..e11a00d 100644 --- a/examples/volumeWatcher.js +++ b/examples/volumeWatcher.js @@ -1,5 +1,4 @@ var Sonos = require('../index').Sonos; -var Listener = require('../lib/events/listener'); var device = new Sonos(process.env.SONOS_HOST || '192.168.2.11'); device.startListeners(function() { diff --git a/lib/events/listener.js b/lib/events/listener.js index e2a201f..f4ec3a8 100644 --- a/lib/events/listener.js +++ b/lib/events/listener.js @@ -16,7 +16,7 @@ function Listener(device) { this.parser = new xml2js.Parser(); this.services = {}; this.serviceEndpoints = {}; -}; +} util.inherits(Listener, events.EventEmitter); @@ -52,7 +52,7 @@ Listener.prototype._startInternalServer = function(callback) { Listener.prototype.addHandler = function(serviceEndpoint, handler, callback) { this.on('serviceEvent', function(endpoint, sid, data) { - if (endpoint == serviceEndpoint) { + if (endpoint === serviceEndpoint) { handler(data); } }); diff --git a/lib/events/volumeListener.js b/lib/events/volumeListener.js index 64cde15..a54c018 100644 --- a/lib/events/volumeListener.js +++ b/lib/events/volumeListener.js @@ -1,17 +1,25 @@ -var Sonos = require('../sonos').Sonos; - var SERVICE_ENDPOINT = '/MediaRenderer/RenderingControl/Event'; -var initVolumeListener = function(baseListener, cb) { +var initVolumeListener = function(baseListener, callback) { + + var initialized = false; baseListener.addHandler(SERVICE_ENDPOINT, function(data) { + // wait for initial data before callback + if (!initialized) { + initialized = true; + callback(null); + } + baseListener.parser.parseString(data.LastChange, function(err, result) { - baseListener.device.state.volume = parseInt(result.Event['InstanceID'][0]['Volume'][0]['$']['val']); + baseListener.device.state.volume = parseInt(result.Event.InstanceID[0].Volume[0].$.val); baseListener.device.emit('volumeChange', baseListener.device.state.volume); }); - }, cb); + }, function(err) { + if (err) callback(err); + }); }; diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index 59b1111..a0b7d54 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -17,35 +17,46 @@ function LogicalDevice(devices) { util.inherits(LogicalDevice, Sonos); +LogicalDevice.prototype.initialize = function(cb) { + async.forEach(this.devices, function(device, done) { + device.initialize(done); + }, cb); +}; + +LogicalDevice.prototype.destroy = function(cb) { + async.forEach(this.devices, function(device, done) { + device.destroy(done); + }, cb); +}; + LogicalDevice.prototype.setVolume = function(volume, cb) { this.getVolume(function(oldVolume) { + var diff = volume - oldVolume; + async.forEach(this.devices, function(device, done) { - device.getVolume(function(err, oldDeviceVolume) { - var newDeviceVolume = oldDeviceVolume + diff; - newDeviceVolume = Math.max(newDeviceVolume, 0); - newDeviceVolume = Math.min(newDeviceVolume, 100); - device.setVolume(newDeviceVolume, done); - }); + var oldDeviceVolume = device.state.volume; + var newDeviceVolume = oldDeviceVolume + diff; + + newDeviceVolume = Math.max(newDeviceVolume, 0); + newDeviceVolume = Math.min(newDeviceVolume, 100); + + device.setVolume(newDeviceVolume, done); + }, cb); + }.bind(this)); }; LogicalDevice.prototype.getVolume = function(cb) { - var results = []; - async.forEach(this.devices, function(device, done) { - device.getVolume(function(err, vol) { - results.push(vol); - done(); - }); - }, function(err) { - if (err) cb(err); - else { - var sum = results.reduce(function(vol, acc) { return vol + acc; }); - cb(sum / results.length); - } + var sum = 0; + + this.devices.forEach(function(device) { + sum += device.state.volume || 0; }); + + cb(sum / this.devices.length); }; module.exports = LogicalDevice; diff --git a/lib/sonos.js b/lib/sonos.js index e8f996d..b748e54 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -79,17 +79,21 @@ var serviceListeners = [ * Start service event listeners * @param {Function} callback {err, results} */ -Sonos.prototype.startListeners = function(cb) { +Sonos.prototype.initialize = function(callback) { this.listener = new Listener(this); this.listener.listen(function(err) { + if (err) callback(err); + else { - async.parallel(serviceListeners.map(function(listenerInit) { - return function(callback) { - listenerInit(this.listener, callback); - }.bind(this); - }.bind(this)), cb); + async.parallel(serviceListeners.map(function(listenerInit) { + return function(callback) { + listenerInit(this.listener, callback); + }.bind(this); + }.bind(this)), callback); + + } }.bind(this)); }; @@ -98,8 +102,8 @@ Sonos.prototype.startListeners = function(cb) { * Stop service event listeners * @param {Function} callback {err, results} */ -Sonos.prototype.stopListeners = function(cb) { - this.listener.removeAllHandlers(cb); +Sonos.prototype.destroy = function(callback) { + this.listener.removeAllHandlers(callback); }; /** diff --git a/package.json b/package.json index 9b64460..c6cdc40 100755 --- a/package.json +++ b/package.json @@ -42,8 +42,9 @@ "url": "http://github.com/bencevans/node-sonos/issues" }, "devDependencies": { - "grunt-contrib-jshint": "~0.8.0", "grunt": "~0.4.2", - "grunt-mocha-test": "~0.10.2" + "grunt-contrib-jshint": "~0.8.0", + "grunt-mocha-test": "~0.10.2", + "keypress": "^0.2.1" } } From 30c4b7b3f937de380283859f6cbd4a63c2ce56ba Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Fri, 5 Dec 2014 22:20:53 -0600 Subject: [PATCH 08/15] Add example for logical devices --- examples/logicalDeviceVolume.js | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/logicalDeviceVolume.js diff --git a/examples/logicalDeviceVolume.js b/examples/logicalDeviceVolume.js new file mode 100644 index 0000000..6189925 --- /dev/null +++ b/examples/logicalDeviceVolume.js @@ -0,0 +1,36 @@ +var Sonos = require('../'); +var keypress = require('keypress'); + +var dev = new Sonos.LogicalDevice([ + { host: '172.17.106.196', port: 1400 }, + { host: '172.17.107.66', port: 1400 } +]); + +dev.initialize(function() { + console.log('use up / down keys to change volume'); + + keypress(process.stdin); + + process.stdin.on('keypress', function (ch, key) { + if (key.name === 'down') { + + dev.getVolume(function(vol) { + dev.setVolume(vol - 5); + }); + + } else if (key.name === 'up') { + + dev.getVolume(function(vol) { + dev.setVolume(vol + 5); + }); + + } + if (key && key.ctrl && key.name === 'c') { + process.exit(); + } + }); + + process.stdin.setRawMode(true); + process.stdin.resume(); + +}); From 6ec35dbcee5a8ae2528fa85b006c220dfe74a1b9 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Mon, 8 Dec 2014 23:37:19 -0600 Subject: [PATCH 09/15] fix volumeWatcher to use initialize() --- examples/volumeWatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/volumeWatcher.js b/examples/volumeWatcher.js index e11a00d..8965931 100644 --- a/examples/volumeWatcher.js +++ b/examples/volumeWatcher.js @@ -1,7 +1,7 @@ var Sonos = require('../index').Sonos; var device = new Sonos(process.env.SONOS_HOST || '192.168.2.11'); -device.startListeners(function() { +device.initialize(function() { device.on('volumeChange', function(volume) { console.log(volume); }); From 84e91c98aa0f9373529f4335fc5a0abd585a644c Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Sat, 6 Dec 2014 01:55:11 -0600 Subject: [PATCH 10/15] Basic logical search (single callback) --- examples/logicalDeviceVolume.js | 4 +++ lib/logicalDevice.js | 63 +++++++++++++++++++++++++++++++-- lib/sonos.js | 2 +- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/examples/logicalDeviceVolume.js b/examples/logicalDeviceVolume.js index 6189925..a33e91f 100644 --- a/examples/logicalDeviceVolume.js +++ b/examples/logicalDeviceVolume.js @@ -1,6 +1,10 @@ var Sonos = require('../'); var keypress = require('keypress'); +Sonos.LogicalDevice.search(function(err, groups) { + console.log(err, groups); +}); + var dev = new Sonos.LogicalDevice([ { host: '172.17.106.196', port: 1400 }, { host: '172.17.107.66', port: 1400 } diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index a0b7d54..0252d36 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -1,13 +1,17 @@ var async = require('async'), Sonos = require('./sonos').Sonos, - util = require('util'); + sonosSearch = require('./sonos').search, + util = require('util'), + url = require('url'); -function LogicalDevice(devices) { +function LogicalDevice(devices, coordinator) { if (devices.length === 0) { throw new Error('Logical device must be initialized with at least one device (' + devices.length + ' given)'); } - Sonos.call(this, devices[0].host, devices[0].port); + var coordinatorDevice = coordinator || devices[0]; + + Sonos.call(this, coordinatorDevice.host, coordinatorDevice.port); var sonosDevices = devices.map(function(device) { return new Sonos(device.host, device.post); }); @@ -59,4 +63,57 @@ LogicalDevice.prototype.getVolume = function(cb) { cb(sum / this.devices.length); }; +/** + * Create a Search Instance (emits 'DeviceAvailable' with a found Logical Sonos Component) + * @param {Function} Optional 'DeviceAvailable' listener (sonos) + * @return {Search/EventEmitter Instance} + */ +var search = function(callback) { + var search = sonosSearch(); + + search.once('DeviceAvailable', function(device) { + device.getTopology(function(err, topology) { + if (err) callback(err); + else { + + var devices = topology.zones; + var groups = {}; + + // bucket devices by groupid + devices.forEach(function(device) { + + if (device.name === 'BRIDGE') return; // bridges aren't nameable..! + if (!groups[device.group]) groups[device.group] = { members: [] }; + + var parsedLocation = url.parse(device.location); + var sonosDevice = new Sonos(parsedLocation.hostname, parsedLocation.port); + + if (device.coordinator === 'true') groups[device.group].coordinator = sonosDevice; + groups[device.group].members.push(sonosDevice); + + }); + + // initialze all of the logical devices brefore callback + var logicalDevices = Object.keys(groups).map(function(groupId) { + var group = groups[groupId]; + return new LogicalDevice(group.members, group.coordinator); + }); + + async.forEach(logicalDevices, function(device) { + device.initialize(function(err) { + if (err) callback(err); + else { + callback(null, logicalDevices); + } + }); + }); + } + + }); + }); + + return search; +}; + module.exports = LogicalDevice; +module.exports.search = search; diff --git a/lib/sonos.js b/lib/sonos.js index 6ae6c0e..d07f6ec 100644 --- a/lib/sonos.js +++ b/lib/sonos.js @@ -814,6 +814,6 @@ var search = function(listener) { */ module.exports.Sonos = Sonos; -module.exports.LogicalDevice = require('./logicalDevice'); module.exports.search = search; +module.exports.LogicalDevice = require('./logicalDevice'); module.exports.Services = Services; From bdd51cc009a29b11d7ab51a0fb74388fc5eb8d83 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Sun, 15 Feb 2015 01:10:02 -0800 Subject: [PATCH 11/15] Ignore BOOST device --- lib/logicalDevice.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index 0252d36..42d2342 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -82,7 +82,8 @@ var search = function(callback) { // bucket devices by groupid devices.forEach(function(device) { - if (device.name === 'BRIDGE') return; // bridges aren't nameable..! + if (device.name === 'BRIDGE' || device.name === 'BOOST') return; // devices to ignore in search + if (!groups[device.group]) groups[device.group] = { members: [] }; var parsedLocation = url.parse(device.location); From aca92a81d502bf3f8f65d82c61757962571c497b Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Sun, 15 Feb 2015 01:15:20 -0800 Subject: [PATCH 12/15] Fix #59 - bug where EQ changes would break the volumeListener --- lib/events/volumeListener.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/events/volumeListener.js b/lib/events/volumeListener.js index a54c018..14b5b03 100644 --- a/lib/events/volumeListener.js +++ b/lib/events/volumeListener.js @@ -13,6 +13,9 @@ var initVolumeListener = function(baseListener, callback) { } baseListener.parser.parseString(data.LastChange, function(err, result) { + + if (!result.Event.InstanceID[0].Volume) return; // non-volume related change to rendering + baseListener.device.state.volume = parseInt(result.Event.InstanceID[0].Volume[0].$.val); baseListener.device.emit('volumeChange', baseListener.device.state.volume); }); From da7dac0ee9a46bc86838bea85b583399a8c87597 Mon Sep 17 00:00:00 2001 From: LiRen Zhu Date: Thu, 25 Jun 2015 01:30:43 -0400 Subject: [PATCH 13/15] Fixed async usage to only invoke LogicalDevice.search callback when all device initializations have completed. --- lib/logicalDevice.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index 42d2342..6768267 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -100,13 +100,18 @@ var search = function(callback) { return new LogicalDevice(group.members, group.coordinator); }); - async.forEach(logicalDevices, function(device) { + async.forEach(logicalDevices, function(device, done) { device.initialize(function(err) { - if (err) callback(err); + if (err) done(err); else { - callback(null, logicalDevices); + done(null); } }); + }, function(err) { + if (err) callback(err); + else { + callback(null, logicalDevices); + } }); } From c2fb438f9b282d8d7a5eacc81c98c5c688e7a226 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Mon, 10 Aug 2015 00:39:27 -0700 Subject: [PATCH 14/15] Save groupId --- lib/logicalDevice.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index 42d2342..d863e0d 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -4,7 +4,7 @@ var async = require('async'), util = require('util'), url = require('url'); -function LogicalDevice(devices, coordinator) { +function LogicalDevice(devices, coordinator, groupId) { if (devices.length === 0) { throw new Error('Logical device must be initialized with at least one device (' + devices.length + ' given)'); } @@ -17,6 +17,7 @@ function LogicalDevice(devices, coordinator) { }); this.devices = sonosDevices; + this.groupId = groupId; } util.inherits(LogicalDevice, Sonos); @@ -97,7 +98,7 @@ var search = function(callback) { // initialze all of the logical devices brefore callback var logicalDevices = Object.keys(groups).map(function(groupId) { var group = groups[groupId]; - return new LogicalDevice(group.members, group.coordinator); + return new LogicalDevice(group.members, group.coordinator, groupId); }); async.forEach(logicalDevices, function(device) { From 6b9aad9904fc96bd93a995ce892e10cd0dcfc8d9 Mon Sep 17 00:00:00 2001 From: Pedro Paixao Date: Tue, 14 Jun 2016 00:50:57 -0400 Subject: [PATCH 15/15] ignore coordinator devices --- lib/logicalDevice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logicalDevice.js b/lib/logicalDevice.js index 2f343de..88f3d2c 100644 --- a/lib/logicalDevice.js +++ b/lib/logicalDevice.js @@ -83,7 +83,7 @@ var search = function(callback) { // bucket devices by groupid devices.forEach(function(device) { - if (device.name === 'BRIDGE' || device.name === 'BOOST') return; // devices to ignore in search + if (device.coordinator === 'false' || device.name === 'BRIDGE' || device.name === 'BOOST') return; // devices to ignore in search if (!groups[device.group]) groups[device.group] = { members: [] };