bumble 0.0.193__py3-none-any.whl → 0.0.195__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/hci.py CHANGED
@@ -26,8 +26,8 @@ import struct
26
26
  from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar
27
27
 
28
28
  from bumble import crypto
29
- from .colors import color
30
- from .core import (
29
+ from bumble.colors import color
30
+ from bumble.core import (
31
31
  BT_BR_EDR_TRANSPORT,
32
32
  AdvertisingData,
33
33
  DeviceClass,
@@ -36,6 +36,7 @@ from .core import (
36
36
  name_or_number,
37
37
  padded_bytes,
38
38
  )
39
+ from bumble.utils import OpenIntEnum
39
40
 
40
41
 
41
42
  # -----------------------------------------------------------------------------
@@ -1104,7 +1105,7 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
1104
1105
 
1105
1106
  # LE Supported Features
1106
1107
  # See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT
1107
- class LeFeature(enum.IntEnum):
1108
+ class LeFeature(OpenIntEnum):
1108
1109
  LE_ENCRYPTION = 0
1109
1110
  CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1
1110
1111
  EXTENDED_REJECT_INDICATION = 2
@@ -1380,7 +1381,7 @@ class LmpFeatureMask(enum.IntFlag):
1380
1381
  STATUS_SPEC = {'size': 1, 'mapper': lambda x: HCI_Constant.status_name(x)}
1381
1382
 
1382
1383
 
1383
- class CodecID(enum.IntEnum):
1384
+ class CodecID(OpenIntEnum):
1384
1385
  # fmt: off
1385
1386
  U_LOG = 0x00
1386
1387
  A_LOG = 0x01
@@ -1967,6 +1968,9 @@ class Address:
1967
1968
  def __str__(self):
1968
1969
  return self.to_string()
1969
1970
 
1971
+ def __repr__(self):
1972
+ return f'Address({self.to_string(False)}/{self.address_type_name(self.address_type)})'
1973
+
1970
1974
 
1971
1975
  # Predefined address values
1972
1976
  Address.NIL = Address(b"\xff\xff\xff\xff\xff\xff", Address.PUBLIC_DEVICE_ADDRESS)
@@ -4452,6 +4456,80 @@ class HCI_LE_Extended_Create_Connection_Command(HCI_Command):
4452
4456
  )
4453
4457
 
4454
4458
 
4459
+ # -----------------------------------------------------------------------------
4460
+ @HCI_Command.command(
4461
+ [
4462
+ (
4463
+ 'options',
4464
+ {
4465
+ 'size': 1,
4466
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Create_Sync_Command.Options(
4467
+ x
4468
+ ).name,
4469
+ },
4470
+ ),
4471
+ ('advertising_sid', 1),
4472
+ ('advertiser_address_type', Address.ADDRESS_TYPE_SPEC),
4473
+ ('advertiser_address', Address.parse_address_preceded_by_type),
4474
+ ('skip', 2),
4475
+ ('sync_timeout', 2),
4476
+ (
4477
+ 'sync_cte_type',
4478
+ {
4479
+ 'size': 1,
4480
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Create_Sync_Command.CteType(
4481
+ x
4482
+ ).name,
4483
+ },
4484
+ ),
4485
+ ]
4486
+ )
4487
+ class HCI_LE_Periodic_Advertising_Create_Sync_Command(HCI_Command):
4488
+ '''
4489
+ See Bluetooth spec @ 7.8.67 LE Periodic Advertising Create Sync command
4490
+ '''
4491
+
4492
+ class Options(enum.IntFlag):
4493
+ USE_PERIODIC_ADVERTISER_LIST = 1 << 0
4494
+ REPORTING_INITIALLY_DISABLED = 1 << 1
4495
+ DUPLICATE_FILTERING_INITIALLY_ENABLED = 1 << 2
4496
+
4497
+ class CteType(enum.IntFlag):
4498
+ DO_NOT_SYNC_TO_PACKETS_WITH_AN_AOA_CONSTANT_TONE_EXTENSION = 1 << 0
4499
+ DO_NOT_SYNC_TO_PACKETS_WITH_AN_AOD_CONSTANT_TONE_EXTENSION_1US = 1 << 1
4500
+ DO_NOT_SYNC_TO_PACKETS_WITH_AN_AOD_CONSTANT_TONE_EXTENSION_2US = 1 << 2
4501
+ DO_NOT_SYNC_TO_PACKETS_WITH_A_TYPE_3_CONSTANT_TONE_EXTENSION = 1 << 3
4502
+ DO_NOT_SYNC_TO_PACKETS_WITHOUT_A_CONSTANT_TONE_EXTENSION = 1 << 4
4503
+
4504
+
4505
+ # -----------------------------------------------------------------------------
4506
+ @HCI_Command.command()
4507
+ class HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command(HCI_Command):
4508
+ '''
4509
+ See Bluetooth spec @ 7.8.68 LE Periodic Advertising Create Sync Cancel Command
4510
+ '''
4511
+
4512
+
4513
+ # -----------------------------------------------------------------------------
4514
+ @HCI_Command.command([('sync_handle', 2)])
4515
+ class HCI_LE_Periodic_Advertising_Terminate_Sync_Command(HCI_Command):
4516
+ '''
4517
+ See Bluetooth spec @ 7.8.69 LE Periodic Advertising Terminate Sync Command
4518
+ '''
4519
+
4520
+
4521
+ # -----------------------------------------------------------------------------
4522
+ @HCI_Command.command([('sync_handle', 2), ('enable', 1)])
4523
+ class HCI_LE_Set_Periodic_Advertising_Receive_Enable_Command(HCI_Command):
4524
+ '''
4525
+ See Bluetooth spec @ 7.8.88 LE Set Periodic Advertising Receive Enable Command
4526
+ '''
4527
+
4528
+ class Enable(enum.IntFlag):
4529
+ REPORTING_ENABLED = 1 << 0
4530
+ DUPLICATE_FILTERING_ENABLED = 1 << 1
4531
+
4532
+
4455
4533
  # -----------------------------------------------------------------------------
4456
4534
  @HCI_Command.command(
4457
4535
  [
@@ -4487,14 +4565,6 @@ class HCI_LE_Set_Privacy_Mode_Command(HCI_Command):
4487
4565
  return name_or_number(cls.PRIVACY_MODE_NAMES, privacy_mode)
4488
4566
 
4489
4567
 
4490
- # -----------------------------------------------------------------------------
4491
- @HCI_Command.command([('bit_number', 1), ('bit_value', 1)])
4492
- class HCI_LE_Set_Host_Feature_Command(HCI_Command):
4493
- '''
4494
- See Bluetooth spec @ 7.8.115 LE Set Host Feature Command
4495
- '''
4496
-
4497
-
4498
4568
  # -----------------------------------------------------------------------------
4499
4569
  @HCI_Command.command(
4500
4570
  fields=[
@@ -4655,6 +4725,14 @@ class HCI_LE_Remove_ISO_Data_Path_Command(HCI_Command):
4655
4725
  data_path_direction: int
4656
4726
 
4657
4727
 
4728
+ # -----------------------------------------------------------------------------
4729
+ @HCI_Command.command([('bit_number', 1), ('bit_value', 1)])
4730
+ class HCI_LE_Set_Host_Feature_Command(HCI_Command):
4731
+ '''
4732
+ See Bluetooth spec @ 7.8.115 LE Set Host Feature Command
4733
+ '''
4734
+
4735
+
4658
4736
  # -----------------------------------------------------------------------------
4659
4737
  # HCI Events
4660
4738
  # -----------------------------------------------------------------------------
@@ -5271,6 +5349,142 @@ HCI_LE_Meta_Event.subevent_classes[HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT] = (
5271
5349
  )
5272
5350
 
5273
5351
 
5352
+ # -----------------------------------------------------------------------------
5353
+ @HCI_LE_Meta_Event.event(
5354
+ [
5355
+ ('status', STATUS_SPEC),
5356
+ ('sync_handle', 2),
5357
+ ('advertising_sid', 1),
5358
+ ('advertiser_address_type', Address.ADDRESS_TYPE_SPEC),
5359
+ ('advertiser_address', Address.parse_address_preceded_by_type),
5360
+ ('advertiser_phy', {'size': 1, 'mapper': HCI_Constant.le_phy_name}),
5361
+ ('periodic_advertising_interval', 2),
5362
+ ('advertiser_clock_accuracy', 1),
5363
+ ]
5364
+ )
5365
+ class HCI_LE_Periodic_Advertising_Sync_Established_Event(HCI_LE_Meta_Event):
5366
+ '''
5367
+ See Bluetooth spec @ 7.7.65.14 LE Periodic Advertising Sync Established Event
5368
+ '''
5369
+
5370
+
5371
+ # -----------------------------------------------------------------------------
5372
+ @HCI_LE_Meta_Event.event(
5373
+ [
5374
+ ('status', STATUS_SPEC),
5375
+ ('sync_handle', 2),
5376
+ ('advertising_sid', 1),
5377
+ ('advertiser_address_type', Address.ADDRESS_TYPE_SPEC),
5378
+ ('advertiser_address', Address.parse_address_preceded_by_type),
5379
+ ('advertiser_phy', {'size': 1, 'mapper': HCI_Constant.le_phy_name}),
5380
+ ('periodic_advertising_interval', 2),
5381
+ ('advertiser_clock_accuracy', 1),
5382
+ ('num_subevents', 1),
5383
+ ('subevent_interval', 1),
5384
+ ('response_slot_delay', 1),
5385
+ ('response_slot_spacing', 1),
5386
+ ]
5387
+ )
5388
+ class HCI_LE_Periodic_Advertising_Sync_Established_V2_Event(HCI_LE_Meta_Event):
5389
+ '''
5390
+ See Bluetooth spec @ 7.7.65.14 LE Periodic Advertising Sync Established Event
5391
+ '''
5392
+
5393
+
5394
+ # -----------------------------------------------------------------------------
5395
+ @HCI_LE_Meta_Event.event(
5396
+ [
5397
+ ('sync_handle', 2),
5398
+ ('tx_power', -1),
5399
+ ('rssi', -1),
5400
+ (
5401
+ 'cte_type',
5402
+ {
5403
+ 'size': 1,
5404
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Report_Event.CteType(
5405
+ x
5406
+ ).name,
5407
+ },
5408
+ ),
5409
+ (
5410
+ 'data_status',
5411
+ {
5412
+ 'size': 1,
5413
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Report_Event.DataStatus(
5414
+ x
5415
+ ).name,
5416
+ },
5417
+ ),
5418
+ ('data', 'v'),
5419
+ ]
5420
+ )
5421
+ class HCI_LE_Periodic_Advertising_Report_Event(HCI_LE_Meta_Event):
5422
+ '''
5423
+ See Bluetooth spec @ 7.7.65.15 LE Periodic Advertising Report Event
5424
+ '''
5425
+
5426
+ TX_POWER_INFORMATION_NOT_AVAILABLE = 0x7F
5427
+ RSSI_NOT_AVAILABLE = 0x7F
5428
+
5429
+ class CteType(OpenIntEnum):
5430
+ AOA_CONSTANT_TONE_EXTENSION = 0x00
5431
+ AOD_CONSTANT_TONE_EXTENSION_1US = 0x01
5432
+ AOD_CONSTANT_TONE_EXTENSION_2US = 0x02
5433
+ NO_CONSTANT_TONE_EXTENSION = 0xFF
5434
+
5435
+ class DataStatus(OpenIntEnum):
5436
+ DATA_COMPLETE = 0x00
5437
+ DATA_INCOMPLETE_MORE_TO_COME = 0x01
5438
+ DATA_INCOMPLETE_TRUNCATED_NO_MORE_TO_COME = 0x02
5439
+
5440
+
5441
+ # -----------------------------------------------------------------------------
5442
+ @HCI_LE_Meta_Event.event(
5443
+ [
5444
+ ('sync_handle', 2),
5445
+ ('tx_power', -1),
5446
+ ('rssi', -1),
5447
+ (
5448
+ 'cte_type',
5449
+ {
5450
+ 'size': 1,
5451
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Report_Event.CteType(
5452
+ x
5453
+ ).name,
5454
+ },
5455
+ ),
5456
+ ('periodic_event_counter', 2),
5457
+ ('subevent', 1),
5458
+ (
5459
+ 'data_status',
5460
+ {
5461
+ 'size': 1,
5462
+ 'mapper': lambda x: HCI_LE_Periodic_Advertising_Report_Event.DataStatus(
5463
+ x
5464
+ ).name,
5465
+ },
5466
+ ),
5467
+ ('data', 'v'),
5468
+ ]
5469
+ )
5470
+ class HCI_LE_Periodic_Advertising_Report_V2_Event(HCI_LE_Meta_Event):
5471
+ '''
5472
+ See Bluetooth spec @ 7.7.65.15 LE Periodic Advertising Report Event
5473
+ '''
5474
+
5475
+
5476
+ # -----------------------------------------------------------------------------
5477
+ @HCI_LE_Meta_Event.event(
5478
+ [
5479
+ ('sync_handle', 2),
5480
+ ]
5481
+ )
5482
+ class HCI_LE_Periodic_Advertising_Sync_Lost_Event(HCI_LE_Meta_Event):
5483
+ '''
5484
+ See Bluetooth spec @ 7.7.65.16 LE Periodic Advertising Sync Lost Event
5485
+ '''
5486
+
5487
+
5274
5488
  # -----------------------------------------------------------------------------
5275
5489
  @HCI_LE_Meta_Event.event(
5276
5490
  [
@@ -5336,6 +5550,30 @@ class HCI_LE_CIS_Request_Event(HCI_LE_Meta_Event):
5336
5550
  '''
5337
5551
 
5338
5552
 
5553
+ # -----------------------------------------------------------------------------
5554
+ @HCI_LE_Meta_Event.event(
5555
+ [
5556
+ ('sync_handle', 2),
5557
+ ('num_bis', 1),
5558
+ ('nse', 1),
5559
+ ('iso_interval', 2),
5560
+ ('bn', 1),
5561
+ ('pto', 1),
5562
+ ('irc', 1),
5563
+ ('max_pdu', 2),
5564
+ ('sdu_interval', 3),
5565
+ ('max_sdu', 2),
5566
+ ('phy', {'size': 1, 'mapper': HCI_Constant.le_phy_name}),
5567
+ ('framing', 1),
5568
+ ('encryption', 1),
5569
+ ]
5570
+ )
5571
+ class HCI_LE_BIGInfo_Advertising_Report_Event(HCI_LE_Meta_Event):
5572
+ '''
5573
+ See Bluetooth spec @ 7.7.65.34 LE BIGInfo Advertising Report Event
5574
+ '''
5575
+
5576
+
5339
5577
  # -----------------------------------------------------------------------------
5340
5578
  @HCI_Event.event([('status', STATUS_SPEC)])
5341
5579
  class HCI_Inquiry_Complete_Event(HCI_Event):
bumble/host.py CHANGED
@@ -787,6 +787,10 @@ class Host(AbortableEventEmitter):
787
787
  # Just use the same implementation as for the non-enhanced event for now
788
788
  self.on_hci_le_connection_complete_event(event)
789
789
 
790
+ def on_hci_le_enhanced_connection_complete_v2_event(self, event):
791
+ # Just use the same implementation as for the v1 event for now
792
+ self.on_hci_le_enhanced_connection_complete_event(event)
793
+
790
794
  def on_hci_connection_complete_event(self, event):
791
795
  if event.status == hci.HCI_SUCCESS:
792
796
  # Create/update the connection
@@ -905,6 +909,27 @@ class Host(AbortableEventEmitter):
905
909
  event.num_completed_extended_advertising_events,
906
910
  )
907
911
 
912
+ def on_hci_le_periodic_advertising_sync_established_event(self, event):
913
+ self.emit(
914
+ 'periodic_advertising_sync_establishment',
915
+ event.status,
916
+ event.sync_handle,
917
+ event.advertising_sid,
918
+ event.advertiser_address,
919
+ event.advertiser_phy,
920
+ event.periodic_advertising_interval,
921
+ event.advertiser_clock_accuracy,
922
+ )
923
+
924
+ def on_hci_le_periodic_advertising_sync_lost_event(self, event):
925
+ self.emit('periodic_advertising_sync_loss', event.sync_handle)
926
+
927
+ def on_hci_le_periodic_advertising_report_event(self, event):
928
+ self.emit('periodic_advertising_report', event.sync_handle, event)
929
+
930
+ def on_hci_le_biginfo_advertising_report_event(self, event):
931
+ self.emit('biginfo_advertising_report', event.sync_handle, event)
932
+
908
933
  def on_hci_le_cis_request_event(self, event):
909
934
  self.emit(
910
935
  'cis_request',
bumble/l2cap.py CHANGED
@@ -70,6 +70,7 @@ L2CAP_LE_SIGNALING_CID = 0x05
70
70
 
71
71
  L2CAP_MIN_LE_MTU = 23
72
72
  L2CAP_MIN_BR_EDR_MTU = 48
73
+ L2CAP_MAX_BR_EDR_MTU = 65535
73
74
 
74
75
  L2CAP_DEFAULT_MTU = 2048 # Default value for the MTU we are willing to accept
75
76
 
@@ -832,7 +833,9 @@ class ClassicChannel(EventEmitter):
832
833
 
833
834
  # Wait for the connection to succeed or fail
834
835
  try:
835
- return await self.connection_result
836
+ return await self.connection.abort_on(
837
+ 'disconnection', self.connection_result
838
+ )
836
839
  finally:
837
840
  self.connection_result = None
838
841
 
@@ -2225,7 +2228,7 @@ class ChannelManager:
2225
2228
  # Connect
2226
2229
  try:
2227
2230
  await channel.connect()
2228
- except Exception as e:
2231
+ except BaseException as e:
2229
2232
  del connection_channels[source_cid]
2230
2233
  raise e
2231
2234
 
bumble/pandora/host.py CHANGED
@@ -28,6 +28,7 @@ from bumble.core import (
28
28
  BT_PERIPHERAL_ROLE,
29
29
  UUID,
30
30
  AdvertisingData,
31
+ Appearance,
31
32
  ConnectionError,
32
33
  )
33
34
  from bumble.device import (
@@ -988,8 +989,8 @@ class HostService(HostServicer):
988
989
  dt.random_target_addresses.extend(
989
990
  [data[i * 6 :: i * 6 + 6] for i in range(int(len(data) / 6))]
990
991
  )
991
- if i := cast(int, ad.get(AdvertisingData.APPEARANCE)):
992
- dt.appearance = i
992
+ if appearance := cast(Appearance, ad.get(AdvertisingData.APPEARANCE)):
993
+ dt.appearance = int(appearance)
993
994
  if i := cast(int, ad.get(AdvertisingData.ADVERTISING_INTERVAL)):
994
995
  dt.advertising_interval = i
995
996
  if s := cast(str, ad.get(AdvertisingData.URI)):
bumble/profiles/bap.py CHANGED
@@ -25,6 +25,7 @@ import struct
25
25
  import functools
26
26
  import logging
27
27
  from typing import Optional, List, Union, Type, Dict, Any, Tuple
28
+ from typing_extensions import Self
28
29
 
29
30
  from bumble import core
30
31
  from bumble import colors
@@ -32,6 +33,8 @@ from bumble import device
32
33
  from bumble import hci
33
34
  from bumble import gatt
34
35
  from bumble import gatt_client
36
+ from bumble import utils
37
+ from bumble.profiles import le_audio
35
38
 
36
39
 
37
40
  # -----------------------------------------------------------------------------
@@ -115,7 +118,7 @@ class ContextType(enum.IntFlag):
115
118
  EMERGENCY_ALARM = 0x0800
116
119
 
117
120
 
118
- class SamplingFrequency(enum.IntEnum):
121
+ class SamplingFrequency(utils.OpenIntEnum):
119
122
  '''Bluetooth Assigned Numbers, Section 6.12.5.1 - Sampling Frequency'''
120
123
 
121
124
  # fmt: off
@@ -240,7 +243,7 @@ class SupportedFrameDuration(enum.IntFlag):
240
243
  DURATION_10000_US_PREFERRED = 0b0010
241
244
 
242
245
 
243
- class AnnouncementType(enum.IntEnum):
246
+ class AnnouncementType(utils.OpenIntEnum):
244
247
  '''Basic Audio Profile, 3.5.3. Additional Audio Stream Control Service requirements'''
245
248
 
246
249
  # fmt: off
@@ -613,7 +616,7 @@ class CodecSpecificConfiguration:
613
616
  * Basic Audio Profile, 4.3.2 - Codec_Specific_Capabilities LTV requirements
614
617
  '''
615
618
 
616
- class Type(enum.IntEnum):
619
+ class Type(utils.OpenIntEnum):
617
620
  # fmt: off
618
621
  SAMPLING_FREQUENCY = 0x01
619
622
  FRAME_DURATION = 0x02
@@ -725,6 +728,99 @@ class PacRecord:
725
728
  )
726
729
 
727
730
 
731
+ @dataclasses.dataclass
732
+ class BroadcastAudioAnnouncement:
733
+ broadcast_id: int
734
+
735
+ @classmethod
736
+ def from_bytes(cls, data: bytes) -> Self:
737
+ return cls(int.from_bytes(data[:3], 'little'))
738
+
739
+
740
+ @dataclasses.dataclass
741
+ class BasicAudioAnnouncement:
742
+ @dataclasses.dataclass
743
+ class BIS:
744
+ index: int
745
+ codec_specific_configuration: CodecSpecificConfiguration
746
+
747
+ @dataclasses.dataclass
748
+ class CodecInfo:
749
+ coding_format: hci.CodecID
750
+ company_id: int
751
+ vendor_specific_codec_id: int
752
+
753
+ @classmethod
754
+ def from_bytes(cls, data: bytes) -> Self:
755
+ coding_format = hci.CodecID(data[0])
756
+ company_id = int.from_bytes(data[1:3], 'little')
757
+ vendor_specific_codec_id = int.from_bytes(data[3:5], 'little')
758
+ return cls(coding_format, company_id, vendor_specific_codec_id)
759
+
760
+ @dataclasses.dataclass
761
+ class Subgroup:
762
+ codec_id: BasicAudioAnnouncement.CodecInfo
763
+ codec_specific_configuration: CodecSpecificConfiguration
764
+ metadata: le_audio.Metadata
765
+ bis: List[BasicAudioAnnouncement.BIS]
766
+
767
+ presentation_delay: int
768
+ subgroups: List[BasicAudioAnnouncement.Subgroup]
769
+
770
+ @classmethod
771
+ def from_bytes(cls, data: bytes) -> Self:
772
+ presentation_delay = int.from_bytes(data[:3], 'little')
773
+ subgroups = []
774
+ offset = 4
775
+ for _ in range(data[3]):
776
+ num_bis = data[offset]
777
+ offset += 1
778
+ codec_id = cls.CodecInfo.from_bytes(data[offset : offset + 5])
779
+ offset += 5
780
+ codec_specific_configuration_length = data[offset]
781
+ offset += 1
782
+ codec_specific_configuration = data[
783
+ offset : offset + codec_specific_configuration_length
784
+ ]
785
+ offset += codec_specific_configuration_length
786
+ metadata_length = data[offset]
787
+ offset += 1
788
+ metadata = le_audio.Metadata.from_bytes(
789
+ data[offset : offset + metadata_length]
790
+ )
791
+ offset += metadata_length
792
+
793
+ bis = []
794
+ for _ in range(num_bis):
795
+ bis_index = data[offset]
796
+ offset += 1
797
+ bis_codec_specific_configuration_length = data[offset]
798
+ offset += 1
799
+ bis_codec_specific_configuration = data[
800
+ offset : offset + bis_codec_specific_configuration_length
801
+ ]
802
+ offset += bis_codec_specific_configuration_length
803
+ bis.append(
804
+ cls.BIS(
805
+ bis_index,
806
+ CodecSpecificConfiguration.from_bytes(
807
+ bis_codec_specific_configuration
808
+ ),
809
+ )
810
+ )
811
+
812
+ subgroups.append(
813
+ cls.Subgroup(
814
+ codec_id,
815
+ CodecSpecificConfiguration.from_bytes(codec_specific_configuration),
816
+ metadata,
817
+ bis,
818
+ )
819
+ )
820
+
821
+ return cls(presentation_delay, subgroups)
822
+
823
+
728
824
  # -----------------------------------------------------------------------------
729
825
  # Server
730
826
  # -----------------------------------------------------------------------------
@@ -744,9 +840,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
744
840
  supported_sink_context: ContextType,
745
841
  available_source_context: ContextType,
746
842
  available_sink_context: ContextType,
747
- sink_pac: Sequence[PacRecord] = [],
843
+ sink_pac: Sequence[PacRecord] = (),
748
844
  sink_audio_locations: Optional[AudioLocation] = None,
749
- source_pac: Sequence[PacRecord] = [],
845
+ source_pac: Sequence[PacRecord] = (),
750
846
  source_audio_locations: Optional[AudioLocation] = None,
751
847
  ) -> None:
752
848
  characteristics = []
@@ -0,0 +1,49 @@
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+ import dataclasses
20
+ from typing import List
21
+ from typing_extensions import Self
22
+
23
+
24
+ # -----------------------------------------------------------------------------
25
+ # Classes
26
+ # -----------------------------------------------------------------------------
27
+ @dataclasses.dataclass
28
+ class Metadata:
29
+ @dataclasses.dataclass
30
+ class Entry:
31
+ tag: int
32
+ data: bytes
33
+
34
+ entries: List[Entry]
35
+
36
+ @classmethod
37
+ def from_bytes(cls, data: bytes) -> Self:
38
+ entries = []
39
+ offset = 0
40
+ length = len(data)
41
+ while length >= 2:
42
+ entry_length = data[offset]
43
+ entry_tag = data[offset + 1]
44
+ entry_data = data[offset + 2 : offset + 2 + entry_length - 1]
45
+ entries.append(cls.Entry(entry_tag, entry_data))
46
+ length -= entry_length
47
+ offset += entry_length
48
+
49
+ return cls(entries)
bumble/profiles/pbp.py ADDED
@@ -0,0 +1,46 @@
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+ import dataclasses
20
+ import enum
21
+ from typing_extensions import Self
22
+
23
+ from bumble.profiles import le_audio
24
+
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # Classes
28
+ # -----------------------------------------------------------------------------
29
+ @dataclasses.dataclass
30
+ class PublicBroadcastAnnouncement:
31
+ class Features(enum.IntFlag):
32
+ ENCRYPTED = 1 << 0
33
+ STANDARD_QUALITY_CONFIGURATION = 1 << 1
34
+ HIGH_QUALITY_CONFIGURATION = 1 << 2
35
+
36
+ features: Features
37
+ metadata: le_audio.Metadata
38
+
39
+ @classmethod
40
+ def from_bytes(cls, data: bytes) -> Self:
41
+ features = cls.Features(data[0])
42
+ metadata_length = data[1]
43
+ metadata_ltv = data[1 : 1 + metadata_length]
44
+ return cls(
45
+ features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
46
+ )