bumble 0.0.218__py3-none-any.whl → 0.0.220__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/link.py CHANGED
@@ -11,6 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ from __future__ import annotations
14
15
 
15
16
  import asyncio
16
17
 
@@ -20,16 +21,7 @@ import asyncio
20
21
  import logging
21
22
  from typing import Optional
22
23
 
23
- from bumble import controller, core
24
- from bumble.hci import (
25
- HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
26
- HCI_PAGE_TIMEOUT_ERROR,
27
- HCI_SUCCESS,
28
- HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
29
- Address,
30
- HCI_Connection_Complete_Event,
31
- Role,
32
- )
24
+ from bumble import controller, core, hci, lmp
33
25
 
34
26
  # -----------------------------------------------------------------------------
35
27
  # Logging
@@ -40,13 +32,6 @@ logger = logging.getLogger(__name__)
40
32
  # -----------------------------------------------------------------------------
41
33
  # Utils
42
34
  # -----------------------------------------------------------------------------
43
- def parse_parameters(params_str):
44
- result = {}
45
- for param_str in params_str.split(','):
46
- if '=' in param_str:
47
- key, value = param_str.split('=')
48
- result[key] = value
49
- return result
50
35
 
51
36
 
52
37
  # -----------------------------------------------------------------------------
@@ -69,21 +54,21 @@ class LocalLink:
69
54
  # Common utils
70
55
  ############################################################
71
56
 
72
- def add_controller(self, controller):
57
+ def add_controller(self, controller: controller.Controller):
73
58
  logger.debug(f'new controller: {controller}')
74
59
  self.controllers.add(controller)
75
60
 
76
- def remove_controller(self, controller):
61
+ def remove_controller(self, controller: controller.Controller):
77
62
  self.controllers.remove(controller)
78
63
 
79
- def find_controller(self, address):
64
+ def find_controller(self, address: hci.Address) -> controller.Controller | None:
80
65
  for controller in self.controllers:
81
66
  if controller.random_address == address:
82
67
  return controller
83
68
  return None
84
69
 
85
70
  def find_classic_controller(
86
- self, address: Address
71
+ self, address: hci.Address
87
72
  ) -> Optional[controller.Controller]:
88
73
  for controller in self.controllers:
89
74
  if controller.public_address == address:
@@ -100,13 +85,19 @@ class LocalLink:
100
85
  def on_address_changed(self, controller):
101
86
  pass
102
87
 
103
- def send_advertising_data(self, sender_address, data):
88
+ def send_advertising_data(self, sender_address: hci.Address, data: bytes):
104
89
  # Send the advertising data to all controllers, except the sender
105
90
  for controller in self.controllers:
106
91
  if controller.random_address != sender_address:
107
92
  controller.on_link_advertising_data(sender_address, data)
108
93
 
109
- def send_acl_data(self, sender_controller, destination_address, transport, data):
94
+ def send_acl_data(
95
+ self,
96
+ sender_controller: controller.Controller,
97
+ destination_address: hci.Address,
98
+ transport: core.PhysicalTransport,
99
+ data: bytes,
100
+ ):
110
101
  # Send the data to the first controller with a matching address
111
102
  if transport == core.PhysicalTransport.LE:
112
103
  destination_controller = self.find_controller(destination_address)
@@ -118,9 +109,13 @@ class LocalLink:
118
109
  raise ValueError("unsupported transport type")
119
110
 
120
111
  if destination_controller is not None:
121
- destination_controller.on_link_acl_data(source_address, transport, data)
112
+ asyncio.get_running_loop().call_soon(
113
+ lambda: destination_controller.on_link_acl_data(
114
+ source_address, transport, data
115
+ )
116
+ )
122
117
 
123
- def on_connection_complete(self):
118
+ def on_connection_complete(self) -> None:
124
119
  # Check that we expect this call
125
120
  if not self.pending_connection:
126
121
  logger.warning('on_connection_complete with no pending connection')
@@ -139,17 +134,21 @@ class LocalLink:
139
134
  le_create_connection_command.peer_address
140
135
  ):
141
136
  central_controller.on_link_peripheral_connection_complete(
142
- le_create_connection_command, HCI_SUCCESS
137
+ le_create_connection_command, hci.HCI_SUCCESS
143
138
  )
144
139
  peripheral_controller.on_link_central_connected(central_address)
145
140
  return
146
141
 
147
142
  # No peripheral found
148
143
  central_controller.on_link_peripheral_connection_complete(
149
- le_create_connection_command, HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR
144
+ le_create_connection_command, hci.HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR
150
145
  )
151
146
 
152
- def connect(self, central_address, le_create_connection_command):
147
+ def connect(
148
+ self,
149
+ central_address: hci.Address,
150
+ le_create_connection_command: hci.HCI_LE_Create_Connection_Command,
151
+ ):
153
152
  logger.debug(
154
153
  f'$$$ CONNECTION {central_address} -> '
155
154
  f'{le_create_connection_command.peer_address}'
@@ -158,7 +157,10 @@ class LocalLink:
158
157
  asyncio.get_running_loop().call_soon(self.on_connection_complete)
159
158
 
160
159
  def on_disconnection_complete(
161
- self, initiating_address, target_address, disconnect_command
160
+ self,
161
+ initiating_address: hci.Address,
162
+ target_address: hci.Address,
163
+ disconnect_command: hci.HCI_Disconnect_Command,
162
164
  ):
163
165
  # Find the controller that initiated the disconnection
164
166
  if not (initiating_controller := self.find_controller(initiating_address)):
@@ -172,20 +174,32 @@ class LocalLink:
172
174
  )
173
175
 
174
176
  initiating_controller.on_link_disconnection_complete(
175
- disconnect_command, HCI_SUCCESS
177
+ disconnect_command, hci.HCI_SUCCESS
176
178
  )
177
179
 
178
- def disconnect(self, initiating_address, target_address, disconnect_command):
180
+ def disconnect(
181
+ self,
182
+ initiating_address: hci.Address,
183
+ target_address: hci.Address,
184
+ disconnect_command: hci.HCI_Disconnect_Command,
185
+ ):
179
186
  logger.debug(
180
187
  f'$$$ DISCONNECTION {initiating_address} -> '
181
188
  f'{target_address}: reason = {disconnect_command.reason}'
182
189
  )
183
- args = [initiating_address, target_address, disconnect_command]
184
- asyncio.get_running_loop().call_soon(self.on_disconnection_complete, *args)
190
+ asyncio.get_running_loop().call_soon(
191
+ lambda: self.on_disconnection_complete(
192
+ initiating_address, target_address, disconnect_command
193
+ )
194
+ )
185
195
 
186
- # pylint: disable=too-many-arguments
187
196
  def on_connection_encrypted(
188
- self, central_address, peripheral_address, rand, ediv, ltk
197
+ self,
198
+ central_address: hci.Address,
199
+ peripheral_address: hci.Address,
200
+ rand: bytes,
201
+ ediv: int,
202
+ ltk: bytes,
189
203
  ):
190
204
  logger.debug(f'*** ENCRYPTION {central_address} -> {peripheral_address}')
191
205
 
@@ -198,7 +212,7 @@ class LocalLink:
198
212
  def create_cis(
199
213
  self,
200
214
  central_controller: controller.Controller,
201
- peripheral_address: Address,
215
+ peripheral_address: hci.Address,
202
216
  cig_id: int,
203
217
  cis_id: int,
204
218
  ) -> None:
@@ -216,7 +230,7 @@ class LocalLink:
216
230
  def accept_cis(
217
231
  self,
218
232
  peripheral_controller: controller.Controller,
219
- central_address: Address,
233
+ central_address: hci.Address,
220
234
  cig_id: int,
221
235
  cis_id: int,
222
236
  ) -> None:
@@ -224,17 +238,16 @@ class LocalLink:
224
238
  f'$$$ CIS Accept {peripheral_controller.random_address} -> {central_address}'
225
239
  )
226
240
  if central_controller := self.find_controller(central_address):
227
- asyncio.get_running_loop().call_soon(
228
- central_controller.on_link_cis_established, cig_id, cis_id
229
- )
230
- asyncio.get_running_loop().call_soon(
241
+ loop = asyncio.get_running_loop()
242
+ loop.call_soon(central_controller.on_link_cis_established, cig_id, cis_id)
243
+ loop.call_soon(
231
244
  peripheral_controller.on_link_cis_established, cig_id, cis_id
232
245
  )
233
246
 
234
247
  def disconnect_cis(
235
248
  self,
236
249
  initiator_controller: controller.Controller,
237
- peer_address: Address,
250
+ peer_address: hci.Address,
238
251
  cig_id: int,
239
252
  cis_id: int,
240
253
  ) -> None:
@@ -242,138 +255,28 @@ class LocalLink:
242
255
  f'$$$ CIS Disconnect {initiator_controller.random_address} -> {peer_address}'
243
256
  )
244
257
  if peer_controller := self.find_controller(peer_address):
245
- asyncio.get_running_loop().call_soon(
258
+ loop = asyncio.get_running_loop()
259
+ loop.call_soon(
246
260
  initiator_controller.on_link_cis_disconnected, cig_id, cis_id
247
261
  )
248
- asyncio.get_running_loop().call_soon(
249
- peer_controller.on_link_cis_disconnected, cig_id, cis_id
250
- )
262
+ loop.call_soon(peer_controller.on_link_cis_disconnected, cig_id, cis_id)
251
263
 
252
264
  ############################################################
253
265
  # Classic handlers
254
266
  ############################################################
255
267
 
256
- def classic_connect(self, initiator_controller, responder_address):
257
- logger.debug(
258
- f'[Classic] {initiator_controller.public_address} connects to {responder_address}'
259
- )
260
- responder_controller = self.find_classic_controller(responder_address)
261
- if responder_controller is None:
262
- initiator_controller.on_classic_connection_complete(
263
- responder_address, HCI_PAGE_TIMEOUT_ERROR
264
- )
265
- return
266
- self.pending_classic_connection = (initiator_controller, responder_controller)
267
-
268
- responder_controller.on_classic_connection_request(
269
- initiator_controller.public_address,
270
- HCI_Connection_Complete_Event.LinkType.ACL,
271
- )
272
-
273
- def classic_accept_connection(
274
- self, responder_controller, initiator_address, responder_role
275
- ):
276
- logger.debug(
277
- f'[Classic] {responder_controller.public_address} accepts to connect {initiator_address}'
278
- )
279
- initiator_controller = self.find_classic_controller(initiator_address)
280
- if initiator_controller is None:
281
- responder_controller.on_classic_connection_complete(
282
- responder_controller.public_address, HCI_PAGE_TIMEOUT_ERROR
283
- )
284
- return
285
-
286
- async def task():
287
- if responder_role != Role.PERIPHERAL:
288
- initiator_controller.on_classic_role_change(
289
- responder_controller.public_address, int(not (responder_role))
290
- )
291
- initiator_controller.on_classic_connection_complete(
292
- responder_controller.public_address, HCI_SUCCESS
293
- )
294
-
295
- asyncio.create_task(task())
296
- responder_controller.on_classic_role_change(
297
- initiator_controller.public_address, responder_role
298
- )
299
- responder_controller.on_classic_connection_complete(
300
- initiator_controller.public_address, HCI_SUCCESS
301
- )
302
- self.pending_classic_connection = None
303
-
304
- def classic_disconnect(self, initiator_controller, responder_address, reason):
305
- logger.debug(
306
- f'[Classic] {initiator_controller.public_address} disconnects {responder_address}'
307
- )
308
- responder_controller = self.find_classic_controller(responder_address)
309
-
310
- async def task():
311
- initiator_controller.on_classic_disconnected(responder_address, reason)
312
-
313
- asyncio.create_task(task())
314
- responder_controller.on_classic_disconnected(
315
- initiator_controller.public_address, reason
316
- )
317
-
318
- def classic_switch_role(
319
- self, initiator_controller, responder_address, initiator_new_role
320
- ):
321
- responder_controller = self.find_classic_controller(responder_address)
322
- if responder_controller is None:
323
- return
324
-
325
- async def task():
326
- initiator_controller.on_classic_role_change(
327
- responder_address, initiator_new_role
328
- )
329
-
330
- asyncio.create_task(task())
331
- responder_controller.on_classic_role_change(
332
- initiator_controller.public_address, int(not (initiator_new_role))
333
- )
334
-
335
- def classic_sco_connect(
336
- self,
337
- initiator_controller: controller.Controller,
338
- responder_address: Address,
339
- link_type: int,
340
- ):
341
- logger.debug(
342
- f'[Classic] {initiator_controller.public_address} connects SCO to {responder_address}'
343
- )
344
- responder_controller = self.find_classic_controller(responder_address)
345
- # Initiator controller should handle it.
346
- assert responder_controller
347
-
348
- responder_controller.on_classic_connection_request(
349
- initiator_controller.public_address,
350
- link_type,
351
- )
352
-
353
- def classic_accept_sco_connection(
268
+ def send_lmp_packet(
354
269
  self,
355
- responder_controller: controller.Controller,
356
- initiator_address: Address,
357
- link_type: int,
270
+ sender_controller: controller.Controller,
271
+ receiver_address: hci.Address,
272
+ packet: lmp.Packet,
358
273
  ):
359
- logger.debug(
360
- f'[Classic] {responder_controller.public_address} accepts to connect SCO {initiator_address}'
361
- )
362
- initiator_controller = self.find_classic_controller(initiator_address)
363
- if initiator_controller is None:
364
- responder_controller.on_classic_sco_connection_complete(
365
- responder_controller.public_address,
366
- HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
367
- link_type,
274
+ if not (receiver_controller := self.find_classic_controller(receiver_address)):
275
+ raise core.InvalidArgumentError(
276
+ f"Unable to find controller for address {receiver_address}"
368
277
  )
369
- return
370
-
371
- async def task():
372
- initiator_controller.on_classic_sco_connection_complete(
373
- responder_controller.public_address, HCI_SUCCESS, link_type
278
+ asyncio.get_running_loop().call_soon(
279
+ lambda: receiver_controller.on_lmp_packet(
280
+ sender_controller.public_address, packet
374
281
  )
375
-
376
- asyncio.create_task(task())
377
- responder_controller.on_classic_sco_connection_complete(
378
- initiator_controller.public_address, HCI_SUCCESS, link_type
379
282
  )
bumble/lmp.py ADDED
@@ -0,0 +1,324 @@
1
+ # Copyright 2021-2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+
20
+ import struct
21
+ from dataclasses import dataclass, field
22
+ from typing import TypeVar
23
+
24
+ from bumble import hci, utils
25
+
26
+
27
+ class Opcode(utils.OpenIntEnum):
28
+ '''
29
+ See Bluetooth spec @ Vol 2, Part C - 5.1 PDU summary.
30
+
31
+ Follow the alphabetical order defined there.
32
+ '''
33
+
34
+ # fmt: off
35
+ LMP_ACCEPTED = 3
36
+ LMP_ACCEPTED_EXT = 127 << 8 + 1
37
+ LMP_AU_RAND = 11
38
+ LMP_AUTO_RATE = 35
39
+ LMP_CHANNEL_CLASSIFICATION = 127 << 8 + 17
40
+ LMP_CHANNEL_CLASSIFICATION_REQ = 127 << 8 + 16
41
+ LMP_CLK_ADJ = 127 << 8 + 5
42
+ LMP_CLK_ADJ_ACK = 127 << 8 + 6
43
+ LMP_CLK_ADJ_REQ = 127 << 8 + 7
44
+ LMP_CLKOFFSET_REQ = 5
45
+ LMP_CLKOFFSET_RES = 6
46
+ LMP_COMB_KEY = 9
47
+ LMP_DECR_POWER_REQ = 32
48
+ LMP_DETACH = 7
49
+ LMP_DHKEY_CHECK = 65
50
+ LMP_ENCAPSULATED_HEADER = 61
51
+ LMP_ENCAPSULATED_PAYLOAD = 62
52
+ LMP_ENCRYPTION_KEY_SIZE_MASK_REQ= 58
53
+ LMP_ENCRYPTION_KEY_SIZE_MASK_RES= 59
54
+ LMP_ENCRYPTION_KEY_SIZE_REQ = 16
55
+ LMP_ENCRYPTION_MODE_REQ = 15
56
+ LMP_ESCO_LINK_REQ = 127 << 8 + 12
57
+ LMP_FEATURES_REQ = 39
58
+ LMP_FEATURES_REQ_EXT = 127 << 8 + 3
59
+ LMP_FEATURES_RES = 40
60
+ LMP_FEATURES_RES_EXT = 127 << 8 + 4
61
+ LMP_HOLD = 20
62
+ LMP_HOLD_REQ = 21
63
+ LMP_HOST_CONNECTION_REQ = 51
64
+ LMP_IN_RAND = 8
65
+ LMP_INCR_POWER_REQ = 31
66
+ LMP_IO_CAPABILITY_REQ = 127 << 8 + 25
67
+ LMP_IO_CAPABILITY_RES = 127 << 8 + 26
68
+ LMP_KEYPRESS_NOTIFICATION = 127 << 8 + 30
69
+ LMP_MAX_POWER = 33
70
+ LMP_MAX_SLOT = 45
71
+ LMP_MAX_SLOT_REQ = 46
72
+ LMP_MIN_POWER = 34
73
+ LMP_NAME_REQ = 1
74
+ LMP_NAME_RES = 2
75
+ LMP_NOT_ACCEPTED = 4
76
+ LMP_NOT_ACCEPTED_EXT = 127 << 8 + 2
77
+ LMP_NUMERIC_COMPARISON_FAILED = 127 << 8 + 27
78
+ LMP_OOB_FAILED = 127 << 8 + 29
79
+ LMP_PACKET_TYPE_TABLE_REQ = 127 << 8 + 11
80
+ LMP_PAGE_MODE_REQ = 53
81
+ LMP_PAGE_SCAN_MODE_REQ = 54
82
+ LMP_PASSKEY_FAILED = 127 << 8 + 28
83
+ LMP_PAUSE_ENCRYPTION_AES_REQ = 66
84
+ LMP_PAUSE_ENCRYPTION_REQ = 127 << 8 + 23
85
+ LMP_PING_REQ = 127 << 8 + 33
86
+ LMP_PING_RES = 127 << 8 + 34
87
+ LMP_POWER_CONTROL_REQ = 127 << 8 + 31
88
+ LMP_POWER_CONTROL_RES = 127 << 8 + 32
89
+ LMP_PREFERRED_RATE = 36
90
+ LMP_QUALITY_OF_SERVICE = 41
91
+ LMP_QUALITY_OF_SERVICE_REQ = 42
92
+ LMP_REMOVE_ESCO_LINK_REQ = 127 << 8 + 13
93
+ LMP_REMOVE_SCO_LINK_REQ = 44
94
+ LMP_RESUME_ENCRYPTION_REQ = 127 << 8 + 24
95
+ LMP_SAM_DEFINE_MAP = 127 << 8 + 36
96
+ LMP_SAM_SET_TYPE0 = 127 << 8 + 35
97
+ LMP_SAM_SWITCH = 127 << 8 + 37
98
+ LMP_SCO_LINK_REQ = 43
99
+ LMP_SET_AFH = 60
100
+ LMP_SETUP_COMPLETE = 49
101
+ LMP_SIMPLE_PAIRING_CONFIRM = 63
102
+ LMP_SIMPLE_PAIRING_NUMBER = 64
103
+ LMP_SLOT_OFFSET = 52
104
+ LMP_SNIFF_REQ = 23
105
+ LMP_SNIFF_SUBRATING_REQ = 127 << 8 + 21
106
+ LMP_SNIFF_SUBRATING_RES = 127 << 8 + 22
107
+ LMP_SRES = 12
108
+ LMP_START_ENCRYPTION_REQ = 17
109
+ LMP_STOP_ENCRYPTION_REQ = 18
110
+ LMP_SUPERVISION_TIMEOUT = 55
111
+ LMP_SWITCH_REQ = 19
112
+ LMP_TEMP_KEY = 14
113
+ LMP_TEMP_RAND = 13
114
+ LMP_TEST_ACTIVATE = 56
115
+ LMP_TEST_CONTROL = 57
116
+ LMP_TIMING_ACCURACY_REQ = 47
117
+ LMP_TIMING_ACCURACY_RES = 48
118
+ LMP_UNIT_KEY = 10
119
+ LMP_UNSNIFF_REQ = 24
120
+ LMP_USE_SEMI_PERMANENT_KEY = 50
121
+ LMP_VERSION_REQ = 37
122
+ LMP_VERSION_RES = 38
123
+ # fmt: on
124
+
125
+ @classmethod
126
+ def parse_from(cls, data: bytes, offset: int = 0) -> tuple[int, Opcode]:
127
+ opcode = data[offset]
128
+ if opcode in (124, 127):
129
+ opcode = struct.unpack('>H', data)[0]
130
+ return offset + 2, Opcode(opcode)
131
+ return offset + 1, Opcode(opcode)
132
+
133
+ def __bytes__(self) -> bytes:
134
+ if self.value >> 8:
135
+ return struct.pack('>H', self.value)
136
+ return bytes([self.value])
137
+
138
+ @classmethod
139
+ def type_metadata(cls):
140
+ return hci.metadata(
141
+ {
142
+ 'serializer': bytes,
143
+ 'parser': lambda data, offset: (Opcode.parse_from(data, offset)),
144
+ }
145
+ )
146
+
147
+
148
+ class Packet:
149
+ '''
150
+ See Bluetooth spec @ Vol 2, Part C - 5.1 PDU summary
151
+ '''
152
+
153
+ subclasses: dict[int, type[Packet]] = {}
154
+ opcode: Opcode
155
+ fields: hci.Fields = ()
156
+ _payload: bytes = b''
157
+
158
+ _Packet = TypeVar("_Packet", bound="Packet")
159
+
160
+ @classmethod
161
+ def subclass(cls, subclass: type[_Packet]) -> type[_Packet]:
162
+ # Register a factory for this class
163
+ cls.subclasses[subclass.opcode] = subclass
164
+ subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
165
+
166
+ return subclass
167
+
168
+ @classmethod
169
+ def from_bytes(cls, data: bytes) -> Packet:
170
+ offset, opcode = Opcode.parse_from(data)
171
+ if not (subclass := cls.subclasses.get(opcode)):
172
+ instance = Packet()
173
+ instance.opcode = opcode
174
+ else:
175
+ instance = subclass(
176
+ **hci.HCI_Object.dict_from_bytes(data, offset, subclass.fields)
177
+ )
178
+ instance.payload = data[offset:]
179
+ return instance
180
+
181
+ @property
182
+ def payload(self) -> bytes:
183
+ if self._payload is None:
184
+ self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
185
+ return self._payload
186
+
187
+ @payload.setter
188
+ def payload(self, value: bytes) -> None:
189
+ self._payload = value
190
+
191
+ def __bytes__(self) -> bytes:
192
+ return bytes(self.opcode) + self.payload
193
+
194
+
195
+ @Packet.subclass
196
+ @dataclass
197
+ class LmpAccepted(Packet):
198
+ opcode = Opcode.LMP_ACCEPTED
199
+
200
+ response_opcode: Opcode = field(metadata=Opcode.type_metadata())
201
+
202
+
203
+ @Packet.subclass
204
+ @dataclass
205
+ class LmpNotAccepted(Packet):
206
+ opcode = Opcode.LMP_NOT_ACCEPTED
207
+
208
+ response_opcode: Opcode = field(metadata=Opcode.type_metadata())
209
+ error_code: int = field(metadata=hci.metadata(1))
210
+
211
+
212
+ @Packet.subclass
213
+ @dataclass
214
+ class LmpAcceptedExt(Packet):
215
+ opcode = Opcode.LMP_ACCEPTED_EXT
216
+
217
+ response_opcode: Opcode = field(metadata=Opcode.type_metadata())
218
+
219
+
220
+ @Packet.subclass
221
+ @dataclass
222
+ class LmpNotAcceptedExt(Packet):
223
+ opcode = Opcode.LMP_NOT_ACCEPTED_EXT
224
+
225
+ response_opcode: Opcode = field(metadata=Opcode.type_metadata())
226
+ error_code: int = field(metadata=hci.metadata(1))
227
+
228
+
229
+ @Packet.subclass
230
+ @dataclass
231
+ class LmpAuRand(Packet):
232
+ opcode = Opcode.LMP_AU_RAND
233
+
234
+ random_number: bytes = field(metadata=hci.metadata(16))
235
+
236
+
237
+ @Packet.subclass
238
+ @dataclass
239
+ class LmpDetach(Packet):
240
+ opcode = Opcode.LMP_DETACH
241
+
242
+ error_code: int = field(metadata=hci.metadata(1))
243
+
244
+
245
+ @Packet.subclass
246
+ @dataclass
247
+ class LmpEscoLinkReq(Packet):
248
+ opcode = Opcode.LMP_ESCO_LINK_REQ
249
+
250
+ esco_handle: int = field(metadata=hci.metadata(1))
251
+ esco_lt_addr: int = field(metadata=hci.metadata(1))
252
+ timing_control_flags: int = field(metadata=hci.metadata(1))
253
+ d_esco: int = field(metadata=hci.metadata(1))
254
+ t_esco: int = field(metadata=hci.metadata(1))
255
+ w_esco: int = field(metadata=hci.metadata(1))
256
+ esco_packet_type_c_to_p: int = field(metadata=hci.metadata(1))
257
+ esco_packet_type_p_to_c: int = field(metadata=hci.metadata(1))
258
+ packet_length_c_to_p: int = field(metadata=hci.metadata(2))
259
+ packet_length_p_to_c: int = field(metadata=hci.metadata(2))
260
+ air_mode: int = field(metadata=hci.metadata(1))
261
+ negotiation_state: int = field(metadata=hci.metadata(1))
262
+
263
+
264
+ @Packet.subclass
265
+ @dataclass
266
+ class LmpHostConnectionReq(Packet):
267
+ opcode = Opcode.LMP_HOST_CONNECTION_REQ
268
+
269
+
270
+ @Packet.subclass
271
+ @dataclass
272
+ class LmpRemoveEscoLinkReq(Packet):
273
+ opcode = Opcode.LMP_REMOVE_ESCO_LINK_REQ
274
+
275
+ esco_handle: int = field(metadata=hci.metadata(1))
276
+ error_code: int = field(metadata=hci.metadata(1))
277
+
278
+
279
+ @Packet.subclass
280
+ @dataclass
281
+ class LmpRemoveScoLinkReq(Packet):
282
+ opcode = Opcode.LMP_REMOVE_SCO_LINK_REQ
283
+
284
+ sco_handle: int = field(metadata=hci.metadata(1))
285
+ error_code: int = field(metadata=hci.metadata(1))
286
+
287
+
288
+ @Packet.subclass
289
+ @dataclass
290
+ class LmpScoLinkReq(Packet):
291
+ opcode = Opcode.LMP_SCO_LINK_REQ
292
+
293
+ sco_handle: int = field(metadata=hci.metadata(1))
294
+ timing_control_flags: int = field(metadata=hci.metadata(1))
295
+ d_sco: int = field(metadata=hci.metadata(1))
296
+ t_sco: int = field(metadata=hci.metadata(1))
297
+ sco_packet: int = field(metadata=hci.metadata(1))
298
+ air_mode: int = field(metadata=hci.metadata(1))
299
+
300
+
301
+ @Packet.subclass
302
+ @dataclass
303
+ class LmpSwitchReq(Packet):
304
+ opcode = Opcode.LMP_SWITCH_REQ
305
+
306
+ switch_instant: int = field(metadata=hci.metadata(4), default=0)
307
+
308
+
309
+ @Packet.subclass
310
+ @dataclass
311
+ class LmpNameReq(Packet):
312
+ opcode = Opcode.LMP_NAME_REQ
313
+
314
+ name_offset: int = field(metadata=hci.metadata(2))
315
+
316
+
317
+ @Packet.subclass
318
+ @dataclass
319
+ class LmpNameRes(Packet):
320
+ opcode = Opcode.LMP_NAME_RES
321
+
322
+ name_offset: int = field(metadata=hci.metadata(2))
323
+ name_length: int = field(metadata=hci.metadata(3))
324
+ name_fregment: bytes = field(metadata=hci.metadata('*'))