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.
Files changed (43) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/controller_info.py +90 -114
  3. bumble/apps/controller_loopback.py +11 -9
  4. bumble/apps/gg_bridge.py +1 -1
  5. bumble/apps/hci_bridge.py +3 -1
  6. bumble/apps/l2cap_bridge.py +1 -1
  7. bumble/apps/rfcomm_bridge.py +1 -1
  8. bumble/apps/scan.py +10 -4
  9. bumble/apps/speaker/speaker.py +1 -1
  10. bumble/apps/usb_probe.py +15 -2
  11. bumble/att.py +97 -32
  12. bumble/avctp.py +1 -1
  13. bumble/avdtp.py +3 -3
  14. bumble/avrcp.py +366 -190
  15. bumble/bridge.py +10 -2
  16. bumble/controller.py +14 -1
  17. bumble/core.py +1 -1
  18. bumble/device.py +999 -577
  19. bumble/drivers/intel.py +45 -39
  20. bumble/drivers/rtk.py +102 -43
  21. bumble/gatt.py +2 -2
  22. bumble/gatt_client.py +5 -4
  23. bumble/gatt_server.py +100 -1
  24. bumble/hci.py +1367 -844
  25. bumble/hid.py +2 -2
  26. bumble/host.py +339 -157
  27. bumble/l2cap.py +13 -6
  28. bumble/pandora/l2cap.py +1 -1
  29. bumble/profiles/battery_service.py +25 -34
  30. bumble/profiles/heart_rate_service.py +130 -121
  31. bumble/rfcomm.py +1 -1
  32. bumble/sdp.py +2 -2
  33. bumble/smp.py +8 -3
  34. bumble/snoop.py +111 -1
  35. bumble/transport/android_netsim.py +1 -1
  36. bumble/vendor/android/hci.py +108 -86
  37. bumble/vendor/zephyr/hci.py +24 -18
  38. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
  39. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/RECORD +43 -43
  40. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
  41. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
  42. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
  43. {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/top_level.txt +0 -0
bumble/l2cap.py CHANGED
@@ -1647,7 +1647,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1647
1647
  self.connection_result = None
1648
1648
  self.disconnection_result = None
1649
1649
  self.drained = asyncio.Event()
1650
- self.att_mtu = 0 # Filled by GATT client or server later.
1650
+ # Core Specification Vol 3, Part G, 5.3.1 ATT_MTU
1651
+ # ATT_MTU shall be set to the minimum of the MTU field values of the two devices.
1652
+ self.att_mtu = min(mtu, peer_mtu)
1651
1653
 
1652
1654
  self.drained.set()
1653
1655
 
@@ -2340,8 +2342,8 @@ class ChannelManager:
2340
2342
  cid,
2341
2343
  L2CAP_Connection_Response(
2342
2344
  identifier=request.identifier,
2343
- destination_cid=request.source_cid,
2344
- source_cid=0,
2345
+ destination_cid=0,
2346
+ source_cid=request.source_cid,
2345
2347
  result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
2346
2348
  status=0x0000,
2347
2349
  ),
@@ -2353,7 +2355,12 @@ class ChannelManager:
2353
2355
  f'creating server channel with cid={source_cid} for psm {request.psm}'
2354
2356
  )
2355
2357
  channel = ClassicChannel(
2356
- self, connection, cid, request.psm, source_cid, server.spec
2358
+ manager=self,
2359
+ connection=connection,
2360
+ signaling_cid=cid,
2361
+ psm=request.psm,
2362
+ source_cid=source_cid,
2363
+ spec=server.spec,
2357
2364
  )
2358
2365
  connection_channels[source_cid] = channel
2359
2366
 
@@ -2370,8 +2377,8 @@ class ChannelManager:
2370
2377
  cid,
2371
2378
  L2CAP_Connection_Response(
2372
2379
  identifier=request.identifier,
2373
- destination_cid=request.source_cid,
2374
- source_cid=0,
2380
+ destination_cid=0,
2381
+ source_cid=request.source_cid,
2375
2382
  result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
2376
2383
  status=0x0000,
2377
2384
  ),
bumble/pandora/l2cap.py CHANGED
@@ -278,7 +278,7 @@ class L2CAPService(L2CAPServicer):
278
278
  if not l2cap_channel:
279
279
  return SendResponse(error=COMMAND_NOT_UNDERSTOOD)
280
280
  if isinstance(l2cap_channel, ClassicChannel):
281
- l2cap_channel.send_pdu(request.data)
281
+ l2cap_channel.write(request.data)
282
282
  else:
283
283
  l2cap_channel.write(request.data)
284
284
  return SendResponse(success=empty_pb2.Empty())
@@ -16,35 +16,28 @@
16
16
  # -----------------------------------------------------------------------------
17
17
  # Imports
18
18
  # -----------------------------------------------------------------------------
19
+ from collections.abc import Callable
19
20
 
20
- from bumble.gatt import (
21
- GATT_BATTERY_LEVEL_CHARACTERISTIC,
22
- GATT_BATTERY_SERVICE,
23
- Characteristic,
24
- CharacteristicValue,
25
- TemplateService,
26
- )
27
- from bumble.gatt_adapters import (
28
- PackedCharacteristicAdapter,
29
- PackedCharacteristicProxyAdapter,
30
- )
31
- from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy
21
+ from bumble import device, gatt, gatt_adapters, gatt_client
32
22
 
33
23
 
34
24
  # -----------------------------------------------------------------------------
35
- class BatteryService(TemplateService):
36
- UUID = GATT_BATTERY_SERVICE
25
+ class BatteryService(gatt.TemplateService):
26
+ UUID = gatt.GATT_BATTERY_SERVICE
37
27
  BATTERY_LEVEL_FORMAT = 'B'
38
28
 
39
- battery_level_characteristic: Characteristic[int]
40
-
41
- def __init__(self, read_battery_level):
42
- self.battery_level_characteristic = PackedCharacteristicAdapter(
43
- Characteristic(
44
- GATT_BATTERY_LEVEL_CHARACTERISTIC,
45
- Characteristic.Properties.READ | Characteristic.Properties.NOTIFY,
46
- Characteristic.READABLE,
47
- CharacteristicValue(read=read_battery_level),
29
+ battery_level_characteristic: gatt.Characteristic[int]
30
+
31
+ def __init__(self, read_battery_level: Callable[[device.Connection], int]) -> None:
32
+ self.battery_level_characteristic = gatt_adapters.PackedCharacteristicAdapter(
33
+ gatt.Characteristic(
34
+ gatt.GATT_BATTERY_LEVEL_CHARACTERISTIC,
35
+ properties=(
36
+ gatt.Characteristic.Properties.READ
37
+ | gatt.Characteristic.Properties.NOTIFY
38
+ ),
39
+ permissions=gatt.Characteristic.READABLE,
40
+ value=gatt.CharacteristicValue(read=read_battery_level),
48
41
  ),
49
42
  pack_format=BatteryService.BATTERY_LEVEL_FORMAT,
50
43
  )
@@ -52,19 +45,17 @@ class BatteryService(TemplateService):
52
45
 
53
46
 
54
47
  # -----------------------------------------------------------------------------
55
- class BatteryServiceProxy(ProfileServiceProxy):
48
+ class BatteryServiceProxy(gatt_client.ProfileServiceProxy):
56
49
  SERVICE_CLASS = BatteryService
57
50
 
58
- battery_level: CharacteristicProxy[int] | None
51
+ battery_level: gatt_client.CharacteristicProxy[int]
59
52
 
60
- def __init__(self, service_proxy):
53
+ def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
61
54
  self.service_proxy = service_proxy
62
55
 
63
- if characteristics := service_proxy.get_characteristics_by_uuid(
64
- GATT_BATTERY_LEVEL_CHARACTERISTIC
65
- ):
66
- self.battery_level = PackedCharacteristicProxyAdapter(
67
- characteristics[0], pack_format=BatteryService.BATTERY_LEVEL_FORMAT
68
- )
69
- else:
70
- self.battery_level = None
56
+ self.battery_level = gatt_adapters.PackedCharacteristicProxyAdapter(
57
+ service_proxy.get_required_characteristic_by_uuid(
58
+ gatt.GATT_BATTERY_LEVEL_CHARACTERISTIC
59
+ ),
60
+ pack_format=BatteryService.BATTERY_LEVEL_FORMAT,
61
+ )
@@ -18,40 +18,30 @@
18
18
  # -----------------------------------------------------------------------------
19
19
  from __future__ import annotations
20
20
 
21
+ import dataclasses
22
+ import enum
21
23
  import struct
22
- from enum import IntEnum
23
-
24
- from bumble import core
25
- from bumble.att import ATT_Error
26
- from bumble.gatt import (
27
- GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
28
- GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
29
- GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
30
- GATT_HEART_RATE_SERVICE,
31
- Characteristic,
32
- CharacteristicValue,
33
- TemplateService,
34
- )
35
- from bumble.gatt_adapters import (
36
- DelegatedCharacteristicAdapter,
37
- PackedCharacteristicAdapter,
38
- SerializableCharacteristicAdapter,
39
- )
40
- from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy
24
+ from collections.abc import Callable, Sequence
25
+ from typing import Any
26
+
27
+ from typing_extensions import Self
28
+
29
+ from bumble import att, core, device, gatt, gatt_adapters, gatt_client, utils
41
30
 
42
31
 
43
32
  # -----------------------------------------------------------------------------
44
- class HeartRateService(TemplateService):
45
- UUID = GATT_HEART_RATE_SERVICE
33
+ class HeartRateService(gatt.TemplateService):
34
+ UUID = gatt.GATT_HEART_RATE_SERVICE
35
+
46
36
  HEART_RATE_CONTROL_POINT_FORMAT = 'B'
47
37
  CONTROL_POINT_NOT_SUPPORTED = 0x80
48
38
  RESET_ENERGY_EXPENDED = 0x01
49
39
 
50
- heart_rate_measurement_characteristic: Characteristic[HeartRateMeasurement]
51
- body_sensor_location_characteristic: Characteristic[BodySensorLocation]
52
- heart_rate_control_point_characteristic: Characteristic[int]
40
+ heart_rate_measurement_characteristic: gatt.Characteristic[HeartRateMeasurement]
41
+ body_sensor_location_characteristic: gatt.Characteristic[BodySensorLocation]
42
+ heart_rate_control_point_characteristic: gatt.Characteristic[int]
53
43
 
54
- class BodySensorLocation(IntEnum):
44
+ class BodySensorLocation(utils.OpenIntEnum):
55
45
  OTHER = 0
56
46
  CHEST = 1
57
47
  WRIST = 2
@@ -60,82 +50,90 @@ class HeartRateService(TemplateService):
60
50
  EAR_LOBE = 5
61
51
  FOOT = 6
62
52
 
53
+ @dataclasses.dataclass
63
54
  class HeartRateMeasurement:
64
- def __init__(
65
- self,
66
- heart_rate,
67
- sensor_contact_detected=None,
68
- energy_expended=None,
69
- rr_intervals=None,
70
- ):
71
- if heart_rate < 0 or heart_rate > 0xFFFF:
55
+ heart_rate: int
56
+ sensor_contact_detected: bool | None = None
57
+ energy_expended: int | None = None
58
+ rr_intervals: Sequence[float] | None = None
59
+
60
+ class Flag(enum.IntFlag):
61
+ INT16_HEART_RATE = 1 << 0
62
+ SENSOR_CONTACT_DETECTED = 1 << 1
63
+ SENSOR_CONTACT_SUPPORTED = 1 << 2
64
+ ENERGY_EXPENDED_STATUS = 1 << 3
65
+ RR_INTERVAL = 1 << 4
66
+
67
+ def __post_init__(self) -> None:
68
+ if self.heart_rate < 0 or self.heart_rate > 0xFFFF:
72
69
  raise core.InvalidArgumentError('heart_rate out of range')
73
70
 
74
- if energy_expended is not None and (
75
- energy_expended < 0 or energy_expended > 0xFFFF
71
+ if self.energy_expended is not None and (
72
+ self.energy_expended < 0 or self.energy_expended > 0xFFFF
76
73
  ):
77
74
  raise core.InvalidArgumentError('energy_expended out of range')
78
75
 
79
- if rr_intervals:
80
- for rr_interval in rr_intervals:
76
+ if self.rr_intervals:
77
+ for rr_interval in self.rr_intervals:
81
78
  if rr_interval < 0 or rr_interval * 1024 > 0xFFFF:
82
79
  raise core.InvalidArgumentError('rr_intervals out of range')
83
80
 
84
- self.heart_rate = heart_rate
85
- self.sensor_contact_detected = sensor_contact_detected
86
- self.energy_expended = energy_expended
87
- self.rr_intervals = rr_intervals
88
-
89
81
  @classmethod
90
- def from_bytes(cls, data):
82
+ def from_bytes(cls, data: bytes) -> Self:
91
83
  flags = data[0]
92
84
  offset = 1
93
85
 
94
- if flags & 1:
95
- hr = struct.unpack_from('<H', data, offset)[0]
86
+ if flags & cls.Flag.INT16_HEART_RATE:
87
+ heart_rate = struct.unpack_from('<H', data, offset)[0]
96
88
  offset += 2
97
89
  else:
98
- hr = struct.unpack_from('B', data, offset)[0]
90
+ heart_rate = struct.unpack_from('B', data, offset)[0]
99
91
  offset += 1
100
92
 
101
- if flags & (1 << 2):
102
- sensor_contact_detected = flags & (1 << 1) != 0
93
+ if flags & cls.Flag.SENSOR_CONTACT_SUPPORTED:
94
+ sensor_contact_detected = flags & cls.Flag.SENSOR_CONTACT_DETECTED != 0
103
95
  else:
104
96
  sensor_contact_detected = None
105
97
 
106
- if flags & (1 << 3):
98
+ if flags & cls.Flag.ENERGY_EXPENDED_STATUS:
107
99
  energy_expended = struct.unpack_from('<H', data, offset)[0]
108
100
  offset += 2
109
101
  else:
110
102
  energy_expended = None
111
103
 
112
- if flags & (1 << 4):
104
+ rr_intervals: Sequence[float] | None = None
105
+ if flags & cls.Flag.RR_INTERVAL:
113
106
  rr_intervals = tuple(
114
- struct.unpack_from('<H', data, offset + i * 2)[0] / 1024
115
- for i in range((len(data) - offset) // 2)
107
+ struct.unpack_from('<H', data, i)[0] / 1024
108
+ for i in range(offset, len(data), 2)
116
109
  )
117
- else:
118
- rr_intervals = ()
119
110
 
120
- return cls(hr, sensor_contact_detected, energy_expended, rr_intervals)
111
+ return cls(
112
+ heart_rate=heart_rate,
113
+ sensor_contact_detected=sensor_contact_detected,
114
+ energy_expended=energy_expended,
115
+ rr_intervals=rr_intervals,
116
+ )
121
117
 
122
- def __bytes__(self):
118
+ def __bytes__(self) -> bytes:
119
+ flags = 0
123
120
  if self.heart_rate < 256:
124
- flags = 0
125
121
  data = struct.pack('B', self.heart_rate)
126
122
  else:
127
- flags = 1
123
+ flags |= self.Flag.INT16_HEART_RATE
128
124
  data = struct.pack('<H', self.heart_rate)
129
125
 
130
126
  if self.sensor_contact_detected is not None:
131
- flags |= ((1 if self.sensor_contact_detected else 0) << 1) | (1 << 2)
127
+ flags |= self.Flag.SENSOR_CONTACT_SUPPORTED
128
+ if self.sensor_contact_detected:
129
+ flags |= self.Flag.SENSOR_CONTACT_DETECTED
132
130
 
133
131
  if self.energy_expended is not None:
134
- flags |= 1 << 3
132
+ flags |= self.Flag.ENERGY_EXPENDED_STATUS
135
133
  data += struct.pack('<H', self.energy_expended)
136
134
 
137
- if self.rr_intervals:
138
- flags |= 1 << 4
135
+ if self.rr_intervals is not None:
136
+ flags |= self.Flag.RR_INTERVAL
139
137
  data += b''.join(
140
138
  [
141
139
  struct.pack('<H', int(rr_interval * 1024))
@@ -145,57 +143,67 @@ class HeartRateService(TemplateService):
145
143
 
146
144
  return bytes([flags]) + data
147
145
 
148
- def __str__(self):
149
- return (
150
- f'HeartRateMeasurement(heart_rate={self.heart_rate},'
151
- f' sensor_contact_detected={self.sensor_contact_detected},'
152
- f' energy_expended={self.energy_expended},'
153
- f' rr_intervals={self.rr_intervals})'
154
- )
155
-
156
146
  def __init__(
157
147
  self,
158
- read_heart_rate_measurement,
159
- body_sensor_location=None,
160
- reset_energy_expended=None,
148
+ read_heart_rate_measurement: Callable[
149
+ [device.Connection], HeartRateMeasurement
150
+ ],
151
+ body_sensor_location: HeartRateService.BodySensorLocation | None = None,
152
+ reset_energy_expended: Callable[[device.Connection], Any] | None = None,
161
153
  ):
162
- self.heart_rate_measurement_characteristic = SerializableCharacteristicAdapter(
163
- Characteristic(
164
- GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
165
- Characteristic.Properties.NOTIFY,
166
- 0,
167
- CharacteristicValue(read=read_heart_rate_measurement),
168
- ),
169
- HeartRateService.HeartRateMeasurement,
154
+ self.heart_rate_measurement_characteristic = (
155
+ gatt_adapters.SerializableCharacteristicAdapter(
156
+ gatt.Characteristic(
157
+ uuid=gatt.GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
158
+ properties=gatt.Characteristic.Properties.NOTIFY,
159
+ permissions=gatt.Characteristic.Permissions(0),
160
+ value=gatt.CharacteristicValue(read=read_heart_rate_measurement),
161
+ ),
162
+ HeartRateService.HeartRateMeasurement,
163
+ )
170
164
  )
171
- characteristics = [self.heart_rate_measurement_characteristic]
165
+ characteristics: list[gatt.Characteristic] = [
166
+ self.heart_rate_measurement_characteristic
167
+ ]
172
168
 
173
169
  if body_sensor_location is not None:
174
- self.body_sensor_location_characteristic = Characteristic(
175
- GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
176
- Characteristic.Properties.READ,
177
- Characteristic.READABLE,
178
- bytes([int(body_sensor_location)]),
170
+ self.body_sensor_location_characteristic = (
171
+ gatt_adapters.EnumCharacteristicAdapter(
172
+ gatt.Characteristic(
173
+ uuid=gatt.GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
174
+ properties=gatt.Characteristic.Properties.READ,
175
+ permissions=gatt.Characteristic.READABLE,
176
+ value=body_sensor_location,
177
+ ),
178
+ cls=self.BodySensorLocation,
179
+ length=1,
180
+ )
179
181
  )
180
182
  characteristics.append(self.body_sensor_location_characteristic)
181
183
 
182
184
  if reset_energy_expended:
183
185
 
184
- def write_heart_rate_control_point_value(connection, value):
186
+ def write_heart_rate_control_point_value(
187
+ connection: device.Connection, value: bytes
188
+ ) -> None:
185
189
  if value == self.RESET_ENERGY_EXPENDED:
186
190
  if reset_energy_expended is not None:
187
191
  reset_energy_expended(connection)
188
192
  else:
189
- raise ATT_Error(self.CONTROL_POINT_NOT_SUPPORTED)
190
-
191
- self.heart_rate_control_point_characteristic = PackedCharacteristicAdapter(
192
- Characteristic(
193
- GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
194
- Characteristic.Properties.WRITE,
195
- Characteristic.WRITEABLE,
196
- CharacteristicValue(write=write_heart_rate_control_point_value),
197
- ),
198
- pack_format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT,
193
+ raise att.ATT_Error(self.CONTROL_POINT_NOT_SUPPORTED)
194
+
195
+ self.heart_rate_control_point_characteristic = (
196
+ gatt_adapters.PackedCharacteristicAdapter(
197
+ gatt.Characteristic(
198
+ uuid=gatt.GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
199
+ properties=gatt.Characteristic.Properties.WRITE,
200
+ permissions=gatt.Characteristic.WRITEABLE,
201
+ value=gatt.CharacteristicValue(
202
+ write=write_heart_rate_control_point_value
203
+ ),
204
+ ),
205
+ pack_format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT,
206
+ )
199
207
  )
200
208
  characteristics.append(self.heart_rate_control_point_characteristic)
201
209
 
@@ -203,50 +211,51 @@ class HeartRateService(TemplateService):
203
211
 
204
212
 
205
213
  # -----------------------------------------------------------------------------
206
- class HeartRateServiceProxy(ProfileServiceProxy):
214
+ class HeartRateServiceProxy(gatt_client.ProfileServiceProxy):
207
215
  SERVICE_CLASS = HeartRateService
208
216
 
209
- heart_rate_measurement: (
210
- CharacteristicProxy[HeartRateService.HeartRateMeasurement] | None
211
- )
217
+ heart_rate_measurement: gatt_client.CharacteristicProxy[
218
+ HeartRateService.HeartRateMeasurement
219
+ ]
212
220
  body_sensor_location: (
213
- CharacteristicProxy[HeartRateService.BodySensorLocation] | None
221
+ gatt_client.CharacteristicProxy[HeartRateService.BodySensorLocation] | None
214
222
  )
215
- heart_rate_control_point: CharacteristicProxy[int] | None
223
+ heart_rate_control_point: gatt_client.CharacteristicProxy[int] | None
216
224
 
217
- def __init__(self, service_proxy):
225
+ def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
218
226
  self.service_proxy = service_proxy
219
227
 
220
- if characteristics := service_proxy.get_characteristics_by_uuid(
221
- GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC
222
- ):
223
- self.heart_rate_measurement = SerializableCharacteristicAdapter(
224
- characteristics[0], HeartRateService.HeartRateMeasurement
228
+ self.heart_rate_measurement = (
229
+ gatt_adapters.SerializableCharacteristicProxyAdapter(
230
+ service_proxy.get_required_characteristic_by_uuid(
231
+ gatt.GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC
232
+ ),
233
+ HeartRateService.HeartRateMeasurement,
225
234
  )
226
- else:
227
- self.heart_rate_measurement = None
235
+ )
228
236
 
229
237
  if characteristics := service_proxy.get_characteristics_by_uuid(
230
- GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC
238
+ gatt.GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC
231
239
  ):
232
- self.body_sensor_location = DelegatedCharacteristicAdapter(
233
- characteristics[0],
234
- decode=lambda value: HeartRateService.BodySensorLocation(value[0]),
240
+ self.body_sensor_location = gatt_adapters.EnumCharacteristicProxyAdapter(
241
+ characteristics[0], cls=HeartRateService.BodySensorLocation, length=1
235
242
  )
236
243
  else:
237
244
  self.body_sensor_location = None
238
245
 
239
246
  if characteristics := service_proxy.get_characteristics_by_uuid(
240
- GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC
247
+ gatt.GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC
241
248
  ):
242
- self.heart_rate_control_point = PackedCharacteristicAdapter(
243
- characteristics[0],
244
- pack_format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT,
249
+ self.heart_rate_control_point = (
250
+ gatt_adapters.PackedCharacteristicProxyAdapter(
251
+ characteristics[0],
252
+ pack_format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT,
253
+ )
245
254
  )
246
255
  else:
247
256
  self.heart_rate_control_point = None
248
257
 
249
- async def reset_energy_expended(self):
258
+ async def reset_energy_expended(self) -> None:
250
259
  if self.heart_rate_control_point is not None:
251
260
  return await self.heart_rate_control_point.write_value(
252
261
  HeartRateService.RESET_ENERGY_EXPENDED
bumble/rfcomm.py CHANGED
@@ -800,7 +800,7 @@ class Multiplexer(utils.EventEmitter):
800
800
 
801
801
  def send_frame(self, frame: RFCOMM_Frame) -> None:
802
802
  logger.debug(f'>>> Multiplexer sending {frame}')
803
- self.l2cap_channel.send_pdu(frame)
803
+ self.l2cap_channel.write(bytes(frame))
804
804
 
805
805
  def on_pdu(self, pdu: bytes) -> None:
806
806
  frame = RFCOMM_Frame.from_bytes(pdu)
bumble/sdp.py CHANGED
@@ -847,7 +847,7 @@ class Client:
847
847
  self.pending_request = request
848
848
 
849
849
  try:
850
- self.channel.send_pdu(bytes(request))
850
+ self.channel.write(bytes(request))
851
851
  return await self.pending_response
852
852
  finally:
853
853
  self.pending_request = None
@@ -1061,7 +1061,7 @@ class Server:
1061
1061
 
1062
1062
  def send_response(self, response):
1063
1063
  logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}')
1064
- self.channel.send_pdu(response)
1064
+ self.channel.write(response)
1065
1065
 
1066
1066
  def match_services(self, search_pattern: DataElement) -> dict[int, Service]:
1067
1067
  # Find the services for which the attributes in the pattern is a subset of the
bumble/smp.py CHANGED
@@ -27,7 +27,7 @@ from __future__ import annotations
27
27
  import asyncio
28
28
  import enum
29
29
  import logging
30
- from collections.abc import Awaitable, Callable
30
+ from collections.abc import Awaitable, Callable, Sequence
31
31
  from dataclasses import dataclass, field
32
32
  from typing import TYPE_CHECKING, ClassVar, TypeVar, cast
33
33
 
@@ -507,10 +507,15 @@ def smp_auth_req(bonding: bool, mitm: bool, sc: bool, keypress: bool, ct2: bool)
507
507
 
508
508
  # -----------------------------------------------------------------------------
509
509
  class AddressResolver:
510
- def __init__(self, resolving_keys):
510
+ def __init__(self, resolving_keys: Sequence[tuple[bytes, Address]]) -> None:
511
511
  self.resolving_keys = resolving_keys
512
512
 
513
- def resolve(self, address):
513
+ def can_resolve_to(self, address: Address) -> bool:
514
+ return any(
515
+ resolved_address == address for _, resolved_address in self.resolving_keys
516
+ )
517
+
518
+ def resolve(self, address: Address) -> Address | None:
514
519
  address_bytes = bytes(address)
515
520
  hash_part = address_bytes[0:3]
516
521
  prand = address_bytes[3:6]