bumble 0.0.211__py3-none-any.whl → 0.0.213__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 (95) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +6 -0
  3. bumble/apps/README.md +0 -3
  4. bumble/apps/auracast.py +11 -9
  5. bumble/apps/bench.py +482 -31
  6. bumble/apps/console.py +5 -5
  7. bumble/apps/controller_info.py +47 -10
  8. bumble/apps/controller_loopback.py +7 -3
  9. bumble/apps/controllers.py +2 -2
  10. bumble/apps/device_info.py +2 -2
  11. bumble/apps/gatt_dump.py +2 -2
  12. bumble/apps/gg_bridge.py +2 -2
  13. bumble/apps/hci_bridge.py +2 -2
  14. bumble/apps/l2cap_bridge.py +2 -2
  15. bumble/apps/lea_unicast/app.py +6 -1
  16. bumble/apps/pair.py +204 -43
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/rfcomm_bridge.py +1 -1
  19. bumble/apps/scan.py +2 -2
  20. bumble/apps/show.py +4 -2
  21. bumble/apps/speaker/speaker.html +1 -0
  22. bumble/apps/speaker/speaker.js +113 -62
  23. bumble/apps/speaker/speaker.py +126 -18
  24. bumble/at.py +4 -4
  25. bumble/att.py +15 -18
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +5 -5
  28. bumble/avdtp.py +138 -88
  29. bumble/avrcp.py +52 -58
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/core.py +13 -7
  33. bumble/{crypto.py → crypto/__init__.py} +11 -95
  34. bumble/crypto/builtin.py +652 -0
  35. bumble/crypto/cryptography.py +84 -0
  36. bumble/device.py +688 -345
  37. bumble/drivers/__init__.py +2 -2
  38. bumble/drivers/common.py +0 -2
  39. bumble/drivers/intel.py +40 -40
  40. bumble/drivers/rtk.py +28 -35
  41. bumble/gatt.py +7 -9
  42. bumble/gatt_adapters.py +4 -5
  43. bumble/gatt_client.py +31 -34
  44. bumble/gatt_server.py +15 -17
  45. bumble/hci.py +2635 -2878
  46. bumble/helpers.py +4 -5
  47. bumble/hfp.py +76 -57
  48. bumble/hid.py +24 -12
  49. bumble/host.py +117 -34
  50. bumble/keys.py +68 -52
  51. bumble/l2cap.py +329 -403
  52. bumble/link.py +6 -270
  53. bumble/pairing.py +23 -20
  54. bumble/pandora/__init__.py +1 -1
  55. bumble/pandora/config.py +2 -2
  56. bumble/pandora/device.py +6 -6
  57. bumble/pandora/host.py +38 -39
  58. bumble/pandora/l2cap.py +4 -4
  59. bumble/pandora/security.py +73 -57
  60. bumble/pandora/utils.py +3 -3
  61. bumble/profiles/aics.py +3 -5
  62. bumble/profiles/ancs.py +3 -1
  63. bumble/profiles/ascs.py +143 -136
  64. bumble/profiles/asha.py +13 -8
  65. bumble/profiles/bap.py +3 -4
  66. bumble/profiles/csip.py +3 -5
  67. bumble/profiles/device_information_service.py +2 -2
  68. bumble/profiles/gap.py +2 -2
  69. bumble/profiles/gatt_service.py +1 -3
  70. bumble/profiles/hap.py +42 -58
  71. bumble/profiles/le_audio.py +4 -4
  72. bumble/profiles/mcp.py +16 -13
  73. bumble/profiles/vcs.py +8 -10
  74. bumble/profiles/vocs.py +6 -9
  75. bumble/rfcomm.py +27 -18
  76. bumble/rtp.py +1 -2
  77. bumble/sdp.py +2 -2
  78. bumble/smp.py +71 -69
  79. bumble/tools/rtk_util.py +2 -2
  80. bumble/transport/__init__.py +2 -16
  81. bumble/transport/android_netsim.py +5 -5
  82. bumble/transport/common.py +4 -4
  83. bumble/transport/pyusb.py +2 -2
  84. bumble/utils.py +2 -5
  85. bumble/vendor/android/hci.py +118 -200
  86. bumble/vendor/zephyr/hci.py +32 -27
  87. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
  88. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
  89. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  90. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  91. bumble/apps/link_relay/__init__.py +0 -0
  92. bumble/apps/link_relay/link_relay.py +0 -289
  93. bumble/apps/link_relay/logging.yml +0 -21
  94. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  95. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
  import logging
24
24
  import pathlib
25
25
  import platform
26
- from typing import Dict, Iterable, Optional, Type, TYPE_CHECKING
26
+ from typing import Iterable, Optional, TYPE_CHECKING
27
27
 
28
28
  from bumble.drivers import rtk, intel
29
29
  from bumble.drivers.common import Driver
@@ -45,7 +45,7 @@ async def get_driver_for_host(host: Host) -> Optional[Driver]:
45
45
  found.
46
46
  If a "driver" HCI metadata entry is present, only that driver class will be probed.
47
47
  """
48
- driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver, "intel": intel.Driver}
48
+ driver_classes: dict[str, type[Driver]] = {"rtk": rtk.Driver, "intel": intel.Driver}
49
49
  probe_list: Iterable[str]
50
50
  if driver_name := host.hci_metadata.get("driver"):
51
51
  # Only probe a single driver
bumble/drivers/common.py CHANGED
@@ -20,8 +20,6 @@ Common types for drivers.
20
20
  # -----------------------------------------------------------------------------
21
21
  import abc
22
22
 
23
- from bumble import core
24
-
25
23
 
26
24
  # -----------------------------------------------------------------------------
27
25
  # Classes
bumble/drivers/intel.py CHANGED
@@ -28,7 +28,7 @@ import os
28
28
  import pathlib
29
29
  import platform
30
30
  import struct
31
- from typing import Any, Deque, Optional, TYPE_CHECKING
31
+ from typing import Any, Optional, TYPE_CHECKING
32
32
 
33
33
  from bumble import core
34
34
  from bumble.drivers import common
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
50
50
 
51
51
  INTEL_USB_PRODUCTS = {
52
52
  (0x8087, 0x0032), # AX210
53
+ (0x8087, 0x0033), # AX211
53
54
  (0x8087, 0x0036), # BE200
54
55
  }
55
56
 
@@ -89,54 +90,51 @@ HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND = hci.hci_vendor_command_op_code(0x000E)
89
90
  hci.HCI_Command.register_commands(globals())
90
91
 
91
92
 
92
- @hci.HCI_Command.command(
93
- fields=[
94
- ("param0", 1),
95
- ],
96
- return_parameters_fields=[
93
+ @hci.HCI_Command.command
94
+ @dataclasses.dataclass
95
+ class HCI_Intel_Read_Version_Command(hci.HCI_Command):
96
+ param0: int = dataclasses.field(metadata=hci.metadata(1))
97
+
98
+ return_parameters_fields = [
97
99
  ("status", hci.STATUS_SPEC),
98
100
  ("tlv", "*"),
99
- ],
100
- )
101
- class HCI_Intel_Read_Version_Command(hci.HCI_Command):
102
- pass
101
+ ]
103
102
 
104
103
 
105
- @hci.HCI_Command.command(
106
- fields=[("data_type", 1), ("data", "*")],
107
- return_parameters_fields=[
108
- ("status", 1),
109
- ],
110
- )
104
+ @hci.HCI_Command.command
105
+ @dataclasses.dataclass
111
106
  class Hci_Intel_Secure_Send_Command(hci.HCI_Command):
112
- pass
113
-
114
-
115
- @hci.HCI_Command.command(
116
- fields=[
117
- ("reset_type", 1),
118
- ("patch_enable", 1),
119
- ("ddc_reload", 1),
120
- ("boot_option", 1),
121
- ("boot_address", 4),
122
- ],
123
- return_parameters_fields=[
124
- ("data", "*"),
125
- ],
126
- )
107
+ data_type: int = dataclasses.field(metadata=hci.metadata(1))
108
+ data: bytes = dataclasses.field(metadata=hci.metadata("*"))
109
+
110
+ return_parameters_fields = [
111
+ ("status", 1),
112
+ ]
113
+
114
+
115
+ @hci.HCI_Command.command
116
+ @dataclasses.dataclass
127
117
  class HCI_Intel_Reset_Command(hci.HCI_Command):
128
- pass
118
+ reset_type: int = dataclasses.field(metadata=hci.metadata(1))
119
+ patch_enable: int = dataclasses.field(metadata=hci.metadata(1))
120
+ ddc_reload: int = dataclasses.field(metadata=hci.metadata(1))
121
+ boot_option: int = dataclasses.field(metadata=hci.metadata(1))
122
+ boot_address: int = dataclasses.field(metadata=hci.metadata(4))
123
+
124
+ return_parameters_fields = [
125
+ ("data", "*"),
126
+ ]
129
127
 
130
128
 
131
- @hci.HCI_Command.command(
132
- fields=[("data", "*")],
133
- return_parameters_fields=[
129
+ @hci.HCI_Command.command
130
+ @dataclasses.dataclass
131
+ class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
132
+ data: bytes = dataclasses.field(metadata=hci.metadata("*"))
133
+
134
+ return_parameters_fields = [
134
135
  ("status", hci.STATUS_SPEC),
135
136
  ("params", "*"),
136
- ],
137
- )
138
- class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
139
- pass
137
+ ]
140
138
 
141
139
 
142
140
  # -----------------------------------------------------------------------------
@@ -293,6 +291,7 @@ class HardwareVariant(utils.OpenIntEnum):
293
291
  # This is a just a partial list.
294
292
  # Add other constants here as new hardware is encountered and tested.
295
293
  TYPHOON_PEAK = 0x17
294
+ GARFIELD_PEAK = 0x19
296
295
  GALE_PEAK = 0x1C
297
296
 
298
297
 
@@ -346,7 +345,7 @@ class Driver(common.Driver):
346
345
  def __init__(self, host: Host) -> None:
347
346
  self.host = host
348
347
  self.max_in_flight_firmware_load_commands = 1
349
- self.pending_firmware_load_commands: Deque[hci.HCI_Command] = (
348
+ self.pending_firmware_load_commands: collections.deque[hci.HCI_Command] = (
350
349
  collections.deque()
351
350
  )
352
351
  self.can_send_firmware_load_command = asyncio.Event()
@@ -471,6 +470,7 @@ class Driver(common.Driver):
471
470
  raise DriverError("hardware platform not supported")
472
471
  if hardware_info.variant not in (
473
472
  HardwareVariant.TYPHOON_PEAK,
473
+ HardwareVariant.GARFIELD_PEAK,
474
474
  HardwareVariant.GALE_PEAK,
475
475
  ):
476
476
  raise DriverError("hardware variant not supported")
bumble/drivers/rtk.py CHANGED
@@ -20,7 +20,7 @@ Based on various online bits of information, including the Linux kernel.
20
20
  # -----------------------------------------------------------------------------
21
21
  # Imports
22
22
  # -----------------------------------------------------------------------------
23
- from dataclasses import dataclass
23
+ from dataclasses import dataclass, field
24
24
  import asyncio
25
25
  import enum
26
26
  import logging
@@ -29,19 +29,11 @@ import os
29
29
  import pathlib
30
30
  import platform
31
31
  import struct
32
- from typing import Tuple
33
32
  import weakref
34
33
 
35
34
 
36
35
  from bumble import core
37
- from bumble.hci import (
38
- hci_vendor_command_op_code,
39
- STATUS_SPEC,
40
- HCI_SUCCESS,
41
- HCI_Command,
42
- HCI_Reset_Command,
43
- HCI_Read_Local_Version_Information_Command,
44
- )
36
+ from bumble import hci
45
37
  from bumble.drivers import common
46
38
 
47
39
  # -----------------------------------------------------------------------------
@@ -183,27 +175,29 @@ RTK_USB_PRODUCTS = {
183
175
  # -----------------------------------------------------------------------------
184
176
  # HCI Commands
185
177
  # -----------------------------------------------------------------------------
186
- HCI_RTK_READ_ROM_VERSION_COMMAND = hci_vendor_command_op_code(0x6D)
187
- HCI_RTK_DOWNLOAD_COMMAND = hci_vendor_command_op_code(0x20)
188
- HCI_RTK_DROP_FIRMWARE_COMMAND = hci_vendor_command_op_code(0x66)
189
- HCI_Command.register_commands(globals())
178
+ HCI_RTK_READ_ROM_VERSION_COMMAND = hci.hci_vendor_command_op_code(0x6D)
179
+ HCI_RTK_DOWNLOAD_COMMAND = hci.hci_vendor_command_op_code(0x20)
180
+ HCI_RTK_DROP_FIRMWARE_COMMAND = hci.hci_vendor_command_op_code(0x66)
181
+ hci.HCI_Command.register_commands(globals())
190
182
 
191
183
 
192
- @HCI_Command.command(return_parameters_fields=[("status", STATUS_SPEC), ("version", 1)])
193
- class HCI_RTK_Read_ROM_Version_Command(HCI_Command):
194
- pass
184
+ @hci.HCI_Command.command
185
+ @dataclass
186
+ class HCI_RTK_Read_ROM_Version_Command(hci.HCI_Command):
187
+ return_parameters_fields = [("status", hci.STATUS_SPEC), ("version", 1)]
195
188
 
196
189
 
197
- @HCI_Command.command(
198
- fields=[("index", 1), ("payload", RTK_FRAGMENT_LENGTH)],
199
- return_parameters_fields=[("status", STATUS_SPEC), ("index", 1)],
200
- )
201
- class HCI_RTK_Download_Command(HCI_Command):
202
- pass
190
+ @hci.HCI_Command.command
191
+ @dataclass
192
+ class HCI_RTK_Download_Command(hci.HCI_Command):
193
+ index: int = field(metadata=hci.metadata(1))
194
+ payload: bytes = field(metadata=hci.metadata(RTK_FRAGMENT_LENGTH))
195
+ return_parameters_fields = [("status", hci.STATUS_SPEC), ("index", 1)]
203
196
 
204
197
 
205
- @HCI_Command.command()
206
- class HCI_RTK_Drop_Firmware_Command(HCI_Command):
198
+ @hci.HCI_Command.command
199
+ @dataclass
200
+ class HCI_RTK_Drop_Firmware_Command(hci.HCI_Command):
207
201
  pass
208
202
 
209
203
 
@@ -294,7 +288,7 @@ class Driver(common.Driver):
294
288
  @dataclass
295
289
  class DriverInfo:
296
290
  rom: int
297
- hci: Tuple[int, int]
291
+ hci: tuple[int, int]
298
292
  config_needed: bool
299
293
  has_rom_version: bool
300
294
  has_msft_ext: bool = False
@@ -499,17 +493,17 @@ class Driver(common.Driver):
499
493
  async def driver_info_for_host(cls, host):
500
494
  try:
501
495
  await host.send_command(
502
- HCI_Reset_Command(),
496
+ hci.HCI_Reset_Command(),
503
497
  check_result=True,
504
498
  response_timeout=cls.POST_RESET_DELAY,
505
499
  )
506
500
  host.ready = True # Needed to let the host know the controller is ready.
507
501
  except asyncio.exceptions.TimeoutError:
508
502
  logger.warning("timeout waiting for hci reset, retrying")
509
- await host.send_command(HCI_Reset_Command(), check_result=True)
503
+ await host.send_command(hci.HCI_Reset_Command(), check_result=True)
510
504
  host.ready = True
511
505
 
512
- command = HCI_Read_Local_Version_Information_Command()
506
+ command = hci.HCI_Read_Local_Version_Information_Command()
513
507
  response = await host.send_command(command, check_result=True)
514
508
  if response.command_opcode != command.op_code:
515
509
  logger.error("failed to probe local version information")
@@ -596,7 +590,7 @@ class Driver(common.Driver):
596
590
  response = await self.host.send_command(
597
591
  HCI_RTK_Read_ROM_Version_Command(), check_result=True
598
592
  )
599
- if response.return_parameters.status != HCI_SUCCESS:
593
+ if response.return_parameters.status != hci.HCI_SUCCESS:
600
594
  logger.warning("can't get ROM version")
601
595
  return
602
596
  rom_version = response.return_parameters.version
@@ -634,9 +628,8 @@ class Driver(common.Driver):
634
628
  fragment = payload[fragment_offset : fragment_offset + RTK_FRAGMENT_LENGTH]
635
629
  logger.debug(f"downloading fragment {fragment_index}")
636
630
  await self.host.send_command(
637
- HCI_RTK_Download_Command(
638
- index=download_index, payload=fragment, check_result=True
639
- )
631
+ HCI_RTK_Download_Command(index=download_index, payload=fragment),
632
+ check_result=True,
640
633
  )
641
634
 
642
635
  logger.debug("download complete!")
@@ -645,7 +638,7 @@ class Driver(common.Driver):
645
638
  response = await self.host.send_command(
646
639
  HCI_RTK_Read_ROM_Version_Command(), check_result=True
647
640
  )
648
- if response.return_parameters.status != HCI_SUCCESS:
641
+ if response.return_parameters.status != hci.HCI_SUCCESS:
649
642
  logger.warning("can't get ROM version")
650
643
  else:
651
644
  rom_version = response.return_parameters.version
@@ -668,7 +661,7 @@ class Driver(common.Driver):
668
661
 
669
662
  async def init_controller(self):
670
663
  await self.download_firmware()
671
- await self.host.send_command(HCI_Reset_Command(), check_result=True)
664
+ await self.host.send_command(hci.HCI_Reset_Command(), check_result=True)
672
665
  logger.info(f"loaded FW image {self.driver_info.fw_name}")
673
666
 
674
667
 
bumble/gatt.py CHANGED
@@ -27,7 +27,7 @@ import enum
27
27
  import functools
28
28
  import logging
29
29
  import struct
30
- from typing import Iterable, List, Optional, Sequence, TypeVar, Union
30
+ from typing import Iterable, Optional, Sequence, TypeVar, Union
31
31
 
32
32
  from bumble.colors import color
33
33
  from bumble.core import BaseBumbleError, UUID
@@ -350,8 +350,8 @@ class Service(Attribute):
350
350
  '''
351
351
 
352
352
  uuid: UUID
353
- characteristics: List[Characteristic]
354
- included_services: List[Service]
353
+ characteristics: list[Characteristic]
354
+ included_services: list[Service]
355
355
 
356
356
  def __init__(
357
357
  self,
@@ -448,6 +448,8 @@ class Characteristic(Attribute[_T]):
448
448
  uuid: UUID
449
449
  properties: Characteristic.Properties
450
450
 
451
+ EVENT_SUBSCRIPTION = "subscription"
452
+
451
453
  class Properties(enum.IntFlag):
452
454
  """Property flags"""
453
455
 
@@ -472,7 +474,7 @@ class Characteristic(Attribute[_T]):
472
474
  # The check for `p.name is not None` here is needed because for InFlag
473
475
  # enums, the .name property can be None, when the enum value is 0,
474
476
  # so the type hint for .name is Optional[str].
475
- enum_list: List[str] = [p.name for p in cls if p.name is not None]
477
+ enum_list: list[str] = [p.name for p in cls if p.name is not None]
476
478
  enum_list_str = ",".join(enum_list)
477
479
  raise TypeError(
478
480
  f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
@@ -577,11 +579,7 @@ class Descriptor(Attribute):
577
579
  if isinstance(self.value, bytes):
578
580
  value_str = self.value.hex()
579
581
  elif isinstance(self.value, CharacteristicValue):
580
- value = self.value.read(None)
581
- if isinstance(value, bytes):
582
- value_str = value.hex()
583
- else:
584
- value_str = '<async>'
582
+ value_str = '<dynamic>'
585
583
  else:
586
584
  value_str = '<...>'
587
585
  return (
bumble/gatt_adapters.py CHANGED
@@ -28,7 +28,6 @@ from typing import (
28
28
  Iterable,
29
29
  Literal,
30
30
  Optional,
31
- Type,
32
31
  TypeVar,
33
32
  )
34
33
 
@@ -270,7 +269,7 @@ class SerializableCharacteristicAdapter(CharacteristicAdapter[_T2]):
270
269
  `to_bytes` and `__bytes__` methods, respectively.
271
270
  '''
272
271
 
273
- def __init__(self, characteristic: Characteristic, cls: Type[_T2]) -> None:
272
+ def __init__(self, characteristic: Characteristic, cls: type[_T2]) -> None:
274
273
  super().__init__(characteristic)
275
274
  self.cls = cls
276
275
 
@@ -289,7 +288,7 @@ class SerializableCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T2]):
289
288
  '''
290
289
 
291
290
  def __init__(
292
- self, characteristic_proxy: CharacteristicProxy, cls: Type[_T2]
291
+ self, characteristic_proxy: CharacteristicProxy, cls: type[_T2]
293
292
  ) -> None:
294
293
  super().__init__(characteristic_proxy)
295
294
  self.cls = cls
@@ -311,7 +310,7 @@ class EnumCharacteristicAdapter(CharacteristicAdapter[_T3]):
311
310
  def __init__(
312
311
  self,
313
312
  characteristic: Characteristic,
314
- cls: Type[_T3],
313
+ cls: type[_T3],
315
314
  length: int,
316
315
  byteorder: Literal['little', 'big'] = 'little',
317
316
  ):
@@ -347,7 +346,7 @@ class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
347
346
  def __init__(
348
347
  self,
349
348
  characteristic_proxy: CharacteristicProxy,
350
- cls: Type[_T3],
349
+ cls: type[_T3],
351
350
  length: int,
352
351
  byteorder: Literal['little', 'big'] = 'little',
353
352
  ):
bumble/gatt_client.py CHANGED
@@ -31,15 +31,10 @@ from datetime import datetime
31
31
  from typing import (
32
32
  Any,
33
33
  Callable,
34
- Dict,
35
34
  Generic,
36
35
  Iterable,
37
- List,
38
36
  Optional,
39
- Set,
40
- Tuple,
41
37
  Union,
42
- Type,
43
38
  TypeVar,
44
39
  TYPE_CHECKING,
45
40
  )
@@ -149,8 +144,8 @@ class AttributeProxy(utils.EventEmitter, Generic[_T]):
149
144
 
150
145
  class ServiceProxy(AttributeProxy):
151
146
  uuid: UUID
152
- characteristics: List[CharacteristicProxy[bytes]]
153
- included_services: List[ServiceProxy]
147
+ characteristics: list[CharacteristicProxy[bytes]]
148
+ included_services: list[ServiceProxy]
154
149
 
155
150
  @staticmethod
156
151
  def from_client(service_class, client: Client, service_uuid: UUID):
@@ -199,8 +194,10 @@ class ServiceProxy(AttributeProxy):
199
194
 
200
195
  class CharacteristicProxy(AttributeProxy[_T]):
201
196
  properties: Characteristic.Properties
202
- descriptors: List[DescriptorProxy]
203
- subscribers: Dict[Any, Callable[[_T], Any]]
197
+ descriptors: list[DescriptorProxy]
198
+ subscribers: dict[Any, Callable[[_T], Any]]
199
+
200
+ EVENT_UPDATE = "update"
204
201
 
205
202
  def __init__(
206
203
  self,
@@ -275,7 +272,7 @@ class ProfileServiceProxy:
275
272
  Base class for profile-specific service proxies
276
273
  '''
277
274
 
278
- SERVICE_CLASS: Type[TemplateService]
275
+ SERVICE_CLASS: type[TemplateService]
279
276
 
280
277
  @classmethod
281
278
  def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
@@ -286,13 +283,13 @@ class ProfileServiceProxy:
286
283
  # GATT Client
287
284
  # -----------------------------------------------------------------------------
288
285
  class Client:
289
- services: List[ServiceProxy]
290
- cached_values: Dict[int, Tuple[datetime, bytes]]
291
- notification_subscribers: Dict[
292
- int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
286
+ services: list[ServiceProxy]
287
+ cached_values: dict[int, tuple[datetime, bytes]]
288
+ notification_subscribers: dict[
289
+ int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
293
290
  ]
294
- indication_subscribers: Dict[
295
- int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
291
+ indication_subscribers: dict[
292
+ int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
296
293
  ]
297
294
  pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
298
295
  pending_request: Optional[ATT_PDU]
@@ -308,7 +305,7 @@ class Client:
308
305
  self.services = []
309
306
  self.cached_values = {}
310
307
 
311
- connection.on('disconnection', self.on_disconnection)
308
+ connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
312
309
 
313
310
  def send_gatt_pdu(self, pdu: bytes) -> None:
314
311
  self.connection.send_l2cap_pdu(ATT_CID, pdu)
@@ -377,12 +374,12 @@ class Client:
377
374
 
378
375
  return self.connection.att_mtu
379
376
 
380
- def get_services_by_uuid(self, uuid: UUID) -> List[ServiceProxy]:
377
+ def get_services_by_uuid(self, uuid: UUID) -> list[ServiceProxy]:
381
378
  return [service for service in self.services if service.uuid == uuid]
382
379
 
383
380
  def get_characteristics_by_uuid(
384
381
  self, uuid: UUID, service: Optional[ServiceProxy] = None
385
- ) -> List[CharacteristicProxy[bytes]]:
382
+ ) -> list[CharacteristicProxy[bytes]]:
386
383
  services = [service] if service else self.services
387
384
  return [
388
385
  c
@@ -393,8 +390,8 @@ class Client:
393
390
  def get_attribute_grouping(self, attribute_handle: int) -> Optional[
394
391
  Union[
395
392
  ServiceProxy,
396
- Tuple[ServiceProxy, CharacteristicProxy],
397
- Tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy],
393
+ tuple[ServiceProxy, CharacteristicProxy],
394
+ tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy],
398
395
  ]
399
396
  ]:
400
397
  """
@@ -427,7 +424,7 @@ class Client:
427
424
  if not already_known:
428
425
  self.services.append(service)
429
426
 
430
- async def discover_services(self, uuids: Iterable[UUID] = ()) -> List[ServiceProxy]:
427
+ async def discover_services(self, uuids: Iterable[UUID] = ()) -> list[ServiceProxy]:
431
428
  '''
432
429
  See Vol 3, Part G - 4.4.1 Discover All Primary Services
433
430
  '''
@@ -499,7 +496,7 @@ class Client:
499
496
 
500
497
  return services
501
498
 
502
- async def discover_service(self, uuid: Union[str, UUID]) -> List[ServiceProxy]:
499
+ async def discover_service(self, uuid: Union[str, UUID]) -> list[ServiceProxy]:
503
500
  '''
504
501
  See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
505
502
  '''
@@ -570,7 +567,7 @@ class Client:
570
567
 
571
568
  async def discover_included_services(
572
569
  self, service: ServiceProxy
573
- ) -> List[ServiceProxy]:
570
+ ) -> list[ServiceProxy]:
574
571
  '''
575
572
  See Vol 3, Part G - 4.5.1 Find Included Services
576
573
  '''
@@ -578,7 +575,7 @@ class Client:
578
575
  starting_handle = service.handle
579
576
  ending_handle = service.end_group_handle
580
577
 
581
- included_services: List[ServiceProxy] = []
578
+ included_services: list[ServiceProxy] = []
582
579
  while starting_handle <= ending_handle:
583
580
  response = await self.send_request(
584
581
  ATT_Read_By_Type_Request(
@@ -634,7 +631,7 @@ class Client:
634
631
 
635
632
  async def discover_characteristics(
636
633
  self, uuids, service: Optional[ServiceProxy]
637
- ) -> List[CharacteristicProxy[bytes]]:
634
+ ) -> list[CharacteristicProxy[bytes]]:
638
635
  '''
639
636
  See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
640
637
  Discover Characteristics by UUID
@@ -647,12 +644,12 @@ class Client:
647
644
  services = [service] if service else self.services
648
645
 
649
646
  # Perform characteristic discovery for each service
650
- discovered_characteristics: List[CharacteristicProxy[bytes]] = []
647
+ discovered_characteristics: list[CharacteristicProxy[bytes]] = []
651
648
  for service in services:
652
649
  starting_handle = service.handle
653
650
  ending_handle = service.end_group_handle
654
651
 
655
- characteristics: List[CharacteristicProxy[bytes]] = []
652
+ characteristics: list[CharacteristicProxy[bytes]] = []
656
653
  while starting_handle <= ending_handle:
657
654
  response = await self.send_request(
658
655
  ATT_Read_By_Type_Request(
@@ -723,7 +720,7 @@ class Client:
723
720
  characteristic: Optional[CharacteristicProxy] = None,
724
721
  start_handle: Optional[int] = None,
725
722
  end_handle: Optional[int] = None,
726
- ) -> List[DescriptorProxy]:
723
+ ) -> list[DescriptorProxy]:
727
724
  '''
728
725
  See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
729
726
  '''
@@ -736,7 +733,7 @@ class Client:
736
733
  else:
737
734
  return []
738
735
 
739
- descriptors: List[DescriptorProxy] = []
736
+ descriptors: list[DescriptorProxy] = []
740
737
  while starting_handle <= ending_handle:
741
738
  response = await self.send_request(
742
739
  ATT_Find_Information_Request(
@@ -785,7 +782,7 @@ class Client:
785
782
 
786
783
  return descriptors
787
784
 
788
- async def discover_attributes(self) -> List[AttributeProxy[bytes]]:
785
+ async def discover_attributes(self) -> list[AttributeProxy[bytes]]:
789
786
  '''
790
787
  Discover all attributes, regardless of type
791
788
  '''
@@ -1000,7 +997,7 @@ class Client:
1000
997
 
1001
998
  async def read_characteristics_by_uuid(
1002
999
  self, uuid: UUID, service: Optional[ServiceProxy]
1003
- ) -> List[bytes]:
1000
+ ) -> list[bytes]:
1004
1001
  '''
1005
1002
  See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
1006
1003
  '''
@@ -1142,7 +1139,7 @@ class Client:
1142
1139
  if callable(subscriber):
1143
1140
  subscriber(notification.attribute_value)
1144
1141
  else:
1145
- subscriber.emit('update', notification.attribute_value)
1142
+ subscriber.emit(subscriber.EVENT_UPDATE, notification.attribute_value)
1146
1143
 
1147
1144
  def on_att_handle_value_indication(self, indication):
1148
1145
  # Call all subscribers
@@ -1157,7 +1154,7 @@ class Client:
1157
1154
  if callable(subscriber):
1158
1155
  subscriber(indication.attribute_value)
1159
1156
  else:
1160
- subscriber.emit('update', indication.attribute_value)
1157
+ subscriber.emit(subscriber.EVENT_UPDATE, indication.attribute_value)
1161
1158
 
1162
1159
  # Confirm that we received the indication
1163
1160
  self.send_confirmation(ATT_Handle_Value_Confirmation())