bumble 0.0.195__py3-none-any.whl → 0.0.199__py3-none-any.whl

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.
Files changed (61) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/auracast.py +351 -66
  3. bumble/apps/console.py +5 -20
  4. bumble/apps/device_info.py +230 -0
  5. bumble/apps/gatt_dump.py +4 -0
  6. bumble/apps/lea_unicast/app.py +16 -17
  7. bumble/apps/pair.py +32 -5
  8. bumble/at.py +12 -6
  9. bumble/att.py +56 -40
  10. bumble/avc.py +8 -5
  11. bumble/avctp.py +3 -2
  12. bumble/avdtp.py +7 -3
  13. bumble/avrcp.py +2 -1
  14. bumble/codecs.py +17 -13
  15. bumble/colors.py +6 -2
  16. bumble/core.py +37 -7
  17. bumble/decoder.py +14 -10
  18. bumble/device.py +382 -111
  19. bumble/drivers/rtk.py +32 -13
  20. bumble/gatt.py +30 -20
  21. bumble/gatt_client.py +15 -29
  22. bumble/gatt_server.py +14 -6
  23. bumble/hci.py +322 -32
  24. bumble/hid.py +24 -28
  25. bumble/host.py +20 -6
  26. bumble/l2cap.py +24 -17
  27. bumble/link.py +8 -3
  28. bumble/pandora/__init__.py +3 -0
  29. bumble/pandora/l2cap.py +310 -0
  30. bumble/profiles/aics.py +520 -0
  31. bumble/profiles/ascs.py +739 -0
  32. bumble/profiles/asha.py +295 -0
  33. bumble/profiles/bap.py +1 -874
  34. bumble/profiles/bass.py +440 -0
  35. bumble/profiles/csip.py +4 -4
  36. bumble/profiles/gap.py +110 -0
  37. bumble/profiles/hap.py +665 -0
  38. bumble/profiles/heart_rate_service.py +4 -3
  39. bumble/profiles/le_audio.py +43 -9
  40. bumble/profiles/mcp.py +448 -0
  41. bumble/profiles/pacs.py +210 -0
  42. bumble/profiles/tmap.py +89 -0
  43. bumble/profiles/vcp.py +5 -3
  44. bumble/rfcomm.py +4 -2
  45. bumble/sdp.py +13 -11
  46. bumble/smp.py +43 -12
  47. bumble/snoop.py +5 -4
  48. bumble/transport/__init__.py +8 -2
  49. bumble/transport/android_emulator.py +9 -3
  50. bumble/transport/android_netsim.py +9 -7
  51. bumble/transport/common.py +46 -18
  52. bumble/transport/pyusb.py +21 -4
  53. bumble/transport/unix.py +56 -0
  54. bumble/transport/usb.py +57 -46
  55. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/METADATA +41 -41
  56. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/RECORD +60 -49
  57. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/WHEEL +1 -1
  58. bumble/profiles/asha_service.py +0 -193
  59. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/LICENSE +0 -0
  60. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/entry_points.txt +0 -0
  61. {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/top_level.txt +0 -0
bumble/avc.py CHANGED
@@ -20,6 +20,7 @@ import enum
20
20
  import struct
21
21
  from typing import Dict, Type, Union, Tuple
22
22
 
23
+ from bumble import core
23
24
  from bumble.utils import OpenIntEnum
24
25
 
25
26
 
@@ -88,7 +89,9 @@ class Frame:
88
89
  short_name = subclass.__name__.replace("ResponseFrame", "")
89
90
  category_class = ResponseFrame
90
91
  else:
91
- raise ValueError(f"invalid subclass name {subclass.__name__}")
92
+ raise core.InvalidArgumentError(
93
+ f"invalid subclass name {subclass.__name__}"
94
+ )
92
95
 
93
96
  uppercase_indexes = [
94
97
  i for i in range(len(short_name)) if short_name[i].isupper()
@@ -106,7 +109,7 @@ class Frame:
106
109
  @staticmethod
107
110
  def from_bytes(data: bytes) -> Frame:
108
111
  if data[0] >> 4 != 0:
109
- raise ValueError("first 4 bits must be 0s")
112
+ raise core.InvalidPacketError("first 4 bits must be 0s")
110
113
 
111
114
  ctype_or_response = data[0] & 0xF
112
115
  subunit_type = Frame.SubunitType(data[1] >> 3)
@@ -122,7 +125,7 @@ class Frame:
122
125
  # Extended to the next byte
123
126
  extension = data[2]
124
127
  if extension == 0:
125
- raise ValueError("extended subunit ID value reserved")
128
+ raise core.InvalidPacketError("extended subunit ID value reserved")
126
129
  if extension == 0xFF:
127
130
  subunit_id = 5 + 254 + data[3]
128
131
  opcode_offset = 4
@@ -131,7 +134,7 @@ class Frame:
131
134
  opcode_offset = 3
132
135
 
133
136
  elif subunit_id == 6:
134
- raise ValueError("reserved subunit ID")
137
+ raise core.InvalidPacketError("reserved subunit ID")
135
138
 
136
139
  opcode = Frame.OperationCode(data[opcode_offset])
137
140
  operands = data[opcode_offset + 1 :]
@@ -448,7 +451,7 @@ class PassThroughFrame:
448
451
  operation_data: bytes,
449
452
  ) -> None:
450
453
  if len(operation_data) > 255:
451
- raise ValueError("operation data must be <= 255 bytes")
454
+ raise core.InvalidArgumentError("operation data must be <= 255 bytes")
452
455
  self.state_flag = state_flag
453
456
  self.operation_id = operation_id
454
457
  self.operation_data = operation_data
bumble/avctp.py CHANGED
@@ -23,6 +23,7 @@ from typing import Callable, cast, Dict, Optional
23
23
 
24
24
  from bumble.colors import color
25
25
  from bumble import avc
26
+ from bumble import core
26
27
  from bumble import l2cap
27
28
 
28
29
  # -----------------------------------------------------------------------------
@@ -275,7 +276,7 @@ class Protocol:
275
276
  self, pid: int, handler: Protocol.CommandHandler
276
277
  ) -> None:
277
278
  if pid not in self.command_handlers or self.command_handlers[pid] != handler:
278
- raise ValueError("command handler not registered")
279
+ raise core.InvalidArgumentError("command handler not registered")
279
280
  del self.command_handlers[pid]
280
281
 
281
282
  def register_response_handler(
@@ -287,5 +288,5 @@ class Protocol:
287
288
  self, pid: int, handler: Protocol.ResponseHandler
288
289
  ) -> None:
289
290
  if pid not in self.response_handlers or self.response_handlers[pid] != handler:
290
- raise ValueError("response handler not registered")
291
+ raise core.InvalidArgumentError("response handler not registered")
291
292
  del self.response_handlers[pid]
bumble/avdtp.py CHANGED
@@ -43,6 +43,7 @@ from .core import (
43
43
  BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
44
44
  InvalidStateError,
45
45
  ProtocolError,
46
+ InvalidArgumentError,
46
47
  name_or_number,
47
48
  )
48
49
  from .a2dp import (
@@ -579,10 +580,10 @@ class ServiceCapabilities:
579
580
  self.service_category = service_category
580
581
  self.service_capabilities_bytes = service_capabilities_bytes
581
582
 
582
- def to_string(self, details: List[str] = []) -> str:
583
+ def to_string(self, details: Optional[List[str]] = None) -> str:
583
584
  attributes = ','.join(
584
585
  [name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)]
585
- + details
586
+ + (details or [])
586
587
  )
587
588
  return f'ServiceCapabilities({attributes})'
588
589
 
@@ -700,7 +701,7 @@ class Message: # pylint:disable=attribute-defined-outside-init
700
701
  signal_identifier_str = name[:-7]
701
702
  message_type = Message.MessageType.RESPONSE_REJECT
702
703
  else:
703
- raise ValueError('invalid class name')
704
+ raise InvalidArgumentError('invalid class name')
704
705
 
705
706
  subclass.message_type = message_type
706
707
 
@@ -2162,6 +2163,9 @@ class LocalStreamEndPoint(StreamEndPoint, EventEmitter):
2162
2163
  def on_abort_command(self):
2163
2164
  self.emit('abort')
2164
2165
 
2166
+ def on_delayreport_command(self, delay: int):
2167
+ self.emit('delay_report', delay)
2168
+
2165
2169
  def on_rtp_channel_open(self):
2166
2170
  self.emit('rtp_channel_open')
2167
2171
 
bumble/avrcp.py CHANGED
@@ -55,6 +55,7 @@ from bumble.sdp import (
55
55
  )
56
56
  from bumble.utils import AsyncRunner, OpenIntEnum
57
57
  from bumble.core import (
58
+ InvalidArgumentError,
58
59
  ProtocolError,
59
60
  BT_L2CAP_PROTOCOL_ID,
60
61
  BT_AVCTP_PROTOCOL_ID,
@@ -1411,7 +1412,7 @@ class Protocol(pyee.EventEmitter):
1411
1412
  def notify_track_changed(self, identifier: bytes) -> None:
1412
1413
  """Notify the connected peer of a Track change."""
1413
1414
  if len(identifier) != 8:
1414
- raise ValueError("identifier must be 8 bytes")
1415
+ raise InvalidArgumentError("identifier must be 8 bytes")
1415
1416
  self.notify_event(TrackChangedEvent(identifier))
1416
1417
 
1417
1418
  def notify_playback_position_changed(self, position: int) -> None:
bumble/codecs.py CHANGED
@@ -18,6 +18,8 @@
18
18
  from __future__ import annotations
19
19
  from dataclasses import dataclass
20
20
 
21
+ from bumble import core
22
+
21
23
 
22
24
  # -----------------------------------------------------------------------------
23
25
  class BitReader:
@@ -40,7 +42,7 @@ class BitReader:
40
42
  """ "Read up to 32 bits."""
41
43
 
42
44
  if bits > 32:
43
- raise ValueError('maximum read size is 32')
45
+ raise core.InvalidArgumentError('maximum read size is 32')
44
46
 
45
47
  if self.bits_cached >= bits:
46
48
  # We have enough bits.
@@ -53,7 +55,7 @@ class BitReader:
53
55
  feed_size = len(feed_bytes)
54
56
  feed_int = int.from_bytes(feed_bytes, byteorder='big')
55
57
  if 8 * feed_size + self.bits_cached < bits:
56
- raise ValueError('trying to read past the data')
58
+ raise core.InvalidArgumentError('trying to read past the data')
57
59
  self.byte_position += feed_size
58
60
 
59
61
  # Combine the new cache and the old cache
@@ -68,7 +70,7 @@ class BitReader:
68
70
 
69
71
  def read_bytes(self, count: int):
70
72
  if self.bit_position + 8 * count > 8 * len(self.data):
71
- raise ValueError('not enough data')
73
+ raise core.InvalidArgumentError('not enough data')
72
74
 
73
75
  if self.bit_position % 8:
74
76
  # Not byte aligned
@@ -113,7 +115,7 @@ class AacAudioRtpPacket:
113
115
 
114
116
  @staticmethod
115
117
  def program_config_element(reader: BitReader):
116
- raise ValueError('program_config_element not supported')
118
+ raise core.InvalidPacketError('program_config_element not supported')
117
119
 
118
120
  @dataclass
119
121
  class GASpecificConfig:
@@ -140,7 +142,7 @@ class AacAudioRtpPacket:
140
142
  aac_spectral_data_resilience_flags = reader.read(1)
141
143
  extension_flag_3 = reader.read(1)
142
144
  if extension_flag_3 == 1:
143
- raise ValueError('extensionFlag3 == 1 not supported')
145
+ raise core.InvalidPacketError('extensionFlag3 == 1 not supported')
144
146
 
145
147
  @staticmethod
146
148
  def audio_object_type(reader: BitReader):
@@ -216,7 +218,7 @@ class AacAudioRtpPacket:
216
218
  reader, self.channel_configuration, self.audio_object_type
217
219
  )
218
220
  else:
219
- raise ValueError(
221
+ raise core.InvalidPacketError(
220
222
  f'audioObjectType {self.audio_object_type} not supported'
221
223
  )
222
224
 
@@ -260,7 +262,7 @@ class AacAudioRtpPacket:
260
262
  else:
261
263
  audio_mux_version_a = 0
262
264
  if audio_mux_version_a != 0:
263
- raise ValueError('audioMuxVersionA != 0 not supported')
265
+ raise core.InvalidPacketError('audioMuxVersionA != 0 not supported')
264
266
  if audio_mux_version == 1:
265
267
  tara_buffer_fullness = AacAudioRtpPacket.latm_value(reader)
266
268
  stream_cnt = 0
@@ -268,10 +270,10 @@ class AacAudioRtpPacket:
268
270
  num_sub_frames = reader.read(6)
269
271
  num_program = reader.read(4)
270
272
  if num_program != 0:
271
- raise ValueError('num_program != 0 not supported')
273
+ raise core.InvalidPacketError('num_program != 0 not supported')
272
274
  num_layer = reader.read(3)
273
275
  if num_layer != 0:
274
- raise ValueError('num_layer != 0 not supported')
276
+ raise core.InvalidPacketError('num_layer != 0 not supported')
275
277
  if audio_mux_version == 0:
276
278
  self.audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig(
277
279
  reader
@@ -284,7 +286,7 @@ class AacAudioRtpPacket:
284
286
  )
285
287
  audio_specific_config_len = reader.bit_position - marker
286
288
  if asc_len < audio_specific_config_len:
287
- raise ValueError('audio_specific_config_len > asc_len')
289
+ raise core.InvalidPacketError('audio_specific_config_len > asc_len')
288
290
  asc_len -= audio_specific_config_len
289
291
  reader.skip(asc_len)
290
292
  frame_length_type = reader.read(3)
@@ -293,7 +295,9 @@ class AacAudioRtpPacket:
293
295
  elif frame_length_type == 1:
294
296
  frame_length = reader.read(9)
295
297
  else:
296
- raise ValueError(f'frame_length_type {frame_length_type} not supported')
298
+ raise core.InvalidPacketError(
299
+ f'frame_length_type {frame_length_type} not supported'
300
+ )
297
301
 
298
302
  self.other_data_present = reader.read(1)
299
303
  if self.other_data_present:
@@ -318,12 +322,12 @@ class AacAudioRtpPacket:
318
322
 
319
323
  def __init__(self, reader: BitReader, mux_config_present: int):
320
324
  if mux_config_present == 0:
321
- raise ValueError('muxConfigPresent == 0 not supported')
325
+ raise core.InvalidPacketError('muxConfigPresent == 0 not supported')
322
326
 
323
327
  # AudioMuxElement - ISO/EIC 14496-3 Table 1.41
324
328
  use_same_stream_mux = reader.read(1)
325
329
  if use_same_stream_mux:
326
- raise ValueError('useSameStreamMux == 1 not supported')
330
+ raise core.InvalidPacketError('useSameStreamMux == 1 not supported')
327
331
  self.stream_mux_config = AacAudioRtpPacket.StreamMuxConfig(reader)
328
332
 
329
333
  # We only support:
bumble/colors.py CHANGED
@@ -16,6 +16,10 @@ from functools import partial
16
16
  from typing import List, Optional, Union
17
17
 
18
18
 
19
+ class ColorError(ValueError):
20
+ """Error raised when a color spec is invalid."""
21
+
22
+
19
23
  # ANSI color names. There is also a "default"
20
24
  COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
21
25
 
@@ -52,7 +56,7 @@ def _color_code(spec: ColorSpec, base: int) -> str:
52
56
  elif isinstance(spec, int) and 0 <= spec <= 255:
53
57
  return _join(base + 8, 5, spec)
54
58
  else:
55
- raise ValueError('Invalid color spec "%s"' % spec)
59
+ raise ColorError('Invalid color spec "%s"' % spec)
56
60
 
57
61
 
58
62
  def color(
@@ -72,7 +76,7 @@ def color(
72
76
  if style_part in STYLES:
73
77
  codes.append(STYLES.index(style_part))
74
78
  else:
75
- raise ValueError('Invalid style "%s"' % style_part)
79
+ raise ColorError('Invalid style "%s"' % style_part)
76
80
 
77
81
  if codes:
78
82
  return '\x1b[{0}m{1}\x1b[0m'.format(_join(*codes), s)
bumble/core.py CHANGED
@@ -79,7 +79,13 @@ def get_dict_key_by_value(dictionary, value):
79
79
  # -----------------------------------------------------------------------------
80
80
  # Exceptions
81
81
  # -----------------------------------------------------------------------------
82
- class BaseError(Exception):
82
+
83
+
84
+ class BaseBumbleError(Exception):
85
+ """Base Error raised by Bumble."""
86
+
87
+
88
+ class BaseError(BaseBumbleError):
83
89
  """Base class for errors with an error code, error name and namespace"""
84
90
 
85
91
  def __init__(
@@ -118,18 +124,42 @@ class ProtocolError(BaseError):
118
124
  """Protocol Error"""
119
125
 
120
126
 
121
- class TimeoutError(Exception): # pylint: disable=redefined-builtin
127
+ class TimeoutError(BaseBumbleError): # pylint: disable=redefined-builtin
122
128
  """Timeout Error"""
123
129
 
124
130
 
125
- class CommandTimeoutError(Exception):
131
+ class CommandTimeoutError(BaseBumbleError):
126
132
  """Command Timeout Error"""
127
133
 
128
134
 
129
- class InvalidStateError(Exception):
135
+ class InvalidStateError(BaseBumbleError):
130
136
  """Invalid State Error"""
131
137
 
132
138
 
139
+ class InvalidArgumentError(BaseBumbleError, ValueError):
140
+ """Invalid Argument Error"""
141
+
142
+
143
+ class InvalidPacketError(BaseBumbleError, ValueError):
144
+ """Invalid Packet Error"""
145
+
146
+
147
+ class InvalidOperationError(BaseBumbleError, RuntimeError):
148
+ """Invalid Operation Error"""
149
+
150
+
151
+ class NotSupportedError(BaseBumbleError, RuntimeError):
152
+ """Not Supported"""
153
+
154
+
155
+ class OutOfResourcesError(BaseBumbleError, RuntimeError):
156
+ """Out of Resources Error"""
157
+
158
+
159
+ class UnreachableError(BaseBumbleError):
160
+ """The code path raising this error should be unreachable."""
161
+
162
+
133
163
  class ConnectionError(BaseError): # pylint: disable=redefined-builtin
134
164
  """Connection Error"""
135
165
 
@@ -188,12 +218,12 @@ class UUID:
188
218
  or uuid_str_or_int[18] != '-'
189
219
  or uuid_str_or_int[23] != '-'
190
220
  ):
191
- raise ValueError('invalid UUID format')
221
+ raise InvalidArgumentError('invalid UUID format')
192
222
  uuid_str = uuid_str_or_int.replace('-', '')
193
223
  else:
194
224
  uuid_str = uuid_str_or_int
195
225
  if len(uuid_str) != 32 and len(uuid_str) != 8 and len(uuid_str) != 4:
196
- raise ValueError(f"invalid UUID format: {uuid_str}")
226
+ raise InvalidArgumentError(f"invalid UUID format: {uuid_str}")
197
227
  self.uuid_bytes = bytes(reversed(bytes.fromhex(uuid_str)))
198
228
  self.name = name
199
229
 
@@ -218,7 +248,7 @@ class UUID:
218
248
 
219
249
  return self.register()
220
250
 
221
- raise ValueError('only 2, 4 and 16 bytes are allowed')
251
+ raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed')
222
252
 
223
253
  @classmethod
224
254
  def from_16_bits(cls, uuid_16: int, name: Optional[str] = None) -> UUID:
bumble/decoder.py CHANGED
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from typing import Union
16
+
15
17
  # -----------------------------------------------------------------------------
16
18
  # Constants
17
19
  # -----------------------------------------------------------------------------
@@ -149,7 +151,7 @@ QMF_COEFFS = [3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11]
149
151
  # -----------------------------------------------------------------------------
150
152
  # Classes
151
153
  # -----------------------------------------------------------------------------
152
- class G722Decoder(object):
154
+ class G722Decoder:
153
155
  """G.722 decoder with bitrate 64kbit/s.
154
156
 
155
157
  For the Blocks in the sub-band decoders, please refer to the G.722
@@ -157,7 +159,7 @@ class G722Decoder(object):
157
159
  https://www.itu.int/rec/T-REC-G.722-201209-I
158
160
  """
159
161
 
160
- def __init__(self):
162
+ def __init__(self) -> None:
161
163
  self._x = [0] * 24
162
164
  self._band = [Band(), Band()]
163
165
  # The initial value in BLOCK 3L
@@ -165,12 +167,12 @@ class G722Decoder(object):
165
167
  # The initial value in BLOCK 3H
166
168
  self._band[1].det = 8
167
169
 
168
- def decode_frame(self, encoded_data) -> bytearray:
170
+ def decode_frame(self, encoded_data: Union[bytes, bytearray]) -> bytearray:
169
171
  result_array = bytearray(len(encoded_data) * 4)
170
172
  self.g722_decode(result_array, encoded_data)
171
173
  return result_array
172
174
 
173
- def g722_decode(self, result_array, encoded_data) -> int:
175
+ def g722_decode(self, result_array, encoded_data: Union[bytes, bytearray]) -> int:
174
176
  """Decode the data frame using g722 decoder."""
175
177
  result_length = 0
176
178
 
@@ -198,14 +200,16 @@ class G722Decoder(object):
198
200
 
199
201
  return result_length
200
202
 
201
- def update_decoded_result(self, xout, byte_length, byte_array) -> int:
203
+ def update_decoded_result(
204
+ self, xout: int, byte_length: int, byte_array: bytearray
205
+ ) -> int:
202
206
  result = (int)(xout >> 11)
203
207
  bytes_result = result.to_bytes(2, 'little', signed=True)
204
208
  byte_array[byte_length] = bytes_result[0]
205
209
  byte_array[byte_length + 1] = bytes_result[1]
206
210
  return byte_length + 2
207
211
 
208
- def lower_sub_band_decoder(self, lower_bits) -> int:
212
+ def lower_sub_band_decoder(self, lower_bits: int) -> int:
209
213
  """Lower sub-band decoder for last six bits."""
210
214
 
211
215
  # Block 5L
@@ -258,7 +262,7 @@ class G722Decoder(object):
258
262
 
259
263
  return rlow
260
264
 
261
- def higher_sub_band_decoder(self, higher_bits) -> int:
265
+ def higher_sub_band_decoder(self, higher_bits: int) -> int:
262
266
  """Higher sub-band decoder for first two bits."""
263
267
 
264
268
  # Block 2H
@@ -306,14 +310,14 @@ class G722Decoder(object):
306
310
 
307
311
 
308
312
  # -----------------------------------------------------------------------------
309
- class Band(object):
310
- """Structure for G722 decode proccessing."""
313
+ class Band:
314
+ """Structure for G722 decode processing."""
311
315
 
312
316
  s: int = 0
313
317
  nb: int = 0
314
318
  det: int = 0
315
319
 
316
- def __init__(self):
320
+ def __init__(self) -> None:
317
321
  self._sp = 0
318
322
  self._sz = 0
319
323
  self._r = [0] * 3