Skip to content
This repository has been archived by the owner on Jun 4, 2023. It is now read-only.

adding support for manual setup of Sonos devices #88

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Changelog
=========

##### 0.1.1-iona5
- Added support for selecting devices without discovery through `--devices <device_list>' to 0.1.1 tree

##### 0.1.1
- Added support for grouped and paired devices

Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ Searching for Sonos devices on network...
Setting up AirSonos for Playroom {172.17.105.103:1400}
```

Overriding autodiscovery (iona5)
-------------
Sometimes autodiscovery fails. So i added a `--devices <device_list>` option to the command line.
When this option is set, airsonos skips the autodiscovery part and tries to connect to the devices
specified in `<device_list>`. A Sonos device is specified by its IP.
Optionally you can set also its TCP port if the device is set to not use the default port (1400).

As long as airsonos is able to connect to at least one device, it will not end itself.

This is the syntax:
```
airsonos --devices SONOS1_IP[:SONOS1_PORT][,SONOS2_IP[:SONOS2_PORT][,...]]
```

For example if you have one Sonos device with the IP 192.168.0.100 you would specify
```
airsonos --devices 192.168.0.100
```

If you have another Sonos at 192.168.0.101 configured with the non-default port 1500, you
can create an airsonos server for both devices with
```
airsonos --devices 192.168.0.100,192.168.0.101:1500
```


Development
-----------
```
Expand Down
189 changes: 112 additions & 77 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,107 +11,142 @@ flags.defineBoolean('diagnostics', false, 'run diagnostics utility');
flags.defineBoolean('version', false, 'return version number');
flags.defineInteger('timeout', 5, 'disconnect timeout (in seconds)');
flags.defineBoolean('verbose', false, 'show verbose output');
flags.defineStringList('devices', [], 'a comma separated list of IP[:port] values where Sonos devices can be found, disables discovery');
flags.parse();

if (flags.get('version')) {

var pjson = require('../package.json');
console.log(pjson.version);

} else if (flags.get('diagnostics')) {

var diag = require('./diagnostics');
diag();

} else {

console.log('Searching for Sonos devices on network...');
sonos.LogicalDevice.search(function(err, devices) {
devices.forEach(function(device) {

device.getZoneAttrs(function(err, zoneAttrs) {
if (err) throw err;
var setupSonos = function(device) {
device.getZoneAttrs(function(err, zoneAttrs) {
if (err) throw err;

var deviceName = zoneAttrs.CurrentZoneName;
var deviceName = zoneAttrs.CurrentZoneName;

console.log('Setting up AirSonos for', deviceName, '{' + device.host + ':' + device.port + '}');
console.log('Setting up AirSonos for', deviceName, '{' + device.host + ':' + device.port + '}');

var airplayServer = new NodeTunes({
serverName: deviceName + ' (AirSonos)',
verbose: flags.get('verbose'),
controlTimeout: flags.get('timeout')
});
var airplayServer = new NodeTunes({
serverName: deviceName + ' (AirSonos)',
verbose: flags.get('verbose'),
controlTimeout: flags.get('timeout')
});

var clientName = 'AirSonos';
airplayServer.on('clientNameChange', function(name) {
clientName = 'AirSonos @ ' + name;
});
var clientName = 'AirSonos';
airplayServer.on('clientNameChange', function(name) {
clientName = 'AirSonos @ ' + name;
});

airplayServer.on('error', function(err) {
if (err.code === 415) {
console.error('Warning!', err.message);
console.error('AirSonos currently does not support codecs used by applications such as iTunes or AirFoil.');
console.error('Progress on this issue: https://github.com/stephen/nodetunes/issues/1');
} else {
console.error('Unknown error:');
console.error(err);
}
})
airplayServer.on('error', function(err) {
if (err.code === 415) {
console.error('Warning!', err.message);
console.error('AirSonos currently does not support codecs used by applications such as iTunes or AirFoil.');
console.error('Progress on this issue: https://github.com/stephen/nodetunes/issues/1');
} else {
console.error('Unknown error:');
console.error(err);
}
})

airplayServer.on('clientConnected', function(audioStream) {
airplayServer.on('clientConnected', function(audioStream) {

portastic.find({
portastic.find({
min : 8000,
max : 8050,
retrieve: 1
}, function(err, port) {
retrieve: 1
}, function(err, port) {
if (err) throw err;

var icecastServer = new Nicercast(audioStream, {
name: 'AirSonos @ ' + deviceName
});
var icecastServer = new Nicercast(audioStream, {
name: 'AirSonos @ ' + deviceName
});

airplayServer.on('metadataChange', function(metadata) {
if (metadata.minm)
airplayServer.on('metadataChange', function(metadata) {
if (metadata.minm)
icecastServer.setMetadata(metadata.minm + (metadata.asar ? ' - ' + metadata.asar : '') + (metadata.asal ? ' (' + metadata.asal + ')' : ''));
});

airplayServer.on('clientDisconnected', function() {
icecastServer.stop();
});

icecastServer.start(port);

device.play({
uri: 'x-rincon-mp3radio://' + ip.address() + ':' + port + '/listen.m3u',
metadata: '<?xml version="1.0"?>' +
'<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/">' +
'<item id="R:0/0/49" parentID="R:0/0" restricted="true">' +
'<dc:title>' + clientName + '</dc:title>' +
'<upnp:class>object.item.audioItem.audioBroadcast</upnp:class>' +
'<desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc>' +
'</item>' +
'</DIDL-Lite>'
});

});
});

airplayServer.on('clientDisconnected', function() {
device.stop(function() {});
icecastServer.stop();
});

airplayServer.on('volumeChange', function(vol) {
vol = 100 - Math.floor(-1 * (Math.max(vol, -30) / 30) * 100);
device.setVolume(vol, function() {
// ?
});
icecastServer.start(port);

device.play({
uri: 'x-rincon-mp3radio://' + ip.address() + ':' + port + '/listen.m3u',
metadata: '<?xml version="1.0"?>' +
'<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/">' +
'<item id="R:0/0/49" parentID="R:0/0" restricted="true">' +
'<dc:title>' + clientName + '</dc:title>' +
'<upnp:class>object.item.audioItem.audioBroadcast</upnp:class>' +
'<desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc>' +
'</item>' +
'</DIDL-Lite>'
});

airplayServer.start();
});
});

airplayServer.on('clientDisconnected', function() {
device.stop(function() {});
});

airplayServer.on('volumeChange', function(vol) {
vol = 100 - Math.floor(-1 * (Math.max(vol, -30) / 30) * 100);
device.setVolume(vol, function() {
// ?
});
});

airplayServer.start();

});
};

if (flags.get('version')) {

var pjson = require('../package.json');
console.log(pjson.version);

} else if (flags.get('diagnostics')) {

var diag = require('./diagnostics');
diag();

} else if (flags.get('devices').length > 0) {

var deviceArguments = flags.get('devices');
for(var i = 0; i < deviceArguments.length; i++) {

var deviceArg = deviceArguments[i];

// set up a domain to catch errors from setting up individual
// devices. For example it catches the timeout when non-existing IPs
// are entered.
var setupDomain = require('domain').create();
setupDomain.on('error', function(err) {
console.error('Error on setting up device "' + deviceArg + '": ' + err);
});

var sonosDevice;

// Check if the argument contains a port specifier
var ipPortSplit = deviceArg.split(':');
if (ipPortSplit.length > 1) {
sonosDevice = new sonos.Sonos(ipPortSplit[0], ipPortSplit[1]);
} else {
sonosDevice = new sonos.Sonos(deviceArg);
}

// run the setup in a domain so we can catch errors for each device
// individually:
setupDomain.run(function() {
setupSonos(sonosDevice);
});
}

} else {

console.log('Searching for Sonos devices on network...');
sonos.LogicalDevice.search(function(err, devices) {
devices.forEach(function(device) {
setupSonos(device);
});
});
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"name": "airsonos",
"version": "0.1.1",
"version": "0.1.1-iona5",
"author": "Stephen Wan <[email protected]>",
"description": "AirTunes to Sonos devices",
"contributors": [
{
"name": "Stephen Wan",
"email": "[email protected]"
},
{
"name": "@iona5 (GitHub)"
}
],
"scripts": {
Expand Down