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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +482 -31
- bumble/apps/console.py +5 -5
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +204 -43
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +15 -18
- bumble/avc.py +7 -7
- bumble/avctp.py +5 -5
- bumble/avdtp.py +138 -88
- bumble/avrcp.py +52 -58
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +688 -345
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +40 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +7 -9
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +31 -34
- bumble/gatt_server.py +15 -17
- bumble/hci.py +2635 -2878
- bumble/helpers.py +4 -5
- bumble/hfp.py +76 -57
- bumble/hid.py +24 -12
- bumble/host.py +117 -34
- bumble/keys.py +68 -52
- bumble/l2cap.py +329 -403
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +38 -39
- bumble/pandora/l2cap.py +4 -4
- bumble/pandora/security.py +73 -57
- bumble/pandora/utils.py +3 -3
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +143 -136
- bumble/profiles/asha.py +13 -8
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +3 -5
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +42 -58
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +16 -13
- bumble/profiles/vcs.py +8 -10
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +27 -18
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +71 -69
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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:
|
|
509
|
-
supported_hf_indicators:
|
|
510
|
-
supported_audio_codecs:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
730
|
+
supported_audio_codecs: list[AudioCodec]
|
|
728
731
|
|
|
729
732
|
supported_ag_features: int
|
|
730
|
-
supported_ag_call_hold_operations:
|
|
733
|
+
supported_ag_call_hold_operations: list[CallHoldOperation]
|
|
731
734
|
|
|
732
|
-
ag_indicators:
|
|
733
|
-
hf_indicators:
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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(
|
|
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) ->
|
|
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(
|
|
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(
|
|
1117
|
+
self.emit(self.EVENT_SPEAKER_VOLUME, int(result.parameters[0]))
|
|
1114
1118
|
elif result.code == "+VGM":
|
|
1115
|
-
self.emit(
|
|
1119
|
+
self.emit(self.EVENT_MICROPHONE_VOLUME, int(result.parameters[0]))
|
|
1116
1120
|
elif result.code == "RING":
|
|
1117
|
-
self.emit(
|
|
1121
|
+
self.emit(self.EVENT_RING)
|
|
1118
1122
|
elif result.code == "+CLIP":
|
|
1119
1123
|
self.emit(
|
|
1120
|
-
|
|
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
|
-
|
|
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:
|
|
1184
|
-
supported_audio_codecs:
|
|
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:
|
|
1206
|
+
supported_ag_call_hold_operations: list[CallHoldOperation]
|
|
1188
1207
|
|
|
1189
|
-
ag_indicators:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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[
|
|
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[
|
|
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) ->
|
|
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(
|
|
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(
|
|
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(
|
|
269
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
382
|
+
self.emit(self.EVENT_SUSPEND)
|
|
371
383
|
elif param == Message.ControlCommand.EXIT_SUSPEND:
|
|
372
384
|
logger.debug('<<< HID EXIT SUSPEND')
|
|
373
|
-
self.emit(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
560
|
+
self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
|
|
549
561
|
else:
|
|
550
562
|
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
|
|
551
563
|
else:
|