bumble 0.0.220__py3-none-any.whl → 0.0.222__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 (102) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +5 -5
  3. bumble/apps/auracast.py +746 -473
  4. bumble/apps/bench.py +4 -5
  5. bumble/apps/console.py +5 -10
  6. bumble/apps/controller_info.py +12 -7
  7. bumble/apps/controller_loopback.py +1 -2
  8. bumble/apps/device_info.py +2 -3
  9. bumble/apps/gatt_dump.py +0 -1
  10. bumble/apps/lea_unicast/app.py +1 -1
  11. bumble/apps/pair.py +49 -46
  12. bumble/apps/pandora_server.py +2 -2
  13. bumble/apps/player/player.py +10 -12
  14. bumble/apps/rfcomm_bridge.py +10 -11
  15. bumble/apps/scan.py +1 -3
  16. bumble/apps/speaker/speaker.py +3 -4
  17. bumble/at.py +4 -5
  18. bumble/att.py +91 -25
  19. bumble/audio/io.py +5 -3
  20. bumble/avc.py +1 -2
  21. bumble/avctp.py +2 -3
  22. bumble/avdtp.py +53 -57
  23. bumble/avrcp.py +25 -27
  24. bumble/codecs.py +15 -15
  25. bumble/colors.py +7 -8
  26. bumble/controller.py +663 -391
  27. bumble/core.py +41 -49
  28. bumble/crypto/__init__.py +2 -1
  29. bumble/crypto/builtin.py +2 -8
  30. bumble/data_types.py +2 -1
  31. bumble/decoder.py +2 -3
  32. bumble/device.py +171 -142
  33. bumble/drivers/__init__.py +3 -2
  34. bumble/drivers/intel.py +6 -8
  35. bumble/drivers/rtk.py +1 -1
  36. bumble/gatt.py +9 -9
  37. bumble/gatt_adapters.py +6 -6
  38. bumble/gatt_client.py +110 -60
  39. bumble/gatt_server.py +209 -139
  40. bumble/hci.py +87 -74
  41. bumble/helpers.py +5 -5
  42. bumble/hfp.py +27 -26
  43. bumble/hid.py +9 -9
  44. bumble/host.py +44 -50
  45. bumble/keys.py +17 -17
  46. bumble/l2cap.py +1070 -218
  47. bumble/link.py +26 -159
  48. bumble/ll.py +200 -0
  49. bumble/pairing.py +14 -15
  50. bumble/pandora/__init__.py +2 -2
  51. bumble/pandora/device.py +6 -4
  52. bumble/pandora/host.py +19 -10
  53. bumble/pandora/l2cap.py +8 -9
  54. bumble/pandora/security.py +18 -16
  55. bumble/pandora/utils.py +4 -4
  56. bumble/profiles/aics.py +6 -8
  57. bumble/profiles/ams.py +3 -5
  58. bumble/profiles/ancs.py +11 -11
  59. bumble/profiles/ascs.py +5 -5
  60. bumble/profiles/asha.py +10 -9
  61. bumble/profiles/bass.py +9 -3
  62. bumble/profiles/battery_service.py +1 -2
  63. bumble/profiles/csip.py +9 -10
  64. bumble/profiles/device_information_service.py +16 -17
  65. bumble/profiles/gap.py +3 -4
  66. bumble/profiles/gatt_service.py +0 -1
  67. bumble/profiles/gmap.py +12 -13
  68. bumble/profiles/hap.py +3 -3
  69. bumble/profiles/heart_rate_service.py +7 -8
  70. bumble/profiles/le_audio.py +1 -1
  71. bumble/profiles/mcp.py +28 -28
  72. bumble/profiles/pacs.py +13 -17
  73. bumble/profiles/pbp.py +16 -0
  74. bumble/profiles/vcs.py +2 -2
  75. bumble/profiles/vocs.py +6 -9
  76. bumble/rfcomm.py +19 -18
  77. bumble/sdp.py +12 -11
  78. bumble/smp.py +20 -30
  79. bumble/snoop.py +2 -1
  80. bumble/tools/generate_company_id_list.py +1 -1
  81. bumble/tools/intel_util.py +2 -2
  82. bumble/tools/rtk_fw_download.py +1 -1
  83. bumble/tools/rtk_util.py +1 -1
  84. bumble/transport/__init__.py +1 -2
  85. bumble/transport/android_emulator.py +2 -3
  86. bumble/transport/android_netsim.py +49 -40
  87. bumble/transport/common.py +9 -9
  88. bumble/transport/file.py +1 -2
  89. bumble/transport/hci_socket.py +2 -3
  90. bumble/transport/pty.py +3 -5
  91. bumble/transport/pyusb.py +8 -5
  92. bumble/transport/serial.py +1 -2
  93. bumble/transport/vhci.py +1 -2
  94. bumble/transport/ws_server.py +2 -3
  95. bumble/utils.py +22 -9
  96. bumble/vendor/android/hci.py +4 -2
  97. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/METADATA +3 -2
  98. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/RECORD +102 -101
  99. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/WHEEL +0 -0
  100. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/entry_points.txt +0 -0
  101. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/licenses/LICENSE +0 -0
  102. {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/top_level.txt +0 -0
bumble/att.py CHANGED
@@ -29,18 +29,18 @@ import enum
29
29
  import functools
30
30
  import inspect
31
31
  import struct
32
+ from collections.abc import Awaitable, Callable
32
33
  from typing import (
33
34
  TYPE_CHECKING,
34
- Awaitable,
35
- Callable,
36
35
  ClassVar,
37
36
  Generic,
38
- Optional,
37
+ TypeAlias,
39
38
  TypeVar,
40
- Union,
41
39
  )
42
40
 
43
- from bumble import hci, utils
41
+ from typing_extensions import TypeIs
42
+
43
+ from bumble import hci, l2cap, utils
44
44
  from bumble.colors import color
45
45
  from bumble.core import UUID, InvalidOperationError, ProtocolError
46
46
  from bumble.hci import HCI_Object
@@ -53,6 +53,14 @@ if TYPE_CHECKING:
53
53
 
54
54
  _T = TypeVar('_T')
55
55
 
56
+ Bearer: TypeAlias = "Connection | l2cap.LeCreditBasedChannel"
57
+ EnhancedBearer: TypeAlias = l2cap.LeCreditBasedChannel
58
+
59
+
60
+ def is_enhanced_bearer(bearer: Bearer) -> TypeIs[EnhancedBearer]:
61
+ return isinstance(bearer, EnhancedBearer)
62
+
63
+
56
64
  # -----------------------------------------------------------------------------
57
65
  # Constants
58
66
  # -----------------------------------------------------------------------------
@@ -61,6 +69,7 @@ _T = TypeVar('_T')
61
69
 
62
70
  ATT_CID = 0x04
63
71
  ATT_PSM = 0x001F
72
+ EATT_PSM = 0x0027
64
73
 
65
74
  class Opcode(hci.SpecableEnum):
66
75
  ATT_ERROR_RESPONSE = 0x01
@@ -220,7 +229,7 @@ class ATT_PDU:
220
229
  fields: ClassVar[hci.Fields] = ()
221
230
  op_code: int = dataclasses.field(init=False)
222
231
  name: str = dataclasses.field(init=False)
223
- _payload: Optional[bytes] = dataclasses.field(default=None, init=False)
232
+ _payload: bytes | None = dataclasses.field(default=None, init=False)
224
233
 
225
234
  @classmethod
226
235
  def from_bytes(cls, pdu: bytes) -> ATT_PDU:
@@ -760,31 +769,66 @@ class AttributeValue(Generic[_T]):
760
769
 
761
770
  def __init__(
762
771
  self,
763
- read: Union[
764
- Callable[[Connection], _T],
765
- Callable[[Connection], Awaitable[_T]],
766
- None,
767
- ] = None,
768
- write: Union[
769
- Callable[[Connection, _T], None],
770
- Callable[[Connection, _T], Awaitable[None]],
771
- None,
772
- ] = None,
772
+ read: (
773
+ Callable[[Connection], _T] | Callable[[Connection], Awaitable[_T]] | None
774
+ ) = None,
775
+ write: (
776
+ Callable[[Connection, _T], None]
777
+ | Callable[[Connection, _T], Awaitable[None]]
778
+ | None
779
+ ) = None,
773
780
  ):
774
781
  self._read = read
775
782
  self._write = write
776
783
 
777
- def read(self, connection: Connection) -> Union[_T, Awaitable[_T]]:
784
+ def read(self, connection: Connection) -> _T | Awaitable[_T]:
778
785
  if self._read is None:
779
786
  raise InvalidOperationError('AttributeValue has no read function')
780
787
  return self._read(connection)
781
788
 
782
- def write(self, connection: Connection, value: _T) -> Union[Awaitable[None], None]:
789
+ def write(self, connection: Connection, value: _T) -> Awaitable[None] | None:
783
790
  if self._write is None:
784
791
  raise InvalidOperationError('AttributeValue has no write function')
785
792
  return self._write(connection, value)
786
793
 
787
794
 
795
+ # -----------------------------------------------------------------------------
796
+ class AttributeValueV2(Generic[_T]):
797
+ '''
798
+ Attribute value compatible with enhanced bearers.
799
+
800
+ The only difference between AttributeValue and AttributeValueV2 is that the actual
801
+ bearer (ACL connection for un-enhanced bearer, L2CAP channel for enhanced bearer)
802
+ will be passed into read and write callbacks in V2, while in V1 it is always
803
+ the base ACL connection.
804
+
805
+ This is only required when attributes must distinguish bearers, otherwise normal
806
+ `AttributeValue` objects are also applicable in enhanced bearers.
807
+ '''
808
+
809
+ def __init__(
810
+ self,
811
+ read: Callable[[Bearer], Awaitable[_T]] | Callable[[Bearer], _T] | None = None,
812
+ write: (
813
+ Callable[[Bearer, _T], Awaitable[None]]
814
+ | Callable[[Bearer, _T], None]
815
+ | None
816
+ ) = None,
817
+ ):
818
+ self._read = read
819
+ self._write = write
820
+
821
+ def read(self, bearer: Bearer) -> _T | Awaitable[_T]:
822
+ if self._read is None:
823
+ raise InvalidOperationError('AttributeValue has no read function')
824
+ return self._read(bearer)
825
+
826
+ def write(self, bearer: Bearer, value: _T) -> Awaitable[None] | None:
827
+ if self._write is None:
828
+ raise InvalidOperationError('AttributeValue has no write function')
829
+ return self._write(bearer, value)
830
+
831
+
788
832
  # -----------------------------------------------------------------------------
789
833
  class Attribute(utils.EventEmitter, Generic[_T]):
790
834
  class Permissions(enum.IntFlag):
@@ -828,13 +872,13 @@ class Attribute(utils.EventEmitter, Generic[_T]):
828
872
  EVENT_READ = "read"
829
873
  EVENT_WRITE = "write"
830
874
 
831
- value: Union[AttributeValue[_T], _T, None]
875
+ value: AttributeValue[_T] | _T | None
832
876
 
833
877
  def __init__(
834
878
  self,
835
- attribute_type: Union[str, bytes, UUID],
836
- permissions: Union[str, Attribute.Permissions],
837
- value: Union[AttributeValue[_T], _T, None] = None,
879
+ attribute_type: str | bytes | UUID,
880
+ permissions: str | Attribute.Permissions,
881
+ value: AttributeValue[_T] | _T | None = None,
838
882
  ) -> None:
839
883
  utils.EventEmitter.__init__(self)
840
884
  self.handle = 0
@@ -860,7 +904,8 @@ class Attribute(utils.EventEmitter, Generic[_T]):
860
904
  def decode_value(self, value: bytes) -> _T:
861
905
  return value # type: ignore
862
906
 
863
- async def read_value(self, connection: Connection) -> bytes:
907
+ async def read_value(self, bearer: Bearer) -> bytes:
908
+ connection = bearer.connection if is_enhanced_bearer(bearer) else bearer
864
909
  if (
865
910
  (self.permissions & self.READ_REQUIRES_ENCRYPTION)
866
911
  and connection is not None
@@ -883,7 +928,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
883
928
  error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
884
929
  )
885
930
 
886
- value: Union[_T, None]
931
+ value: _T | None
887
932
  if isinstance(self.value, AttributeValue):
888
933
  try:
889
934
  read_value = self.value.read(connection)
@@ -895,6 +940,17 @@ class Attribute(utils.EventEmitter, Generic[_T]):
895
940
  raise ATT_Error(
896
941
  error_code=error.error_code, att_handle=self.handle
897
942
  ) from error
943
+ elif isinstance(self.value, AttributeValueV2):
944
+ try:
945
+ read_value = self.value.read(bearer)
946
+ if inspect.isawaitable(read_value):
947
+ value = await read_value
948
+ else:
949
+ value = read_value
950
+ except ATT_Error as error:
951
+ raise ATT_Error(
952
+ error_code=error.error_code, att_handle=self.handle
953
+ ) from error
898
954
  else:
899
955
  value = self.value
900
956
 
@@ -902,7 +958,8 @@ class Attribute(utils.EventEmitter, Generic[_T]):
902
958
 
903
959
  return b'' if value is None else self.encode_value(value)
904
960
 
905
- async def write_value(self, connection: Connection, value: bytes) -> None:
961
+ async def write_value(self, bearer: Bearer, value: bytes) -> None:
962
+ connection = bearer.connection if is_enhanced_bearer(bearer) else bearer
906
963
  if (
907
964
  (self.permissions & self.WRITE_REQUIRES_ENCRYPTION)
908
965
  and connection is not None
@@ -936,6 +993,15 @@ class Attribute(utils.EventEmitter, Generic[_T]):
936
993
  raise ATT_Error(
937
994
  error_code=error.error_code, att_handle=self.handle
938
995
  ) from error
996
+ elif isinstance(self.value, AttributeValueV2):
997
+ try:
998
+ result = self.value.write(bearer, decoded_value)
999
+ if inspect.isawaitable(result):
1000
+ await result
1001
+ except ATT_Error as error:
1002
+ raise ATT_Error(
1003
+ error_code=error.error_code, att_handle=self.handle
1004
+ ) from error
939
1005
  else:
940
1006
  self.value = decoded_value
941
1007
 
bumble/audio/io.py CHANGED
@@ -26,7 +26,8 @@ import logging
26
26
  import pathlib
27
27
  import sys
28
28
  import wave
29
- from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO
29
+ from collections.abc import AsyncGenerator
30
+ from typing import TYPE_CHECKING, BinaryIO
30
31
 
31
32
  from bumble.colors import color
32
33
 
@@ -545,5 +546,6 @@ class SoundDeviceAudioInput(ThreadedAudioInput):
545
546
  return bytes(pcm_buffer)
546
547
 
547
548
  def _close(self):
548
- self._stream.stop()
549
- self._stream = None
549
+ if self._stream:
550
+ self._stream.stop()
551
+ self._stream = None
bumble/avc.py CHANGED
@@ -19,7 +19,6 @@ from __future__ import annotations
19
19
 
20
20
  import enum
21
21
  import struct
22
- from typing import Union
23
22
 
24
23
  from bumble import core, utils
25
24
 
@@ -166,7 +165,7 @@ class Frame:
166
165
 
167
166
  def to_bytes(
168
167
  self,
169
- ctype_or_response: Union[CommandFrame.CommandType, ResponseFrame.ResponseCode],
168
+ ctype_or_response: CommandFrame.CommandType | ResponseFrame.ResponseCode,
170
169
  ) -> bytes:
171
170
  # TODO: support extended subunit types and ids.
172
171
  return (
bumble/avctp.py CHANGED
@@ -21,7 +21,6 @@ import logging
21
21
  import struct
22
22
  from collections.abc import Callable
23
23
  from enum import IntEnum
24
- from typing import Optional
25
24
 
26
25
  from bumble import core, l2cap
27
26
  from bumble.colors import color
@@ -147,7 +146,7 @@ class MessageAssembler:
147
146
  class Protocol:
148
147
  CommandHandler = Callable[[int, bytes], None]
149
148
  command_handlers: dict[int, CommandHandler] # Command handlers, by PID
150
- ResponseHandler = Callable[[int, Optional[bytes]], None]
149
+ ResponseHandler = Callable[[int, bytes | None], None]
151
150
  response_handlers: dict[int, ResponseHandler] # Response handlers, by PID
152
151
  next_transaction_label: int
153
152
  message_assembler: MessageAssembler
@@ -258,7 +257,7 @@ class Protocol:
258
257
 
259
258
  def send_ipid(self, transaction_label: int, pid: int) -> None:
260
259
  logger.debug(
261
- ">>> AVCTP ipid: " f"transaction_label={transaction_label}, " f"pid={pid}"
260
+ f">>> AVCTP ipid: transaction_label={transaction_label}, pid={pid}"
262
261
  )
263
262
  self.send_message(transaction_label, False, True, pid, b'')
264
263