Skip to content

Commit 8e6a912

Browse files
authored
Merge pull request #1 from tannewt/lint
Lint it all.
2 parents 08e7a32 + cbab52f commit 8e6a912

File tree

6 files changed

+106
-27
lines changed

6 files changed

+106
-27
lines changed

README.rst

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ This is easily achieved by downloading
2828

2929
Installing from PyPI
3030
=====================
31-
.. note:: This library is not available on PyPI yet. Install documentation is included
32-
as a standard element. Stay tuned for PyPI availability!
33-
34-
.. todo:: Remove the above note if PyPI version is/will be available at time of release.
35-
If the library is not planned for PyPI, remove the entire 'Installing from PyPI' section.
3631

3732
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
3833
PyPI <https://pypi.org/project/adafruit-circuitpython-ble_apple_media/>`_. To install for current user:
@@ -59,7 +54,50 @@ To install in a virtual environment in your current project:
5954
Usage Example
6055
=============
6156

62-
.. todo:: Add a quick, simple example. It and other examples should live in the examples folder and be included in docs/examples.rst.
57+
.. code-block:: python
58+
59+
import adafruit_ble
60+
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
61+
from adafruit_ble_apple_media import AppleMediaService
62+
63+
radio = adafruit_ble.BLERadio()
64+
a = SolicitServicesAdvertisement()
65+
a.solicited_services.append(AppleMediaService)
66+
radio.start_advertising(a)
67+
68+
while not radio.connected:
69+
pass
70+
71+
print("connected")
72+
73+
known_notifications = set()
74+
75+
i = 0
76+
while radio.connected:
77+
for connection in radio.connections:
78+
if not connection.paired:
79+
connection.pair()
80+
print("paired")
81+
82+
ams = connection[AppleMediaService]
83+
print("App:", ams.player_name)
84+
print("Title:", ams.title)
85+
print("Album:", ams.album)
86+
print("Artist:", ams.artist)
87+
if ams.playing:
88+
print("Playing")
89+
elif ams.paused:
90+
print("Paused")
91+
92+
if i > 3:
93+
ams.toggle_play_pause()
94+
i = 0
95+
print()
96+
time.sleep(3)
97+
i += 1
98+
99+
print("disconnected")
100+
63101
64102
Contributing
65103
============

adafruit_ble_apple_media.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,18 @@
3232
import struct
3333
import time
3434

35+
import _bleio
36+
3537
from adafruit_ble.attributes import Attribute
3638
from adafruit_ble.characteristics import Characteristic, ComplexCharacteristic
3739
from adafruit_ble.uuid import VendorUUID
3840
from adafruit_ble.services import Service
3941

40-
import _bleio
41-
4242
__version__ = "0.0.0-auto.0"
4343
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_Apple_Media.git"
4444

45+
# Disable protected access checks since our private classes are tightly coupled.
46+
# pylint: disable=protected-access
4547

4648
class _RemoteCommand(ComplexCharacteristic):
4749
"""Endpoint for sending commands to a media player. The value read will list all available
@@ -78,7 +80,7 @@ def bind(self, service):
7880
return _bleio.PacketBuffer(bound_characteristic,
7981
buffer_size=8)
8082

81-
class _EntityAttribute(Characteristic):
83+
class _EntityAttribute(Characteristic): # pylint: disable=too-few-public-methods
8284
"""UTF-8 Encoded string characteristic."""
8385
uuid = VendorUUID("C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7")
8486

@@ -91,15 +93,18 @@ class _MediaAttribute:
9193
def __init__(self, entity_id, attribute_id):
9294
self.key = (entity_id, attribute_id)
9395

94-
def _update(self, obj):
96+
@staticmethod
97+
def _update(obj):
9598
if not obj._buffer:
9699
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:
100103
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")
103108
obj._attribute_cache[(entity_id, attribute_id)] = value
104109

105110
def __get__(self, obj, cls):
@@ -137,35 +142,58 @@ def __get__(self, obj, cls):
137142
return 0
138143

139144
class UnsupportedCommand(Exception):
140-
pass
145+
"""Raised when the command isn't available with current media player app."""
141146

142147
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+
"""
144155
uuid = VendorUUID("89D3502B-0F36-433A-8EF4-C502AD55F8DC")
145156

146157
_remote_command = _RemoteCommand()
147158
_entity_update = _EntityUpdate()
148159
_entity_attribute = _EntityAttribute()
149160

150161
player_name = _MediaAttribute(0, 0)
162+
"""Name of the media player app"""
151163
_playback_info = _MediaAttribute(0, 1)
152164
paused = _MediaAttributePlaybackState(0)
165+
"""True when playback is paused. False otherwise."""
153166
playing = _MediaAttributePlaybackState(1)
167+
"""True when playback is playing. False otherwise."""
154168
rewinding = _MediaAttributePlaybackState(2)
169+
"""True when playback is rewinding. False otherwise."""
155170
fast_forwarding = _MediaAttributePlaybackState(3)
171+
"""True when playback is fast-forwarding. False otherwise."""
156172
playback_rate = _MediaAttributePlaybackInfo(1)
173+
"""Playback rate as a decimal of normal speed."""
157174
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`."""
158177
volume = _MediaAttribute(0, 2)
178+
"""Current volume"""
159179

160180
queue_index = _MediaAttribute(1, 0)
181+
"""Current track's index in the queue."""
161182
queue_length = _MediaAttribute(1, 1)
183+
"""Count of tracks in the queue."""
162184
shuffle_mode = _MediaAttribute(1, 2)
185+
"""Current shuffle mode as an integer. Off (0), One (1), and All (2)"""
163186
repeat_mode = _MediaAttribute(1, 3)
187+
"""Current repeat mode as an integer. Off (0), One (1), and All (2)"""
164188

165189
artist = _MediaAttribute(2, 0)
190+
"""Current track's artist name."""
166191
album = _MediaAttribute(2, 1)
192+
"""Current track's album name."""
167193
title = _MediaAttribute(2, 2)
194+
"""Current track's title."""
168195
duration = _MediaAttribute(2, 3)
196+
"""Current track's duration as a string."""
169197

170198
def __init__(self, **kwargs):
171199
super().__init__(**kwargs)
@@ -179,7 +207,7 @@ def __init__(self, **kwargs):
179207
def _send_command(self, command_id):
180208
if not self._command_buffer:
181209
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
183211
if i > 0:
184212
self._supported_commands = list(self._command_buffer[:i])
185213
if command_id not in self._supported_commands:
@@ -189,46 +217,60 @@ def _send_command(self, command_id):
189217
if not self._cmd:
190218
self._cmd = bytearray(1)
191219
self._cmd[0] = command_id
192-
self._remote_command.write(self._cmd)
220+
self._remote_command.write(self._cmd) # pylint: disable=no-member
193221

194222
def play(self):
223+
"""Plays the current track. Does nothing if already playing."""
195224
self._send_command(0)
196225

197226
def pause(self):
227+
"""Pauses the current track. Does nothing if already paused."""
198228
self._send_command(1)
199229

200230
def toggle_play_pause(self):
231+
"""Plays the current track if it is paused. Otherwise it pauses the track."""
201232
self._send_command(2)
202233

203234
def next_track(self):
235+
"""Stops playing the current track and plays the next one."""
204236
self._send_command(3)
205237

206238
def previous_track(self):
239+
"""Stops playing the current track and plays the previous track."""
207240
self._send_command(4)
208241

209242
def volume_up(self):
243+
"""Increases the playback volume."""
210244
self._send_command(5)
211245

212246
def volume_down(self):
247+
"""Decreases the playback volume."""
213248
self._send_command(6)
214249

215250
def advance_repeat_mode(self):
251+
"""Advances the repeat mode. Modes are: Off, One and All"""
216252
self._send_command(7)
217253

218254
def advance_shuffle_mode(self):
255+
"""Advances the shuffle mode. Modes are: Off, One and All"""
219256
self._send_command(8)
220257

221258
def skip_forward(self):
259+
"""Skips forwards in the current track"""
222260
self._send_command(9)
223261

224262
def skip_backward(self):
263+
"""Skips backwards in the current track"""
225264
self._send_command(10)
226265

227266
def like_track(self):
267+
"""Likes the current track"""
228268
self._send_command(11)
229269

230270
def dislike_track(self):
271+
"""Dislikes the current track"""
231272
self._send_command(12)
232273

233274
def bookmark_track(self):
275+
"""Bookmarks the current track"""
234276
self._send_command(13)

docs/conf.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
'sphinx.ext.todo',
1717
]
1818

19-
# TODO: Please Read!
2019
# Uncomment the below if you use native CircuitPython modules such as
2120
# digitalio, micropython and busio. List the modules you use. Without it, the
2221
# autodoc module docs will fail to generate with a warning.
23-
# autodoc_mock_imports = ["digitalio", "busio"]
22+
# autodoc_mock_imports = []
2423

2524

2625
intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)}

docs/index.rst

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ Table of Contents
2323
.. toctree::
2424
:caption: Tutorials
2525

26-
.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
27-
the toctree above for use later.
28-
2926
.. toctree::
3027
:caption: Related Products
3128

32-
.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
33-
the toctree above for use later.
29+
Adafruit Feather nRF52840 Express <https://www.adafruit.com/product/4062>
30+
31+
Adafruit ItsyBitsy nRF52840 Express - Bluetooth LE <https://www.adafruit.com/product/4481>
3432

3533
.. toctree::
3634
:caption: Other Links

examples/ble_apple_media_simpletest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
99
from adafruit_ble_apple_media import AppleMediaService
1010

11-
radio = adafruit_ble.BLERadio()
11+
# PyLint can't find BLERadio for some reason so special case it here.
12+
radio = adafruit_ble.BLERadio() # pylint: disable=no-member
1213
a = SolicitServicesAdvertisement()
1314
a.solicited_services.append(AppleMediaService)
1415
radio.start_advertising(a)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
Adafruit-Blinka
2+
adafruit-circuitpython-ble

0 commit comments

Comments
 (0)