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.
@@ -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: