sonos 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'rake'
7
+ gem 'yard'
@@ -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). Control would not be possible without his work.
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
- gem 'sonos'
11
+ ``` ruby
12
+ gem 'sonos'
13
+ ```
12
14
 
13
15
  And then execute:
14
16
 
15
- $ bundle
17
+ ``` shell
18
+ $ bundle
19
+ ```
16
20
 
17
21
  Or install it yourself as:
18
22
 
19
- $ gem install sonos
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
- > require 'rubygems'
32
- > require 'sonos'
33
- > speaker = Sonos::Speaker('10.0.1.10') # or whatever the IP is
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
- > speaker.pause # Pause whatever is playing
40
- > speaker.play # Resumes the playlist
41
- > speaker.play 'http://assets.samsoff.es/music/Airports.mp3' # Stream!
42
- > speaker.now_playing
43
- > speaker.volume
44
- > speaker.volume = 70
45
- > speaker.volume -= 10
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
- ## Todo
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
@@ -2,8 +2,8 @@ require 'sonos/version'
2
2
  require 'sonos/speaker'
3
3
 
4
4
  module Sonos
5
- PORT = 1400.freeze
6
- NAMESPACE = 'http://www.sonos.com/Services/1.1'.freeze
5
+ PORT = 1400
6
+ NAMESPACE = 'http://www.sonos.com/Services/1.1'
7
7
 
8
8
  def self.Speaker(ip)
9
9
  Speaker.new(ip)
@@ -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
@@ -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
- TRANSPORT_ENDPOINT = '/MediaRenderer/AVTransport/Control'.freeze
6
- RENDERING_ENDPOINT = '/MediaRenderer/RenderingControl/Control'.freeze
7
- DEVICE_ENDPOINT = '/DeviceProperties/Control'.freeze
8
+ include Transport
9
+ include Rendering
10
+ include Device
8
11
 
9
- attr_accessor :zone_name, :zone_icon, :uid, :serial_number, :software_version, :hardware_version, :mac_address
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 meta data
15
- self.get_speaker_info
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
- # Get information about the Sonos speaker.
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
- self.zone_name = doc.xpath('.//ZoneName').inner_text
84
- self.zone_icon = doc.xpath('.//ZoneIcon').inner_text
85
- self.uid = doc.xpath('.//LocalUID').inner_text
86
- self.serial_number = doc.xpath('.//SerialNumber').inner_text
87
- self.software_version = doc.xpath('.//SoftwareVersion').inner_text
88
- self.hardware_version = doc.xpath('.//HardwareVersion').inner_text
89
- self.mac_address = doc.xpath('.//MACAddress').inner_text
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
@@ -1,3 +1,3 @@
1
1
  module Sonos
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
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.1
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-23 00:00:00.000000000 Z
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: -970140811595000206
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: -970140811595000206
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: