bumble 0.0.199__py3-none-any.whl → 0.0.200__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.
bumble/codecs.py CHANGED
@@ -17,6 +17,7 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  from __future__ import annotations
19
19
  from dataclasses import dataclass
20
+ from typing_extensions import Self
20
21
 
21
22
  from bumble import core
22
23
 
@@ -101,12 +102,40 @@ class BitReader:
101
102
  break
102
103
 
103
104
 
105
+ # -----------------------------------------------------------------------------
106
+ class BitWriter:
107
+ """Simple but not optimized bit stream writer."""
108
+
109
+ data: int
110
+ bit_count: int
111
+
112
+ def __init__(self) -> None:
113
+ self.data = 0
114
+ self.bit_count = 0
115
+
116
+ def write(self, value: int, bit_count: int) -> None:
117
+ self.data = (self.data << bit_count) | value
118
+ self.bit_count += bit_count
119
+
120
+ def write_bytes(self, data: bytes) -> None:
121
+ bit_count = 8 * len(data)
122
+ self.data = (self.data << bit_count) | int.from_bytes(data, 'big')
123
+ self.bit_count += bit_count
124
+
125
+ def __bytes__(self) -> bytes:
126
+ return (self.data << ((8 - (self.bit_count % 8)) % 8)).to_bytes(
127
+ (self.bit_count + 7) // 8, 'big'
128
+ )
129
+
130
+
104
131
  # -----------------------------------------------------------------------------
105
132
  class AacAudioRtpPacket:
106
133
  """AAC payload encapsulated in an RTP packet payload"""
107
134
 
135
+ audio_mux_element: AudioMuxElement
136
+
108
137
  @staticmethod
109
- def latm_value(reader: BitReader) -> int:
138
+ def read_latm_value(reader: BitReader) -> int:
110
139
  bytes_for_value = reader.read(2)
111
140
  value = 0
112
141
  for _ in range(bytes_for_value + 1):
@@ -114,24 +143,33 @@ class AacAudioRtpPacket:
114
143
  return value
115
144
 
116
145
  @staticmethod
117
- def program_config_element(reader: BitReader):
118
- raise core.InvalidPacketError('program_config_element not supported')
146
+ def read_audio_object_type(reader: BitReader):
147
+ # GetAudioObjectType - ISO/EIC 14496-3 Table 1.16
148
+ audio_object_type = reader.read(5)
149
+ if audio_object_type == 31:
150
+ audio_object_type = 32 + reader.read(6)
151
+
152
+ return audio_object_type
119
153
 
120
154
  @dataclass
121
155
  class GASpecificConfig:
122
- def __init__(
123
- self, reader: BitReader, channel_configuration: int, audio_object_type: int
124
- ) -> None:
156
+ audio_object_type: int
157
+ # NOTE: other fields not supported
158
+
159
+ @classmethod
160
+ def from_bits(
161
+ cls, reader: BitReader, channel_configuration: int, audio_object_type: int
162
+ ) -> Self:
125
163
  # GASpecificConfig - ISO/EIC 14496-3 Table 4.1
126
164
  frame_length_flag = reader.read(1)
127
165
  depends_on_core_coder = reader.read(1)
128
166
  if depends_on_core_coder:
129
- self.core_coder_delay = reader.read(14)
167
+ core_coder_delay = reader.read(14)
130
168
  extension_flag = reader.read(1)
131
169
  if not channel_configuration:
132
- AacAudioRtpPacket.program_config_element(reader)
170
+ raise core.InvalidPacketError('program_config_element not supported')
133
171
  if audio_object_type in (6, 20):
134
- self.layer_nr = reader.read(3)
172
+ layer_nr = reader.read(3)
135
173
  if extension_flag:
136
174
  if audio_object_type == 22:
137
175
  num_of_sub_frame = reader.read(5)
@@ -144,14 +182,13 @@ class AacAudioRtpPacket:
144
182
  if extension_flag_3 == 1:
145
183
  raise core.InvalidPacketError('extensionFlag3 == 1 not supported')
146
184
 
147
- @staticmethod
148
- def audio_object_type(reader: BitReader):
149
- # GetAudioObjectType - ISO/EIC 14496-3 Table 1.16
150
- audio_object_type = reader.read(5)
151
- if audio_object_type == 31:
152
- audio_object_type = 32 + reader.read(6)
185
+ return cls(audio_object_type)
153
186
 
154
- return audio_object_type
187
+ def to_bits(self, writer: BitWriter) -> None:
188
+ assert self.audio_object_type in (1, 2)
189
+ writer.write(0, 1) # frame_length_flag = 0
190
+ writer.write(0, 1) # depends_on_core_coder = 0
191
+ writer.write(0, 1) # extension_flag = 0
155
192
 
156
193
  @dataclass
157
194
  class AudioSpecificConfig:
@@ -159,6 +196,7 @@ class AacAudioRtpPacket:
159
196
  sampling_frequency_index: int
160
197
  sampling_frequency: int
161
198
  channel_configuration: int
199
+ ga_specific_config: AacAudioRtpPacket.GASpecificConfig
162
200
  sbr_present_flag: int
163
201
  ps_present_flag: int
164
202
  extension_audio_object_type: int
@@ -182,44 +220,73 @@ class AacAudioRtpPacket:
182
220
  7350,
183
221
  ]
184
222
 
185
- def __init__(self, reader: BitReader) -> None:
223
+ @classmethod
224
+ def for_simple_aac(
225
+ cls,
226
+ audio_object_type: int,
227
+ sampling_frequency: int,
228
+ channel_configuration: int,
229
+ ) -> Self:
230
+ if sampling_frequency not in cls.SAMPLING_FREQUENCIES:
231
+ raise ValueError(f'invalid sampling frequency {sampling_frequency}')
232
+
233
+ ga_specific_config = AacAudioRtpPacket.GASpecificConfig(audio_object_type)
234
+
235
+ return cls(
236
+ audio_object_type=audio_object_type,
237
+ sampling_frequency_index=cls.SAMPLING_FREQUENCIES.index(
238
+ sampling_frequency
239
+ ),
240
+ sampling_frequency=sampling_frequency,
241
+ channel_configuration=channel_configuration,
242
+ ga_specific_config=ga_specific_config,
243
+ sbr_present_flag=0,
244
+ ps_present_flag=0,
245
+ extension_audio_object_type=0,
246
+ extension_sampling_frequency_index=0,
247
+ extension_sampling_frequency=0,
248
+ extension_channel_configuration=0,
249
+ )
250
+
251
+ @classmethod
252
+ def from_bits(cls, reader: BitReader) -> Self:
186
253
  # AudioSpecificConfig - ISO/EIC 14496-3 Table 1.15
187
- self.audio_object_type = AacAudioRtpPacket.audio_object_type(reader)
188
- self.sampling_frequency_index = reader.read(4)
189
- if self.sampling_frequency_index == 0xF:
190
- self.sampling_frequency = reader.read(24)
254
+ audio_object_type = AacAudioRtpPacket.read_audio_object_type(reader)
255
+ sampling_frequency_index = reader.read(4)
256
+ if sampling_frequency_index == 0xF:
257
+ sampling_frequency = reader.read(24)
191
258
  else:
192
- self.sampling_frequency = self.SAMPLING_FREQUENCIES[
193
- self.sampling_frequency_index
194
- ]
195
- self.channel_configuration = reader.read(4)
196
- self.sbr_present_flag = -1
197
- self.ps_present_flag = -1
198
- if self.audio_object_type in (5, 29):
199
- self.extension_audio_object_type = 5
200
- self.sbc_present_flag = 1
201
- if self.audio_object_type == 29:
202
- self.ps_present_flag = 1
203
- self.extension_sampling_frequency_index = reader.read(4)
204
- if self.extension_sampling_frequency_index == 0xF:
205
- self.extension_sampling_frequency = reader.read(24)
259
+ sampling_frequency = cls.SAMPLING_FREQUENCIES[sampling_frequency_index]
260
+ channel_configuration = reader.read(4)
261
+ sbr_present_flag = 0
262
+ ps_present_flag = 0
263
+ extension_sampling_frequency_index = 0
264
+ extension_sampling_frequency = 0
265
+ extension_channel_configuration = 0
266
+ extension_audio_object_type = 0
267
+ if audio_object_type in (5, 29):
268
+ extension_audio_object_type = 5
269
+ sbr_present_flag = 1
270
+ if audio_object_type == 29:
271
+ ps_present_flag = 1
272
+ extension_sampling_frequency_index = reader.read(4)
273
+ if extension_sampling_frequency_index == 0xF:
274
+ extension_sampling_frequency = reader.read(24)
206
275
  else:
207
- self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[
208
- self.extension_sampling_frequency_index
276
+ extension_sampling_frequency = cls.SAMPLING_FREQUENCIES[
277
+ extension_sampling_frequency_index
209
278
  ]
210
- self.audio_object_type = AacAudioRtpPacket.audio_object_type(reader)
211
- if self.audio_object_type == 22:
212
- self.extension_channel_configuration = reader.read(4)
213
- else:
214
- self.extension_audio_object_type = 0
279
+ audio_object_type = AacAudioRtpPacket.read_audio_object_type(reader)
280
+ if audio_object_type == 22:
281
+ extension_channel_configuration = reader.read(4)
215
282
 
216
- if self.audio_object_type in (1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23):
217
- ga_specific_config = AacAudioRtpPacket.GASpecificConfig(
218
- reader, self.channel_configuration, self.audio_object_type
283
+ if audio_object_type in (1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23):
284
+ ga_specific_config = AacAudioRtpPacket.GASpecificConfig.from_bits(
285
+ reader, channel_configuration, audio_object_type
219
286
  )
220
287
  else:
221
288
  raise core.InvalidPacketError(
222
- f'audioObjectType {self.audio_object_type} not supported'
289
+ f'audioObjectType {audio_object_type} not supported'
223
290
  )
224
291
 
225
292
  # if self.extension_audio_object_type != 5 and bits_to_decode >= 16:
@@ -248,13 +315,44 @@ class AacAudioRtpPacket:
248
315
  # self.extension_sampling_frequency = self.SAMPLING_FREQUENCIES[self.extension_sampling_frequency_index]
249
316
  # self.extension_channel_configuration = reader.read(4)
250
317
 
318
+ return cls(
319
+ audio_object_type,
320
+ sampling_frequency_index,
321
+ sampling_frequency,
322
+ channel_configuration,
323
+ ga_specific_config,
324
+ sbr_present_flag,
325
+ ps_present_flag,
326
+ extension_audio_object_type,
327
+ extension_sampling_frequency_index,
328
+ extension_sampling_frequency,
329
+ extension_channel_configuration,
330
+ )
331
+
332
+ def to_bits(self, writer: BitWriter) -> None:
333
+ if self.sampling_frequency_index >= 15:
334
+ raise ValueError(
335
+ f"unsupported sampling frequency index {self.sampling_frequency_index}"
336
+ )
337
+
338
+ if self.audio_object_type not in (1, 2):
339
+ raise ValueError(
340
+ f"unsupported audio object type {self.audio_object_type} "
341
+ )
342
+
343
+ writer.write(self.audio_object_type, 5)
344
+ writer.write(self.sampling_frequency_index, 4)
345
+ writer.write(self.channel_configuration, 4)
346
+ self.ga_specific_config.to_bits(writer)
347
+
251
348
  @dataclass
252
349
  class StreamMuxConfig:
253
350
  other_data_present: int
254
351
  other_data_len_bits: int
255
352
  audio_specific_config: AacAudioRtpPacket.AudioSpecificConfig
256
353
 
257
- def __init__(self, reader: BitReader) -> None:
354
+ @classmethod
355
+ def from_bits(cls, reader: BitReader) -> Self:
258
356
  # StreamMuxConfig - ISO/EIC 14496-3 Table 1.42
259
357
  audio_mux_version = reader.read(1)
260
358
  if audio_mux_version == 1:
@@ -264,7 +362,7 @@ class AacAudioRtpPacket:
264
362
  if audio_mux_version_a != 0:
265
363
  raise core.InvalidPacketError('audioMuxVersionA != 0 not supported')
266
364
  if audio_mux_version == 1:
267
- tara_buffer_fullness = AacAudioRtpPacket.latm_value(reader)
365
+ tara_buffer_fullness = AacAudioRtpPacket.read_latm_value(reader)
268
366
  stream_cnt = 0
269
367
  all_streams_same_time_framing = reader.read(1)
270
368
  num_sub_frames = reader.read(6)
@@ -275,13 +373,13 @@ class AacAudioRtpPacket:
275
373
  if num_layer != 0:
276
374
  raise core.InvalidPacketError('num_layer != 0 not supported')
277
375
  if audio_mux_version == 0:
278
- self.audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig(
376
+ audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig.from_bits(
279
377
  reader
280
378
  )
281
379
  else:
282
- asc_len = AacAudioRtpPacket.latm_value(reader)
380
+ asc_len = AacAudioRtpPacket.read_latm_value(reader)
283
381
  marker = reader.bit_position
284
- self.audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig(
382
+ audio_specific_config = AacAudioRtpPacket.AudioSpecificConfig.from_bits(
285
383
  reader
286
384
  )
287
385
  audio_specific_config_len = reader.bit_position - marker
@@ -299,36 +397,49 @@ class AacAudioRtpPacket:
299
397
  f'frame_length_type {frame_length_type} not supported'
300
398
  )
301
399
 
302
- self.other_data_present = reader.read(1)
303
- if self.other_data_present:
400
+ other_data_present = reader.read(1)
401
+ other_data_len_bits = 0
402
+ if other_data_present:
304
403
  if audio_mux_version == 1:
305
- self.other_data_len_bits = AacAudioRtpPacket.latm_value(reader)
404
+ other_data_len_bits = AacAudioRtpPacket.read_latm_value(reader)
306
405
  else:
307
- self.other_data_len_bits = 0
308
406
  while True:
309
- self.other_data_len_bits *= 256
407
+ other_data_len_bits *= 256
310
408
  other_data_len_esc = reader.read(1)
311
- self.other_data_len_bits += reader.read(8)
409
+ other_data_len_bits += reader.read(8)
312
410
  if other_data_len_esc == 0:
313
411
  break
314
412
  crc_check_present = reader.read(1)
315
413
  if crc_check_present:
316
414
  crc_checksum = reader.read(8)
317
415
 
416
+ return cls(other_data_present, other_data_len_bits, audio_specific_config)
417
+
418
+ def to_bits(self, writer: BitWriter) -> None:
419
+ writer.write(0, 1) # audioMuxVersion = 0
420
+ writer.write(1, 1) # allStreamsSameTimeFraming = 1
421
+ writer.write(0, 6) # numSubFrames = 0
422
+ writer.write(0, 4) # numProgram = 0
423
+ writer.write(0, 3) # numLayer = 0
424
+ self.audio_specific_config.to_bits(writer)
425
+ writer.write(0, 3) # frameLengthType = 0
426
+ writer.write(0, 8) # latmBufferFullness = 0
427
+ writer.write(0, 1) # otherDataPresent = 0
428
+ writer.write(0, 1) # crcCheckPresent = 0
429
+
318
430
  @dataclass
319
431
  class AudioMuxElement:
320
- payload: bytes
321
432
  stream_mux_config: AacAudioRtpPacket.StreamMuxConfig
433
+ payload: bytes
322
434
 
323
- def __init__(self, reader: BitReader, mux_config_present: int):
324
- if mux_config_present == 0:
325
- raise core.InvalidPacketError('muxConfigPresent == 0 not supported')
326
-
435
+ @classmethod
436
+ def from_bits(cls, reader: BitReader) -> Self:
327
437
  # AudioMuxElement - ISO/EIC 14496-3 Table 1.41
438
+ # (only supports mux_config_present=1)
328
439
  use_same_stream_mux = reader.read(1)
329
440
  if use_same_stream_mux:
330
441
  raise core.InvalidPacketError('useSameStreamMux == 1 not supported')
331
- self.stream_mux_config = AacAudioRtpPacket.StreamMuxConfig(reader)
442
+ stream_mux_config = AacAudioRtpPacket.StreamMuxConfig.from_bits(reader)
332
443
 
333
444
  # We only support:
334
445
  # allStreamsSameTimeFraming == 1
@@ -344,19 +455,46 @@ class AacAudioRtpPacket:
344
455
  if tmp != 255:
345
456
  break
346
457
 
347
- self.payload = reader.read_bytes(mux_slot_length_bytes)
458
+ payload = reader.read_bytes(mux_slot_length_bytes)
348
459
 
349
- if self.stream_mux_config.other_data_present:
350
- reader.skip(self.stream_mux_config.other_data_len_bits)
460
+ if stream_mux_config.other_data_present:
461
+ reader.skip(stream_mux_config.other_data_len_bits)
351
462
 
352
463
  # ByteAlign
353
464
  while reader.bit_position % 8:
354
465
  reader.read(1)
355
466
 
356
- def __init__(self, data: bytes) -> None:
467
+ return cls(stream_mux_config, payload)
468
+
469
+ def to_bits(self, writer: BitWriter) -> None:
470
+ writer.write(0, 1) # useSameStreamMux = 0
471
+ self.stream_mux_config.to_bits(writer)
472
+ mux_slot_length_bytes = len(self.payload)
473
+ while mux_slot_length_bytes > 255:
474
+ writer.write(255, 8)
475
+ mux_slot_length_bytes -= 255
476
+ writer.write(mux_slot_length_bytes, 8)
477
+ if mux_slot_length_bytes == 255:
478
+ writer.write(0, 8)
479
+ writer.write_bytes(self.payload)
480
+
481
+ @classmethod
482
+ def from_bytes(cls, data: bytes) -> Self:
357
483
  # Parse the bit stream
358
484
  reader = BitReader(data)
359
- self.audio_mux_element = self.AudioMuxElement(reader, mux_config_present=1)
485
+ return cls(cls.AudioMuxElement.from_bits(reader))
486
+
487
+ @classmethod
488
+ def for_simple_aac(
489
+ cls, sampling_frequency: int, channel_configuration: int, payload: bytes
490
+ ) -> Self:
491
+ audio_specific_config = cls.AudioSpecificConfig.for_simple_aac(
492
+ 2, sampling_frequency, channel_configuration
493
+ )
494
+ stream_mux_config = cls.StreamMuxConfig(0, 0, audio_specific_config)
495
+ audio_mux_element = cls.AudioMuxElement(stream_mux_config, payload)
496
+
497
+ return cls(audio_mux_element)
360
498
 
361
499
  def to_adts(self):
362
500
  # pylint: disable=line-too-long
@@ -383,3 +521,11 @@ class AacAudioRtpPacket:
383
521
  )
384
522
  + self.audio_mux_element.payload
385
523
  )
524
+
525
+ def __init__(self, audio_mux_element: AudioMuxElement) -> None:
526
+ self.audio_mux_element = audio_mux_element
527
+
528
+ def __bytes__(self) -> bytes:
529
+ writer = BitWriter()
530
+ self.audio_mux_element.to_bits(writer)
531
+ return bytes(writer)
bumble/device.py CHANGED
@@ -1571,14 +1571,22 @@ class Connection(CompositeEventEmitter):
1571
1571
  raise
1572
1572
 
1573
1573
  def __str__(self):
1574
- return (
1575
- f'Connection(handle=0x{self.handle:04X}, '
1576
- f'role={self.role_name}, '
1577
- f'self_address={self.self_address}, '
1578
- f'self_resolvable_address={self.self_resolvable_address}, '
1579
- f'peer_address={self.peer_address}, '
1580
- f'peer_resolvable_address={self.peer_resolvable_address})'
1581
- )
1574
+ if self.transport == BT_LE_TRANSPORT:
1575
+ return (
1576
+ f'Connection(transport=LE, handle=0x{self.handle:04X}, '
1577
+ f'role={self.role_name}, '
1578
+ f'self_address={self.self_address}, '
1579
+ f'self_resolvable_address={self.self_resolvable_address}, '
1580
+ f'peer_address={self.peer_address}, '
1581
+ f'peer_resolvable_address={self.peer_resolvable_address})'
1582
+ )
1583
+ else:
1584
+ return (
1585
+ f'Connection(transport=BR/EDR, handle=0x{self.handle:04X}, '
1586
+ f'role={self.role_name}, '
1587
+ f'self_address={self.self_address}, '
1588
+ f'peer_address={self.peer_address})'
1589
+ )
1582
1590
 
1583
1591
 
1584
1592
  # -----------------------------------------------------------------------------
@@ -1766,9 +1774,9 @@ device_host_event_handlers: List[str] = []
1766
1774
  # -----------------------------------------------------------------------------
1767
1775
  class Device(CompositeEventEmitter):
1768
1776
  # Incomplete list of fields.
1769
- random_address: Address # Random address that may change with RPA
1770
- public_address: Address # Public address (obtained from the controller)
1771
- static_address: Address # Random address that can be set but does not change
1777
+ random_address: Address # Random private address that may change periodically
1778
+ public_address: Address # Public address that is globally unique (from controller)
1779
+ static_address: Address # Random static address that does not change once set
1772
1780
  classic_enabled: bool
1773
1781
  name: str
1774
1782
  class_of_device: int
bumble/hci.py CHANGED
@@ -3440,11 +3440,11 @@ class HCI_Read_Local_Supported_Codecs_V2_Command(HCI_Command):
3440
3440
  See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command
3441
3441
  '''
3442
3442
 
3443
- class Transport(OpenIntEnum):
3444
- BR_EDR_ACL = 0x00
3445
- BR_EDR_SCO = 0x01
3446
- LE_CIS = 0x02
3447
- LE_BIS = 0x03
3443
+ class Transport(enum.IntFlag):
3444
+ BR_EDR_ACL = 1 << 0
3445
+ BR_EDR_SCO = 1 << 1
3446
+ LE_CIS = 1 << 2
3447
+ LE_BIS = 1 << 3
3448
3448
 
3449
3449
 
3450
3450
  # -----------------------------------------------------------------------------
@@ -6065,6 +6065,32 @@ class HCI_Read_Remote_Version_Information_Complete_Event(HCI_Event):
6065
6065
  '''
6066
6066
 
6067
6067
 
6068
+ # -----------------------------------------------------------------------------
6069
+ @HCI_Event.event(
6070
+ [
6071
+ ('status', STATUS_SPEC),
6072
+ ('connection_handle', 2),
6073
+ ('unused', 1),
6074
+ (
6075
+ 'service_type',
6076
+ {
6077
+ 'size': 1,
6078
+ 'mapper': lambda x: HCI_QOS_Setup_Complete_Event.ServiceType(x).name,
6079
+ },
6080
+ ),
6081
+ ]
6082
+ )
6083
+ class HCI_QOS_Setup_Complete_Event(HCI_Event):
6084
+ '''
6085
+ See Bluetooth spec @ 7.7.13 QoS Setup Complete Event
6086
+ '''
6087
+
6088
+ class ServiceType(OpenIntEnum):
6089
+ NO_TRAFFIC_AVAILABLE = 0x00
6090
+ BEST_EFFORT_AVAILABLE = 0x01
6091
+ GUARANTEED_AVAILABLE = 0x02
6092
+
6093
+
6068
6094
  # -----------------------------------------------------------------------------
6069
6095
  @HCI_Event.event(
6070
6096
  [
bumble/hfp.py CHANGED
@@ -795,29 +795,32 @@ class HfProtocol(pyee.EventEmitter):
795
795
  # Append to the read buffer.
796
796
  self.read_buffer.extend(data)
797
797
 
798
- # Locate header and trailer.
799
- header = self.read_buffer.find(b'\r\n')
800
- trailer = self.read_buffer.find(b'\r\n', header + 2)
801
- if header == -1 or trailer == -1:
802
- return
803
-
804
- # Isolate the AT response code and parameters.
805
- raw_response = self.read_buffer[header + 2 : trailer]
806
- response = AtResponse.parse_from(raw_response)
807
- logger.debug(f"<<< {raw_response.decode()}")
808
-
809
- # Consume the response bytes.
810
- self.read_buffer = self.read_buffer[trailer + 2 :]
811
-
812
- # Forward the received code to the correct queue.
813
- if self.command_lock.locked() and (
814
- response.code in STATUS_CODES or response.code in RESPONSE_CODES
815
- ):
816
- self.response_queue.put_nowait(response)
817
- elif response.code in UNSOLICITED_CODES:
818
- self.unsolicited_queue.put_nowait(response)
819
- else:
820
- logger.warning(f"dropping unexpected response with code '{response.code}'")
798
+ while self.read_buffer:
799
+ # Locate header and trailer.
800
+ header = self.read_buffer.find(b'\r\n')
801
+ trailer = self.read_buffer.find(b'\r\n', header + 2)
802
+ if header == -1 or trailer == -1:
803
+ return
804
+
805
+ # Isolate the AT response code and parameters.
806
+ raw_response = self.read_buffer[header + 2 : trailer]
807
+ response = AtResponse.parse_from(raw_response)
808
+ logger.debug(f"<<< {raw_response.decode()}")
809
+
810
+ # Consume the response bytes.
811
+ self.read_buffer = self.read_buffer[trailer + 2 :]
812
+
813
+ # Forward the received code to the correct queue.
814
+ if self.command_lock.locked() and (
815
+ response.code in STATUS_CODES or response.code in RESPONSE_CODES
816
+ ):
817
+ self.response_queue.put_nowait(response)
818
+ elif response.code in UNSOLICITED_CODES:
819
+ self.unsolicited_queue.put_nowait(response)
820
+ else:
821
+ logger.warning(
822
+ f"dropping unexpected response with code '{response.code}'"
823
+ )
821
824
 
822
825
  async def execute_command(
823
826
  self,
@@ -1244,31 +1247,32 @@ class AgProtocol(pyee.EventEmitter):
1244
1247
  # Append to the read buffer.
1245
1248
  self.read_buffer.extend(data)
1246
1249
 
1247
- # Locate the trailer.
1248
- trailer = self.read_buffer.find(b'\r')
1249
- if trailer == -1:
1250
- return
1251
-
1252
- # Isolate the AT response code and parameters.
1253
- raw_command = self.read_buffer[:trailer]
1254
- command = AtCommand.parse_from(raw_command)
1255
- logger.debug(f"<<< {raw_command.decode()}")
1256
-
1257
- # Consume the response bytes.
1258
- self.read_buffer = self.read_buffer[trailer + 1 :]
1259
-
1260
- if command.sub_code == AtCommand.SubCode.TEST:
1261
- handler_name = f'_on_{command.code.lower()}_test'
1262
- elif command.sub_code == AtCommand.SubCode.READ:
1263
- handler_name = f'_on_{command.code.lower()}_read'
1264
- else:
1265
- handler_name = f'_on_{command.code.lower()}'
1266
-
1267
- if handler := getattr(self, handler_name, None):
1268
- handler(*command.parameters)
1269
- else:
1270
- logger.warning('Handler %s not found', handler_name)
1271
- self.send_response('ERROR')
1250
+ while self.read_buffer:
1251
+ # Locate the trailer.
1252
+ trailer = self.read_buffer.find(b'\r')
1253
+ if trailer == -1:
1254
+ return
1255
+
1256
+ # Isolate the AT response code and parameters.
1257
+ raw_command = self.read_buffer[:trailer]
1258
+ command = AtCommand.parse_from(raw_command)
1259
+ logger.debug(f"<<< {raw_command.decode()}")
1260
+
1261
+ # Consume the response bytes.
1262
+ self.read_buffer = self.read_buffer[trailer + 1 :]
1263
+
1264
+ if command.sub_code == AtCommand.SubCode.TEST:
1265
+ handler_name = f'_on_{command.code.lower()}_test'
1266
+ elif command.sub_code == AtCommand.SubCode.READ:
1267
+ handler_name = f'_on_{command.code.lower()}_read'
1268
+ else:
1269
+ handler_name = f'_on_{command.code.lower()}'
1270
+
1271
+ if handler := getattr(self, handler_name, None):
1272
+ handler(*command.parameters)
1273
+ else:
1274
+ logger.warning('Handler %s not found', handler_name)
1275
+ self.send_response('ERROR')
1272
1276
 
1273
1277
  def send_response(self, response: str) -> None:
1274
1278
  """Sends an AT response."""
bumble/host.py CHANGED
@@ -1106,6 +1106,18 @@ class Host(AbortableEventEmitter):
1106
1106
  event.status,
1107
1107
  )
1108
1108
 
1109
+ def on_hci_qos_setup_complete_event(self, event):
1110
+ if event.status == hci.HCI_SUCCESS:
1111
+ self.emit(
1112
+ 'connection_qos_setup', event.connection_handle, event.service_type
1113
+ )
1114
+ else:
1115
+ self.emit(
1116
+ 'connection_qos_setup_failure',
1117
+ event.connection_handle,
1118
+ event.status,
1119
+ )
1120
+
1109
1121
  def on_hci_link_supervision_timeout_changed_event(self, event):
1110
1122
  pass
1111
1123