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/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
|
-
|
|
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=
|
|
2344
|
-
source_cid=
|
|
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,
|
|
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=
|
|
2374
|
-
source_cid=
|
|
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.
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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]
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
from
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 &
|
|
95
|
-
|
|
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
|
-
|
|
90
|
+
heart_rate = struct.unpack_from('B', data, offset)[0]
|
|
99
91
|
offset += 1
|
|
100
92
|
|
|
101
|
-
if flags &
|
|
102
|
-
sensor_contact_detected = flags &
|
|
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 &
|
|
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
|
-
|
|
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,
|
|
115
|
-
for i in range(
|
|
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(
|
|
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
|
|
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 |=
|
|
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 |=
|
|
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 |=
|
|
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
|
-
|
|
160
|
-
|
|
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 =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 = [
|
|
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 =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
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 =
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
243
|
-
|
|
244
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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]
|