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
bumble/helpers.py CHANGED
@@ -34,9 +34,8 @@ from bumble.att import ATT_CID, ATT_PDU
34
34
  from bumble.smp import SMP_CID, SMP_Command
35
35
  from bumble.core import name_or_number
36
36
  from bumble.l2cap import (
37
+ CommandCode,
37
38
  L2CAP_PDU,
38
- L2CAP_CONNECTION_REQUEST,
39
- L2CAP_CONNECTION_RESPONSE,
40
39
  L2CAP_SIGNALING_CID,
41
40
  L2CAP_LE_SIGNALING_CID,
42
41
  L2CAP_Control_Frame,
@@ -106,14 +105,14 @@ class PacketTracer:
106
105
  self.analyzer.emit(control_frame)
107
106
 
108
107
  # Check if this signals a new channel
109
- if control_frame.code == L2CAP_CONNECTION_REQUEST:
108
+ if control_frame.code == CommandCode.L2CAP_CONNECTION_REQUEST:
110
109
  connection_request = cast(L2CAP_Connection_Request, control_frame)
111
110
  self.psms[connection_request.source_cid] = connection_request.psm
112
- elif control_frame.code == L2CAP_CONNECTION_RESPONSE:
111
+ elif control_frame.code == CommandCode.L2CAP_CONNECTION_RESPONSE:
113
112
  connection_response = cast(L2CAP_Connection_Response, control_frame)
114
113
  if (
115
114
  connection_response.result
116
- == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL
115
+ == L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL
117
116
  ):
118
117
  if self.peer and (
119
118
  psm := self.peer.psms.get(connection_response.source_cid)
bumble/hfp.py CHANGED
@@ -26,14 +26,9 @@ import enum
26
26
  import traceback
27
27
  import re
28
28
  from typing import (
29
- Dict,
30
- List,
31
29
  Union,
32
- Set,
33
30
  Any,
34
31
  Optional,
35
- Type,
36
- Tuple,
37
32
  ClassVar,
38
33
  Iterable,
39
34
  TYPE_CHECKING,
@@ -375,7 +370,7 @@ class CallLineIdentification:
375
370
  cli_validity: Optional[int] = None
376
371
 
377
372
  @classmethod
378
- def parse_from(cls: Type[Self], parameters: List[bytes]) -> Self:
373
+ def parse_from(cls, parameters: list[bytes]) -> Self:
379
374
  return cls(
380
375
  number=parameters[0].decode(),
381
376
  type=int(parameters[1]),
@@ -505,9 +500,9 @@ STATUS_CODES = {
505
500
 
506
501
  @dataclasses.dataclass
507
502
  class HfConfiguration:
508
- supported_hf_features: List[HfFeature]
509
- supported_hf_indicators: List[HfIndicator]
510
- supported_audio_codecs: List[AudioCodec]
503
+ supported_hf_features: list[HfFeature]
504
+ supported_hf_indicators: list[HfIndicator]
505
+ supported_audio_codecs: list[AudioCodec]
511
506
 
512
507
 
513
508
  @dataclasses.dataclass
@@ -535,7 +530,7 @@ class AtResponse:
535
530
  parameters: list
536
531
 
537
532
  @classmethod
538
- def parse_from(cls: Type[Self], buffer: bytearray) -> Self:
533
+ def parse_from(cls: type[Self], buffer: bytearray) -> Self:
539
534
  code_and_parameters = buffer.split(b':')
540
535
  parameters = (
541
536
  code_and_parameters[1] if len(code_and_parameters) > 1 else bytearray()
@@ -563,7 +558,7 @@ class AtCommand:
563
558
  )
564
559
 
565
560
  @classmethod
566
- def parse_from(cls: Type[Self], buffer: bytearray) -> Self:
561
+ def parse_from(cls: type[Self], buffer: bytearray) -> Self:
567
562
  if not (match := cls._PARSE_PATTERN.fullmatch(buffer.decode())):
568
563
  if buffer.startswith(b'ATA'):
569
564
  return cls(code='A', sub_code=AtCommand.SubCode.NONE, parameters=[])
@@ -598,7 +593,7 @@ class AgIndicatorState:
598
593
  """
599
594
 
600
595
  indicator: AgIndicator
601
- supported_values: Set[int]
596
+ supported_values: set[int]
602
597
  current_status: int
603
598
  index: Optional[int] = None
604
599
  enabled: bool = True
@@ -616,14 +611,14 @@ class AgIndicatorState:
616
611
  return f'(\"{self.indicator.value}\",{supported_values_text})'
617
612
 
618
613
  @classmethod
619
- def call(cls: Type[Self]) -> Self:
614
+ def call(cls: type[Self]) -> Self:
620
615
  """Default call indicator state."""
621
616
  return cls(
622
617
  indicator=AgIndicator.CALL, supported_values={0, 1}, current_status=0
623
618
  )
624
619
 
625
620
  @classmethod
626
- def callsetup(cls: Type[Self]) -> Self:
621
+ def callsetup(cls: type[Self]) -> Self:
627
622
  """Default callsetup indicator state."""
628
623
  return cls(
629
624
  indicator=AgIndicator.CALL_SETUP,
@@ -632,7 +627,7 @@ class AgIndicatorState:
632
627
  )
633
628
 
634
629
  @classmethod
635
- def callheld(cls: Type[Self]) -> Self:
630
+ def callheld(cls: type[Self]) -> Self:
636
631
  """Default call indicator state."""
637
632
  return cls(
638
633
  indicator=AgIndicator.CALL_HELD,
@@ -641,14 +636,14 @@ class AgIndicatorState:
641
636
  )
642
637
 
643
638
  @classmethod
644
- def service(cls: Type[Self]) -> Self:
639
+ def service(cls: type[Self]) -> Self:
645
640
  """Default service indicator state."""
646
641
  return cls(
647
642
  indicator=AgIndicator.SERVICE, supported_values={0, 1}, current_status=0
648
643
  )
649
644
 
650
645
  @classmethod
651
- def signal(cls: Type[Self]) -> Self:
646
+ def signal(cls: type[Self]) -> Self:
652
647
  """Default signal indicator state."""
653
648
  return cls(
654
649
  indicator=AgIndicator.SIGNAL,
@@ -657,14 +652,14 @@ class AgIndicatorState:
657
652
  )
658
653
 
659
654
  @classmethod
660
- def roam(cls: Type[Self]) -> Self:
655
+ def roam(cls: type[Self]) -> Self:
661
656
  """Default roam indicator state."""
662
657
  return cls(
663
658
  indicator=AgIndicator.CALL, supported_values={0, 1}, current_status=0
664
659
  )
665
660
 
666
661
  @classmethod
667
- def battchg(cls: Type[Self]) -> Self:
662
+ def battchg(cls: type[Self]) -> Self:
668
663
  """Default battery charge indicator state."""
669
664
  return cls(
670
665
  indicator=AgIndicator.BATTERY_CHARGE,
@@ -720,17 +715,25 @@ class HfProtocol(utils.EventEmitter):
720
715
  vrec: VoiceRecognitionState
721
716
  """
722
717
 
718
+ EVENT_CODEC_NEGOTIATION = "codec_negotiation"
719
+ EVENT_AG_INDICATOR = "ag_indicator"
720
+ EVENT_SPEAKER_VOLUME = "speaker_volume"
721
+ EVENT_MICROPHONE_VOLUME = "microphone_volume"
722
+ EVENT_RING = "ring"
723
+ EVENT_CLI_NOTIFICATION = "cli_notification"
724
+ EVENT_VOICE_RECOGNITION = "voice_recognition"
725
+
723
726
  class HfLoopTermination(HfpProtocolError):
724
727
  """Termination signal for run() loop."""
725
728
 
726
729
  supported_hf_features: int
727
- supported_audio_codecs: List[AudioCodec]
730
+ supported_audio_codecs: list[AudioCodec]
728
731
 
729
732
  supported_ag_features: int
730
- supported_ag_call_hold_operations: List[CallHoldOperation]
733
+ supported_ag_call_hold_operations: list[CallHoldOperation]
731
734
 
732
- ag_indicators: List[AgIndicatorState]
733
- hf_indicators: Dict[HfIndicator, HfIndicatorState]
735
+ ag_indicators: list[AgIndicatorState]
736
+ hf_indicators: dict[HfIndicator, HfIndicatorState]
734
737
 
735
738
  dlc: rfcomm.DLC
736
739
  command_lock: asyncio.Lock
@@ -777,7 +780,8 @@ class HfProtocol(utils.EventEmitter):
777
780
  self.dlc.sink = self._read_at
778
781
  # Stop the run() loop when L2CAP is closed.
779
782
  self.dlc.multiplexer.l2cap_channel.on(
780
- 'close', lambda: self.unsolicited_queue.put_nowait(None)
783
+ self.dlc.multiplexer.l2cap_channel.EVENT_CLOSE,
784
+ lambda: self.unsolicited_queue.put_nowait(None),
781
785
  )
782
786
 
783
787
  def supports_hf_feature(self, feature: HfFeature) -> bool:
@@ -827,7 +831,7 @@ class HfProtocol(utils.EventEmitter):
827
831
  cmd: str,
828
832
  timeout: float = 1.0,
829
833
  response_type: AtResponseType = AtResponseType.NONE,
830
- ) -> Union[None, AtResponse, List[AtResponse]]:
834
+ ) -> Union[None, AtResponse, list[AtResponse]]:
831
835
  """
832
836
  Sends an AT command and wait for the peer response.
833
837
  Wait for the AT responses sent by the peer, to the status code.
@@ -844,7 +848,7 @@ class HfProtocol(utils.EventEmitter):
844
848
  async with self.command_lock:
845
849
  logger.debug(f">>> {cmd}")
846
850
  self.dlc.write(cmd + '\r')
847
- responses: List[AtResponse] = []
851
+ responses: list[AtResponse] = []
848
852
 
849
853
  while True:
850
854
  result = await asyncio.wait_for(
@@ -1034,7 +1038,7 @@ class HfProtocol(utils.EventEmitter):
1034
1038
  # ID. The HF shall be ready to accept the synchronous connection
1035
1039
  # establishment as soon as it has sent the AT commands AT+BCS=<Codec ID>.
1036
1040
  self.active_codec = AudioCodec(codec_id)
1037
- self.emit('codec_negotiation', self.active_codec)
1041
+ self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
1038
1042
 
1039
1043
  logger.info("codec connection setup completed")
1040
1044
 
@@ -1064,7 +1068,7 @@ class HfProtocol(utils.EventEmitter):
1064
1068
  # code, with the value indicating (call=0).
1065
1069
  await self.execute_command("AT+CHUP")
1066
1070
 
1067
- async def query_current_calls(self) -> List[CallInfo]:
1071
+ async def query_current_calls(self) -> list[CallInfo]:
1068
1072
  """4.32.1 Query List of Current Calls in AG.
1069
1073
 
1070
1074
  Return:
@@ -1095,7 +1099,7 @@ class HfProtocol(utils.EventEmitter):
1095
1099
  # CIEV is in 1-index, while ag_indicators is in 0-index.
1096
1100
  ag_indicator = self.ag_indicators[index - 1]
1097
1101
  ag_indicator.current_status = value
1098
- self.emit('ag_indicator', ag_indicator)
1102
+ self.emit(self.EVENT_AG_INDICATOR, ag_indicator)
1099
1103
  logger.info(f"AG indicator updated: {ag_indicator.indicator}, {value}")
1100
1104
 
1101
1105
  async def handle_unsolicited(self):
@@ -1110,19 +1114,21 @@ class HfProtocol(utils.EventEmitter):
1110
1114
  int(result.parameters[0]), int(result.parameters[1])
1111
1115
  )
1112
1116
  elif result.code == "+VGS":
1113
- self.emit('speaker_volume', int(result.parameters[0]))
1117
+ self.emit(self.EVENT_SPEAKER_VOLUME, int(result.parameters[0]))
1114
1118
  elif result.code == "+VGM":
1115
- self.emit('microphone_volume', int(result.parameters[0]))
1119
+ self.emit(self.EVENT_MICROPHONE_VOLUME, int(result.parameters[0]))
1116
1120
  elif result.code == "RING":
1117
- self.emit('ring')
1121
+ self.emit(self.EVENT_RING)
1118
1122
  elif result.code == "+CLIP":
1119
1123
  self.emit(
1120
- 'cli_notification', CallLineIdentification.parse_from(result.parameters)
1124
+ self.EVENT_CLI_NOTIFICATION,
1125
+ CallLineIdentification.parse_from(result.parameters),
1121
1126
  )
1122
1127
  elif result.code == "+BVRA":
1123
1128
  # TODO: Support Enhanced Voice Recognition.
1124
1129
  self.emit(
1125
- 'voice_recognition', VoiceRecognitionState(int(result.parameters[0]))
1130
+ self.EVENT_VOICE_RECOGNITION,
1131
+ VoiceRecognitionState(int(result.parameters[0])),
1126
1132
  )
1127
1133
  else:
1128
1134
  logging.info(f"unhandled unsolicited response {result.code}")
@@ -1179,28 +1185,41 @@ class AgProtocol(utils.EventEmitter):
1179
1185
  volume: Int
1180
1186
  """
1181
1187
 
1188
+ EVENT_SLC_COMPLETE = "slc_complete"
1189
+ EVENT_SUPPORTED_AUDIO_CODECS = "supported_audio_codecs"
1190
+ EVENT_CODEC_NEGOTIATION = "codec_negotiation"
1191
+ EVENT_VOICE_RECOGNITION = "voice_recognition"
1192
+ EVENT_CALL_HOLD = "call_hold"
1193
+ EVENT_HF_INDICATOR = "hf_indicator"
1194
+ EVENT_CODEC_CONNECTION_REQUEST = "codec_connection_request"
1195
+ EVENT_ANSWER = "answer"
1196
+ EVENT_DIAL = "dial"
1197
+ EVENT_HANG_UP = "hang_up"
1198
+ EVENT_SPEAKER_VOLUME = "speaker_volume"
1199
+ EVENT_MICROPHONE_VOLUME = "microphone_volume"
1200
+
1182
1201
  supported_hf_features: int
1183
- supported_hf_indicators: Set[HfIndicator]
1184
- supported_audio_codecs: List[AudioCodec]
1202
+ supported_hf_indicators: set[HfIndicator]
1203
+ supported_audio_codecs: list[AudioCodec]
1185
1204
 
1186
1205
  supported_ag_features: int
1187
- supported_ag_call_hold_operations: List[CallHoldOperation]
1206
+ supported_ag_call_hold_operations: list[CallHoldOperation]
1188
1207
 
1189
- ag_indicators: List[AgIndicatorState]
1208
+ ag_indicators: list[AgIndicatorState]
1190
1209
  hf_indicators: collections.OrderedDict[HfIndicator, HfIndicatorState]
1191
1210
 
1192
1211
  dlc: rfcomm.DLC
1193
1212
 
1194
1213
  read_buffer: bytearray
1195
1214
  active_codec: AudioCodec
1196
- calls: List[CallInfo]
1215
+ calls: list[CallInfo]
1197
1216
 
1198
1217
  indicator_report_enabled: bool
1199
1218
  inband_ringtone_enabled: bool
1200
1219
  cme_error_enabled: bool
1201
1220
  cli_notification_enabled: bool
1202
1221
  call_waiting_enabled: bool
1203
- _remained_slc_setup_features: Set[HfFeature]
1222
+ _remained_slc_setup_features: set[HfFeature]
1204
1223
 
1205
1224
  def __init__(self, dlc: rfcomm.DLC, configuration: AgConfiguration) -> None:
1206
1225
  super().__init__()
@@ -1371,7 +1390,7 @@ class AgProtocol(utils.EventEmitter):
1371
1390
 
1372
1391
  def _check_remained_slc_commands(self) -> None:
1373
1392
  if not self._remained_slc_setup_features:
1374
- self.emit('slc_complete')
1393
+ self.emit(self.EVENT_SLC_COMPLETE)
1375
1394
 
1376
1395
  def _on_brsf(self, hf_features: bytes) -> None:
1377
1396
  self.supported_hf_features = int(hf_features)
@@ -1390,17 +1409,17 @@ class AgProtocol(utils.EventEmitter):
1390
1409
 
1391
1410
  def _on_bac(self, *args) -> None:
1392
1411
  self.supported_audio_codecs = [AudioCodec(int(value)) for value in args]
1393
- self.emit('supported_audio_codecs', self.supported_audio_codecs)
1412
+ self.emit(self.EVENT_SUPPORTED_AUDIO_CODECS, self.supported_audio_codecs)
1394
1413
  self.send_ok()
1395
1414
 
1396
1415
  def _on_bcs(self, codec: bytes) -> None:
1397
1416
  self.active_codec = AudioCodec(int(codec))
1398
1417
  self.send_ok()
1399
- self.emit('codec_negotiation', self.active_codec)
1418
+ self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
1400
1419
 
1401
1420
  def _on_bvra(self, vrec: bytes) -> None:
1402
1421
  self.send_ok()
1403
- self.emit('voice_recognition', VoiceRecognitionState(int(vrec)))
1422
+ self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
1404
1423
 
1405
1424
  def _on_chld(self, operation_code: bytes) -> None:
1406
1425
  call_index: Optional[int] = None
@@ -1427,7 +1446,7 @@ class AgProtocol(utils.EventEmitter):
1427
1446
  # Real three-way calls have more complicated situations, but this is not a popular issue - let users to handle the remaining :)
1428
1447
 
1429
1448
  self.send_ok()
1430
- self.emit('call_hold', operation, call_index)
1449
+ self.emit(self.EVENT_CALL_HOLD, operation, call_index)
1431
1450
 
1432
1451
  def _on_chld_test(self) -> None:
1433
1452
  if not self.supports_ag_feature(AgFeature.THREE_WAY_CALLING):
@@ -1553,7 +1572,7 @@ class AgProtocol(utils.EventEmitter):
1553
1572
  return
1554
1573
 
1555
1574
  self.hf_indicators[index].current_status = int(value_bytes)
1556
- self.emit('hf_indicator', self.hf_indicators[index])
1575
+ self.emit(self.EVENT_HF_INDICATOR, self.hf_indicators[index])
1557
1576
  self.send_ok()
1558
1577
 
1559
1578
  def _on_bia(self, *args) -> None:
@@ -1562,21 +1581,21 @@ class AgProtocol(utils.EventEmitter):
1562
1581
  self.send_ok()
1563
1582
 
1564
1583
  def _on_bcc(self) -> None:
1565
- self.emit('codec_connection_request')
1584
+ self.emit(self.EVENT_CODEC_CONNECTION_REQUEST)
1566
1585
  self.send_ok()
1567
1586
 
1568
1587
  def _on_a(self) -> None:
1569
1588
  """ATA handler."""
1570
- self.emit('answer')
1589
+ self.emit(self.EVENT_ANSWER)
1571
1590
  self.send_ok()
1572
1591
 
1573
1592
  def _on_d(self, number: bytes) -> None:
1574
1593
  """ATD handler."""
1575
- self.emit('dial', number.decode())
1594
+ self.emit(self.EVENT_DIAL, number.decode())
1576
1595
  self.send_ok()
1577
1596
 
1578
1597
  def _on_chup(self) -> None:
1579
- self.emit('hang_up')
1598
+ self.emit(self.EVENT_HANG_UP)
1580
1599
  self.send_ok()
1581
1600
 
1582
1601
  def _on_clcc(self) -> None:
@@ -1602,11 +1621,11 @@ class AgProtocol(utils.EventEmitter):
1602
1621
  self.send_ok()
1603
1622
 
1604
1623
  def _on_vgs(self, level: bytes) -> None:
1605
- self.emit('speaker_volume', int(level))
1624
+ self.emit(self.EVENT_SPEAKER_VOLUME, int(level))
1606
1625
  self.send_ok()
1607
1626
 
1608
1627
  def _on_vgm(self, level: bytes) -> None:
1609
- self.emit('microphone_volume', int(level))
1628
+ self.emit(self.EVENT_MICROPHONE_VOLUME, int(level))
1610
1629
  self.send_ok()
1611
1630
 
1612
1631
 
@@ -1670,7 +1689,7 @@ def make_hf_sdp_records(
1670
1689
  rfcomm_channel: int,
1671
1690
  configuration: HfConfiguration,
1672
1691
  version: ProfileVersion = ProfileVersion.V1_8,
1673
- ) -> List[sdp.ServiceAttribute]:
1692
+ ) -> list[sdp.ServiceAttribute]:
1674
1693
  """
1675
1694
  Generates the SDP record for HFP Hands-Free support.
1676
1695
 
@@ -1756,7 +1775,7 @@ def make_ag_sdp_records(
1756
1775
  rfcomm_channel: int,
1757
1776
  configuration: AgConfiguration,
1758
1777
  version: ProfileVersion = ProfileVersion.V1_8,
1759
- ) -> List[sdp.ServiceAttribute]:
1778
+ ) -> list[sdp.ServiceAttribute]:
1760
1779
  """
1761
1780
  Generates the SDP record for HFP Audio-Gateway support.
1762
1781
 
@@ -1836,7 +1855,7 @@ def make_ag_sdp_records(
1836
1855
 
1837
1856
  async def find_hf_sdp_record(
1838
1857
  connection: device.Connection,
1839
- ) -> Optional[Tuple[int, ProfileVersion, HfSdpFeature]]:
1858
+ ) -> Optional[tuple[int, ProfileVersion, HfSdpFeature]]:
1840
1859
  """Searches a Hands-Free SDP record from remote device.
1841
1860
 
1842
1861
  Args:
@@ -1888,7 +1907,7 @@ async def find_hf_sdp_record(
1888
1907
 
1889
1908
  async def find_ag_sdp_record(
1890
1909
  connection: device.Connection,
1891
- ) -> Optional[Tuple[int, ProfileVersion, AgSdpFeature]]:
1910
+ ) -> Optional[tuple[int, ProfileVersion, AgSdpFeature]]:
1892
1911
  """Searches an Audio-Gateway SDP record from remote device.
1893
1912
 
1894
1913
  Args:
@@ -1986,7 +2005,7 @@ class EscoParameters:
1986
2005
  transmit_codec_frame_size: int = 60
1987
2006
  receive_codec_frame_size: int = 60
1988
2007
 
1989
- def asdict(self) -> Dict[str, Any]:
2008
+ def asdict(self) -> dict[str, Any]:
1990
2009
  # dataclasses.asdict() will recursively deep-copy the entire object,
1991
2010
  # which is expensive and breaks CodingFormat object, so let it simply copy here.
1992
2011
  return self.__dict__
bumble/hid.py CHANGED
@@ -201,6 +201,13 @@ class HID(ABC, utils.EventEmitter):
201
201
  l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
202
202
  connection: Optional[device.Connection] = None
203
203
 
204
+ EVENT_INTERRUPT_DATA = "interrupt_data"
205
+ EVENT_CONTROL_DATA = "control_data"
206
+ EVENT_SUSPEND = "suspend"
207
+ EVENT_EXIT_SUSPEND = "exit_suspend"
208
+ EVENT_VIRTUAL_CABLE_UNPLUG = "virtual_cable_unplug"
209
+ EVENT_HANDSHAKE = "handshake"
210
+
204
211
  class Role(enum.IntEnum):
205
212
  HOST = 0x00
206
213
  DEVICE = 0x01
@@ -215,7 +222,7 @@ class HID(ABC, utils.EventEmitter):
215
222
  device.register_l2cap_server(HID_CONTROL_PSM, self.on_l2cap_connection)
216
223
  device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_l2cap_connection)
217
224
 
218
- device.on('connection', self.on_device_connection)
225
+ device.on(device.EVENT_CONNECTION, self.on_device_connection)
219
226
 
220
227
  async def connect_control_channel(self) -> None:
221
228
  # Create a new L2CAP connection - control channel
@@ -258,15 +265,20 @@ class HID(ABC, utils.EventEmitter):
258
265
  def on_device_connection(self, connection: device.Connection) -> None:
259
266
  self.connection = connection
260
267
  self.remote_device_bd_address = connection.peer_address
261
- connection.on('disconnection', self.on_device_disconnection)
268
+ connection.on(connection.EVENT_DISCONNECTION, self.on_device_disconnection)
262
269
 
263
270
  def on_device_disconnection(self, reason: int) -> None:
264
271
  self.connection = None
265
272
 
266
273
  def on_l2cap_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
267
274
  logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
268
- l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
269
- l2cap_channel.on('close', lambda: self.on_l2cap_channel_close(l2cap_channel))
275
+ l2cap_channel.on(
276
+ l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
277
+ )
278
+ l2cap_channel.on(
279
+ l2cap_channel.EVENT_CLOSE,
280
+ lambda: self.on_l2cap_channel_close(l2cap_channel),
281
+ )
270
282
 
271
283
  def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
272
284
  if l2cap_channel.psm == HID_CONTROL_PSM:
@@ -290,7 +302,7 @@ class HID(ABC, utils.EventEmitter):
290
302
 
291
303
  def on_intr_pdu(self, pdu: bytes) -> None:
292
304
  logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
293
- self.emit("interrupt_data", pdu)
305
+ self.emit(self.EVENT_INTERRUPT_DATA, pdu)
294
306
 
295
307
  def send_pdu_on_ctrl(self, msg: bytes) -> None:
296
308
  assert self.l2cap_ctrl_channel
@@ -363,17 +375,17 @@ class Device(HID):
363
375
  self.handle_set_protocol(pdu)
364
376
  elif message_type == Message.MessageType.DATA:
365
377
  logger.debug('<<< HID CONTROL DATA')
366
- self.emit('control_data', pdu)
378
+ self.emit(self.EVENT_CONTROL_DATA, pdu)
367
379
  elif message_type == Message.MessageType.CONTROL:
368
380
  if param == Message.ControlCommand.SUSPEND:
369
381
  logger.debug('<<< HID SUSPEND')
370
- self.emit('suspend')
382
+ self.emit(self.EVENT_SUSPEND)
371
383
  elif param == Message.ControlCommand.EXIT_SUSPEND:
372
384
  logger.debug('<<< HID EXIT SUSPEND')
373
- self.emit('exit_suspend')
385
+ self.emit(self.EVENT_EXIT_SUSPEND)
374
386
  elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
375
387
  logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
376
- self.emit('virtual_cable_unplug')
388
+ self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
377
389
  else:
378
390
  logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
379
391
  else:
@@ -538,14 +550,14 @@ class Host(HID):
538
550
  message_type = pdu[0] >> 4
539
551
  if message_type == Message.MessageType.HANDSHAKE:
540
552
  logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
541
- self.emit('handshake', Message.Handshake(param))
553
+ self.emit(self.EVENT_HANDSHAKE, Message.Handshake(param))
542
554
  elif message_type == Message.MessageType.DATA:
543
555
  logger.debug('<<< HID CONTROL DATA')
544
- self.emit('control_data', pdu)
556
+ self.emit(self.EVENT_CONTROL_DATA, pdu)
545
557
  elif message_type == Message.MessageType.CONTROL:
546
558
  if param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
547
559
  logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
548
- self.emit('virtual_cable_unplug')
560
+ self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
549
561
  else:
550
562
  logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
551
563
  else: