bumble 0.0.195__py3-none-any.whl → 0.0.199__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/auracast.py +351 -66
- bumble/apps/console.py +5 -20
- bumble/apps/device_info.py +230 -0
- bumble/apps/gatt_dump.py +4 -0
- bumble/apps/lea_unicast/app.py +16 -17
- bumble/apps/pair.py +32 -5
- bumble/at.py +12 -6
- bumble/att.py +56 -40
- bumble/avc.py +8 -5
- bumble/avctp.py +3 -2
- bumble/avdtp.py +7 -3
- bumble/avrcp.py +2 -1
- bumble/codecs.py +17 -13
- bumble/colors.py +6 -2
- bumble/core.py +37 -7
- bumble/decoder.py +14 -10
- bumble/device.py +382 -111
- bumble/drivers/rtk.py +32 -13
- bumble/gatt.py +30 -20
- bumble/gatt_client.py +15 -29
- bumble/gatt_server.py +14 -6
- bumble/hci.py +322 -32
- bumble/hid.py +24 -28
- bumble/host.py +20 -6
- bumble/l2cap.py +24 -17
- bumble/link.py +8 -3
- bumble/pandora/__init__.py +3 -0
- bumble/pandora/l2cap.py +310 -0
- bumble/profiles/aics.py +520 -0
- bumble/profiles/ascs.py +739 -0
- bumble/profiles/asha.py +295 -0
- bumble/profiles/bap.py +1 -874
- bumble/profiles/bass.py +440 -0
- bumble/profiles/csip.py +4 -4
- bumble/profiles/gap.py +110 -0
- bumble/profiles/hap.py +665 -0
- bumble/profiles/heart_rate_service.py +4 -3
- bumble/profiles/le_audio.py +43 -9
- bumble/profiles/mcp.py +448 -0
- bumble/profiles/pacs.py +210 -0
- bumble/profiles/tmap.py +89 -0
- bumble/profiles/vcp.py +5 -3
- bumble/rfcomm.py +4 -2
- bumble/sdp.py +13 -11
- bumble/smp.py +43 -12
- bumble/snoop.py +5 -4
- bumble/transport/__init__.py +8 -2
- bumble/transport/android_emulator.py +9 -3
- bumble/transport/android_netsim.py +9 -7
- bumble/transport/common.py +46 -18
- bumble/transport/pyusb.py +21 -4
- bumble/transport/unix.py +56 -0
- bumble/transport/usb.py +57 -46
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/METADATA +41 -41
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/RECORD +60 -49
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/WHEEL +1 -1
- bumble/profiles/asha_service.py +0 -193
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/LICENSE +0 -0
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.195.dist-info → bumble-0.0.199.dist-info}/top_level.txt +0 -0
bumble/drivers/rtk.py
CHANGED
|
@@ -33,6 +33,7 @@ from typing import Tuple
|
|
|
33
33
|
import weakref
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
from bumble import core
|
|
36
37
|
from bumble.hci import (
|
|
37
38
|
hci_vendor_command_op_code,
|
|
38
39
|
STATUS_SPEC,
|
|
@@ -49,6 +50,10 @@ from bumble.drivers import common
|
|
|
49
50
|
logger = logging.getLogger(__name__)
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
class RtkFirmwareError(core.BaseBumbleError):
|
|
54
|
+
"""Error raised when RTK firmware initialization fails."""
|
|
55
|
+
|
|
56
|
+
|
|
52
57
|
# -----------------------------------------------------------------------------
|
|
53
58
|
# Constants
|
|
54
59
|
# -----------------------------------------------------------------------------
|
|
@@ -208,15 +213,15 @@ class Firmware:
|
|
|
208
213
|
extension_sig = bytes([0x51, 0x04, 0xFD, 0x77])
|
|
209
214
|
|
|
210
215
|
if not firmware.startswith(RTK_EPATCH_SIGNATURE):
|
|
211
|
-
raise
|
|
216
|
+
raise RtkFirmwareError("Firmware does not start with epatch signature")
|
|
212
217
|
|
|
213
218
|
if not firmware.endswith(extension_sig):
|
|
214
|
-
raise
|
|
219
|
+
raise RtkFirmwareError("Firmware does not end with extension sig")
|
|
215
220
|
|
|
216
221
|
# The firmware should start with a 14 byte header.
|
|
217
222
|
epatch_header_size = 14
|
|
218
223
|
if len(firmware) < epatch_header_size:
|
|
219
|
-
raise
|
|
224
|
+
raise RtkFirmwareError("Firmware too short")
|
|
220
225
|
|
|
221
226
|
# Look for the "project ID", starting from the end.
|
|
222
227
|
offset = len(firmware) - len(extension_sig)
|
|
@@ -230,7 +235,7 @@ class Firmware:
|
|
|
230
235
|
break
|
|
231
236
|
|
|
232
237
|
if length == 0:
|
|
233
|
-
raise
|
|
238
|
+
raise RtkFirmwareError("Invalid 0-length instruction")
|
|
234
239
|
|
|
235
240
|
if opcode == 0 and length == 1:
|
|
236
241
|
project_id = firmware[offset - 1]
|
|
@@ -239,7 +244,7 @@ class Firmware:
|
|
|
239
244
|
offset -= length
|
|
240
245
|
|
|
241
246
|
if project_id < 0:
|
|
242
|
-
raise
|
|
247
|
+
raise RtkFirmwareError("Project ID not found")
|
|
243
248
|
|
|
244
249
|
self.project_id = project_id
|
|
245
250
|
|
|
@@ -252,7 +257,7 @@ class Firmware:
|
|
|
252
257
|
# <PatchLength_1><PatchLength_2>...<PatchLength_N> (16 bits each)
|
|
253
258
|
# <PatchOffset_1><PatchOffset_2>...<PatchOffset_N> (32 bits each)
|
|
254
259
|
if epatch_header_size + 8 * num_patches > len(firmware):
|
|
255
|
-
raise
|
|
260
|
+
raise RtkFirmwareError("Firmware too short")
|
|
256
261
|
chip_id_table_offset = epatch_header_size
|
|
257
262
|
patch_length_table_offset = chip_id_table_offset + 2 * num_patches
|
|
258
263
|
patch_offset_table_offset = chip_id_table_offset + 4 * num_patches
|
|
@@ -266,7 +271,7 @@ class Firmware:
|
|
|
266
271
|
"<I", firmware, patch_offset_table_offset + 4 * patch_index
|
|
267
272
|
)
|
|
268
273
|
if patch_offset + patch_length > len(firmware):
|
|
269
|
-
raise
|
|
274
|
+
raise RtkFirmwareError("Firmware too short")
|
|
270
275
|
|
|
271
276
|
# Get the SVN version for the patch
|
|
272
277
|
(svn_version,) = struct.unpack_from(
|
|
@@ -296,6 +301,8 @@ class Driver(common.Driver):
|
|
|
296
301
|
fw_name: str = ""
|
|
297
302
|
config_name: str = ""
|
|
298
303
|
|
|
304
|
+
POST_RESET_DELAY: float = 0.2
|
|
305
|
+
|
|
299
306
|
DRIVER_INFOS = [
|
|
300
307
|
# 8723A
|
|
301
308
|
DriverInfo(
|
|
@@ -490,12 +497,24 @@ class Driver(common.Driver):
|
|
|
490
497
|
|
|
491
498
|
@classmethod
|
|
492
499
|
async def driver_info_for_host(cls, host):
|
|
493
|
-
|
|
494
|
-
|
|
500
|
+
try:
|
|
501
|
+
await host.send_command(
|
|
502
|
+
HCI_Reset_Command(),
|
|
503
|
+
check_result=True,
|
|
504
|
+
response_timeout=cls.POST_RESET_DELAY,
|
|
505
|
+
)
|
|
506
|
+
host.ready = True # Needed to let the host know the controller is ready.
|
|
507
|
+
except asyncio.exceptions.TimeoutError:
|
|
508
|
+
logger.warning("timeout waiting for hci reset, retrying")
|
|
509
|
+
await host.send_command(HCI_Reset_Command(), check_result=True)
|
|
510
|
+
host.ready = True
|
|
511
|
+
|
|
512
|
+
command = HCI_Read_Local_Version_Information_Command()
|
|
513
|
+
response = await host.send_command(command, check_result=True)
|
|
514
|
+
if response.command_opcode != command.op_code:
|
|
515
|
+
logger.error("failed to probe local version information")
|
|
516
|
+
return None
|
|
495
517
|
|
|
496
|
-
response = await host.send_command(
|
|
497
|
-
HCI_Read_Local_Version_Information_Command(), check_result=True
|
|
498
|
-
)
|
|
499
518
|
local_version = response.return_parameters
|
|
500
519
|
|
|
501
520
|
logger.debug(
|
|
@@ -645,7 +664,7 @@ class Driver(common.Driver):
|
|
|
645
664
|
):
|
|
646
665
|
return await self.download_for_rtl8723b()
|
|
647
666
|
|
|
648
|
-
raise
|
|
667
|
+
raise RtkFirmwareError("ROM not supported")
|
|
649
668
|
|
|
650
669
|
async def init_controller(self):
|
|
651
670
|
await self.download_firmware()
|
bumble/gatt.py
CHANGED
|
@@ -39,7 +39,7 @@ from typing import (
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
from bumble.colors import color
|
|
42
|
-
from bumble.core import UUID
|
|
42
|
+
from bumble.core import BaseBumbleError, UUID
|
|
43
43
|
from bumble.att import Attribute, AttributeValue
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING:
|
|
@@ -238,22 +238,22 @@ GATT_SEARCH_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x
|
|
|
238
238
|
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Content Control Id')
|
|
239
239
|
|
|
240
240
|
# Telephone Bearer Service (TBS)
|
|
241
|
-
GATT_BEARER_PROVIDER_NAME_CHARACTERISTIC = UUID.from_16_bits(
|
|
242
|
-
GATT_BEARER_UCI_CHARACTERISTIC = UUID.from_16_bits(
|
|
243
|
-
GATT_BEARER_TECHNOLOGY_CHARACTERISTIC = UUID.from_16_bits(
|
|
244
|
-
GATT_BEARER_URI_SCHEMES_SUPPORTED_LIST_CHARACTERISTIC = UUID.from_16_bits(
|
|
245
|
-
GATT_BEARER_SIGNAL_STRENGTH_CHARACTERISTIC = UUID.from_16_bits(
|
|
246
|
-
GATT_BEARER_SIGNAL_STRENGTH_REPORTING_INTERVAL_CHARACTERISTIC = UUID.from_16_bits(
|
|
247
|
-
GATT_BEARER_LIST_CURRENT_CALLS_CHARACTERISTIC = UUID.from_16_bits(
|
|
248
|
-
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(
|
|
249
|
-
GATT_STATUS_FLAGS_CHARACTERISTIC = UUID.from_16_bits(
|
|
250
|
-
GATT_INCOMING_CALL_TARGET_BEARER_URI_CHARACTERISTIC = UUID.from_16_bits(
|
|
251
|
-
GATT_CALL_STATE_CHARACTERISTIC = UUID.from_16_bits(
|
|
252
|
-
GATT_CALL_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(
|
|
253
|
-
GATT_CALL_CONTROL_POINT_OPTIONAL_OPCODES_CHARACTERISTIC = UUID.from_16_bits(
|
|
254
|
-
GATT_TERMINATION_REASON_CHARACTERISTIC = UUID.from_16_bits(
|
|
255
|
-
GATT_INCOMING_CALL_CHARACTERISTIC = UUID.from_16_bits(
|
|
256
|
-
GATT_CALL_FRIENDLY_NAME_CHARACTERISTIC = UUID.from_16_bits(
|
|
241
|
+
GATT_BEARER_PROVIDER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BB3, 'Bearer Provider Name')
|
|
242
|
+
GATT_BEARER_UCI_CHARACTERISTIC = UUID.from_16_bits(0x2BB4, 'Bearer UCI')
|
|
243
|
+
GATT_BEARER_TECHNOLOGY_CHARACTERISTIC = UUID.from_16_bits(0x2BB5, 'Bearer Technology')
|
|
244
|
+
GATT_BEARER_URI_SCHEMES_SUPPORTED_LIST_CHARACTERISTIC = UUID.from_16_bits(0x2BB6, 'Bearer URI Schemes Supported List')
|
|
245
|
+
GATT_BEARER_SIGNAL_STRENGTH_CHARACTERISTIC = UUID.from_16_bits(0x2BB7, 'Bearer Signal Strength')
|
|
246
|
+
GATT_BEARER_SIGNAL_STRENGTH_REPORTING_INTERVAL_CHARACTERISTIC = UUID.from_16_bits(0x2BB8, 'Bearer Signal Strength Reporting Interval')
|
|
247
|
+
GATT_BEARER_LIST_CURRENT_CALLS_CHARACTERISTIC = UUID.from_16_bits(0x2BB9, 'Bearer List Current Calls')
|
|
248
|
+
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Content Control ID')
|
|
249
|
+
GATT_STATUS_FLAGS_CHARACTERISTIC = UUID.from_16_bits(0x2BBB, 'Status Flags')
|
|
250
|
+
GATT_INCOMING_CALL_TARGET_BEARER_URI_CHARACTERISTIC = UUID.from_16_bits(0x2BBC, 'Incoming Call Target Bearer URI')
|
|
251
|
+
GATT_CALL_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BBD, 'Call State')
|
|
252
|
+
GATT_CALL_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BBE, 'Call Control Point')
|
|
253
|
+
GATT_CALL_CONTROL_POINT_OPTIONAL_OPCODES_CHARACTERISTIC = UUID.from_16_bits(0x2BBF, 'Call Control Point Optional Opcodes')
|
|
254
|
+
GATT_TERMINATION_REASON_CHARACTERISTIC = UUID.from_16_bits(0x2BC0, 'Termination Reason')
|
|
255
|
+
GATT_INCOMING_CALL_CHARACTERISTIC = UUID.from_16_bits(0x2BC1, 'Incoming Call')
|
|
256
|
+
GATT_CALL_FRIENDLY_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BC2, 'Call Friendly Name')
|
|
257
257
|
|
|
258
258
|
# Microphone Control Service (MICS)
|
|
259
259
|
GATT_MUTE_CHARACTERISTIC = UUID.from_16_bits(0x2BC3, 'Mute')
|
|
@@ -275,6 +275,11 @@ GATT_SOURCE_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCC, 'Sou
|
|
|
275
275
|
GATT_AVAILABLE_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCD, 'Available Audio Contexts')
|
|
276
276
|
GATT_SUPPORTED_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCE, 'Supported Audio Contexts')
|
|
277
277
|
|
|
278
|
+
# Hearing Access Service
|
|
279
|
+
GATT_HEARING_AID_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2BDA, 'Hearing Aid Features')
|
|
280
|
+
GATT_HEARING_AID_PRESET_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BDB, 'Hearing Aid Preset Control Point')
|
|
281
|
+
GATT_ACTIVE_PRESET_INDEX_CHARACTERISTIC = UUID.from_16_bits(0x2BDC, 'Active Preset Index')
|
|
282
|
+
|
|
278
283
|
# ASHA Service
|
|
279
284
|
GATT_ASHA_SERVICE = UUID.from_16_bits(0xFDF0, 'Audio Streaming for Hearing Aid')
|
|
280
285
|
GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC = UUID('6333651e-c481-4a3e-9169-7c902aad37bb', 'ReadOnlyProperties')
|
|
@@ -320,6 +325,11 @@ def show_services(services: Iterable[Service]) -> None:
|
|
|
320
325
|
print(color(' ' + str(descriptor), 'green'))
|
|
321
326
|
|
|
322
327
|
|
|
328
|
+
# -----------------------------------------------------------------------------
|
|
329
|
+
class InvalidServiceError(BaseBumbleError):
|
|
330
|
+
"""The service is not compliant with the spec/profile"""
|
|
331
|
+
|
|
332
|
+
|
|
323
333
|
# -----------------------------------------------------------------------------
|
|
324
334
|
class Service(Attribute):
|
|
325
335
|
'''
|
|
@@ -335,7 +345,7 @@ class Service(Attribute):
|
|
|
335
345
|
uuid: Union[str, UUID],
|
|
336
346
|
characteristics: List[Characteristic],
|
|
337
347
|
primary=True,
|
|
338
|
-
included_services:
|
|
348
|
+
included_services: Iterable[Service] = (),
|
|
339
349
|
) -> None:
|
|
340
350
|
# Convert the uuid to a UUID object if it isn't already
|
|
341
351
|
if isinstance(uuid, str):
|
|
@@ -351,7 +361,7 @@ class Service(Attribute):
|
|
|
351
361
|
uuid.to_pdu_bytes(),
|
|
352
362
|
)
|
|
353
363
|
self.uuid = uuid
|
|
354
|
-
self.included_services = included_services
|
|
364
|
+
self.included_services = list(included_services)
|
|
355
365
|
self.characteristics = characteristics[:]
|
|
356
366
|
self.primary = primary
|
|
357
367
|
|
|
@@ -385,7 +395,7 @@ class TemplateService(Service):
|
|
|
385
395
|
self,
|
|
386
396
|
characteristics: List[Characteristic],
|
|
387
397
|
primary: bool = True,
|
|
388
|
-
included_services:
|
|
398
|
+
included_services: Iterable[Service] = (),
|
|
389
399
|
) -> None:
|
|
390
400
|
super().__init__(self.UUID, characteristics, primary, included_services)
|
|
391
401
|
|
bumble/gatt_client.py
CHANGED
|
@@ -68,7 +68,7 @@ from .att import (
|
|
|
68
68
|
ATT_Error,
|
|
69
69
|
)
|
|
70
70
|
from . import core
|
|
71
|
-
from .core import UUID, InvalidStateError
|
|
71
|
+
from .core import UUID, InvalidStateError
|
|
72
72
|
from .gatt import (
|
|
73
73
|
GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
|
74
74
|
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
|
@@ -253,7 +253,7 @@ class ProfileServiceProxy:
|
|
|
253
253
|
SERVICE_CLASS: Type[TemplateService]
|
|
254
254
|
|
|
255
255
|
@classmethod
|
|
256
|
-
def from_client(cls, client: Client) -> ProfileServiceProxy:
|
|
256
|
+
def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
|
|
257
257
|
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
|
258
258
|
|
|
259
259
|
|
|
@@ -283,6 +283,8 @@ class Client:
|
|
|
283
283
|
self.services = []
|
|
284
284
|
self.cached_values = {}
|
|
285
285
|
|
|
286
|
+
connection.on('disconnection', self.on_disconnection)
|
|
287
|
+
|
|
286
288
|
def send_gatt_pdu(self, pdu: bytes) -> None:
|
|
287
289
|
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
|
288
290
|
|
|
@@ -331,9 +333,9 @@ class Client:
|
|
|
331
333
|
async def request_mtu(self, mtu: int) -> int:
|
|
332
334
|
# Check the range
|
|
333
335
|
if mtu < ATT_DEFAULT_MTU:
|
|
334
|
-
raise
|
|
336
|
+
raise core.InvalidArgumentError(f'MTU must be >= {ATT_DEFAULT_MTU}')
|
|
335
337
|
if mtu > 0xFFFF:
|
|
336
|
-
raise
|
|
338
|
+
raise core.InvalidArgumentError('MTU must be <= 0xFFFF')
|
|
337
339
|
|
|
338
340
|
# We can only send one request per connection
|
|
339
341
|
if self.mtu_exchange_done:
|
|
@@ -343,12 +345,7 @@ class Client:
|
|
|
343
345
|
self.mtu_exchange_done = True
|
|
344
346
|
response = await self.send_request(ATT_Exchange_MTU_Request(client_rx_mtu=mtu))
|
|
345
347
|
if response.op_code == ATT_ERROR_RESPONSE:
|
|
346
|
-
raise
|
|
347
|
-
response.error_code,
|
|
348
|
-
'att',
|
|
349
|
-
ATT_PDU.error_name(response.error_code),
|
|
350
|
-
response,
|
|
351
|
-
)
|
|
348
|
+
raise ATT_Error(error_code=response.error_code, message=response)
|
|
352
349
|
|
|
353
350
|
# Compute the final MTU
|
|
354
351
|
self.connection.att_mtu = min(mtu, response.server_rx_mtu)
|
|
@@ -405,7 +402,7 @@ class Client:
|
|
|
405
402
|
if not already_known:
|
|
406
403
|
self.services.append(service)
|
|
407
404
|
|
|
408
|
-
async def discover_services(self, uuids: Iterable[UUID] =
|
|
405
|
+
async def discover_services(self, uuids: Iterable[UUID] = ()) -> List[ServiceProxy]:
|
|
409
406
|
'''
|
|
410
407
|
See Vol 3, Part G - 4.4.1 Discover All Primary Services
|
|
411
408
|
'''
|
|
@@ -934,12 +931,7 @@ class Client:
|
|
|
934
931
|
if response is None:
|
|
935
932
|
raise TimeoutError('read timeout')
|
|
936
933
|
if response.op_code == ATT_ERROR_RESPONSE:
|
|
937
|
-
raise
|
|
938
|
-
response.error_code,
|
|
939
|
-
'att',
|
|
940
|
-
ATT_PDU.error_name(response.error_code),
|
|
941
|
-
response,
|
|
942
|
-
)
|
|
934
|
+
raise ATT_Error(error_code=response.error_code, message=response)
|
|
943
935
|
|
|
944
936
|
# If the value is the max size for the MTU, try to read more unless the caller
|
|
945
937
|
# specifically asked not to do that
|
|
@@ -961,12 +953,7 @@ class Client:
|
|
|
961
953
|
ATT_INVALID_OFFSET_ERROR,
|
|
962
954
|
):
|
|
963
955
|
break
|
|
964
|
-
raise
|
|
965
|
-
response.error_code,
|
|
966
|
-
'att',
|
|
967
|
-
ATT_PDU.error_name(response.error_code),
|
|
968
|
-
response,
|
|
969
|
-
)
|
|
956
|
+
raise ATT_Error(error_code=response.error_code, message=response)
|
|
970
957
|
|
|
971
958
|
part = response.part_attribute_value
|
|
972
959
|
attribute_value += part
|
|
@@ -1059,12 +1046,7 @@ class Client:
|
|
|
1059
1046
|
)
|
|
1060
1047
|
)
|
|
1061
1048
|
if response.op_code == ATT_ERROR_RESPONSE:
|
|
1062
|
-
raise
|
|
1063
|
-
response.error_code,
|
|
1064
|
-
'att',
|
|
1065
|
-
ATT_PDU.error_name(response.error_code),
|
|
1066
|
-
response,
|
|
1067
|
-
)
|
|
1049
|
+
raise ATT_Error(error_code=response.error_code, message=response)
|
|
1068
1050
|
else:
|
|
1069
1051
|
await self.send_command(
|
|
1070
1052
|
ATT_Write_Command(
|
|
@@ -1072,6 +1054,10 @@ class Client:
|
|
|
1072
1054
|
)
|
|
1073
1055
|
)
|
|
1074
1056
|
|
|
1057
|
+
def on_disconnection(self, _) -> None:
|
|
1058
|
+
if self.pending_response and not self.pending_response.done():
|
|
1059
|
+
self.pending_response.cancel()
|
|
1060
|
+
|
|
1075
1061
|
def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
|
|
1076
1062
|
logger.debug(
|
|
1077
1063
|
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
|
bumble/gatt_server.py
CHANGED
|
@@ -915,7 +915,7 @@ class Server(EventEmitter):
|
|
|
915
915
|
See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
|
|
916
916
|
'''
|
|
917
917
|
|
|
918
|
-
# Check
|
|
918
|
+
# Check that the attribute exists
|
|
919
919
|
attribute = self.get_attribute(request.attribute_handle)
|
|
920
920
|
if attribute is None:
|
|
921
921
|
self.send_response(
|
|
@@ -942,11 +942,19 @@ class Server(EventEmitter):
|
|
|
942
942
|
)
|
|
943
943
|
return
|
|
944
944
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
945
|
+
try:
|
|
946
|
+
# Accept the value
|
|
947
|
+
await attribute.write_value(connection, request.attribute_value)
|
|
948
|
+
except ATT_Error as error:
|
|
949
|
+
response = ATT_Error_Response(
|
|
950
|
+
request_opcode_in_error=request.op_code,
|
|
951
|
+
attribute_handle_in_error=request.attribute_handle,
|
|
952
|
+
error_code=error.error_code,
|
|
953
|
+
)
|
|
954
|
+
else:
|
|
955
|
+
# Done
|
|
956
|
+
response = ATT_Write_Response()
|
|
957
|
+
self.send_response(connection, response)
|
|
950
958
|
|
|
951
959
|
@AsyncRunner.run_in_task()
|
|
952
960
|
async def on_att_write_command(self, connection, request):
|