sonos 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog.markdown +21 -0
- data/Gemfile +1 -0
- data/Readme.markdown +26 -15
- data/lib/sonos.rb +2 -2
- data/lib/sonos/device.rb +25 -0
- data/lib/sonos/rendering.rb +88 -0
- data/lib/sonos/speaker.rb +19 -106
- data/lib/sonos/transport.rb +76 -0
- data/lib/sonos/version.rb +1 -1
- metadata +9 -4
data/Changelog.markdown
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
### Version 0.1.0 — December 24, 2012
|
2
|
+
|
3
|
+
* **Feature:** Stop
|
4
|
+
* **Feature:** Next
|
5
|
+
* **Feature:** Previous
|
6
|
+
* **Feature:** Mute
|
7
|
+
* **Feature:** Status Light
|
8
|
+
* **Feature:** Bass
|
9
|
+
* **Feature:** Treble
|
10
|
+
* Remove some method aliases
|
11
|
+
|
12
|
+
### Version 0.0.1 — December 23, 2012
|
13
|
+
|
14
|
+
Initial release.
|
15
|
+
|
16
|
+
* **Feature:** Volume
|
17
|
+
* **Feature:** Play
|
18
|
+
* **Feature:** Play Stream
|
19
|
+
* **Feature:** Pause
|
20
|
+
* **Feature:** Now Playing
|
21
|
+
|
data/Gemfile
CHANGED
data/Readme.markdown
CHANGED
@@ -2,21 +2,27 @@
|
|
2
2
|
|
3
3
|
Control Sonos speakers with Ruby.
|
4
4
|
|
5
|
-
Huge thanks to [Rahim Sonawalla](https://github.com/rahims) for making [SoCo](https://github.com/rahims/SoCo).
|
5
|
+
Huge thanks to [Rahim Sonawalla](https://github.com/rahims) for making [SoCo](https://github.com/rahims/SoCo). This gem would not be possible without his work.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
|
-
|
11
|
+
``` ruby
|
12
|
+
gem 'sonos'
|
13
|
+
```
|
12
14
|
|
13
15
|
And then execute:
|
14
16
|
|
15
|
-
|
17
|
+
``` shell
|
18
|
+
$ bundle
|
19
|
+
```
|
16
20
|
|
17
21
|
Or install it yourself as:
|
18
22
|
|
19
|
-
|
23
|
+
``` shell
|
24
|
+
$ gem install sonos
|
25
|
+
```
|
20
26
|
|
21
27
|
## Usage
|
22
28
|
|
@@ -28,25 +34,30 @@ $ irb
|
|
28
34
|
```
|
29
35
|
|
30
36
|
``` ruby
|
31
|
-
|
32
|
-
|
33
|
-
|
37
|
+
require 'rubygems'
|
38
|
+
require 'sonos'
|
39
|
+
speaker = Sonos::Speaker('10.0.1.10') # or whatever the IP is
|
34
40
|
```
|
35
41
|
|
36
42
|
Now that we have a reference to the speaker, we can do all kinds of stuff.
|
37
43
|
|
38
44
|
``` ruby
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
speaker.pause # Pause whatever is playing
|
46
|
+
speaker.play # Resumes the playlist
|
47
|
+
speaker.play 'http://assets.samsoff.es/music/Airports.mp3' # Stream!
|
48
|
+
speaker.now_playing
|
49
|
+
speaker.volume
|
50
|
+
speaker.volume = 70
|
51
|
+
speaker.volume -= 10
|
46
52
|
```
|
47
53
|
|
48
|
-
##
|
54
|
+
## To Do
|
49
55
|
|
56
|
+
* Loudness
|
57
|
+
* Party Mode
|
58
|
+
* Join
|
59
|
+
* Line-in
|
60
|
+
* Handle errors better
|
50
61
|
* Fix album art in `now_playing`
|
51
62
|
* Handle line-in in `now_playing`
|
52
63
|
* Auto-discovery
|
data/lib/sonos.rb
CHANGED
data/lib/sonos/device.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sonos
|
2
|
+
module Device
|
3
|
+
DEVICE_ENDPOINT = '/DeviceProperties/Control'
|
4
|
+
DEVICE_XMLNS = 'urn:schemas-upnp-org:service:DeviceProperties:1'
|
5
|
+
|
6
|
+
# Turn the white status light on or off
|
7
|
+
# @param [Boolean] True to turn on the light. False to turn off the light.
|
8
|
+
def status_light_enabled=(enabled)
|
9
|
+
send_device_message('SetLEDState', enabled ? 'On' : 'Off')
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def device_client
|
15
|
+
@device_client ||= Savon.client endpoint: "http://#{self.ip}:#{PORT}#{DEVICE_ENDPOINT}", namespace: NAMESPACE
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_device_message(name, value)
|
19
|
+
action = "#{DEVICE_XMLNS}##{name}"
|
20
|
+
attribute = name.sub('Set', '')
|
21
|
+
message = %Q{<u:#{name} xmlns:u="#{DEVICE_XMLNS}"><Desired#{attribute}>#{value}</Desired#{attribute}>}
|
22
|
+
device_client.call(name, soap_action: action, message: message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Sonos
|
2
|
+
module Rendering
|
3
|
+
RENDERING_ENDPOINT = '/MediaRenderer/RenderingControl/Control'
|
4
|
+
RENDERING_XMLNS = 'urn:schemas-upnp-org:service:RenderingControl:1'
|
5
|
+
|
6
|
+
# Get the current volume.
|
7
|
+
# @return [Fixnum] the volume from 0 to 100
|
8
|
+
def volume
|
9
|
+
response = send_rendering_message('GetVolume')
|
10
|
+
response.body[:get_volume_response][:current_volume].to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the volume from 0 to 100.
|
14
|
+
# @param [Fixnum] the desired volume from 0 to 100
|
15
|
+
def volume=(value)
|
16
|
+
send_rendering_message('SetVolume', value)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the current bass EQ.
|
20
|
+
# @return [Fixnum] the base EQ from -10 to 10
|
21
|
+
def bass
|
22
|
+
response = send_rendering_message('GetBass')
|
23
|
+
response.body[:get_bass_response][:current_bass].to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the bass EQ from -10 to 10.
|
27
|
+
# @param [Fixnum] the desired bass EQ from -10 to 10
|
28
|
+
def bass=(value)
|
29
|
+
send_rendering_message('SetBass', value)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get the current treble EQ.
|
33
|
+
# @return [Fixnum] the treble EQ from -10 to 10
|
34
|
+
def treble
|
35
|
+
response = send_rendering_message('GetTreble')
|
36
|
+
response.body[:get_treble_response][:current_treble].to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the treble EQ from -10 to 10.
|
40
|
+
# @param [Fixnum] the desired treble EQ from -10 to 10
|
41
|
+
def treble=(value)
|
42
|
+
send_rendering_message('SetTreble', value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Mute the speaker
|
46
|
+
def mute
|
47
|
+
set_mute(true)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Unmute the speaker
|
51
|
+
def unmute
|
52
|
+
set_mute(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Is the speaker muted?
|
56
|
+
# @return [Boolean] true if the speaker is muted and false if it is not
|
57
|
+
def muted?
|
58
|
+
response = send_rendering_message('GetMute')
|
59
|
+
response.body[:get_mute_response][:current_mute] == '1'
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Sets the speaker's mute
|
65
|
+
# @param [Boolean] if the speaker is muted or not
|
66
|
+
def set_mute(value)
|
67
|
+
send_rendering_message('SetMute', value ? 1 : 0)
|
68
|
+
end
|
69
|
+
|
70
|
+
def rendering_client
|
71
|
+
@rendering_client ||= Savon.client endpoint: "http://#{self.ip}:#{PORT}#{RENDERING_ENDPOINT}", namespace: NAMESPACE
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_rendering_message(name, value = nil)
|
75
|
+
action = "#{RENDERING_XMLNS}##{name}"
|
76
|
+
message = %Q{<u:#{name} xmlns:u="#{RENDERING_XMLNS}"><InstanceID>0</InstanceID><Channel>Master</Channel>}
|
77
|
+
|
78
|
+
if value
|
79
|
+
attribute = name.sub('Set', '')
|
80
|
+
message += %Q{<Desired#{attribute}>#{value}</Desired#{attribute}></u:#{name}>}
|
81
|
+
else
|
82
|
+
message += %Q{</u:#{name}>}
|
83
|
+
end
|
84
|
+
|
85
|
+
rendering_client.call(name, soap_action: action, message: message)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/sonos/speaker.rb
CHANGED
@@ -1,124 +1,37 @@
|
|
1
1
|
require 'savon'
|
2
|
+
require 'sonos/transport'
|
3
|
+
require 'sonos/rendering'
|
4
|
+
require 'sonos/device'
|
2
5
|
|
3
6
|
module Sonos
|
4
7
|
class Speaker
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
+
include Transport
|
9
|
+
include Rendering
|
10
|
+
include Device
|
8
11
|
|
9
|
-
|
12
|
+
attr_reader :ip, :zone_name, :zone_icon, :uid, :serial_number, :software_version, :hardware_version, :mac_address
|
10
13
|
|
11
14
|
def initialize(ip)
|
12
15
|
@ip = ip
|
13
16
|
|
14
|
-
# Get
|
15
|
-
|
17
|
+
# Get the speaker's status
|
18
|
+
get_status
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
# Get information about the currently playing track.
|
20
|
-
#
|
21
|
-
def get_position_info
|
22
|
-
action = 'urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo'
|
23
|
-
message = '<u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Channel>Master</Channel></u:GetPositionInfo>'
|
24
|
-
response = transport_client.call(:get_position_info, soap_action: action, message: message)
|
25
|
-
body = response.body[:get_position_info_response]
|
26
|
-
doc = Nokogiri::XML(body[:track_meta_data])
|
27
|
-
|
28
|
-
{
|
29
|
-
title: doc.xpath('//dc:title').inner_text,
|
30
|
-
artist: doc.xpath('//dc:creator').inner_text,
|
31
|
-
album: doc.xpath('//upnp:album').inner_text,
|
32
|
-
playlist_position: body[:track],
|
33
|
-
track_duration: body[:track_duration],
|
34
|
-
current_position: body[:rel_time],
|
35
|
-
uri: body[:track_uri],
|
36
|
-
album_art: "http://#{@ip}:#{PORT}#{doc.xpath('//upnp:albumArtURI').inner_text}"
|
37
|
-
}
|
38
|
-
end
|
39
|
-
alias_method :now_playing, :get_position_info
|
40
|
-
|
41
|
-
#
|
42
|
-
# Pause the currently playing track.
|
43
|
-
#
|
44
|
-
def pause
|
45
|
-
action = 'urn:schemas-upnp-org:service:AVTransport:1#Pause'
|
46
|
-
message = '<u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Speed>1</Speed></u:Pause>'
|
47
|
-
transport_client.call(:play, soap_action: action, message: message)
|
48
|
-
end
|
49
|
-
|
50
|
-
#
|
51
|
-
# Play the currently selected track or play a stream.
|
52
|
-
#
|
53
|
-
def play(uri = nil)
|
54
|
-
# Play a song from the uri
|
55
|
-
if uri
|
56
|
-
self.set_av_transport_uri(uri)
|
57
|
-
return
|
58
|
-
end
|
59
|
-
|
60
|
-
# Play the currently selected track
|
61
|
-
action = 'urn:schemas-upnp-org:service:AVTransport:1#Play'
|
62
|
-
message = '<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Speed>1</Speed></u:Play>'
|
63
|
-
transport_client.call(:play, soap_action: action, message: message)
|
64
|
-
end
|
65
|
-
|
66
|
-
#
|
67
|
-
# Play a stream.
|
68
|
-
#
|
69
|
-
def set_av_transport_uri(uri)
|
70
|
-
action = 'urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI'
|
71
|
-
message = %Q{<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><CurrentURI>#{uri}</CurrentURI><CurrentURIMetaData></CurrentURIMetaData></u:SetAVTransportURI>}
|
72
|
-
transport_client.call(:set_av_transport_uri, soap_action: action, message: message)
|
73
|
-
self.play
|
74
|
-
end
|
75
|
-
alias_method :play_stream, :set_av_transport_uri
|
21
|
+
private
|
76
22
|
|
77
|
-
#
|
78
|
-
|
79
|
-
#
|
80
|
-
def get_speaker_info
|
23
|
+
# Get information about the speaker.
|
24
|
+
def get_status
|
81
25
|
doc = Nokogiri::XML(open("http://#{@ip}:1400/status/zp"))
|
82
26
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
27
|
+
@zone_name = doc.xpath('.//ZoneName').inner_text
|
28
|
+
@zone_icon = doc.xpath('.//ZoneIcon').inner_text
|
29
|
+
@uid = doc.xpath('.//LocalUID').inner_text
|
30
|
+
@serial_number = doc.xpath('.//SerialNumber').inner_text
|
31
|
+
@software_version = doc.xpath('.//SoftwareVersion').inner_text
|
32
|
+
@hardware_version = doc.xpath('.//HardwareVersion').inner_text
|
33
|
+
@mac_address = doc.xpath('.//MACAddress').inner_text
|
90
34
|
self
|
91
35
|
end
|
92
|
-
|
93
|
-
#
|
94
|
-
# Get the current volume.
|
95
|
-
#
|
96
|
-
def get_volume
|
97
|
-
action = 'urn:schemas-upnp-org:service:RenderingControl:1#GetVolume'
|
98
|
-
message = '<u:GetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1"><InstanceID>0</InstanceID><Channel>Master</Channel></u:GetVolume>'
|
99
|
-
response = rendering_client.call(:get_volume, soap_action: action, message: message)
|
100
|
-
response.body[:get_volume_response][:current_volume].to_i
|
101
|
-
end
|
102
|
-
alias_method :volume, :get_volume
|
103
|
-
|
104
|
-
#
|
105
|
-
# Set the volume from 0 to 100.
|
106
|
-
#
|
107
|
-
def set_volume(level)
|
108
|
-
action = 'urn:schemas-upnp-org:service:RenderingControl:1#SetVolume'
|
109
|
-
message = %Q{<u:SetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>#{level}</DesiredVolume></u:SetVolume>}
|
110
|
-
rendering_client.call(:set_volume, soap_action: action, message: message)
|
111
|
-
end
|
112
|
-
alias_method :volume=, :set_volume
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def transport_client
|
117
|
-
@transport_client ||= Savon.client endpoint: "http://#{@ip}:#{PORT}#{TRANSPORT_ENDPOINT}", namespace: NAMESPACE
|
118
|
-
end
|
119
|
-
|
120
|
-
def rendering_client
|
121
|
-
@rendering_client ||= Savon.client endpoint: "http://#{@ip}:#{PORT}#{RENDERING_ENDPOINT}", namespace: NAMESPACE
|
122
|
-
end
|
123
36
|
end
|
124
37
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Sonos
|
2
|
+
module Transport
|
3
|
+
TRANSPORT_ENDPOINT = '/MediaRenderer/AVTransport/Control'
|
4
|
+
TRANSPORT_XMLNS = 'urn:schemas-upnp-org:service:AVTransport:1'
|
5
|
+
|
6
|
+
# Get information about the currently playing track.
|
7
|
+
# @return [Hash] information about the current track.
|
8
|
+
def now_playing
|
9
|
+
response = send_transport_message('GetPositionInfo')
|
10
|
+
body = response.body[:get_position_info_response]
|
11
|
+
doc = Nokogiri::XML(body[:track_meta_data])
|
12
|
+
|
13
|
+
{
|
14
|
+
title: doc.xpath('//dc:title').inner_text,
|
15
|
+
artist: doc.xpath('//dc:creator').inner_text,
|
16
|
+
album: doc.xpath('//upnp:album').inner_text,
|
17
|
+
playlist_position: body[:track],
|
18
|
+
track_duration: body[:track_duration],
|
19
|
+
current_position: body[:rel_time],
|
20
|
+
uri: body[:track_uri],
|
21
|
+
album_art: "http://#{self.ip}:#{PORT}#{doc.xpath('//upnp:albumArtURI').inner_text}"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pause the currently playing track.
|
26
|
+
def pause
|
27
|
+
send_transport_message('Pause')
|
28
|
+
end
|
29
|
+
|
30
|
+
# Play the currently selected track or play a stream.
|
31
|
+
# @param [String] optional uri of the track to play. Leaving this blank, plays the current track.
|
32
|
+
def play(uri = nil)
|
33
|
+
# Play a song from the uri
|
34
|
+
set_av_transport_uri(uri) and return if uri
|
35
|
+
|
36
|
+
# Play the currently selected track
|
37
|
+
send_transport_message('Play')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Stop playing.
|
41
|
+
def stop
|
42
|
+
send_transport_message('Stop')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Play the next track.
|
46
|
+
def next
|
47
|
+
send_transport_message('Next')
|
48
|
+
end
|
49
|
+
|
50
|
+
# Play the previous track.
|
51
|
+
def previous
|
52
|
+
send_transport_message('Previous')
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Play a stream.
|
58
|
+
def set_av_transport_uri(uri)
|
59
|
+
name = 'SetAVTransportURI'
|
60
|
+
action = "#{TRANSPORT_XMLNS}##{name}"
|
61
|
+
message = %Q{<u:#{name} xmlns:u="#{TRANSPORT_XMLNS}"><InstanceID>0</InstanceID><CurrentURI>#{uri}</CurrentURI><CurrentURIMetaData></CurrentURIMetaData></u:#{name}>}
|
62
|
+
transport_client.call(name, soap_action: action, message: message)
|
63
|
+
self.play
|
64
|
+
end
|
65
|
+
|
66
|
+
def transport_client
|
67
|
+
@transport_client ||= Savon.client endpoint: "http://#{self.ip}:#{PORT}#{TRANSPORT_ENDPOINT}", namespace: NAMESPACE
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_transport_message(name)
|
71
|
+
action = "#{TRANSPORT_XMLNS}##{name}"
|
72
|
+
message = %Q{<u:#{name} xmlns:u="#{TRANSPORT_XMLNS}"><InstanceID>0</InstanceID><Speed>1</Speed></u:#{name}>}
|
73
|
+
transport_client.call(name, soap_action: action, message: message)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/sonos/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sonos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: savon
|
@@ -35,12 +35,16 @@ extensions: []
|
|
35
35
|
extra_rdoc_files: []
|
36
36
|
files:
|
37
37
|
- .gitignore
|
38
|
+
- Changelog.markdown
|
38
39
|
- Gemfile
|
39
40
|
- LICENSE
|
40
41
|
- Rakefile
|
41
42
|
- Readme.markdown
|
42
43
|
- lib/sonos.rb
|
44
|
+
- lib/sonos/device.rb
|
45
|
+
- lib/sonos/rendering.rb
|
43
46
|
- lib/sonos/speaker.rb
|
47
|
+
- lib/sonos/transport.rb
|
44
48
|
- lib/sonos/version.rb
|
45
49
|
- sonos.gemspec
|
46
50
|
homepage: https://github.com/soffes/sonos
|
@@ -57,7 +61,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
61
|
version: '0'
|
58
62
|
segments:
|
59
63
|
- 0
|
60
|
-
hash: -
|
64
|
+
hash: -1995798408343589310
|
61
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
66
|
none: false
|
63
67
|
requirements:
|
@@ -66,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
70
|
version: '0'
|
67
71
|
segments:
|
68
72
|
- 0
|
69
|
-
hash: -
|
73
|
+
hash: -1995798408343589310
|
70
74
|
requirements: []
|
71
75
|
rubyforge_project:
|
72
76
|
rubygems_version: 1.8.23
|
@@ -74,3 +78,4 @@ signing_key:
|
|
74
78
|
specification_version: 3
|
75
79
|
summary: Control Sonos speakers with Ruby
|
76
80
|
test_files: []
|
81
|
+
has_rdoc:
|