bumble 0.0.222__py3-none-any.whl → 0.0.224__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/apps/controller_info.py +90 -114
- bumble/apps/controller_loopback.py +11 -9
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/hci_bridge.py +3 -1
- bumble/apps/l2cap_bridge.py +1 -1
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +10 -4
- bumble/apps/speaker/speaker.py +1 -1
- bumble/apps/usb_probe.py +15 -2
- bumble/att.py +97 -32
- bumble/avctp.py +1 -1
- bumble/avdtp.py +3 -3
- bumble/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +999 -577
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +102 -43
- bumble/gatt.py +2 -2
- bumble/gatt_client.py +5 -4
- bumble/gatt_server.py +100 -1
- bumble/hci.py +1367 -844
- bumble/hid.py +2 -2
- bumble/host.py +339 -157
- bumble/l2cap.py +13 -6
- bumble/pandora/l2cap.py +1 -1
- bumble/profiles/battery_service.py +25 -34
- bumble/profiles/heart_rate_service.py +130 -121
- bumble/rfcomm.py +1 -1
- bumble/sdp.py +2 -2
- bumble/smp.py +8 -3
- bumble/snoop.py +111 -1
- bumble/transport/android_netsim.py +1 -1
- bumble/vendor/android/hci.py +108 -86
- bumble/vendor/zephyr/hci.py +24 -18
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/RECORD +43 -43
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.224'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 224)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
bumble/apps/controller_info.py
CHANGED
|
@@ -27,23 +27,17 @@ from bumble.core import name_or_number
|
|
|
27
27
|
from bumble.hci import (
|
|
28
28
|
HCI_LE_READ_BUFFER_SIZE_COMMAND,
|
|
29
29
|
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
|
|
30
|
-
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
|
|
31
30
|
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
|
|
32
|
-
|
|
31
|
+
HCI_LE_READ_MINIMUM_SUPPORTED_CONNECTION_INTERVAL_COMMAND,
|
|
33
32
|
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
|
34
33
|
HCI_READ_BD_ADDR_COMMAND,
|
|
35
34
|
HCI_READ_BUFFER_SIZE_COMMAND,
|
|
36
35
|
HCI_READ_LOCAL_NAME_COMMAND,
|
|
37
|
-
HCI_SUCCESS,
|
|
38
|
-
CodecID,
|
|
39
36
|
HCI_Command,
|
|
40
|
-
HCI_Command_Complete_Event,
|
|
41
|
-
HCI_Command_Status_Event,
|
|
42
37
|
HCI_LE_Read_Buffer_Size_Command,
|
|
43
38
|
HCI_LE_Read_Buffer_Size_V2_Command,
|
|
44
|
-
HCI_LE_Read_Maximum_Advertising_Data_Length_Command,
|
|
45
39
|
HCI_LE_Read_Maximum_Data_Length_Command,
|
|
46
|
-
|
|
40
|
+
HCI_LE_Read_Minimum_Supported_Connection_Interval_Command,
|
|
47
41
|
HCI_LE_Read_Suggested_Default_Data_Length_Command,
|
|
48
42
|
HCI_Read_BD_ADDR_Command,
|
|
49
43
|
HCI_Read_Buffer_Size_Command,
|
|
@@ -59,85 +53,81 @@ from bumble.host import Host
|
|
|
59
53
|
from bumble.transport import open_transport
|
|
60
54
|
|
|
61
55
|
|
|
62
|
-
# -----------------------------------------------------------------------------
|
|
63
|
-
def command_succeeded(response):
|
|
64
|
-
if isinstance(response, HCI_Command_Status_Event):
|
|
65
|
-
return response.status == HCI_SUCCESS
|
|
66
|
-
if isinstance(response, HCI_Command_Complete_Event):
|
|
67
|
-
return response.return_parameters.status == HCI_SUCCESS
|
|
68
|
-
return False
|
|
69
|
-
|
|
70
|
-
|
|
71
56
|
# -----------------------------------------------------------------------------
|
|
72
57
|
async def get_classic_info(host: Host) -> None:
|
|
73
58
|
if host.supports_command(HCI_READ_BD_ADDR_COMMAND):
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
59
|
+
response1 = await host.send_sync_command(HCI_Read_BD_ADDR_Command())
|
|
60
|
+
print()
|
|
61
|
+
print(
|
|
62
|
+
color('Public Address:', 'yellow'),
|
|
63
|
+
response1.bd_addr.to_string(False),
|
|
64
|
+
)
|
|
81
65
|
|
|
82
66
|
if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND):
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
67
|
+
response2 = await host.send_sync_command(HCI_Read_Local_Name_Command())
|
|
68
|
+
print()
|
|
69
|
+
print(
|
|
70
|
+
color('Local Name:', 'yellow'),
|
|
71
|
+
map_null_terminated_utf8_string(response2.local_name),
|
|
72
|
+
)
|
|
90
73
|
|
|
91
74
|
|
|
92
75
|
# -----------------------------------------------------------------------------
|
|
93
76
|
async def get_le_info(host: Host) -> None:
|
|
94
77
|
print()
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
print(
|
|
102
|
-
color('LE Number Of Supported Advertising Sets:', 'yellow'),
|
|
103
|
-
response.return_parameters.num_supported_advertising_sets,
|
|
104
|
-
'\n',
|
|
105
|
-
)
|
|
79
|
+
print(
|
|
80
|
+
color('LE Number Of Supported Advertising Sets:', 'yellow'),
|
|
81
|
+
host.number_of_supported_advertising_sets,
|
|
82
|
+
'\n',
|
|
83
|
+
)
|
|
106
84
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
print(
|
|
113
|
-
color('LE Maximum Advertising Data Length:', 'yellow'),
|
|
114
|
-
response.return_parameters.max_advertising_data_length,
|
|
115
|
-
'\n',
|
|
116
|
-
)
|
|
85
|
+
print(
|
|
86
|
+
color('LE Maximum Advertising Data Length:', 'yellow'),
|
|
87
|
+
host.maximum_advertising_data_length,
|
|
88
|
+
'\n',
|
|
89
|
+
)
|
|
117
90
|
|
|
118
91
|
if host.supports_command(HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
response1 = await host.send_sync_command(
|
|
93
|
+
HCI_LE_Read_Maximum_Data_Length_Command()
|
|
94
|
+
)
|
|
95
|
+
print(
|
|
96
|
+
color('LE Maximum Data Length:', 'yellow'),
|
|
97
|
+
(
|
|
98
|
+
f'tx:{response1.supported_max_tx_octets}/'
|
|
99
|
+
f'{response1.supported_max_tx_time}, '
|
|
100
|
+
f'rx:{response1.supported_max_rx_octets}/'
|
|
101
|
+
f'{response1.supported_max_rx_time}'
|
|
102
|
+
),
|
|
103
|
+
)
|
|
131
104
|
|
|
132
105
|
if host.supports_command(HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND):
|
|
133
|
-
|
|
106
|
+
response2 = await host.send_sync_command(
|
|
134
107
|
HCI_LE_Read_Suggested_Default_Data_Length_Command()
|
|
135
108
|
)
|
|
136
|
-
|
|
109
|
+
print(
|
|
110
|
+
color('LE Suggested Default Data Length:', 'yellow'),
|
|
111
|
+
f'{response2.suggested_max_tx_octets}/'
|
|
112
|
+
f'{response2.suggested_max_tx_time}',
|
|
113
|
+
'\n',
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if host.supports_command(HCI_LE_READ_MINIMUM_SUPPORTED_CONNECTION_INTERVAL_COMMAND):
|
|
117
|
+
response3 = await host.send_sync_command(
|
|
118
|
+
HCI_LE_Read_Minimum_Supported_Connection_Interval_Command()
|
|
119
|
+
)
|
|
120
|
+
print(
|
|
121
|
+
color('LE Minimum Supported Connection Interval:', 'yellow'),
|
|
122
|
+
f'{response3.minimum_supported_connection_interval * 125} µs',
|
|
123
|
+
)
|
|
124
|
+
for group in range(len(response3.group_min)):
|
|
137
125
|
print(
|
|
138
|
-
|
|
139
|
-
f'{
|
|
140
|
-
f'{
|
|
126
|
+
f' Group {group}: '
|
|
127
|
+
f'{response3.group_min[group] * 125} µs to '
|
|
128
|
+
f'{response3.group_max[group] * 125} µs '
|
|
129
|
+
'by increments of '
|
|
130
|
+
f'{response3.group_stride[group] * 125} µs',
|
|
141
131
|
'\n',
|
|
142
132
|
)
|
|
143
133
|
|
|
@@ -151,37 +141,31 @@ async def get_flow_control_info(host: Host) -> None:
|
|
|
151
141
|
print()
|
|
152
142
|
|
|
153
143
|
if host.supports_command(HCI_READ_BUFFER_SIZE_COMMAND):
|
|
154
|
-
|
|
155
|
-
HCI_Read_Buffer_Size_Command(), check_result=True
|
|
156
|
-
)
|
|
144
|
+
response1 = await host.send_sync_command(HCI_Read_Buffer_Size_Command())
|
|
157
145
|
print(
|
|
158
146
|
color('ACL Flow Control:', 'yellow'),
|
|
159
|
-
f'{
|
|
160
|
-
f'packets of size {
|
|
147
|
+
f'{response1.hc_total_num_acl_data_packets} '
|
|
148
|
+
f'packets of size {response1.hc_acl_data_packet_length}',
|
|
161
149
|
)
|
|
162
150
|
|
|
163
151
|
if host.supports_command(HCI_LE_READ_BUFFER_SIZE_V2_COMMAND):
|
|
164
|
-
|
|
165
|
-
HCI_LE_Read_Buffer_Size_V2_Command(), check_result=True
|
|
166
|
-
)
|
|
152
|
+
response2 = await host.send_sync_command(HCI_LE_Read_Buffer_Size_V2_Command())
|
|
167
153
|
print(
|
|
168
154
|
color('LE ACL Flow Control:', 'yellow'),
|
|
169
|
-
f'{
|
|
170
|
-
f'packets of size {
|
|
155
|
+
f'{response2.total_num_le_acl_data_packets} '
|
|
156
|
+
f'packets of size {response2.le_acl_data_packet_length}',
|
|
171
157
|
)
|
|
172
158
|
print(
|
|
173
159
|
color('LE ISO Flow Control:', 'yellow'),
|
|
174
|
-
f'{
|
|
175
|
-
f'packets of size {
|
|
160
|
+
f'{response2.total_num_iso_data_packets} '
|
|
161
|
+
f'packets of size {response2.iso_data_packet_length}',
|
|
176
162
|
)
|
|
177
163
|
elif host.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND):
|
|
178
|
-
|
|
179
|
-
HCI_LE_Read_Buffer_Size_Command(), check_result=True
|
|
180
|
-
)
|
|
164
|
+
response3 = await host.send_sync_command(HCI_LE_Read_Buffer_Size_Command())
|
|
181
165
|
print(
|
|
182
166
|
color('LE ACL Flow Control:', 'yellow'),
|
|
183
|
-
f'{
|
|
184
|
-
f'packets of size {
|
|
167
|
+
f'{response3.total_num_le_acl_data_packets} '
|
|
168
|
+
f'packets of size {response3.le_acl_data_packet_length}',
|
|
185
169
|
)
|
|
186
170
|
|
|
187
171
|
|
|
@@ -190,52 +174,44 @@ async def get_codecs_info(host: Host) -> None:
|
|
|
190
174
|
print()
|
|
191
175
|
|
|
192
176
|
if host.supports_command(HCI_Read_Local_Supported_Codecs_V2_Command.op_code):
|
|
193
|
-
|
|
194
|
-
HCI_Read_Local_Supported_Codecs_V2_Command()
|
|
177
|
+
response1 = await host.send_sync_command(
|
|
178
|
+
HCI_Read_Local_Supported_Codecs_V2_Command()
|
|
195
179
|
)
|
|
196
180
|
print(color('Codecs:', 'yellow'))
|
|
197
181
|
|
|
198
182
|
for codec_id, transport in zip(
|
|
199
|
-
|
|
200
|
-
|
|
183
|
+
response1.standard_codec_ids,
|
|
184
|
+
response1.standard_codec_transports,
|
|
201
185
|
):
|
|
202
|
-
|
|
203
|
-
transport
|
|
204
|
-
).name
|
|
205
|
-
codec_name = CodecID(codec_id).name
|
|
206
|
-
print(f' {codec_name} - {transport_name}')
|
|
186
|
+
print(f' {codec_id.name} - {transport.name}')
|
|
207
187
|
|
|
208
|
-
for
|
|
209
|
-
|
|
210
|
-
|
|
188
|
+
for vendor_codec_id, vendor_transport in zip(
|
|
189
|
+
response1.vendor_specific_codec_ids,
|
|
190
|
+
response1.vendor_specific_codec_transports,
|
|
211
191
|
):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
).name
|
|
215
|
-
company = name_or_number(COMPANY_IDENTIFIERS, codec_id >> 16)
|
|
216
|
-
print(f' {company} / {codec_id & 0xFFFF} - {transport_name}')
|
|
192
|
+
company = name_or_number(COMPANY_IDENTIFIERS, vendor_codec_id >> 16)
|
|
193
|
+
print(f' {company} / {vendor_codec_id & 0xFFFF} - {vendor_transport.name}')
|
|
217
194
|
|
|
218
|
-
if not
|
|
195
|
+
if not response1.standard_codec_ids:
|
|
219
196
|
print(' No standard codecs')
|
|
220
|
-
if not
|
|
197
|
+
if not response1.vendor_specific_codec_ids:
|
|
221
198
|
print(' No Vendor-specific codecs')
|
|
222
199
|
|
|
223
200
|
if host.supports_command(HCI_Read_Local_Supported_Codecs_Command.op_code):
|
|
224
|
-
|
|
225
|
-
HCI_Read_Local_Supported_Codecs_Command()
|
|
201
|
+
response2 = await host.send_sync_command(
|
|
202
|
+
HCI_Read_Local_Supported_Codecs_Command()
|
|
226
203
|
)
|
|
227
204
|
print(color('Codecs (BR/EDR):', 'yellow'))
|
|
228
|
-
for codec_id in
|
|
229
|
-
|
|
230
|
-
print(f' {codec_name}')
|
|
205
|
+
for codec_id in response2.standard_codec_ids:
|
|
206
|
+
print(f' {codec_id.name}')
|
|
231
207
|
|
|
232
|
-
for
|
|
233
|
-
company = name_or_number(COMPANY_IDENTIFIERS,
|
|
234
|
-
print(f' {company} / {
|
|
208
|
+
for vendor_codec_id in response2.vendor_specific_codec_ids:
|
|
209
|
+
company = name_or_number(COMPANY_IDENTIFIERS, vendor_codec_id >> 16)
|
|
210
|
+
print(f' {company} / {vendor_codec_id & 0xFFFF}')
|
|
235
211
|
|
|
236
|
-
if not
|
|
212
|
+
if not response2.standard_codec_ids:
|
|
237
213
|
print(' No standard codecs')
|
|
238
|
-
if not
|
|
214
|
+
if not response2.vendor_specific_codec_ids:
|
|
239
215
|
print(' No Vendor-specific codecs')
|
|
240
216
|
|
|
241
217
|
|
|
@@ -85,7 +85,7 @@ class Loopback:
|
|
|
85
85
|
print(color('@@@ Received last packet', 'green'))
|
|
86
86
|
self.done.set()
|
|
87
87
|
|
|
88
|
-
async def run(self):
|
|
88
|
+
async def run(self) -> None:
|
|
89
89
|
"""Run a loopback throughput test"""
|
|
90
90
|
print(color('>>> Connecting to HCI...', 'green'))
|
|
91
91
|
async with await open_transport(self.transport) as (
|
|
@@ -100,11 +100,15 @@ class Loopback:
|
|
|
100
100
|
# make sure data can fit in one l2cap pdu
|
|
101
101
|
l2cap_header_size = 4
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
packet_queue = (
|
|
104
104
|
host.acl_packet_queue
|
|
105
105
|
if host.acl_packet_queue
|
|
106
106
|
else host.le_acl_packet_queue
|
|
107
|
-
)
|
|
107
|
+
)
|
|
108
|
+
if packet_queue is None:
|
|
109
|
+
print(color('!!! No packet queue', 'red'))
|
|
110
|
+
return
|
|
111
|
+
max_packet_size = packet_queue.max_packet_size - l2cap_header_size
|
|
108
112
|
if self.packet_size > max_packet_size:
|
|
109
113
|
print(
|
|
110
114
|
color(
|
|
@@ -128,20 +132,18 @@ class Loopback:
|
|
|
128
132
|
loopback_mode = LoopbackMode.LOCAL
|
|
129
133
|
|
|
130
134
|
print(color('### Setting loopback mode', 'blue'))
|
|
131
|
-
await host.
|
|
135
|
+
await host.send_sync_command(
|
|
132
136
|
HCI_Write_Loopback_Mode_Command(loopback_mode=LoopbackMode.LOCAL),
|
|
133
|
-
check_result=True,
|
|
134
137
|
)
|
|
135
138
|
|
|
136
139
|
print(color('### Checking loopback mode', 'blue'))
|
|
137
|
-
response = await host.
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
if response.return_parameters.loopback_mode != loopback_mode:
|
|
140
|
+
response = await host.send_sync_command(HCI_Read_Loopback_Mode_Command())
|
|
141
|
+
if response.loopback_mode != loopback_mode:
|
|
141
142
|
print(color('!!! Loopback mode mismatch', 'red'))
|
|
142
143
|
return
|
|
143
144
|
|
|
144
145
|
await self.connection_event.wait()
|
|
146
|
+
assert self.connection_handle is not None
|
|
145
147
|
print(color('### Connected', 'cyan'))
|
|
146
148
|
|
|
147
149
|
print(color('=== Start sending', 'magenta'))
|
bumble/apps/gg_bridge.py
CHANGED
bumble/apps/hci_bridge.py
CHANGED
|
@@ -81,7 +81,9 @@ async def async_main():
|
|
|
81
81
|
response = hci.HCI_Command_Complete_Event(
|
|
82
82
|
num_hci_command_packets=1,
|
|
83
83
|
command_opcode=hci_packet.op_code,
|
|
84
|
-
return_parameters=
|
|
84
|
+
return_parameters=hci.HCI_StatusReturnParameters(
|
|
85
|
+
status=hci.HCI_ErrorCode.SUCCESS
|
|
86
|
+
),
|
|
85
87
|
)
|
|
86
88
|
# Return a packet with 'respond to sender' set to True
|
|
87
89
|
return (bytes(response), True)
|
bumble/apps/l2cap_bridge.py
CHANGED
|
@@ -268,7 +268,7 @@ async def run(device_config, hci_transport, bridge):
|
|
|
268
268
|
await bridge.start(device)
|
|
269
269
|
|
|
270
270
|
# Wait until the transport terminates
|
|
271
|
-
await hci_source.
|
|
271
|
+
await hci_source.terminated
|
|
272
272
|
|
|
273
273
|
|
|
274
274
|
# -----------------------------------------------------------------------------
|
bumble/apps/rfcomm_bridge.py
CHANGED
|
@@ -421,7 +421,7 @@ async def run(device_config, hci_transport, bridge):
|
|
|
421
421
|
await bridge.start(device)
|
|
422
422
|
|
|
423
423
|
# Wait until the transport terminates
|
|
424
|
-
await hci_source.
|
|
424
|
+
await hci_source.terminated
|
|
425
425
|
except core.ConnectionError as error:
|
|
426
426
|
print(color(f"!!! Bluetooth connection failed: {error}", "red"))
|
|
427
427
|
except Exception as error:
|
bumble/apps/scan.py
CHANGED
|
@@ -22,7 +22,7 @@ import click
|
|
|
22
22
|
import bumble.logging
|
|
23
23
|
from bumble import data_types
|
|
24
24
|
from bumble.colors import color
|
|
25
|
-
from bumble.device import Advertisement, Device
|
|
25
|
+
from bumble.device import Advertisement, Device, DeviceConfiguration
|
|
26
26
|
from bumble.hci import HCI_LE_1M_PHY, HCI_LE_CODED_PHY, Address, HCI_Constant
|
|
27
27
|
from bumble.keys import JsonKeyStore
|
|
28
28
|
from bumble.smp import AddressResolver
|
|
@@ -144,8 +144,14 @@ async def scan(
|
|
|
144
144
|
device_config, hci_source, hci_sink
|
|
145
145
|
)
|
|
146
146
|
else:
|
|
147
|
-
device = Device.
|
|
148
|
-
|
|
147
|
+
device = Device.from_config_with_hci(
|
|
148
|
+
DeviceConfiguration(
|
|
149
|
+
name='Bumble',
|
|
150
|
+
address=Address('F0:F1:F2:F3:F4:F5'),
|
|
151
|
+
keystore='JsonKeyStore',
|
|
152
|
+
),
|
|
153
|
+
hci_source,
|
|
154
|
+
hci_sink,
|
|
149
155
|
)
|
|
150
156
|
|
|
151
157
|
await device.power_on()
|
|
@@ -190,7 +196,7 @@ async def scan(
|
|
|
190
196
|
scanning_phys=scanning_phys,
|
|
191
197
|
)
|
|
192
198
|
|
|
193
|
-
await hci_source.
|
|
199
|
+
await hci_source.terminated
|
|
194
200
|
|
|
195
201
|
|
|
196
202
|
# -----------------------------------------------------------------------------
|
bumble/apps/speaker/speaker.py
CHANGED
bumble/apps/usb_probe.py
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
# -----------------------------------------------------------------------------
|
|
27
27
|
# Imports
|
|
28
28
|
# -----------------------------------------------------------------------------
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
29
31
|
import click
|
|
30
32
|
import usb1
|
|
31
33
|
|
|
@@ -166,13 +168,16 @@ def is_bluetooth_hci(device):
|
|
|
166
168
|
# -----------------------------------------------------------------------------
|
|
167
169
|
@click.command()
|
|
168
170
|
@click.option('--verbose', is_flag=True, default=False, help='Print more details')
|
|
169
|
-
|
|
171
|
+
@click.option('--hci-only', is_flag=True, default=False, help='only show HCI device')
|
|
172
|
+
@click.option('--manufacturer', help='filter by manufacturer')
|
|
173
|
+
@click.option('--product', help='filter by product')
|
|
174
|
+
def main(verbose: bool, manufacturer: str, product: str, hci_only: bool):
|
|
170
175
|
bumble.logging.setup_basic_logging('WARNING')
|
|
171
176
|
|
|
172
177
|
load_libusb()
|
|
173
178
|
with usb1.USBContext() as context:
|
|
174
179
|
bluetooth_device_count = 0
|
|
175
|
-
devices = {}
|
|
180
|
+
devices: dict[tuple[Any, Any], list[str | None]] = {}
|
|
176
181
|
|
|
177
182
|
for device in context.getDeviceIterator(skip_on_error=True):
|
|
178
183
|
device_class = device.getDeviceClass()
|
|
@@ -234,6 +239,14 @@ def main(verbose):
|
|
|
234
239
|
f'{basic_transport_name}/{device_serial_number}'
|
|
235
240
|
)
|
|
236
241
|
|
|
242
|
+
# Filter
|
|
243
|
+
if product and device_product != product:
|
|
244
|
+
continue
|
|
245
|
+
if manufacturer and device_manufacturer != manufacturer:
|
|
246
|
+
continue
|
|
247
|
+
if not is_bluetooth_hci(device) and hci_only:
|
|
248
|
+
continue
|
|
249
|
+
|
|
237
250
|
# Print the results
|
|
238
251
|
print(
|
|
239
252
|
color(
|
bumble/att.py
CHANGED
|
@@ -29,7 +29,7 @@ import enum
|
|
|
29
29
|
import functools
|
|
30
30
|
import inspect
|
|
31
31
|
import struct
|
|
32
|
-
from collections.abc import Awaitable, Callable
|
|
32
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
33
33
|
from typing import (
|
|
34
34
|
TYPE_CHECKING,
|
|
35
35
|
ClassVar,
|
|
@@ -72,34 +72,36 @@ ATT_PSM = 0x001F
|
|
|
72
72
|
EATT_PSM = 0x0027
|
|
73
73
|
|
|
74
74
|
class Opcode(hci.SpecableEnum):
|
|
75
|
-
ATT_ERROR_RESPONSE
|
|
76
|
-
ATT_EXCHANGE_MTU_REQUEST
|
|
77
|
-
ATT_EXCHANGE_MTU_RESPONSE
|
|
78
|
-
ATT_FIND_INFORMATION_REQUEST
|
|
79
|
-
ATT_FIND_INFORMATION_RESPONSE
|
|
80
|
-
ATT_FIND_BY_TYPE_VALUE_REQUEST
|
|
81
|
-
ATT_FIND_BY_TYPE_VALUE_RESPONSE
|
|
82
|
-
ATT_READ_BY_TYPE_REQUEST
|
|
83
|
-
ATT_READ_BY_TYPE_RESPONSE
|
|
84
|
-
ATT_READ_REQUEST
|
|
85
|
-
ATT_READ_RESPONSE
|
|
86
|
-
ATT_READ_BLOB_REQUEST
|
|
87
|
-
ATT_READ_BLOB_RESPONSE
|
|
88
|
-
ATT_READ_MULTIPLE_REQUEST
|
|
89
|
-
ATT_READ_MULTIPLE_RESPONSE
|
|
90
|
-
ATT_READ_BY_GROUP_TYPE_REQUEST
|
|
91
|
-
ATT_READ_BY_GROUP_TYPE_RESPONSE
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
ATT_ERROR_RESPONSE = 0x01
|
|
76
|
+
ATT_EXCHANGE_MTU_REQUEST = 0x02
|
|
77
|
+
ATT_EXCHANGE_MTU_RESPONSE = 0x03
|
|
78
|
+
ATT_FIND_INFORMATION_REQUEST = 0x04
|
|
79
|
+
ATT_FIND_INFORMATION_RESPONSE = 0x05
|
|
80
|
+
ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
|
|
81
|
+
ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
|
|
82
|
+
ATT_READ_BY_TYPE_REQUEST = 0x08
|
|
83
|
+
ATT_READ_BY_TYPE_RESPONSE = 0x09
|
|
84
|
+
ATT_READ_REQUEST = 0x0A
|
|
85
|
+
ATT_READ_RESPONSE = 0x0B
|
|
86
|
+
ATT_READ_BLOB_REQUEST = 0x0C
|
|
87
|
+
ATT_READ_BLOB_RESPONSE = 0x0D
|
|
88
|
+
ATT_READ_MULTIPLE_REQUEST = 0x0E
|
|
89
|
+
ATT_READ_MULTIPLE_RESPONSE = 0x0F
|
|
90
|
+
ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
|
|
91
|
+
ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
|
|
92
|
+
ATT_READ_MULTIPLE_VARIABLE_REQUEST = 0x20
|
|
93
|
+
ATT_READ_MULTIPLE_VARIABLE_RESPONSE = 0x21
|
|
94
|
+
ATT_WRITE_REQUEST = 0x12
|
|
95
|
+
ATT_WRITE_RESPONSE = 0x13
|
|
96
|
+
ATT_WRITE_COMMAND = 0x52
|
|
97
|
+
ATT_SIGNED_WRITE_COMMAND = 0xD2
|
|
98
|
+
ATT_PREPARE_WRITE_REQUEST = 0x16
|
|
99
|
+
ATT_PREPARE_WRITE_RESPONSE = 0x17
|
|
100
|
+
ATT_EXECUTE_WRITE_REQUEST = 0x18
|
|
101
|
+
ATT_EXECUTE_WRITE_RESPONSE = 0x19
|
|
102
|
+
ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
|
|
103
|
+
ATT_HANDLE_VALUE_INDICATION = 0x1D
|
|
104
|
+
ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
|
|
103
105
|
|
|
104
106
|
ATT_REQUESTS = [
|
|
105
107
|
Opcode.ATT_EXCHANGE_MTU_REQUEST,
|
|
@@ -110,9 +112,10 @@ ATT_REQUESTS = [
|
|
|
110
112
|
Opcode.ATT_READ_BLOB_REQUEST,
|
|
111
113
|
Opcode.ATT_READ_MULTIPLE_REQUEST,
|
|
112
114
|
Opcode.ATT_READ_BY_GROUP_TYPE_REQUEST,
|
|
115
|
+
Opcode.ATT_READ_MULTIPLE_VARIABLE_REQUEST,
|
|
113
116
|
Opcode.ATT_WRITE_REQUEST,
|
|
114
117
|
Opcode.ATT_PREPARE_WRITE_REQUEST,
|
|
115
|
-
Opcode.ATT_EXECUTE_WRITE_REQUEST
|
|
118
|
+
Opcode.ATT_EXECUTE_WRITE_REQUEST,
|
|
116
119
|
]
|
|
117
120
|
|
|
118
121
|
ATT_RESPONSES = [
|
|
@@ -125,9 +128,10 @@ ATT_RESPONSES = [
|
|
|
125
128
|
Opcode.ATT_READ_BLOB_RESPONSE,
|
|
126
129
|
Opcode.ATT_READ_MULTIPLE_RESPONSE,
|
|
127
130
|
Opcode.ATT_READ_BY_GROUP_TYPE_RESPONSE,
|
|
131
|
+
Opcode.ATT_READ_MULTIPLE_VARIABLE_RESPONSE,
|
|
128
132
|
Opcode.ATT_WRITE_RESPONSE,
|
|
129
133
|
Opcode.ATT_PREPARE_WRITE_RESPONSE,
|
|
130
|
-
Opcode.ATT_EXECUTE_WRITE_RESPONSE
|
|
134
|
+
Opcode.ATT_EXECUTE_WRITE_RESPONSE,
|
|
131
135
|
]
|
|
132
136
|
|
|
133
137
|
class ErrorCode(hci.SpecableEnum):
|
|
@@ -185,6 +189,18 @@ ATT_INSUFFICIENT_RESOURCES_ERROR = ErrorCode.INSUFFICIENT_RESOURCES
|
|
|
185
189
|
ATT_DEFAULT_MTU = 23
|
|
186
190
|
|
|
187
191
|
HANDLE_FIELD_SPEC = {'size': 2, 'mapper': lambda x: f'0x{x:04X}'}
|
|
192
|
+
_SET_OF_HANDLES_METADATA = hci.metadata({
|
|
193
|
+
'parser': lambda data, offset: (
|
|
194
|
+
len(data),
|
|
195
|
+
[
|
|
196
|
+
struct.unpack_from('<H', data, i)[0]
|
|
197
|
+
for i in range(offset, len(data), 2)
|
|
198
|
+
],
|
|
199
|
+
),
|
|
200
|
+
'serializer': lambda handles: b''.join(
|
|
201
|
+
[struct.pack('<H', handle) for handle in handles]
|
|
202
|
+
),
|
|
203
|
+
})
|
|
188
204
|
|
|
189
205
|
# fmt: on
|
|
190
206
|
# pylint: enable=line-too-long
|
|
@@ -554,7 +570,7 @@ class ATT_Read_Multiple_Request(ATT_PDU):
|
|
|
554
570
|
See Bluetooth spec @ Vol 3, Part F - 3.4.4.7 Read Multiple Request
|
|
555
571
|
'''
|
|
556
572
|
|
|
557
|
-
set_of_handles:
|
|
573
|
+
set_of_handles: Sequence[int] = dataclasses.field(metadata=_SET_OF_HANDLES_METADATA)
|
|
558
574
|
|
|
559
575
|
|
|
560
576
|
# -----------------------------------------------------------------------------
|
|
@@ -635,6 +651,55 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
|
|
|
635
651
|
return result
|
|
636
652
|
|
|
637
653
|
|
|
654
|
+
# -----------------------------------------------------------------------------
|
|
655
|
+
@ATT_PDU.subclass
|
|
656
|
+
@dataclasses.dataclass
|
|
657
|
+
class ATT_Read_Multiple_Variable_Request(ATT_PDU):
|
|
658
|
+
'''
|
|
659
|
+
See Bluetooth spec @ Vol 3, Part F - 3.4.4.11 Read Multiple Variable Request
|
|
660
|
+
'''
|
|
661
|
+
|
|
662
|
+
set_of_handles: Sequence[int] = dataclasses.field(metadata=_SET_OF_HANDLES_METADATA)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
# -----------------------------------------------------------------------------
|
|
666
|
+
@ATT_PDU.subclass
|
|
667
|
+
@dataclasses.dataclass
|
|
668
|
+
class ATT_Read_Multiple_Variable_Response(ATT_PDU):
|
|
669
|
+
'''
|
|
670
|
+
See Bluetooth spec @ Vol 3, Part F - 3.4.4.12 Read Multiple Variable Response
|
|
671
|
+
'''
|
|
672
|
+
|
|
673
|
+
@classmethod
|
|
674
|
+
def _parse_length_value_tuples(
|
|
675
|
+
cls, data: bytes, offset: int
|
|
676
|
+
) -> tuple[int, list[tuple[int, bytes]]]:
|
|
677
|
+
length_value_tuple_list: list[tuple[int, bytes]] = []
|
|
678
|
+
while offset < len(data):
|
|
679
|
+
length = struct.unpack_from('<H', data, offset)[0]
|
|
680
|
+
length_value_tuple_list.append(
|
|
681
|
+
(length, data[offset + 2 : offset + 2 + length])
|
|
682
|
+
)
|
|
683
|
+
offset += 2 + length
|
|
684
|
+
return (len(data), length_value_tuple_list)
|
|
685
|
+
|
|
686
|
+
length_value_tuple_list: Sequence[tuple[int, bytes]] = dataclasses.field(
|
|
687
|
+
metadata=hci.metadata(
|
|
688
|
+
{
|
|
689
|
+
'parser': lambda data, offset: ATT_Read_Multiple_Variable_Response._parse_length_value_tuples(
|
|
690
|
+
data, offset
|
|
691
|
+
),
|
|
692
|
+
'serializer': lambda length_value_tuple_list: b''.join(
|
|
693
|
+
[
|
|
694
|
+
struct.pack('<H', length) + value
|
|
695
|
+
for length, value in length_value_tuple_list
|
|
696
|
+
]
|
|
697
|
+
),
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
638
703
|
# -----------------------------------------------------------------------------
|
|
639
704
|
@ATT_PDU.subclass
|
|
640
705
|
@dataclasses.dataclass
|