32
32
import struct
33
33
import time
34
34
35
+ import _bleio
36
+
35
37
from adafruit_ble .attributes import Attribute
36
38
from adafruit_ble .characteristics import Characteristic , ComplexCharacteristic
37
39
from adafruit_ble .uuid import VendorUUID
38
40
from adafruit_ble .services import Service
39
41
40
- import _bleio
41
-
42
42
__version__ = "0.0.0-auto.0"
43
43
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_Apple_Media.git"
44
44
45
+ # Disable protected access checks since our private classes are tightly coupled.
46
+ # pylint: disable=protected-access
45
47
46
48
class _RemoteCommand (ComplexCharacteristic ):
47
49
"""Endpoint for sending commands to a media player. The value read will list all available
@@ -78,7 +80,7 @@ def bind(self, service):
78
80
return _bleio .PacketBuffer (bound_characteristic ,
79
81
buffer_size = 8 )
80
82
81
- class _EntityAttribute (Characteristic ):
83
+ class _EntityAttribute (Characteristic ): # pylint: disable=too-few-public-methods
82
84
"""UTF-8 Encoded string characteristic."""
83
85
uuid = VendorUUID ("C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7" )
84
86
@@ -91,15 +93,18 @@ class _MediaAttribute:
91
93
def __init__ (self , entity_id , attribute_id ):
92
94
self .key = (entity_id , attribute_id )
93
95
94
- def _update (self , obj ):
96
+ @staticmethod
97
+ def _update (obj ):
95
98
if not obj ._buffer :
96
99
obj ._buffer = bytearray (128 )
97
- len = obj ._entity_update .readinto (obj ._buffer )
98
- if len > 0 :
99
- if len < 4 :
100
+ length_read = obj ._entity_update .readinto (obj ._buffer )
101
+ if length_read > 0 :
102
+ if length_read < 4 :
100
103
raise RuntimeError ("packet too short" )
101
- entity_id , attribute_id , flags = struct .unpack_from ("<BBB" , obj ._buffer )
102
- value = str (obj ._buffer [3 :len ], "utf-8" )
104
+ # Even though flags is currently unused, if it were removed, it would cause there to be
105
+ # too many values to unpack which would raise a ValueError
106
+ entity_id , attribute_id , flags = struct .unpack_from ("<BBB" , obj ._buffer ) # pylint: disable=unused-variable
107
+ value = str (obj ._buffer [3 :length_read ], "utf-8" )
103
108
obj ._attribute_cache [(entity_id , attribute_id )] = value
104
109
105
110
def __get__ (self , obj , cls ):
@@ -137,35 +142,58 @@ def __get__(self, obj, cls):
137
142
return 0
138
143
139
144
class UnsupportedCommand (Exception ):
140
- pass
145
+ """Raised when the command isn't available with current media player app."""
141
146
142
147
class AppleMediaService (Service ):
143
- """View and control currently playing media. Unimplemented."""
148
+ """View and control currently playing media.
149
+
150
+ Exact functionality varies with different media apps. For example, Spotify will include the
151
+ album name and artist name in `title` when controlling playback on a remote device.
152
+ `artist` includes a description of the remote playback.
153
+
154
+ """
144
155
uuid = VendorUUID ("89D3502B-0F36-433A-8EF4-C502AD55F8DC" )
145
156
146
157
_remote_command = _RemoteCommand ()
147
158
_entity_update = _EntityUpdate ()
148
159
_entity_attribute = _EntityAttribute ()
149
160
150
161
player_name = _MediaAttribute (0 , 0 )
162
+ """Name of the media player app"""
151
163
_playback_info = _MediaAttribute (0 , 1 )
152
164
paused = _MediaAttributePlaybackState (0 )
165
+ """True when playback is paused. False otherwise."""
153
166
playing = _MediaAttributePlaybackState (1 )
167
+ """True when playback is playing. False otherwise."""
154
168
rewinding = _MediaAttributePlaybackState (2 )
169
+ """True when playback is rewinding. False otherwise."""
155
170
fast_forwarding = _MediaAttributePlaybackState (3 )
171
+ """True when playback is fast-forwarding. False otherwise."""
156
172
playback_rate = _MediaAttributePlaybackInfo (1 )
173
+ """Playback rate as a decimal of normal speed."""
157
174
elapsed_time = _MediaAttributePlaybackInfo (2 )
175
+ """Time elapsed in the current track. Not updated as the track plays. Use (the amount of time
176
+ since read elapsed time) * `playback_rate` to estimate the current `elapsed_time`."""
158
177
volume = _MediaAttribute (0 , 2 )
178
+ """Current volume"""
159
179
160
180
queue_index = _MediaAttribute (1 , 0 )
181
+ """Current track's index in the queue."""
161
182
queue_length = _MediaAttribute (1 , 1 )
183
+ """Count of tracks in the queue."""
162
184
shuffle_mode = _MediaAttribute (1 , 2 )
185
+ """Current shuffle mode as an integer. Off (0), One (1), and All (2)"""
163
186
repeat_mode = _MediaAttribute (1 , 3 )
187
+ """Current repeat mode as an integer. Off (0), One (1), and All (2)"""
164
188
165
189
artist = _MediaAttribute (2 , 0 )
190
+ """Current track's artist name."""
166
191
album = _MediaAttribute (2 , 1 )
192
+ """Current track's album name."""
167
193
title = _MediaAttribute (2 , 2 )
194
+ """Current track's title."""
168
195
duration = _MediaAttribute (2 , 3 )
196
+ """Current track's duration as a string."""
169
197
170
198
def __init__ (self , ** kwargs ):
171
199
super ().__init__ (** kwargs )
@@ -179,7 +207,7 @@ def __init__(self, **kwargs):
179
207
def _send_command (self , command_id ):
180
208
if not self ._command_buffer :
181
209
self ._command_buffer = bytearray (13 )
182
- i = self ._remote_command .readinto (self ._command_buffer )
210
+ i = self ._remote_command .readinto (self ._command_buffer ) # pylint: disable=no-member
183
211
if i > 0 :
184
212
self ._supported_commands = list (self ._command_buffer [:i ])
185
213
if command_id not in self ._supported_commands :
@@ -189,46 +217,60 @@ def _send_command(self, command_id):
189
217
if not self ._cmd :
190
218
self ._cmd = bytearray (1 )
191
219
self ._cmd [0 ] = command_id
192
- self ._remote_command .write (self ._cmd )
220
+ self ._remote_command .write (self ._cmd ) # pylint: disable=no-member
193
221
194
222
def play (self ):
223
+ """Plays the current track. Does nothing if already playing."""
195
224
self ._send_command (0 )
196
225
197
226
def pause (self ):
227
+ """Pauses the current track. Does nothing if already paused."""
198
228
self ._send_command (1 )
199
229
200
230
def toggle_play_pause (self ):
231
+ """Plays the current track if it is paused. Otherwise it pauses the track."""
201
232
self ._send_command (2 )
202
233
203
234
def next_track (self ):
235
+ """Stops playing the current track and plays the next one."""
204
236
self ._send_command (3 )
205
237
206
238
def previous_track (self ):
239
+ """Stops playing the current track and plays the previous track."""
207
240
self ._send_command (4 )
208
241
209
242
def volume_up (self ):
243
+ """Increases the playback volume."""
210
244
self ._send_command (5 )
211
245
212
246
def volume_down (self ):
247
+ """Decreases the playback volume."""
213
248
self ._send_command (6 )
214
249
215
250
def advance_repeat_mode (self ):
251
+ """Advances the repeat mode. Modes are: Off, One and All"""
216
252
self ._send_command (7 )
217
253
218
254
def advance_shuffle_mode (self ):
255
+ """Advances the shuffle mode. Modes are: Off, One and All"""
219
256
self ._send_command (8 )
220
257
221
258
def skip_forward (self ):
259
+ """Skips forwards in the current track"""
222
260
self ._send_command (9 )
223
261
224
262
def skip_backward (self ):
263
+ """Skips backwards in the current track"""
225
264
self ._send_command (10 )
226
265
227
266
def like_track (self ):
267
+ """Likes the current track"""
228
268
self ._send_command (11 )
229
269
230
270
def dislike_track (self ):
271
+ """Dislikes the current track"""
231
272
self ._send_command (12 )
232
273
233
274
def bookmark_track (self ):
275
+ """Bookmarks the current track"""
234
276
self ._send_command (13 )
0 commit comments