sonos 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
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)