sonos 0.3.4 → 0.3.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f3ea780abc6b1575275463a67745acf4283db680
4
- data.tar.gz: 7be54b8d1474a567b8e069f95b46ddd7b168b928
3
+ metadata.gz: 2500064f1c4f07a8064c691be658195d7245c15f
4
+ data.tar.gz: 30d941094b116b5c4998c8f6f2cbdf8862de11b9
5
5
  SHA512:
6
- metadata.gz: 4c31701f021123f74e93189c7348e5a79ce774384fd2c87613f919fcd8b9a3387c5fe1eaaeeeb3c0e3e3f6c33f5fdb09462fb3e9046cbb20ca3b6fe0adf2460f
7
- data.tar.gz: 92b8c154f0c759f54e4d24fb51580668bad8b727244c8b51f1594b3f733081fec13d6d3de43592bd0058f3c3ad7826c181b7783f5d95355a62ab2e4d1902aa6e
6
+ metadata.gz: ae738d359b88707fbb0913d26f8760690ba497d3c7d1d9bc8cc4675e637e4e6c9f9617e2906c1aef23b407f710c15761746372d299445c8209f34116cbcb1df7
7
+ data.tar.gz: 438b11f8ffa8033572c44e99bfe91540a3e634558030a248540ec130f1e73b606dc53025fd641371de1e4a8aad151b943b071f09cdf2a4d07c4b1e167e862693
data/Gemfile CHANGED
@@ -6,12 +6,3 @@ gemspec
6
6
  # Development dependencies
7
7
  gem 'rake'
8
8
  gem 'yard'
9
-
10
- # Testing dependencies
11
- group :test do
12
- gem 'minitest'
13
- gem 'minitest-wscolor'
14
- gem 'fakeweb'
15
- gem 'vcr'
16
- gem 'mocha', require: false
17
- end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2013 Sam Soffes, http://soff.es
1
+ Copyright (c) 2012-2014 Sam Soffes, http://soff.es
2
2
 
3
3
  MIT License
4
4
 
data/Rakefile CHANGED
@@ -1,10 +1,4 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'rake/testtask'
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << 'test'
6
- t.pattern = 'test/**/*_test.rb'
7
- end
8
2
 
9
3
  desc "Open an irb session preloaded with this API"
10
4
  task :console do
@@ -14,5 +8,3 @@ task :console do
14
8
  ARGV.clear
15
9
  IRB.start
16
10
  end
17
-
18
- task default: :test
@@ -6,6 +6,8 @@ Huge thanks to [Rahim Sonawalla](https://github.com/rahims) for making [SoCo](ht
6
6
 
7
7
  [![Code Climate](https://codeclimate.com/github/soffes/sonos.png)](https://codeclimate.com/github/soffes/sonos)
8
8
 
9
+ **Note: Currently discovery is broken in Ruby 2.1**
10
+
9
11
  ## Installation
10
12
 
11
13
  Add this line to your application's Gemfile:
@@ -18,4 +18,16 @@ module Sonos
18
18
  # def self.system
19
19
  # @system ||= Sonos::System.new
20
20
  # end
21
+
22
+ unless defined? @@logging_enabled
23
+ @@logging_enabled = false
24
+ end
25
+
26
+ def self.logging_enabled
27
+ @@logging_enabled
28
+ end
29
+
30
+ def self.logging_enabled=(logging)
31
+ @@logging_enabled = logging
32
+ end
21
33
  end
@@ -4,7 +4,7 @@ require 'nokogiri'
4
4
  module Sonos::Device
5
5
  class Base
6
6
  attr_reader :ip, :name, :uid, :serial_number, :software_version, :hardware_version,
7
- :zone_type, :model_number, :mac_address, :group, :icon
7
+ :zone_type, :model_number, :mac_address, :group, :icon, :services
8
8
 
9
9
  attr_accessor :group_master
10
10
 
@@ -40,6 +40,7 @@ module Sonos::Device
40
40
  @hardware_version = data[:hardware_version]
41
41
  @zone_type = data[:zone_type]
42
42
  @model_number = data[:model_number]
43
+ @services = data[:services]
43
44
  end
44
45
 
45
46
  def data
@@ -50,7 +51,8 @@ module Sonos::Device
50
51
  software_version: @software_version,
51
52
  hardware_version: @hardware_version,
52
53
  zone_type: @zone_type,
53
- model_number: @model_number
54
+ model_number: @model_number,
55
+ services: @services
54
56
  }
55
57
  end
56
58
 
@@ -68,6 +70,10 @@ module Sonos::Device
68
70
 
69
71
  protected
70
72
 
73
+ def parse_response(response)
74
+ response.success? ? :success : :failed
75
+ end
76
+
71
77
  def self.retrieve_information(ip)
72
78
  url = "http://#{ip}:#{Sonos::PORT}/xml/device_description.xml"
73
79
  parse_description(Nokogiri::XML(open(url)))
@@ -82,7 +88,9 @@ module Sonos::Device
82
88
  software_version: doc.xpath('/xmlns:root/xmlns:device/xmlns:hardwareVersion').inner_text,
83
89
  hardware_version: doc.xpath('/xmlns:root/xmlns:device/xmlns:softwareVersion').inner_text,
84
90
  zone_type: doc.xpath('/xmlns:root/xmlns:device/xmlns:zoneType').inner_text,
85
- model_number: doc.xpath('/xmlns:root/xmlns:device/xmlns:modelNumber').inner_text
91
+ model_number: doc.xpath('/xmlns:root/xmlns:device/xmlns:modelNumber').inner_text,
92
+ services: doc.xpath('/xmlns:root/xmlns:device/xmlns:serviceList/xmlns:service/xmlns:serviceId').
93
+ collect(&:inner_text)
86
94
  }
87
95
  end
88
96
  end
@@ -29,7 +29,7 @@ module Sonos::Device
29
29
  end
30
30
 
31
31
  def speaker?
32
- true
32
+ services.include?('urn:upnp-org:serviceId:MusicServices')
33
33
  end
34
34
 
35
35
  def shuffle_on
@@ -16,14 +16,13 @@ module Sonos
16
16
  class Discovery
17
17
  MULTICAST_ADDR = '239.255.255.250'
18
18
  MULTICAST_PORT = 1900
19
- DEFAULT_TIMEOUT = 1
20
- DEFAULT_IP = nil
19
+ DEFAULT_TIMEOUT = 2
21
20
 
22
21
  attr_reader :timeout
23
22
  attr_reader :first_device_ip
24
23
  attr_reader :default_ip
25
24
 
26
- def initialize(timeout = DEFAULT_TIMEOUT,default_ip = DEFAULT_IP)
25
+ def initialize(timeout = DEFAULT_TIMEOUT, default_ip = nil)
27
26
  @timeout = timeout
28
27
  @default_ip = default_ip
29
28
  initialize_socket
@@ -65,7 +64,8 @@ module Sonos
65
64
  end
66
65
  end
67
66
  rescue Timeout::Error => ex
68
- puts "Timeout error; switching to the default IP"
67
+ puts 'Timed out...'
68
+ puts 'Switching to the default IP' if @default_ip
69
69
  return @default_ip
70
70
  end
71
71
  end
@@ -74,8 +74,8 @@ module Sonos
74
74
  # Create a socket
75
75
  @socket = UDPSocket.open
76
76
 
77
- # We're going to use IP with the multicast TTL. Mystery third parameter is a mystery.
78
- @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 2)
77
+ # We're going to use IP with the multicast TTL
78
+ @socket.setsockopt(Socket::Option.new(:INET, :IPPROTO_IP, :IP_MULTICAST_TTL, 2.chr))
79
79
  end
80
80
 
81
81
  def search_message
@@ -36,9 +36,21 @@ module Sonos::Endpoint::AVTransport
36
36
  !now_playing.nil?
37
37
  end
38
38
 
39
+ # Get information about the state the player is in.
40
+ def get_player_state
41
+ response = send_transport_message('GetTransportInfo')
42
+ body = response.body[:get_transport_info_response]
43
+
44
+ {
45
+ status: body[:current_transport_status],
46
+ state: body[:current_transport_state],
47
+ speed: body[:current_speed],
48
+ }
49
+ end
50
+
39
51
  # Pause the currently playing track.
40
52
  def pause
41
- send_transport_message('Pause')
53
+ parse_response send_transport_message('Pause')
42
54
  end
43
55
 
44
56
  # Play the currently selected track or play a stream.
@@ -48,22 +60,26 @@ module Sonos::Endpoint::AVTransport
48
60
  set_av_transport_uri(uri) and return if uri
49
61
 
50
62
  # Play the currently selected track
51
- send_transport_message('Play')
63
+ parse_response send_transport_message('Play')
52
64
  end
53
65
 
54
66
  # Stop playing.
55
67
  def stop
56
- send_transport_message('Stop')
68
+ parse_response send_transport_message('Stop')
57
69
  end
58
70
 
59
71
  # Play the next track.
60
72
  def next
61
- send_transport_message('Next')
73
+ parse_response send_transport_message('Next')
62
74
  end
63
75
 
64
76
  # Play the previous track.
65
77
  def previous
66
- send_transport_message('Previous')
78
+ parse_response send_transport_message('Previous')
79
+ end
80
+
81
+ def line_in(speaker)
82
+ set_av_transport_uri('x-rincon-stream:' + speaker.uid.sub('uuid:', ''))
67
83
  end
68
84
 
69
85
  # Seeks to a given timestamp in the current track
@@ -71,24 +87,25 @@ module Sonos::Endpoint::AVTransport
71
87
  def seek(seconds = 0)
72
88
  # Must be sent in the format of HH:MM:SS
73
89
  timestamp = Time.at(seconds).utc.strftime('%H:%M:%S')
74
- send_transport_message('Seek', "<Unit>REL_TIME</Unit><Target>#{timestamp}</Target>")
90
+ parse_response send_transport_message('Seek', "<Unit>REL_TIME</Unit><Target>#{timestamp}</Target>")
75
91
  end
76
92
 
77
93
  # Clear the queue
78
94
  def clear_queue
79
- send_transport_message('RemoveAllTracksFromQueue')
95
+ parse_response parse_response send_transport_message('RemoveAllTracksFromQueue')
80
96
  end
81
97
 
82
98
  # Save queue
83
99
  def save_queue(title)
84
- send_transport_message('SaveQueue', "<Title>#{title}</Title><ObjectID></ObjectID>")
100
+ parse_response send_transport_message('SaveQueue', "<Title>#{title}</Title><ObjectID></ObjectID>")
85
101
  end
86
102
 
87
103
  # Adds a track to the queue
88
104
  # @param[String] uri Uri of track
105
+ # @param[String] didl Stanza of DIDL-Lite metadata (generally created by #add_spotify_to_queue)
89
106
  # @return[Integer] Queue position of the added track
90
- def add_to_queue(uri)
91
- response = send_transport_message('AddURIToQueue', "<EnqueuedURI>#{uri}</EnqueuedURI><EnqueuedURIMetaData></EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>")
107
+ def add_to_queue(uri, didl = '')
108
+ response = send_transport_message('AddURIToQueue', "<EnqueuedURI>#{uri}</EnqueuedURI><EnqueuedURIMetaData>#{didl}</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>")
92
109
  # TODO yeah, this error handling is a bit soft. For consistency's sake :)
93
110
  pos = response.xpath('.//FirstTrackNumberEnqueued').text
94
111
  if pos.length != 0
@@ -96,16 +113,59 @@ module Sonos::Endpoint::AVTransport
96
113
  end
97
114
  end
98
115
 
116
+ # Adds a Spotify track to the queue along with extra data for better metadata retrieval
117
+ # @param[Hash] opts Various options (id, user, region and type)
118
+ # @return[Integer] Queue position of the added track(s)
119
+ def add_spotify_to_queue(opts = {})
120
+ opts = {
121
+ :id => '',
122
+ :user => nil,
123
+ :region => nil,
124
+ :type => 'track'
125
+ }.merge(opts)
126
+
127
+ # Basic validation of the accepted types; playlists need an associated user
128
+ # and the toplist (for tracks and albums) need to specify a region.
129
+ return nil if opts[:type] == 'playlist' and opts[:user].nil?
130
+ return nil if opts[:type] =~ /toplist_tracks/ and opts[:region].nil?
131
+
132
+ # In order for the player to retrieve track duration, artist, album etc
133
+ # we need to pass it some metadata ourselves.
134
+ didl_metadata = "&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;#{rand(10000000..99999999)}spotify%3a#{opts[:type]}%3a#{opts[:id]}&quot; restricted=&quot;true&quot;&gt;&lt;dc:title&gt;&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;"
135
+
136
+ r_id = rand(10000000..99999999)
137
+
138
+ case opts[:type]
139
+ when /playlist/
140
+ uri = "x-rincon-cpcontainer:#{r_id}spotify%3auser%3a#{opts[:user]}%3aplaylist%3a#{opts[:id]}"
141
+ when /toplist_(tracks)/
142
+ subtype = opts[:type].sub('toplist_', '') # only 'tracks' are supported right now by Sonos.
143
+ uri = "x-rincon-cpcontainer:#{r_id}toplist%2f#{subtype}%2fregion%2f#{opts[:region]}"
144
+ when /album/
145
+ uri = "x-rincon-cpcontainer:#{r_id}spotify%3aalbum%3a#{opts[:id]}"
146
+ when /artist/
147
+ uri = "x-rincon-cpcontainer:#{r_id}tophits%3aspotify%3aartist%3a#{opts[:id]}"
148
+ when /starred/
149
+ uri = "x-rincon-cpcontainer:#{r_id}starred"
150
+ when /track/
151
+ uri = "x-sonos-spotify:spotify%3a#{opts[:type]}%3a#{opts[:id]}"
152
+ else
153
+ return nil
154
+ end
155
+
156
+ add_to_queue(uri, didl_metadata)
157
+ end
158
+
99
159
  # Removes a track from the queue
100
160
  # @param[String] object_id Track's queue ID
101
161
  def remove_from_queue(object_id)
102
- send_transport_message('RemoveTrackFromQueue', "<ObjectID>#{object_id}</ObjectID><UpdateID>0</UpdateID></u:RemoveTrackFromQueue>")
162
+ parse_response send_transport_message('RemoveTrackFromQueue', "<ObjectID>#{object_id}</ObjectID><UpdateID>0</UpdateID></u:RemoveTrackFromQueue>")
103
163
  end
104
164
 
105
165
  # Join another speaker's group.
106
166
  # Trying to call this on a stereo pair slave will fail.
107
167
  def join(master)
108
- set_av_transport_uri('x-rincon:' + master.uid.sub('uuid:', ''))
168
+ parse_response set_av_transport_uri('x-rincon:' + master.uid.sub('uuid:', ''))
109
169
  end
110
170
 
111
171
  # Add another speaker to this group.
@@ -117,7 +177,7 @@ module Sonos::Endpoint::AVTransport
117
177
  # Ungroup from its current group.
118
178
  # Trying to call this on a stereo pair slave will fail.
119
179
  def ungroup
120
- send_transport_message('BecomeCoordinatorOfStandaloneGroup')
180
+ parse_response send_transport_message('BecomeCoordinatorOfStandaloneGroup')
121
181
  end
122
182
 
123
183
  private
@@ -128,7 +188,7 @@ module Sonos::Endpoint::AVTransport
128
188
  end
129
189
 
130
190
  def transport_client
131
- @transport_client ||= Savon.client endpoint: "http://#{self.group_master.ip}:#{Sonos::PORT}#{TRANSPORT_ENDPOINT}", namespace: Sonos::NAMESPACE, log_level: :error
191
+ @transport_client ||= Savon.client endpoint: "http://#{self.group_master.ip}:#{Sonos::PORT}#{TRANSPORT_ENDPOINT}", namespace: Sonos::NAMESPACE, log: Sonos.logging_enabled
132
192
  end
133
193
 
134
194
  def send_transport_message(name, part = '<Speed>1</Speed>')
@@ -1,25 +1,3 @@
1
- # POST /AlarmClock/Control HTTP/1.1
2
- # SOAPACTION: "urn:schemas-upnp-org:service:AlarmClock:1#ListAlarms"
3
-
4
- # Request
5
- #<?xml version="1.0" encoding="UTF-8"?>
6
- #<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
7
- # <s:Body>
8
- # <u:ListAlarms xmlns:u="urn:schemas-upnp-org:service:AlarmClock:1" />
9
- # </s:Body>
10
- #</s:Envelope>
11
-
12
- # Response
13
- #<?xml version="1.0" encoding="UTF-8"?>
14
- #<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
15
- # <s:Body>
16
- # <u:ListAlarmsResponse xmlns:u="urn:schemas-upnp-org:service:AlarmClock:1">
17
- # <CurrentAlarmList>&lt;Alarms&gt;&lt;Alarm ID="8" StartTime="19:21:00" Duration="02:00:00" Recurrence="ONCE" Enabled="0" RoomUUID="RINCON_000E583564A601400" ProgramURI="x-rincon-buzzer:0" ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" Volume="25" IncludeLinkedZones="0"/&gt;&lt;/Alarms&gt;</CurrentAlarmList>
18
- # <CurrentAlarmListVersion>RINCON_000E583564A601400:26</CurrentAlarmListVersion>
19
- # </u:ListAlarmsResponse>
20
- # </s:Body>
21
- #</s:Envelope>
22
-
23
1
  module Sonos::Endpoint::Alarm
24
2
  ALARM_CLOCK_ENDPOINT = '/AlarmClock/Control'
25
3
  ALARM_CLOCK_XMLNS = 'urn:schemas-upnp-org:service:AlarmClock:1'
@@ -49,7 +27,7 @@ module Sonos::Endpoint::Alarm
49
27
  hash_of_alarm_hashes[id] = alarm_hash
50
28
  end
51
29
  end
52
- return hash_of_alarm_hashes
30
+ hash_of_alarm_hashes
53
31
  end
54
32
 
55
33
  def create_alarm(startLocalTime, duration, recurrence, enabled, roomUuid, playMode, volume, includeLinkedZones, programUri, programMetaData)
@@ -57,15 +35,11 @@ module Sonos::Endpoint::Alarm
57
35
  :Recurrence => recurrence, :Enabled => enabled, :RoomUUID => roomUuid,
58
36
  :PlayMode => playMode, :Volume => volume, :IncludeLinkedZones => includeLinkedZones,
59
37
  :ProgramURI => programUri, :ProgramMetaData => programMetaData}
60
- #options = {:StartLocalTime => '13:19:00', :Duration => '02:00:00',
61
- # :Recurrence => 'ONCE', :Enabled => 1, :RoomUUID => 'RINCON_000E583564A601400',
62
- # :PlayMode => 'SHUFFLE_NOREPEAT', :Volume => 25, :IncludeLinkedZones => 0,
63
- # :ProgramURI => 'x-rincon-buzzer:0', :ProgramMetaData => ""}
64
- send_alarm_message('CreateAlarm', convert_hash_to_xml(options))
38
+ parse_response send_alarm_message('CreateAlarm', convert_hash_to_xml(options))
65
39
  end
66
40
 
67
41
  def destroy_alarm(id)
68
- send_alarm_message('DestroyAlarm', "<ID>#{id}</ID>")
42
+ parse_response send_alarm_message('DestroyAlarm', "<ID>#{id}</ID>")
69
43
  end
70
44
 
71
45
  def update_alarm(id, startLocalTime, duration, recurrence, enabled, roomUuid, playMode, volume, includeLinkedZones, programUri, programMetaData)
@@ -73,11 +47,7 @@ module Sonos::Endpoint::Alarm
73
47
  :Recurrence => recurrence, :Enabled => enabled, :RoomUUID => roomUuid,
74
48
  :PlayMode => playMode, :Volume => volume, :IncludeLinkedZones => includeLinkedZones,
75
49
  :ProgramURI => programUri, :ProgramMetaData => programMetaData}
76
- #options = {:ID => 8, :StartLocalTime => '13:19:00', :Duration => '02:00:00',
77
- # :Recurrence => 'ONCE', :Enabled => 1, :RoomUUID => 'RINCON_000E583564A601400',
78
- # :PlayMode => 'SHUFFLE_NOREPEAT', :Volume => 25, :IncludeLinkedZones => 0,
79
- # :ProgramURI => 'x-rincon-buzzer:0', :ProgramMetaData => ""}
80
- send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
50
+ parse_response send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
81
51
  end
82
52
 
83
53
  def is_alarm_enabled?(id)
@@ -87,25 +57,25 @@ module Sonos::Endpoint::Alarm
87
57
  def enable_alarm(id)
88
58
  alarm_hash = list_alarms[id]
89
59
  alarm_hash[:Enabled] = '1'
90
- send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
60
+ parse_response send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
91
61
  end
92
62
 
93
63
  def disable_alarm(id)
94
64
  alarm_hash = list_alarms[id]
95
65
  alarm_hash[:Enabled] = '0'
96
- send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
66
+ parse_response send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
97
67
  end
98
68
 
99
69
  def set_alarm_volume(id, volume)
100
70
  alarm_hash = list_alarms[id]
101
71
  alarm_hash[:Volume] = volume
102
- send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
72
+ parse_response send_alarm_message('UpdateAlarm', convert_hash_to_xml(alarm_hash))
103
73
  end
104
74
 
105
75
  private
106
76
 
107
77
  def alarm_client
108
- @alarm_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{ALARM_CLOCK_ENDPOINT}", namespace: Sonos::NAMESPACE
78
+ @alarm_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{ALARM_CLOCK_ENDPOINT}", namespace: Sonos::NAMESPACE, log: Sonos.logging_enabled
109
79
  end
110
80
 
111
81
  def send_alarm_message(name, part = '')
@@ -121,5 +91,4 @@ module Sonos::Endpoint::Alarm
121
91
  end
122
92
  updatePart
123
93
  end
124
-
125
- end
94
+ end
@@ -31,7 +31,7 @@ module Sonos::Endpoint::ContentDirectory
31
31
  private
32
32
 
33
33
  def content_directory_client
34
- @content_directory_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{CONTENT_DIRECTORY_ENDPOINT}", namespace: Sonos::NAMESPACE, log_level: :error
34
+ @content_directory_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{CONTENT_DIRECTORY_ENDPOINT}", namespace: Sonos::NAMESPACE, log: Sonos.logging_enabled
35
35
  end
36
36
 
37
37
  def parse_items(string)
@@ -2,16 +2,24 @@ module Sonos::Endpoint::Device
2
2
  DEVICE_ENDPOINT = '/DeviceProperties/Control'
3
3
  DEVICE_XMLNS = 'urn:schemas-upnp-org:service:DeviceProperties:1'
4
4
 
5
+ # Retrieve the status light state; true if on, false otherwise.
6
+ def status_light_enabled?
7
+ response = send_device_message('GetLEDState', '')
8
+ body = response.body[:get_led_state_response]
9
+
10
+ body[:current_led_state] == 'On' ? true : false
11
+ end
12
+
5
13
  # Turn the white status light on or off
6
14
  # @param [Boolean] True to turn on the light. False to turn off the light.
7
15
  def status_light_enabled=(enabled)
8
- send_device_message('SetLEDState', enabled ? 'On' : 'Off')
16
+ parse_response send_device_message('SetLEDState', enabled ? 'On' : 'Off')
9
17
  end
10
18
 
11
19
  private
12
20
 
13
21
  def device_client
14
- @device_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{DEVICE_ENDPOINT}", namespace: Sonos::NAMESPACE, log_level: :error
22
+ @device_client ||= Savon.client endpoint: "http://#{self.ip}:#{Sonos::PORT}#{DEVICE_ENDPOINT}", namespace: Sonos::NAMESPACE, log: Sonos.logging_enabled
15
23
  end
16
24
 
17
25
  def send_device_message(name, value)