Merge upstream commit 'a2f18cffc96dde7edace9538a117e446d4108d5c' into master
Test: atest avatar
Change-Id: Ie8e172c614e6e20ed8e74d894e90fcae755a108e
diff --git a/.github/workflows/python-build-test.yml b/.github/workflows/python-build-test.yml
index 062f153..ca19386 100644
--- a/.github/workflows/python-build-test.yml
+++ b/.github/workflows/python-build-test.yml
@@ -16,7 +16,12 @@
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - name: Check out from Git
+ uses: actions/checkout@v3
+ - name: Get history and tags for SCM versioning to work
+ run: |
+ git fetch --prune --unshallow
+ git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
@@ -24,11 +29,11 @@
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install ".[test,development,documentation]"
+ python -m pip install ".[build,test,development,documentation]"
- name: Test with pytest
run: |
pytest
- name: Build
run: |
inv build
- inv mkdocs
+ inv build.mkdocs
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 0000000..7d9bc19
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,37 @@
+name: Upload Python Package
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: read
+
+jobs:
+ deploy:
+ name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out from Git
+ uses: actions/checkout@v3
+ - name: Get history and tags for SCM versioning to work
+ run: |
+ git fetch --prune --unshallow
+ git fetch --depth=1 origin +refs/tags/*:refs/tags/*
+ - name: Set up Python
+ uses: actions/setup-python@v3
+ with:
+ python-version: '3.10'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install build
+ - name: Build package
+ run: python -m build
+ - name: Publish package to PyPI
+ if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/README.md b/README.md
index 3370baf..4fc3d2f 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
Bluetooth Stack for Apps, Emulation, Test and Experimentation
=============================================================
-<img src="docs/mkdocs/src/images/logo_framed.png" alt="drawing" width="200" height="200"/>
+<img src="docs/mkdocs/src/images/logo_framed.png" alt="Logo" width="200" height="200"/>
Bumble is a full-featured Bluetooth stack written entirely in Python. It supports most of the common Bluetooth Low Energy (BLE) and Bluetooth Classic (BR/EDR) protocols and profiles, including GAP, L2CAP, ATT, GATT, SMP, SDP, RFCOMM, HFP, HID and A2DP. The stack can be used with physical radios via HCI over USB, UART, or the Linux VHCI, as well as virtual radios, including the virtual Bluetooth support of the Android emulator.
@@ -21,6 +21,37 @@
mkdocs build -f docs/mkdocs/mkdocs.yml
```
+## Usage
+
+### Getting Started
+
+For a quick start to using Bumble, see the [Getting Started](docs/mkdocs/src/getting_started.md) guide.
+
+### Dependencies
+
+To install package dependencies needed to run the bumble examples execute the following commands:
+
+```
+python -m pip install --upgrade pip
+python -m pip install ".[test,development,documentation]"
+```
+
+### Examples
+
+Refer to the [Examples Documentation](examples/README.md) for details on the included example scripts and how to run them.
+
+The complete [list of Examples](/docs/mkdocs/src/examples/index.md), and what they are designed to do is here.
+
+There are also a set of [Apps and Tools](docs/mkdocs/src/apps_and_tools/index.md) that show the utility of Bumble.
+
+### Using Bumble With a USB Dongle
+
+Bumble is easiest to use with a dedicated USB dongle.
+This is because internal Bluetooth interfaces tend to be locked down by the operating system.
+You can use the [usb_probe](/docs/mkdocs/src/apps_and_tools/usb_probe.md) tool (all platforms) or `lsusb` (Linux or macOS) to list the available USB devices on your system.
+
+See the [USB Transport](/docs/mkdocs/src/transports/usb.md) page for details on how to refer to USB devices.
+
## License
Licensed under the [Apache 2.0](LICENSE) License.
diff --git a/apps/console.py b/apps/console.py
index 3ac4957..7ddbae4 100644
--- a/apps/console.py
+++ b/apps/console.py
@@ -32,6 +32,7 @@
from bumble.device import Device, Connection, Peer
from bumble.utils import AsyncRunner
from bumble.transport import open_transport_or_link
+from bumble.gatt import Characteristic
from prompt_toolkit import Application
from prompt_toolkit.history import FileHistory
@@ -121,6 +122,8 @@
},
'read': LiveCompleter(self.known_attributes),
'write': LiveCompleter(self.known_attributes),
+ 'subscribe': LiveCompleter(self.known_attributes),
+ 'unsubscribe': LiveCompleter(self.known_attributes),
'quit': None,
'exit': None
})
@@ -330,9 +333,27 @@
await self.show_attributes(attributes)
+ def find_characteristic(self, param):
+ parts = param.split('.')
+ if len(parts) == 2:
+ service_uuid = UUID(parts[0]) if parts[0] != '*' else None
+ characteristic_uuid = UUID(parts[1])
+ for service in self.connected_peer.services:
+ if service_uuid is None or service.uuid == service_uuid:
+ for characteristic in service.characteristics:
+ if characteristic.uuid == characteristic_uuid:
+ return characteristic
+ elif len(parts) == 1:
+ if parts[0].startswith('#'):
+ attribute_handle = int(f'{parts[0][1:]}', 16)
+ for service in self.connected_peer.services:
+ for characteristic in service.characteristics:
+ if characteristic.handle == attribute_handle:
+ return characteristic
+
async def command(self, command):
try:
- (keyword, *params) = command.strip().split(' ', 1)
+ (keyword, *params) = command.strip().split(' ')
keyword = keyword.replace('-', '_').lower()
handler = getattr(self, f'do_{keyword}', None)
if handler:
@@ -441,26 +462,73 @@
self.show_error('invalid syntax', 'expected read <attribute>')
return
- parts = params[0].split('.')
- if len(parts) == 2:
- service_uuid = UUID(parts[0]) if parts[0] != '*' else None
- characteristic_uuid = UUID(parts[1])
- for service in self.connected_peer.services:
- if service_uuid is None or service.uuid == service_uuid:
- for characteristic in service.characteristics:
- if characteristic.uuid == characteristic_uuid:
- value = await self.connected_peer.read_value(characteristic)
- self.append_to_output(f'VALUE: {value}')
- return
+ characteristic = self.find_characteristic(params[0])
+ if characteristic is None:
self.show_error('no such characteristic')
- elif len(parts) == 1:
- if parts[0].startswith('#'):
- attribute_handle = int(f'{parts[0][1:]}', 16)
- value = await self.connected_peer.read_value(attribute_handle)
- self.append_to_output(f'VALUE: {value}')
- return
+ return
+
+ value = await characteristic.read_value()
+ self.append_to_output(f'VALUE: 0x{value.hex()}')
+
+ async def do_write(self, params):
+ if not self.connected_peer:
+ self.show_error('not connected')
+ return
+
+ if len(params) != 2:
+ self.show_error('invalid syntax', 'expected write <attribute> <value>')
+ return
+
+ if params[1].upper().startswith("0X"):
+ value = bytes.fromhex(params[1][2:]) # parse as hex string
else:
+ try:
+ value = int(params[1]) # try as integer
+ except ValueError:
+ value = str.encode(params[1]) # must be a string
+
+ characteristic = self.find_characteristic(params[0])
+ if characteristic is None:
self.show_error('no such characteristic')
+ return
+
+ # use write with response if supported
+ with_response = characteristic.properties & Characteristic.WRITE
+ await characteristic.write_value(value, with_response=with_response)
+
+ async def do_subscribe(self, params):
+ if not self.connected_peer:
+ self.show_error('not connected')
+ return
+
+ if len(params) != 1:
+ self.show_error('invalid syntax', 'expected subscribe <attribute>')
+ return
+
+ characteristic = self.find_characteristic(params[0])
+ if characteristic is None:
+ self.show_error('no such characteristic')
+ return
+
+ await characteristic.subscribe(
+ lambda value: self.append_to_output(f"{characteristic} VALUE: 0x{value.hex()}"),
+ )
+
+ async def do_unsubscribe(self, params):
+ if not self.connected_peer:
+ self.show_error('not connected')
+ return
+
+ if len(params) != 1:
+ self.show_error('invalid syntax', 'expected subscribe <attribute>')
+ return
+
+ characteristic = self.find_characteristic(params[0])
+ if characteristic is None:
+ self.show_error('no such characteristic')
+ return
+
+ await characteristic.unsubscribe()
async def do_exit(self, params):
self.ui.exit()
diff --git a/apps/controller_info.py b/apps/controller_info.py
new file mode 100644
index 0000000..74d4550
--- /dev/null
+++ b/apps/controller_info.py
@@ -0,0 +1,105 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import asyncio
+import os
+import logging
+import click
+from colors import color
+from bumble.company_ids import COMPANY_IDENTIFIERS
+
+from bumble.core import name_or_number
+from bumble.hci import (
+ map_null_terminated_utf8_string,
+ HCI_LE_SUPPORTED_FEATURES_NAMES,
+ HCI_SUCCESS,
+ HCI_VERSION_NAMES,
+ LMP_VERSION_NAMES,
+ HCI_Command,
+ HCI_Read_BD_ADDR_Command,
+ HCI_READ_BD_ADDR_COMMAND,
+ HCI_Read_Local_Name_Command,
+ HCI_READ_LOCAL_NAME_COMMAND
+)
+from bumble.host import Host
+from bumble.transport import open_transport_or_link
+
+
+# -----------------------------------------------------------------------------
+async def get_classic_info(host):
+ if host.supports_command(HCI_READ_BD_ADDR_COMMAND):
+ response = await host.send_command(HCI_Read_BD_ADDR_Command())
+ if response.return_parameters.status == HCI_SUCCESS:
+ print()
+ print(color('Classic Address:', 'yellow'), response.return_parameters.bd_addr)
+
+ if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND):
+ response = await host.send_command(HCI_Read_Local_Name_Command())
+ if response.return_parameters.status == HCI_SUCCESS:
+ print()
+ print(color('Local Name:', 'yellow'), map_null_terminated_utf8_string(response.return_parameters.local_name))
+
+
+# -----------------------------------------------------------------------------
+async def get_le_info(host):
+ print()
+ print(color('LE Features:', 'yellow'))
+ for feature in host.supported_le_features:
+ print(' ', name_or_number(HCI_LE_SUPPORTED_FEATURES_NAMES, feature))
+
+
+# -----------------------------------------------------------------------------
+async def async_main(transport):
+ print('<<< connecting to HCI...')
+ async with await open_transport_or_link(transport) as (hci_source, hci_sink):
+ print('<<< connected')
+
+ host = Host(hci_source, hci_sink)
+ await host.reset()
+
+ # Print version
+ print(color('Version:', 'yellow'))
+ print(color(' Manufacturer: ', 'green'), name_or_number(COMPANY_IDENTIFIERS, host.local_version.company_identifier))
+ print(color(' HCI Version: ', 'green'), name_or_number(HCI_VERSION_NAMES, host.local_version.hci_version))
+ print(color(' HCI Subversion:', 'green'), host.local_version.hci_subversion)
+ print(color(' LMP Version: ', 'green'), name_or_number(LMP_VERSION_NAMES, host.local_version.lmp_version))
+ print(color(' LMP Subversion:', 'green'), host.local_version.lmp_subversion)
+
+ # Get the Classic info
+ await get_classic_info(host)
+
+ # Get the LE info
+ await get_le_info(host)
+
+ # Print the list of commands supported by the controller
+ print()
+ print(color('Supported Commands:', 'yellow'))
+ for command in host.supported_commands:
+ print(' ', HCI_Command.command_name(command))
+
+
+# -----------------------------------------------------------------------------
[email protected]()
[email protected]('transport')
+def main(transport):
+ logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
+ asyncio.run(async_main(transport))
+
+
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+ main()
diff --git a/apps/usb_probe.py b/apps/usb_probe.py
new file mode 100644
index 0000000..9cded2b
--- /dev/null
+++ b/apps/usb_probe.py
@@ -0,0 +1,157 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# This tool lists all the USB devices, with details about each device.
+# For each device, the different possible Bumble transport strings that can
+# refer to it are listed. If the device is known to be a Bluetooth HCI device,
+# its identifier is printed in reverse colors, and the transport names in cyan color.
+# For other devices, regardless of their type, the transport names are printed
+# in red. Whether that device is actually a Bluetooth device or not depends on
+# whether it is a Bluetooth device that uses a non-standard Class, or some other
+# type of device (there's no way to tell).
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import os
+import logging
+import usb1
+from colors import color
+
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
+USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
+USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
+
+USB_DEVICE_CLASSES = {
+ 0x00: 'Device',
+ 0x01: 'Audio',
+ 0x02: 'Communications and CDC Control',
+ 0x03: 'Human Interface Device',
+ 0x05: 'Physical',
+ 0x06: 'Still Imaging',
+ 0x07: 'Printer',
+ 0x08: 'Mass Storage',
+ 0x09: 'Hub',
+ 0x0A: 'CDC Data',
+ 0x0B: 'Smart Card',
+ 0x0D: 'Content Security',
+ 0x0E: 'Video',
+ 0x0F: 'Personal Healthcare',
+ 0x10: 'Audio/Video',
+ 0x11: 'Billboard',
+ 0x12: 'USB Type-C Bridge',
+ 0x3C: 'I3C',
+ 0xDC: 'Diagnostic',
+ USB_DEVICE_CLASS_WIRELESS_CONTROLLER: (
+ 'Wireless Controller',
+ {
+ 0x01: {
+ 0x01: 'Bluetooth',
+ 0x02: 'UWB',
+ 0x03: 'Remote NDIS',
+ 0x04: 'Bluetooth AMP'
+ }
+ }
+ ),
+ 0xEF: 'Miscellaneous',
+ 0xFE: 'Application Specific',
+ 0xFF: 'Vendor Specific'
+}
+
+
+# -----------------------------------------------------------------------------
+def main():
+ logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
+
+ with usb1.USBContext() as context:
+ bluetooth_device_count = 0
+ devices = {}
+
+ for device in context.getDeviceIterator(skip_on_error=True):
+ device_class = device.getDeviceClass()
+ device_subclass = device.getDeviceSubClass()
+ device_protocol = device.getDeviceProtocol()
+
+ device_id = (device.getVendorID(), device.getProductID())
+
+ device_is_bluetooth_hci = (
+ device_class == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and
+ device_subclass == USB_DEVICE_SUBCLASS_RF_CONTROLLER and
+ device_protocol == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
+ )
+
+ device_class_details = ''
+ device_class_info = USB_DEVICE_CLASSES.get(device_class)
+ if device_class_info is not None:
+ if type(device_class_info) is tuple:
+ device_class = device_class_info[0]
+ device_subclass_info = device_class_info[1].get(device_subclass)
+ if device_subclass_info:
+ device_class_details = f' [{device_subclass_info.get(device_protocol)}]'
+ else:
+ device_class = device_class_info
+
+ if device_is_bluetooth_hci:
+ bluetooth_device_count += 1
+ fg_color = 'black'
+ bg_color = 'yellow'
+ else:
+ fg_color = 'yellow'
+ bg_color = 'black'
+
+ # Compute the different ways this can be referenced as a Bumble transport
+ bumble_transport_names = []
+ basic_transport_name = f'usb:{device.getVendorID():04X}:{device.getProductID():04X}'
+
+ if device_is_bluetooth_hci:
+ bumble_transport_names.append(f'usb:{bluetooth_device_count - 1}')
+
+ serial_number_collision = False
+ if device_id in devices:
+ for device_serial in devices[device_id]:
+ if device_serial == device.getSerialNumber():
+ serial_number_collision = True
+
+ if device_id not in devices:
+ bumble_transport_names.append(basic_transport_name)
+ else:
+ bumble_transport_names.append(f'{basic_transport_name}#{len(devices[device_id])}')
+
+ if device.getSerialNumber() and not serial_number_collision:
+ bumble_transport_names.append(f'{basic_transport_name}/{device.getSerialNumber()}')
+
+ print(color(f'ID {device.getVendorID():04X}:{device.getProductID():04X}', fg=fg_color, bg=bg_color))
+ if bumble_transport_names:
+ print(color(' Bumble Transport Names:', 'blue'), ' or '.join(color(x, 'cyan' if device_is_bluetooth_hci else 'red') for x in bumble_transport_names))
+ print(color(' Bus/Device: ', 'green'), f'{device.getBusNumber():03}/{device.getDeviceAddress():03}')
+ if device.getSerialNumber():
+ print(color(' Serial: ', 'green'), device.getSerialNumber())
+ print(color(' Class: ', 'green'), device_class)
+ print(color(' Subclass/Protocol: ', 'green'), f'{device_subclass}/{device_protocol}{device_class_details}')
+ print(color(' Manufacturer: ', 'green'), device.getManufacturer())
+ print(color(' Product: ', 'green'), device.getProduct())
+ print()
+
+ devices.setdefault(device_id, []).append(device.getSerialNumber())
+
+
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+ main()
diff --git a/bumble/controller.py b/bumble/controller.py
index 35f7e92..c282416 100644
--- a/bumble/controller.py
+++ b/bumble/controller.py
@@ -77,7 +77,7 @@
self.le_features = bytes.fromhex('ff49010000000000')
self.le_states = bytes.fromhex('ffff3fffff030000')
self.avertising_channel_tx_power = 0
- self.white_list_size = 8
+ self.filter_accept_list_size = 8
self.resolving_list_size = 8
self.supported_max_tx_octets = 27
self.supported_max_tx_time = 10000 # microseconds
@@ -731,27 +731,27 @@
'''
return bytes([HCI_SUCCESS])
- def on_hci_le_read_white_list_size_command(self, command):
+ def on_hci_le_read_filter_accept_list_size_command(self, command):
'''
- See Bluetooth spec Vol 2, Part E - 7.8.14 LE Read White List Size Command
+ See Bluetooth spec Vol 2, Part E - 7.8.14 LE Read Filter Accept List Size Command
'''
- return bytes([HCI_SUCCESS, self.white_list_size])
+ return bytes([HCI_SUCCESS, self.filter_accept_list_size])
- def on_hci_le_clear_white_list_command(self, command):
+ def on_hci_le_clear_filter_accept_list_command(self, command):
'''
- See Bluetooth spec Vol 2, Part E - 7.8.15 LE Clear White List Command
+ See Bluetooth spec Vol 2, Part E - 7.8.15 LE Clear Filter Accept List Command
'''
return bytes([HCI_SUCCESS])
- def on_hci_le_add_device_to_white_list_command(self, command):
+ def on_hci_le_add_device_to_filter_accept_list_command(self, command):
'''
- See Bluetooth spec Vol 2, Part E - 7.8.16 LE Add Device To White List Command
+ See Bluetooth spec Vol 2, Part E - 7.8.16 LE Add Device To Filter Accept List Command
'''
return bytes([HCI_SUCCESS])
- def on_hci_le_remove_device_from_white_list_command(self, command):
+ def on_hci_le_remove_device_from_filter_accept_list_command(self, command):
'''
- See Bluetooth spec Vol 2, Part E - 7.8.17 LE Remove Device From White List Command
+ See Bluetooth spec Vol 2, Part E - 7.8.17 LE Remove Device From Filter Accept List Command
'''
return bytes([HCI_SUCCESS])
@@ -780,9 +780,9 @@
'''
return bytes([HCI_SUCCESS]) + struct.pack('Q', random.randint(0, 1 << 64))
- def on_hci_le_start_encryption_command(self, command):
+ def on_hci_le_enable_encryption_command(self, command):
'''
- See Bluetooth spec Vol 2, Part E - 7.8.24 LE Start Encryption Command
+ See Bluetooth spec Vol 2, Part E - 7.8.24 LE Enable Encryption Command
'''
# Check the parameters
diff --git a/bumble/device.py b/bumble/device.py
index 97ead5e..230a1cf 100644
--- a/bumble/device.py
+++ b/bumble/device.py
@@ -18,6 +18,7 @@
import json
import asyncio
import logging
+from contextlib import asynccontextmanager, AsyncExitStack
from .hci import *
from .host import Host
@@ -122,6 +123,9 @@
async def subscribe(self, characteristic, subscriber=None):
return await self.gatt_client.subscribe(characteristic, subscriber)
+ async def unsubscribe(self, characteristic, subscriber=None):
+ return await self.gatt_client.unsubscribe(characteristic, subscriber)
+
async def read_value(self, attribute):
return await self.gatt_client.read_value(attribute)
@@ -148,10 +152,24 @@
await service.discover_characteristics()
return self.create_service_proxy(proxy_class)
+ async def sustain(self, timeout=None):
+ await self.connection.sustain(timeout)
+
# [Classic only]
async def request_name(self):
return await self.connection.request_remote_name()
+ async def __aenter__(self):
+ await self.discover_services()
+ for service in self.services:
+ await self.discover_characteristics()
+
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback):
+ pass
+
+
def __str__(self):
return f'{self.connection.peer_address} as {self.connection.role_name}'
@@ -232,6 +250,21 @@
async def encrypt(self):
return await self.device.encrypt(self)
+ async def sustain(self, timeout=None):
+ """ Idles the current task waiting for a disconnect or timeout """
+
+ abort = asyncio.get_running_loop().create_future()
+ self.on('disconnection', abort.set_result)
+ self.on('disconnection_failure', abort.set_exception)
+
+ try:
+ await asyncio.wait_for(abort, timeout)
+ except asyncio.TimeoutError:
+ pass
+
+ self.remove_listener('disconnection', abort.set_result)
+ self.remove_listener('disconnection_failure', abort.set_exception)
+
async def update_parameters(
self,
conn_interval_min,
@@ -251,6 +284,18 @@
async def request_remote_name(self):
return await self.device.request_remote_name(self)
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback):
+ if exc_type is None:
+ try:
+ await self.disconnect()
+ except HCI_StatusError as e:
+ # Invalid parameter means the connection is no longer valid
+ if e.error_code != HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR:
+ raise
+
def __str__(self):
return f'Connection(handle=0x{self.handle:04X}, role={self.role_name}, address={self.peer_address})'
@@ -270,6 +315,8 @@
self.le_simultaneous_enabled = True
self.classic_sc_enabled = True
self.classic_ssp_enabled = True
+ self.connectable = True
+ self.discoverable = True
self.advertising_data = bytes(
AdvertisingData([(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(self.name, 'utf-8'))])
)
@@ -288,6 +335,8 @@
self.le_simultaneous_enabled = config.get('le_simultaneous_enabled', self.le_simultaneous_enabled)
self.classic_sc_enabled = config.get('classic_sc_enabled', self.classic_sc_enabled)
self.classic_ssp_enabled = config.get('classic_ssp_enabled', self.classic_ssp_enabled)
+ self.connectable = config.get('connectable', self.connectable)
+ self.discoverable = config.get('discoverable', self.discoverable)
# Load or synthesize an IRK
irk = config.get('irk')
@@ -314,6 +363,7 @@
# within a class requires unnecessarily complicated acrobatics)
# -----------------------------------------------------------------------------
+
# Decorator that converts the first argument from a connection handle to a connection
def with_connection_from_handle(function):
@functools.wraps(function)
@@ -400,7 +450,8 @@
self.command_timeout = 10 # seconds
self.gatt_server = gatt_server.Server(self)
self.sdp_server = sdp.Server(self)
- self.l2cap_channel_manager = l2cap.ChannelManager()
+ self.l2cap_channel_manager = l2cap.ChannelManager(
+ [l2cap.L2CAP_Information_Request.EXTENDED_FEATURE_FIXED_CHANNELS])
self.advertisement_data = {}
self.scanning = False
self.discovering = False
@@ -408,8 +459,6 @@
self.disconnecting = False
self.connections = {} # Connections, by connection handle
self.classic_enabled = False
- self.discoverable = False
- self.connectable = False
self.inquiry_response = None
self.address_resolver = None
@@ -430,6 +479,8 @@
self.le_simultaneous_enabled = config.le_simultaneous_enabled
self.classic_ssp_enabled = config.classic_ssp_enabled
self.classic_sc_enabled = config.classic_sc_enabled
+ self.discoverable = config.discoverable
+ self.connectable = config.connectable
# If a name is passed, override the name from the config
if name:
@@ -444,6 +495,10 @@
# Setup SMP
# TODO: allow using a public address
self.smp_manager = smp.Manager(self, self.random_address)
+ self.l2cap_channel_manager.register_fixed_channel(
+ smp.SMP_CID, self.on_smp_pdu)
+ self.l2cap_channel_manager.register_fixed_channel(
+ smp.SMP_BR_CID, self.on_smp_pdu)
# Register the SDP server with the L2CAP Channel Manager
self.sdp_server.register(self.l2cap_channel_manager)
@@ -451,6 +506,7 @@
# Add a GAP Service if requested
if generic_access_service:
self.gatt_server.add_service(GenericAccessService(self.name))
+ self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
# Forward some events
setup_event_forwarding(self.gatt_server, self, 'characteristic_subscription')
@@ -528,11 +584,12 @@
logger.debug(color(f'BD_ADDR: {response.return_parameters.bd_addr}', 'yellow'))
self.public_address = response.return_parameters.bd_addr
+ if self.host.supports_command(HCI_WRITE_LE_HOST_SUPPORT_COMMAND):
+ await self.send_command(HCI_Write_LE_Host_Support_Command(
+ le_supported_host = int(self.le_enabled),
+ simultaneous_le_host = int(self.le_simultaneous_enabled),
+ ))
- await self.send_command(HCI_Write_LE_Host_Support_Command(
- le_supported_host = int(self.le_enabled),
- simultaneous_le_host = int(self.le_simultaneous_enabled),
- ))
if self.le_enabled:
# Set the controller address
await self.send_command(HCI_LE_Set_Random_Address_Command(
@@ -577,6 +634,8 @@
HCI_Write_Secure_Connections_Host_Support_Command(
secure_connections_host_support=int(self.classic_sc_enabled))
)
+ await self.set_connectable(self.connectable)
+ await self.set_discoverable(self.discoverable)
# Let the SMP manager know about the address
# TODO: allow using a public address
@@ -704,7 +763,7 @@
))
if response.status != HCI_Command_Status_Event.PENDING:
self.discovering = False
- raise RuntimeError(f'HCI_Inquiry command failed: {HCI_Constant.status_name(response.status)} ({response.status})')
+ raise HCI_StatusError(response)
self.discovering = True
@@ -785,7 +844,7 @@
try:
peer_address = Address(peer_address)
except ValueError:
- # If the address is not parssable, assume it is a name instead
+ # If the address is not parsable, assume it is a name instead
logger.debug('looking for peer by name')
peer_address = await self.find_peer_by_name(peer_address, transport)
@@ -824,16 +883,25 @@
try:
if result.status != HCI_Command_Status_Event.PENDING:
- raise RuntimeError(f'HCI_LE_Create_Connection_Command failed: {HCI_Constant.status_name(result.status)} ({result.status})')
+ raise HCI_StatusError(result)
# Wait for the connection process to complete
self.connecting = True
return await pending_connection
+
finally:
self.remove_listener('connection', pending_connection.set_result)
self.remove_listener('connection_failure', pending_connection.set_exception)
self.connecting = False
+ @asynccontextmanager
+ async def connect_as_gatt(self, peer_address):
+ async with AsyncExitStack() as stack:
+ connection = await stack.enter_async_context(await self.connect(peer_address))
+ peer = await stack.enter_async_context(Peer(connection))
+
+ yield peer
+
@property
def is_connecting(self):
return self.connecting
@@ -858,7 +926,7 @@
try:
if result.status != HCI_Command_Status_Event.PENDING:
- raise RuntimeError(f'HCI_Disconnect_Command failed: {HCI_Constant.status_name(result.status)} ({result.status})')
+ raise HCI_StatusError(result)
# Wait for the disconnection process to complete
self.disconnecting = True
@@ -1010,7 +1078,7 @@
)
if result.status != HCI_COMMAND_STATUS_PENDING:
logger.warn(f'HCI_Authentication_Requested_Command failed: {HCI_Constant.error_name(result.status)}')
- raise HCI_Error(result.status)
+ raise HCI_StatusError(result)
# Wait for the authentication to complete
await pending_authentication
@@ -1057,7 +1125,7 @@
raise InvalidStateError('only centrals can start encryption')
result = await self.send_command(
- HCI_LE_Start_Encryption_Command(
+ HCI_LE_Enable_Encryption_Command(
connection_handle = connection.handle,
random_number = rand,
encrypted_diversifier = ediv,
@@ -1066,8 +1134,8 @@
)
if result.status != HCI_COMMAND_STATUS_PENDING:
- logger.warn(f'HCI_LE_Start_Encryption_Command failed: {HCI_Constant.error_name(result.status)}')
- raise HCI_Error(result.status)
+ logger.warn(f'HCI_LE_Enable_Encryption_Command failed: {HCI_Constant.error_name(result.status)}')
+ raise HCI_StatusError(result)
else:
result = await self.send_command(
HCI_Set_Connection_Encryption_Command(
@@ -1078,7 +1146,7 @@
if result.status != HCI_COMMAND_STATUS_PENDING:
logger.warn(f'HCI_Set_Connection_Encryption_Command failed: {HCI_Constant.error_name(result.status)}')
- raise HCI_Error(result.status)
+ raise HCI_StatusError(result)
# Wait for the result
await pending_encryption
@@ -1112,7 +1180,7 @@
if result.status != HCI_COMMAND_STATUS_PENDING:
logger.warn(f'HCI_Set_Connection_Encryption_Command failed: {HCI_Constant.error_name(result.status)}')
- raise HCI_Error(result.status)
+ raise HCI_StatusError(result)
# Wait for the result
return await pending_name
@@ -1442,7 +1510,6 @@
def on_pairing_failure(self, connection, reason):
connection.emit('pairing_failure', reason)
- @host_event_handler
@with_connection_from_handle
def on_gatt_pdu(self, connection, pdu):
# Parse the L2CAP payload into an ATT PDU object
@@ -1461,7 +1528,6 @@
return
connection.gatt_server.on_gatt_pdu(connection, att_pdu)
- @host_event_handler
@with_connection_from_handle
def on_smp_pdu(self, connection, pdu):
self.smp_manager.on_smp_pdu(connection, pdu)
diff --git a/bumble/gatt.py b/bumble/gatt.py
index df760c3..90867cd 100644
--- a/bumble/gatt.py
+++ b/bumble/gatt.py
@@ -134,13 +134,21 @@
GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC = UUID.from_16_bits(0x2A2A, 'IEEE 11073-20601 Regulatory Certification Data List')
GATT_PNP_ID_CHARACTERISTIC = UUID.from_16_bits(0x2A50, 'PnP ID')
-# Human Interface Device
+# Human Interface Device Service
GATT_HID_INFORMATION_CHARACTERISTIC = UUID.from_16_bits(0x2A4A, 'HID Information')
GATT_REPORT_MAP_CHARACTERISTIC = UUID.from_16_bits(0x2A4B, 'Report Map')
GATT_HID_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A4C, 'HID Control Point')
GATT_REPORT_CHARACTERISTIC = UUID.from_16_bits(0x2A4D, 'Report')
GATT_PROTOCOL_MODE_CHARACTERISTIC = UUID.from_16_bits(0x2A4E, 'Protocol Mode')
+# Heart Rate Service
+GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC = UUID.from_16_bits(0x2A37, 'Heart Rate Measurement')
+GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2A38, 'Body Sensor Location')
+GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A39, 'Heart Rate Control Point')
+
+# Battery Service
+GATT_BATTERY_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A19, 'Battery Level')
+
# Misc
GATT_DEVICE_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2A00, 'Device Name')
GATT_APPEARANCE_CHARACTERISTIC = UUID.from_16_bits(0x2A01, 'Appearance')
@@ -150,7 +158,6 @@
GATT_SERVICE_CHANGED_CHARACTERISTIC = UUID.from_16_bits(0x2A05, 'Service Changed')
GATT_ALERT_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A06, 'Alert Level')
GATT_TX_POWER_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A07, 'Tx Power Level')
-GATT_BATTERY_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A19, 'Battery Level')
GATT_BOOT_KEYBOARD_INPUT_REPORT_CHARACTERISTIC = UUID.from_16_bits(0x2A22, 'Boot Keyboard Input Report')
GATT_CURRENT_TIME_CHARACTERISTIC = UUID.from_16_bits(0x2A2B, 'Current Time')
GATT_BOOT_KEYBOARD_OUTPUT_REPORT_CHARACTERISTIC = UUID.from_16_bits(0x2A32, 'Boot Keyboard Output Report')
@@ -313,6 +320,12 @@
def __getattr__(self, name):
return getattr(self.wrapped_characteristic, name)
+ def __setattr__(self, name, value):
+ if name in {'wrapped_characteristic', 'read_value', 'write_value', 'subscribe'}:
+ super().__setattr__(name, value)
+ else:
+ setattr(self.wrapped_characteristic, name, value)
+
def read_encoded_value(self, connection):
return self.encode_value(self.wrapped_characteristic.read_value(connection))
@@ -336,19 +349,26 @@
None if subscriber is None else lambda value: subscriber(self.decode_value(value))
)
+ def __str__(self):
+ wrapped = str(self.wrapped_characteristic)
+ return f'{self.__class__.__name__}({wrapped})'
+
# -----------------------------------------------------------------------------
class DelegatedCharacteristicAdapter(CharacteristicAdapter):
- def __init__(self, characteristic, encode, decode):
+ '''
+ Adapter that converts bytes values using an encode and a decode function.
+ '''
+ def __init__(self, characteristic, encode=None, decode=None):
super().__init__(characteristic)
self.encode = encode
self.decode = decode
def encode_value(self, value):
- return self.encode(value)
+ return self.encode(value) if self.encode else value
def decode_value(self, value):
- return self.decode(value)
+ return self.decode(value) if self.decode else value
# -----------------------------------------------------------------------------
diff --git a/bumble/gatt_client.py b/bumble/gatt_client.py
index e817e2e..94799a3 100644
--- a/bumble/gatt_client.py
+++ b/bumble/gatt_client.py
@@ -110,6 +110,9 @@
async def subscribe(self, subscriber=None):
return await self.client.subscribe(self, subscriber)
+ async def unsubscribe(self, subscriber=None):
+ return await self.client.unsubscribe(self, subscriber)
+
def __str__(self):
return f'Characteristic(handle=0x{self.handle:04X}, uuid={self.uuid}, properties={Characteristic.properties_as_string(self.properties)})'
@@ -544,10 +547,36 @@
for subscriber_set in subscriber_sets:
if subscriber is not None:
subscriber_set.add(subscriber)
- subscriber_set.add(lambda value: characteristic.emit('update', self.connection, value))
+ # Add the characteristic as a subscriber, which will result in the characteristic
+ # emitting an 'update' event when a notification or indication is received
+ subscriber_set.add(characteristic)
await self.write_value(cccd, struct.pack('<H', bits), with_response=True)
+ async def unsubscribe(self, characteristic, subscriber=None):
+ # If we haven't already discovered the descriptors for this characteristic, do it now
+ if not characteristic.descriptors_discovered:
+ await self.discover_descriptors(characteristic)
+
+ # Look for the CCCD descriptor
+ cccd = characteristic.get_descriptor(GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR)
+ if not cccd:
+ logger.warning('unsubscribing from characteristic with no CCCD descriptor')
+ return
+
+ if subscriber is not None:
+ # Remove matching subscriber from subscriber sets
+ for subscriber_set in (self.notification_subscribers, self.indication_subscribers):
+ subscribers = subscriber_set.get(characteristic.handle, [])
+ if subscriber in subscribers:
+ subscribers.remove(subscriber)
+ else:
+ # Remove all subscribers for this attribute from the sets!
+ self.notification_subscribers.pop(characteristic.handle, None)
+ self.indication_subscribers.pop(characteristic.handle, None)
+
+ await self.write_value(cccd, b'\x00\x00', with_response=True)
+
async def read_value(self, attribute, no_long_read=False):
'''
See Vol 3, Part G - 4.8.1 Read Characteristic Value
@@ -714,7 +743,10 @@
if not subscribers:
logger.warning('!!! received notification with no subscriber')
for subscriber in subscribers:
- subscriber(notification.attribute_value)
+ if callable(subscriber):
+ subscriber(notification.attribute_value)
+ else:
+ subscriber.emit('update', notification.attribute_value)
def on_att_handle_value_indication(self, indication):
# Call all subscribers
@@ -722,7 +754,10 @@
if not subscribers:
logger.warning('!!! received indication with no subscriber')
for subscriber in subscribers:
- subscriber(indication.attribute_value)
+ if callable(subscriber):
+ subscriber(indication.attribute_value)
+ else:
+ subscriber.emit('update', indication.attribute_value)
# Confirm that we received the indication
self.send_confirmation(ATT_Handle_Value_Confirmation())
diff --git a/bumble/gatt_server.py b/bumble/gatt_server.py
index 3afcecc..8297f11 100644
--- a/bumble/gatt_server.py
+++ b/bumble/gatt_server.py
@@ -545,13 +545,13 @@
value = attribute.read_value(connection)
if request.value_offset > len(value):
response = ATT_Error_Response(
- request_opcode_in_error = request.op_code,
+ request_opcode_in_error = request.op_code,
attribute_handle_in_error = request.attribute_handle,
error_code = ATT_INVALID_OFFSET_ERROR
)
elif len(value) <= mtu - 1:
response = ATT_Error_Response(
- request_opcode_in_error = request.op_code,
+ request_opcode_in_error = request.op_code,
attribute_handle_in_error = request.attribute_handle,
error_code = ATT_ATTRIBUTE_NOT_LONG_ERROR
)
diff --git a/bumble/hci.py b/bumble/hci.py
index 8358e30..9858c04 100644
--- a/bumble/hci.py
+++ b/bumble/hci.py
@@ -81,6 +81,25 @@
HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
+HCI_VERSION_NAMES = {
+ HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
+ HCI_VERSION_BLUETOOTH_CORE_1_1: 'HCI_VERSION_BLUETOOTH_CORE_1_1',
+ HCI_VERSION_BLUETOOTH_CORE_1_2: 'HCI_VERSION_BLUETOOTH_CORE_1_2',
+ HCI_VERSION_BLUETOOTH_CORE_2_0_EDR: 'HCI_VERSION_BLUETOOTH_CORE_2_0_EDR',
+ HCI_VERSION_BLUETOOTH_CORE_2_1_EDR: 'HCI_VERSION_BLUETOOTH_CORE_2_1_EDR',
+ HCI_VERSION_BLUETOOTH_CORE_3_0_HS: 'HCI_VERSION_BLUETOOTH_CORE_3_0_HS',
+ HCI_VERSION_BLUETOOTH_CORE_4_0: 'HCI_VERSION_BLUETOOTH_CORE_4_0',
+ HCI_VERSION_BLUETOOTH_CORE_4_1: 'HCI_VERSION_BLUETOOTH_CORE_4_1',
+ HCI_VERSION_BLUETOOTH_CORE_4_2: 'HCI_VERSION_BLUETOOTH_CORE_4_2',
+ HCI_VERSION_BLUETOOTH_CORE_5_0: 'HCI_VERSION_BLUETOOTH_CORE_5_0',
+ HCI_VERSION_BLUETOOTH_CORE_5_1: 'HCI_VERSION_BLUETOOTH_CORE_5_1',
+ HCI_VERSION_BLUETOOTH_CORE_5_2: 'HCI_VERSION_BLUETOOTH_CORE_5_2',
+ HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3'
+}
+
+# LMP Version
+LMP_VERSION_NAMES = HCI_VERSION_NAMES
+
# HCI Packet types
HCI_COMMAND_PACKET = 0x01
HCI_ACL_DATA_PACKET = 0x02
@@ -88,544 +107,546 @@
HCI_EVENT_PACKET = 0x04
# HCI Event Codes
-HCI_INQUIRY_COMPLETE_EVENT = 0x01
-HCI_INQUIRY_RESULT_EVENT = 0x02
-HCI_CONNECTION_COMPLETE_EVENT = 0x03
-HCI_CONNECTION_REQUEST_EVENT = 0x04
-HCI_DISCONNECTION_COMPLETE_EVENT = 0x05
-HCI_AUTHENTICATION_COMPLETE_EVENT = 0x06
-HCI_REMOTE_NAME_REQUEST_COMPLETE_EVENT = 0x07
-HCI_ENCRYPTION_CHANGE_EVENT = 0x08
-HCI_CHANGE_CONNECTION_LINK_KEY_COMPLETE_EVENT = 0x09
-HCI_LINK_KEY_TYPE_CHANGED_EVENT = 0x0A
-HCI_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE_EVENT = 0x0B
-HCI_READ_REMOTE_VERSION_INFORMATION_COMPLETE_EVENT = 0x0C
-HCI_QOS_SETUP_COMPLETE_EVENT = 0x0D
-HCI_COMMAND_COMPLETE_EVENT = 0x0E
-HCI_COMMAND_STATUS_EVENT = 0x0F
-HCI_HARDWARE_ERROR_EVENT = 0x10
-HCI_FLUSH_OCCURRED_EVENT = 0x11
-HCI_ROLE_CHANGE_EVENT = 0x12
-HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT = 0x13
-HCI_MODE_CHANGE_EVENT = 0x14
-HCI_RETURN_LINK_KEYS_EVENT = 0x15
-HCI_PIN_CODE_REQUEST_EVENT = 0x16
-HCI_LINK_KEY_REQUEST_EVENT = 0x17
-HCI_LINK_KEY_NOTIFICATION_EVENT = 0x18
-HCI_LOOPBACK_COMMAND_EVENT = 0x19
-HCI_DATA_BUFFER_OVERFLOW_EVENT = 0x1A
-HCI_MAX_SLOTS_CHANGE_EVENT = 0x1B
-HCI_READ_CLOCK_OFFSET_COMPLETE_EVENT = 0x1C
-HCI_CONNECTION_PACKET_TYPE_CHANGED_EVENT = 0x1D
-HCI_QOS_VIOLATION_EVENT = 0x1E
-HCI_PAGE_SCAN_REPETITION_MODE_CHANGE_EVENT = 0x20
-HCI_FLOW_SPECIFICATION_COMPLETE_EVENT = 0x21
-HCI_INQUIRY_RESULT_WITH_RSSI_EVENT = 0x22
-HCI_READ_REMOTE_EXTENDED_FEATURES_COMPLETE_EVENT = 0x23
-HCI_SYNCHRONOUS_CONNECTION_COMPLETE_EVENT = 0x2C
-HCI_SYNCHRONOUS_CONNECTION_CHANGED_EVENT = 0x2D
-HCI_SNIFF_SUBRATING_EVENT = 0x2E
-HCI_EXTENDED_INQUIRY_RESULT_EVENT = 0x2F
-HCI_ENCRYPTION_KEY_REFRESH_COMPLETE_EVENT = 0x30
-HCI_IO_CAPABILITY_REQUEST_EVENT = 0x31
-HCI_IO_CAPABILITY_RESPONSE_EVENT = 0x32
-HCI_USER_CONFIRMATION_REQUEST_EVENT = 0x33
-HCI_USER_PASSKEY_REQUEST_EVENT = 0x34
-HCI_REMOTE_OOB_DATA_REQUEST = 0x35
-HCI_SIMPLE_PAIRING_COMPLETE_EVENT = 0x36
-HCI_LINK_SUPERVISION_TIMEOUT_CHANGED_EVENT = 0x38
-HCI_ENHANCED_FLUSH_COMPLETE_EVENT = 0x39
-HCI_USER_PASSKEY_NOTIFICATION_EVENT = 0x3B
-HCI_KEYPRESS_NOTIFICATION_EVENT = 0x3C
-HCI_REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION_EVENT = 0x3D
-HCI_LE_META_EVENT = 0x3E
-HCI_NUMBER_OF_COMPLETED_DATA_BLOCKS_EVENT = 0x48
+HCI_INQUIRY_COMPLETE_EVENT = 0x01
+HCI_INQUIRY_RESULT_EVENT = 0x02
+HCI_CONNECTION_COMPLETE_EVENT = 0x03
+HCI_CONNECTION_REQUEST_EVENT = 0x04
+HCI_DISCONNECTION_COMPLETE_EVENT = 0x05
+HCI_AUTHENTICATION_COMPLETE_EVENT = 0x06
+HCI_REMOTE_NAME_REQUEST_COMPLETE_EVENT = 0x07
+HCI_ENCRYPTION_CHANGE_EVENT = 0x08
+HCI_CHANGE_CONNECTION_LINK_KEY_COMPLETE_EVENT = 0x09
+HCI_LINK_KEY_TYPE_CHANGED_EVENT = 0x0A
+HCI_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE_EVENT = 0x0B
+HCI_READ_REMOTE_VERSION_INFORMATION_COMPLETE_EVENT = 0x0C
+HCI_QOS_SETUP_COMPLETE_EVENT = 0x0D
+HCI_COMMAND_COMPLETE_EVENT = 0x0E
+HCI_COMMAND_STATUS_EVENT = 0x0F
+HCI_HARDWARE_ERROR_EVENT = 0x10
+HCI_FLUSH_OCCURRED_EVENT = 0x11
+HCI_ROLE_CHANGE_EVENT = 0x12
+HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT = 0x13
+HCI_MODE_CHANGE_EVENT = 0x14
+HCI_RETURN_LINK_KEYS_EVENT = 0x15
+HCI_PIN_CODE_REQUEST_EVENT = 0x16
+HCI_LINK_KEY_REQUEST_EVENT = 0x17
+HCI_LINK_KEY_NOTIFICATION_EVENT = 0x18
+HCI_LOOPBACK_COMMAND_EVENT = 0x19
+HCI_DATA_BUFFER_OVERFLOW_EVENT = 0x1A
+HCI_MAX_SLOTS_CHANGE_EVENT = 0x1B
+HCI_READ_CLOCK_OFFSET_COMPLETE_EVENT = 0x1C
+HCI_CONNECTION_PACKET_TYPE_CHANGED_EVENT = 0x1D
+HCI_QOS_VIOLATION_EVENT = 0x1E
+HCI_PAGE_SCAN_REPETITION_MODE_CHANGE_EVENT = 0x20
+HCI_FLOW_SPECIFICATION_COMPLETE_EVENT = 0x21
+HCI_INQUIRY_RESULT_WITH_RSSI_EVENT = 0x22
+HCI_READ_REMOTE_EXTENDED_FEATURES_COMPLETE_EVENT = 0x23
+HCI_SYNCHRONOUS_CONNECTION_COMPLETE_EVENT = 0x2C
+HCI_SYNCHRONOUS_CONNECTION_CHANGED_EVENT = 0x2D
+HCI_SNIFF_SUBRATING_EVENT = 0x2E
+HCI_EXTENDED_INQUIRY_RESULT_EVENT = 0x2F
+HCI_ENCRYPTION_KEY_REFRESH_COMPLETE_EVENT = 0x30
+HCI_IO_CAPABILITY_REQUEST_EVENT = 0x31
+HCI_IO_CAPABILITY_RESPONSE_EVENT = 0x32
+HCI_USER_CONFIRMATION_REQUEST_EVENT = 0x33
+HCI_USER_PASSKEY_REQUEST_EVENT = 0x34
+HCI_REMOTE_OOB_DATA_REQUEST = 0x35
+HCI_SIMPLE_PAIRING_COMPLETE_EVENT = 0x36
+HCI_LINK_SUPERVISION_TIMEOUT_CHANGED_EVENT = 0x38
+HCI_ENHANCED_FLUSH_COMPLETE_EVENT = 0x39
+HCI_USER_PASSKEY_NOTIFICATION_EVENT = 0x3B
+HCI_KEYPRESS_NOTIFICATION_EVENT = 0x3C
+HCI_REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION_EVENT = 0x3D
+HCI_LE_META_EVENT = 0x3E
+HCI_NUMBER_OF_COMPLETED_DATA_BLOCKS_EVENT = 0x48
+HCI_TRIGGERED_CLOCK_CAPTURE_EVENT = 0X4E
+HCI_SYNCHRONIZATION_TRAIN_COMPLETE_EVENT = 0X4F
+HCI_SYNCHRONIZATION_TRAIN_RECEIVED_EVENT = 0X50
+HCI_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE_EVENT = 0X51
+HCI_CONNECTIONLESS_PERIPHERAL_BROADCAST_TIMEOUT_EVENT = 0X52
+HCI_TRUNCATED_PAGE_COMPLETE_EVENT = 0X53
+HCI_PERIPHERAL_PAGE_RESPONSE_TIMEOUT_EVENT = 0X54
+HCI_CONNECTIONLESS_PERIPHERAL_BROADCAST_CHANNEL_MAP_CHANGE_EVENT = 0X55
+HCI_INQUIRY_RESPONSE_NOTIFICATION_EVENT = 0X56
+HCI_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED_EVENT = 0X57
+HCI_SAM_STATUS_CHANGE_EVENT = 0X58
HCI_EVENT_NAMES = {
- HCI_INQUIRY_COMPLETE_EVENT: 'HCI_INQUIRY_COMPLETE_EVENT',
- HCI_INQUIRY_RESULT_EVENT: 'HCI_INQUIRY_RESULT_EVENT',
- HCI_CONNECTION_COMPLETE_EVENT: 'HCI_CONNECTION_COMPLETE_EVENT',
- HCI_CONNECTION_REQUEST_EVENT: 'HCI_CONNECTION_REQUEST_EVENT',
- HCI_DISCONNECTION_COMPLETE_EVENT: 'HCI_DISCONNECTION_COMPLETE_EVENT',
- HCI_AUTHENTICATION_COMPLETE_EVENT: 'HCI_AUTHENTICATION_COMPLETE_EVENT',
- HCI_REMOTE_NAME_REQUEST_COMPLETE_EVENT: 'HCI_REMOTE_NAME_REQUEST_COMPLETE_EVENT',
- HCI_ENCRYPTION_CHANGE_EVENT: 'HCI_ENCRYPTION_CHANGE_EVENT',
- HCI_CHANGE_CONNECTION_LINK_KEY_COMPLETE_EVENT: 'HCI_CHANGE_CONNECTION_LINK_KEY_COMPLETE_EVENT',
- HCI_LINK_KEY_TYPE_CHANGED_EVENT: 'HCI_LINK_KEY_TYPE_CHANGED_EVENT',
- HCI_INQUIRY_RESULT_WITH_RSSI_EVENT: 'HCI_INQUIRY_RESULT_WITH_RSSI_EVENT',
- HCI_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE_EVENT: 'HCI_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE_EVENT',
- HCI_READ_REMOTE_VERSION_INFORMATION_COMPLETE_EVENT: 'HCI_READ_REMOTE_VERSION_INFORMATION_COMPLETE_EVENT',
- HCI_QOS_SETUP_COMPLETE_EVENT: 'HCI_QOS_SETUP_COMPLETE_EVENT',
- HCI_SYNCHRONOUS_CONNECTION_COMPLETE_EVENT: 'HCI_SYNCHRONOUS_CONNECTION_COMPLETE_EVENT',
- HCI_SYNCHRONOUS_CONNECTION_CHANGED_EVENT: 'HCI_SYNCHRONOUS_CONNECTION_CHANGED_EVENT',
- HCI_SNIFF_SUBRATING_EVENT: 'HCI_SNIFF_SUBRATING_EVENT',
- HCI_COMMAND_COMPLETE_EVENT: 'HCI_COMMAND_COMPLETE_EVENT',
- HCI_COMMAND_STATUS_EVENT: 'HCI_COMMAND_STATUS_EVENT',
- HCI_HARDWARE_ERROR_EVENT: 'HCI_HARDWARE_ERROR_EVENT',
- HCI_FLUSH_OCCURRED_EVENT: 'HCI_FLUSH_OCCURRED_EVENT',
- HCI_ROLE_CHANGE_EVENT: 'HCI_ROLE_CHANGE_EVENT',
- HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT: 'HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT',
- HCI_MODE_CHANGE_EVENT: 'HCI_MODE_CHANGE_EVENT',
- HCI_RETURN_LINK_KEYS_EVENT: 'HCI_RETURN_LINK_KEYS_EVENT',
- HCI_PIN_CODE_REQUEST_EVENT: 'HCI_PIN_CODE_REQUEST_EVENT',
- HCI_LINK_KEY_REQUEST_EVENT: 'HCI_LINK_KEY_REQUEST_EVENT',
- HCI_LINK_KEY_NOTIFICATION_EVENT: 'HCI_LINK_KEY_NOTIFICATION_EVENT',
- HCI_LOOPBACK_COMMAND_EVENT: 'HCI_LOOPBACK_COMMAND_EVENT',
- HCI_DATA_BUFFER_OVERFLOW_EVENT: 'HCI_DATA_BUFFER_OVERFLOW_EVENT',
- HCI_MAX_SLOTS_CHANGE_EVENT: 'HCI_MAX_SLOTS_CHANGE_EVENT',
- HCI_READ_CLOCK_OFFSET_COMPLETE_EVENT: 'HCI_READ_CLOCK_OFFSET_COMPLETE_EVENT',
- HCI_CONNECTION_PACKET_TYPE_CHANGED_EVENT: 'HCI_CONNECTION_PACKET_TYPE_CHANGED_EVENT',
- HCI_QOS_VIOLATION_EVENT: 'HCI_QOS_VIOLATION_EVENT',
- HCI_PAGE_SCAN_REPETITION_MODE_CHANGE_EVENT: 'HCI_PAGE_SCAN_REPETITION_MODE_CHANGE_EVENT',
- HCI_FLOW_SPECIFICATION_COMPLETE_EVENT: 'HCI_FLOW_SPECIFICATION_COMPLETE_EVENT',
- HCI_READ_REMOTE_EXTENDED_FEATURES_COMPLETE_EVENT: 'HCI_READ_REMOTE_EXTENDED_FEATURES_COMPLETE_EVENT',
- HCI_EXTENDED_INQUIRY_RESULT_EVENT: 'HCI_EXTENDED_INQUIRY_RESULT_EVENT',
- HCI_ENCRYPTION_KEY_REFRESH_COMPLETE_EVENT: 'HCI_ENCRYPTION_KEY_REFRESH_COMPLETE_EVENT',
- HCI_IO_CAPABILITY_REQUEST_EVENT: 'HCI_IO_CAPABILITY_REQUEST_EVENT',
- HCI_IO_CAPABILITY_RESPONSE_EVENT: 'HCI_IO_CAPABILITY_RESPONSE_EVENT',
- HCI_USER_CONFIRMATION_REQUEST_EVENT: 'HCI_USER_CONFIRMATION_REQUEST_EVENT',
- HCI_USER_PASSKEY_REQUEST_EVENT: 'HCI_USER_PASSKEY_REQUEST_EVENT',
- HCI_REMOTE_OOB_DATA_REQUEST: 'HCI_REMOTE_OOB_DATA_REQUEST',
- HCI_SIMPLE_PAIRING_COMPLETE_EVENT: 'HCI_SIMPLE_PAIRING_COMPLETE_EVENT',
- HCI_LINK_SUPERVISION_TIMEOUT_CHANGED_EVENT: 'HCI_LINK_SUPERVISION_TIMEOUT_CHANGED_EVENT',
- HCI_ENHANCED_FLUSH_COMPLETE_EVENT: 'HCI_ENHANCED_FLUSH_COMPLETE_EVENT',
- HCI_USER_PASSKEY_NOTIFICATION_EVENT: 'HCI_USER_PASSKEY_NOTIFICATION_EVENT',
- HCI_KEYPRESS_NOTIFICATION_EVENT: 'HCI_KEYPRESS_NOTIFICATION_EVENT',
- HCI_REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION_EVENT: 'HCI_REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION_EVENT',
- HCI_LE_META_EVENT: 'HCI_LE_META_EVENT'
+ event_code: event_name for (event_name, event_code) in globals().items()
+ if event_name.startswith('HCI_') and event_name.endswith('_EVENT')
}
# HCI Subevent Codes
-HCI_LE_CONNECTION_COMPLETE_EVENT = 0x01
-HCI_LE_ADVERTISING_REPORT_EVENT = 0x02
-HCI_LE_CONNECTION_UPDATE_COMPLETE_EVENT = 0x03
-HCI_LE_READ_REMOTE_FEATURES_COMPLETE_EVENT = 0x04
-HCI_LE_LONG_TERM_KEY_REQUEST_EVENT = 0x05
-HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT = 0x06
-HCI_LE_DATA_LENGTH_CHANGE_EVENT = 0x07
-HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT = 0x08
-HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT = 0x09
-HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT = 0x0A
-HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT = 0x0B
-HCI_LE_PHY_UPDATE_COMPLETE_EVENT = 0x0C
-HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT = 0x0D
-HCI_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT = 0x0E
-HCI_LE_PERIODIC_ADVERTISING_REPORT_EVENT = 0x0F
-HCI_LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT = 0x10
-HCI_LE_SCAN_TIMEOUT_EVENT = 0x11
-HCI_LE_ADVERTISING_SET_TERMINATED_EVENT = 0x12
-HCI_LE_SCAN_REQUEST_RECEIVED_EVENT = 0x13
-HCI_LE_CHANNEL_SELECTION_ALGORITHM_EVENT = 0x14
+HCI_LE_CONNECTION_COMPLETE_EVENT = 0x01
+HCI_LE_ADVERTISING_REPORT_EVENT = 0x02
+HCI_LE_CONNECTION_UPDATE_COMPLETE_EVENT = 0x03
+HCI_LE_READ_REMOTE_FEATURES_COMPLETE_EVENT = 0x04
+HCI_LE_LONG_TERM_KEY_REQUEST_EVENT = 0x05
+HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT = 0x06
+HCI_LE_DATA_LENGTH_CHANGE_EVENT = 0x07
+HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT = 0x08
+HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT = 0x09
+HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT = 0x0A
+HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT = 0x0B
+HCI_LE_PHY_UPDATE_COMPLETE_EVENT = 0x0C
+HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT = 0x0D
+HCI_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT = 0x0E
+HCI_LE_PERIODIC_ADVERTISING_REPORT_EVENT = 0x0F
+HCI_LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT = 0x10
+HCI_LE_SCAN_TIMEOUT_EVENT = 0x11
+HCI_LE_ADVERTISING_SET_TERMINATED_EVENT = 0x12
+HCI_LE_SCAN_REQUEST_RECEIVED_EVENT = 0x13
+HCI_LE_CHANNEL_SELECTION_ALGORITHM_EVENT = 0x14
+HCI_LE_CONNECTIONLESS_IQ_REPORT_EVENT = 0X15
+HCI_LE_CONNECTION_IQ_REPORT_EVENT = 0X16
+HCI_LE_CTE_REQUEST_FAILED_EVENT = 0X17
+HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_RECEIVED_EVENT = 0X18
+HCI_LE_CIS_ESTABLISHED_EVENT = 0X19
+HCI_LE_CIS_REQUEST_EVENT = 0X1A
+HCI_LE_CREATE_BIG_COMPLETE_EVENT = 0X1B
+HCI_LE_TERMINATE_BIG_COMPLETE_EVENT = 0X1C
+HCI_LE_BIG_SYNC_ESTABLISHED_EVENT = 0X1D
+HCI_LE_BIG_SYNC_LOST_EVENT = 0X1E
+HCI_LE_REQUEST_PEER_SCA_COMPLETE_EVENT = 0X1F
+HCI_LE_PATH_LOSS_THRESHOLD_EVENT = 0X20
+HCI_LE_TRANSMIT_POWER_REPORTING_EVENT = 0X21
+HCI_LE_BIGINFO_ADVERTISING_REPORT_EVENT = 0X22
+HCI_LE_SUBRATE_CHANGE_EVENT = 0X23
HCI_SUBEVENT_NAMES = {
- HCI_LE_CONNECTION_COMPLETE_EVENT: 'HCI_LE_CONNECTION_COMPLETE_EVENT',
- HCI_LE_ADVERTISING_REPORT_EVENT: 'HCI_LE_ADVERTISING_REPORT_EVENT',
- HCI_LE_CONNECTION_UPDATE_COMPLETE_EVENT: 'HCI_LE_CONNECTION_UPDATE_COMPLETE_EVENT',
- HCI_LE_READ_REMOTE_FEATURES_COMPLETE_EVENT: 'HCI_LE_READ_REMOTE_FEATURES_COMPLETE_EVENT',
- HCI_LE_LONG_TERM_KEY_REQUEST_EVENT: 'HCI_LE_LONG_TERM_KEY_REQUEST_EVENT',
- HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT: 'HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT',
- HCI_LE_DATA_LENGTH_CHANGE_EVENT: 'HCI_LE_DATA_LENGTH_CHANGE_EVENT',
- HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT: 'HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT',
- HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT: 'HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT',
- HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT: 'HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT',
- HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT: 'HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT',
- HCI_LE_PHY_UPDATE_COMPLETE_EVENT: 'HCI_LE_PHY_UPDATE_COMPLETE_EVENT',
- HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT: 'HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT',
- HCI_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT: 'HCI_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT',
- HCI_LE_PERIODIC_ADVERTISING_REPORT_EVENT: 'HCI_LE_PERIODIC_ADVERTISING_REPORT_EVENT',
- HCI_LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT: 'HCI_LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT',
- HCI_LE_SCAN_TIMEOUT_EVENT: 'HCI_LE_SCAN_TIMEOUT_EVENT',
- HCI_LE_ADVERTISING_SET_TERMINATED_EVENT: 'HCI_LE_ADVERTISING_SET_TERMINATED_EVENT',
- HCI_LE_SCAN_REQUEST_RECEIVED_EVENT: 'HCI_LE_SCAN_REQUEST_RECEIVED_EVENT',
- HCI_LE_CHANNEL_SELECTION_ALGORITHM_EVENT: 'HCI_LE_CHANNEL_SELECTION_ALGORITHM_EVENT'
+ event_code: event_name for (event_name, event_code) in globals().items()
+ if event_name.startswith('HCI_LE_') and event_name.endswith('_EVENT') and event_code != HCI_LE_META_EVENT
}
# HCI Command
-HCI_INQUIRY_COMMAND = hci_command_op_code(0x01, 0x0001)
-HCI_INQUIRY_CANCEL_COMMAND = hci_command_op_code(0x01, 0x0002)
-HCI_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x01, 0x0005)
-HCI_DISCONNECT_COMMAND = hci_command_op_code(0x01, 0x0006)
-HCI_ACCEPT_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x0009)
-HCI_LINK_KEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x000B)
-HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x000C)
-HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x000E)
-HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND = hci_command_op_code(0x01, 0x000F)
-HCI_AUTHENTICATION_REQUESTED_COMMAND = hci_command_op_code(0x01, 0x0011)
-HCI_SET_CONNECTION_ENCRYPTION_COMMAND = hci_command_op_code(0x01, 0x0013)
-HCI_REMOTE_NAME_REQUEST_COMMAND = hci_command_op_code(0x01, 0x0019)
-HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x01, 0x001B)
-HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND = hci_command_op_code(0x01, 0x001C)
-HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND = hci_command_op_code(0x01, 0x001D)
-HCI_READ_CLOCK_OFFSET_COMMAND = hci_command_op_code(0x01, 0x001F)
-HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002B)
-HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002C)
-HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x002D)
-HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002E)
-HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x002F)
-HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND = hci_command_op_code(0x01, 0x003D)
-HCI_SNIFF_MODE_COMMAND = hci_command_op_code(0x02, 0x0003)
-HCI_EXIT_SNIFF_MODE_COMMAND = hci_command_op_code(0x02, 0x0004)
-HCI_SWITCH_ROLE_COMMAND = hci_command_op_code(0x02, 0x000B)
-HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000D)
-HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000F)
-HCI_SNIFF_SUBRATING_COMMAND = hci_command_op_code(0x02, 0x0011)
-HCI_SET_EVENT_MASK_COMMAND = hci_command_op_code(0x03, 0x0001)
-HCI_RESET_COMMAND = hci_command_op_code(0x03, 0x0003)
-HCI_SET_EVENT_FILTER_COMMAND = hci_command_op_code(0x03, 0x0005)
-HCI_READ_STORED_LINK_KEY_COMMAND = hci_command_op_code(0x03, 0x000D)
-HCI_DELETE_STORED_LINK_KEY_COMMAND = hci_command_op_code(0x03, 0x0012)
-HCI_WRITE_LOCAL_NAME_COMMAND = hci_command_op_code(0x03, 0x0013)
-HCI_READ_LOCAL_NAME_COMMAND = hci_command_op_code(0x03, 0x0014)
-HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0016)
-HCI_WRITE_PAGE_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0018)
-HCI_WRITE_SCAN_ENABLE_COMMAND = hci_command_op_code(0x03, 0x001A)
-HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001B)
-HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001C)
-HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001E)
-HCI_READ_CLASS_OF_DEVICE_COMMAND = hci_command_op_code(0x03, 0x0023)
-HCI_WRITE_CLASS_OF_DEVICE_COMMAND = hci_command_op_code(0x03, 0x0024)
-HCI_READ_VOICE_SETTING_COMMAND = hci_command_op_code(0x03, 0x0025)
-HCI_WRITE_VOICE_SETTING_COMMAND = hci_command_op_code(0x03, 0x0026)
-HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND = hci_command_op_code(0x03, 0x002E)
-HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND = hci_command_op_code(0x03, 0x002F)
-HCI_HOST_BUFFER_SIZE_COMMAND = hci_command_op_code(0x03, 0x0033)
-HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0037)
-HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND = hci_command_op_code(0x03, 0x0038)
-HCI_READ_CURRENT_IAC_LAP_COMMAND = hci_command_op_code(0x03, 0x0039)
-HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0043)
-HCI_WRITE_INQUIRY_MODE_COMMAND = hci_command_op_code(0x03, 0x0045)
-HCI_READ_PAGE_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0046)
-HCI_WRITE_PAGE_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0047)
-HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND = hci_command_op_code(0x03, 0x0052)
-HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND = hci_command_op_code(0x03, 0x0056)
-HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x03, 0x0058)
-HCI_SET_EVENT_MASK_PAGE_2_COMMAND = hci_command_op_code(0x03, 0x0063)
-HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND = hci_command_op_code(0x03, 0x005A)
-HCI_READ_LE_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x006C)
-HCI_WRITE_LE_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x006D)
-HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x007A)
-HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x007C)
-HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND = hci_command_op_code(0x04, 0x0001)
-HCI_READ_LOCAL_SUPPORTED_COMMANDS_COMMAND = hci_command_op_code(0x04, 0x0002)
-HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x04, 0x0003)
-HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND = hci_command_op_code(0x04, 0x0004)
-HCI_READ_BUFFER_SIZE_COMMAND = hci_command_op_code(0x04, 0x0005)
-HCI_READ_BD_ADDR_COMMAND = hci_command_op_code(0x04, 0x0009)
-HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND = hci_command_op_code(0x04, 0x000B)
-HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND = hci_command_op_code(0x05, 0x0008)
-HCI_LE_SET_EVENT_MASK_COMMAND = hci_command_op_code(0x08, 0x0001)
-HCI_LE_READ_BUFFER_SIZE_COMMAND = hci_command_op_code(0x08, 0x0002)
-HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x08, 0x0003)
-HCI_LE_SET_RANDOM_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x0005)
-HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0006)
-HCI_LE_READ_ADVERTISING_CHANNEL_TX_POWER_COMMAND = hci_command_op_code(0x08, 0x0007)
-HCI_LE_SET_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x0008)
-HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND = hci_command_op_code(0x08, 0x0009)
-HCI_LE_SET_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x000A)
-HCI_LE_SET_SCAN_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x000B)
-HCI_LE_SET_SCAN_ENABLE_COMMAND = hci_command_op_code(0x08, 0x000C)
-HCI_LE_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x08, 0x000D)
-HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND = hci_command_op_code(0x08, 0x000E)
-HCI_LE_READ_WHITE_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x000F)
-HCI_LE_CLEAR_WHITE_LIST_COMMAND = hci_command_op_code(0x08, 0x0010)
-HCI_LE_ADD_DEVICE_TO_WHITE_LIST_COMMAND = hci_command_op_code(0x08, 0x0011)
-HCI_LE_REMOVE_DEVICE_FROM_WHITE_LIST_COMMAND = hci_command_op_code(0x08, 0x0012)
-HCI_LE_CONNECTION_UPDATE_COMMAND = hci_command_op_code(0x08, 0x0013)
-HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND = hci_command_op_code(0x08, 0x0014)
-HCI_LE_READ_CHANNEL_MAP_COMMAND = hci_command_op_code(0x08, 0x0015)
-HCI_LE_READ_REMOTE_FEATURES_COMMAND = hci_command_op_code(0x08, 0x0016)
-HCI_LE_ENCRYPT_COMMAND = hci_command_op_code(0x08, 0x0017)
-HCI_LE_RAND_COMMAND = hci_command_op_code(0x08, 0x0018)
-HCI_LE_START_ENCRYPTION_COMMAND = hci_command_op_code(0x08, 0x0019)
-HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x08, 0x001A)
-HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x08, 0x001B)
-HCI_LE_READ_SUPPORTED_STATES_COMMAND = hci_command_op_code(0x08, 0x001C)
-HCI_LE_RECEIVER_TEST_COMMAND = hci_command_op_code(0x08, 0x001D)
-HCI_LE_TRANSMITTER_TEST_COMMAND = hci_command_op_code(0x08, 0x001E)
-HCI_LE_TEST_END_COMMAND = hci_command_op_code(0x08, 0x001F)
-HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND = hci_command_op_code(0x08, 0x0020)
-HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x08, 0x0021)
-HCI_LE_SET_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0022)
-HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0023)
-HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0024)
-HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND = hci_command_op_code(0x08, 0x0025)
-HCI_LE_GENERATE_DHKEY_COMMAND = hci_command_op_code(0x08, 0x0026)
-HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0027)
-HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0028)
-HCI_LE_CLEAR_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0029)
-HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x002A)
-HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x002B)
-HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x002C)
-HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND = hci_command_op_code(0x08, 0x002D)
-HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND = hci_command_op_code(0x08, 0x002E)
-HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x002F)
-HCI_LE_READ_PHY_COMMAND = hci_command_op_code(0x08, 0x0030)
-HCI_LE_SET_DEFAULT_PHY_COMMAND = hci_command_op_code(0x08, 0x0031)
-HCI_LE_SET_PHY_COMMAND = hci_command_op_code(0x08, 0x0032)
-HCI_LE_ENHANCED_RECEIVER_TEST_COMMAND = hci_command_op_code(0x08, 0x0033)
-HCI_LE_ENHANCED_TRANSMITTER_TEST_COMMAND = hci_command_op_code(0x08, 0x0034)
-HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x0035)
-HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0036)
-HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x0037)
-HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND = hci_command_op_code(0x08, 0x0038)
-HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0039)
-HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x003A)
-HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERETISING_SETS_COMMAND = hci_command_op_code(0x08, 0x003B)
-HCI_LE_REMOVE_ADVERTISING_SET_COMMAND = hci_command_op_code(0x08, 0x003C)
-HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND = hci_command_op_code(0x08, 0x003D)
-HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x003E)
-HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x003F)
-HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0040)
-HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0041)
-HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0042)
-HCI_LE_SET_EXTENDED_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x08, 0x0043)
-HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x0044)
-HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND = hci_command_op_code(0x08, 0x0045)
-HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x0046)
-HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0047)
-HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0048)
-HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0049)
-HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x004A)
-HCI_LE_READ_TRANSMIT_POWER_COMMAND = hci_command_op_code(0x08, 0x004B)
-HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND = hci_command_op_code(0x08, 0x004C)
-HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND = hci_command_op_code(0x08, 0x004D)
-HCI_LE_SET_PRIVACY_MODE_COMMAND = hci_command_op_code(0x08, 0x004E)
-
+HCI_INQUIRY_COMMAND = hci_command_op_code(0x01, 0x0001)
+HCI_INQUIRY_CANCEL_COMMAND = hci_command_op_code(0x01, 0x0002)
+HCI_PERIODIC_INQUIRY_MODE_COMMAND = hci_command_op_code(0x01, 0x0003)
+HCI_EXIT_PERIODIC_INQUIRY_MODE_COMMAND = hci_command_op_code(0x01, 0x0004)
+HCI_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x01, 0x0005)
+HCI_DISCONNECT_COMMAND = hci_command_op_code(0x01, 0x0006)
+HCI_CREATE_CONNECTION_CANCEL_COMMAND = hci_command_op_code(0x01, 0x0008)
+HCI_ACCEPT_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x0009)
+HCI_REJECT_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x000A)
+HCI_LINK_KEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x000B)
+HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x000C)
+HCI_PIN_CODE_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x000D)
+HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x000E)
+HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND = hci_command_op_code(0x01, 0x000F)
+HCI_AUTHENTICATION_REQUESTED_COMMAND = hci_command_op_code(0x01, 0x0011)
+HCI_SET_CONNECTION_ENCRYPTION_COMMAND = hci_command_op_code(0x01, 0x0013)
+HCI_CHANGE_CONNECTION_LINK_KEY_COMMAND = hci_command_op_code(0x01, 0x0015)
+HCI_LINK_KEY_SELECTION_COMMAND = hci_command_op_code(0x01, 0x0017)
+HCI_REMOTE_NAME_REQUEST_COMMAND = hci_command_op_code(0x01, 0x0019)
+HCI_REMOTE_NAME_REQUEST_CANCEL_COMMAND = hci_command_op_code(0x01, 0x001A)
+HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x01, 0x001B)
+HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND = hci_command_op_code(0x01, 0x001C)
+HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND = hci_command_op_code(0x01, 0x001D)
+HCI_READ_CLOCK_OFFSET_COMMAND = hci_command_op_code(0x01, 0x001F)
+HCI_READ_LMP_HANDLE_COMMAND = hci_command_op_code(0x01, 0x0020)
+HCI_SETUP_SYNCHRONOUS_CONNECTION_COMMAND = hci_command_op_code(0x01, 0x0028)
+HCI_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x0029)
+HCI_REJECT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x002A)
+HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002B)
+HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002C)
+HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x002D)
+HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x002E)
+HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x002F)
+HCI_REMOTE_OOB_DATA_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x0030)
+HCI_REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x0033)
+HCI_IO_CAPABILITY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x01, 0x0034)
+HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND = hci_command_op_code(0x01, 0x003D)
+HCI_ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND = hci_command_op_code(0x01, 0x003E)
+HCI_TRUNCATED_PAGE_COMMAND = hci_command_op_code(0x01, 0x003F)
+HCI_TRUNCATED_PAGE_CANCEL_COMMAND = hci_command_op_code(0x01, 0x0040)
+HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_COMMAND = hci_command_op_code(0x01, 0x0041)
+HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE_COMMAND = hci_command_op_code(0x01, 0x0042)
+HCI_START_SYNCHRONIZATION_TRAIN_COMMAND = hci_command_op_code(0x01, 0x0043)
+HCI_RECEIVE_SYNCHRONIZATION_TRAIN_COMMAND = hci_command_op_code(0x01, 0x0044)
+HCI_REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY_COMMAND = hci_command_op_code(0x01, 0x0045)
+HCI_HOLD_MODE_COMMAND = hci_command_op_code(0x02, 0x0001)
+HCI_SNIFF_MODE_COMMAND = hci_command_op_code(0x02, 0x0003)
+HCI_EXIT_SNIFF_MODE_COMMAND = hci_command_op_code(0x02, 0x0004)
+HCI_QOS_SETUP_COMMAND = hci_command_op_code(0x02, 0x0007)
+HCI_ROLE_DISCOVERY_COMMAND = hci_command_op_code(0x02, 0x0009)
+HCI_SWITCH_ROLE_COMMAND = hci_command_op_code(0x02, 0x000B)
+HCI_READ_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000C)
+HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000D)
+HCI_READ_DEFAULT_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000E)
+HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND = hci_command_op_code(0x02, 0x000F)
+HCI_FLOW_SPECIFICATION_COMMAND = hci_command_op_code(0x02, 0x0010)
+HCI_SNIFF_SUBRATING_COMMAND = hci_command_op_code(0x02, 0x0011)
+HCI_SET_EVENT_MASK_COMMAND = hci_command_op_code(0x03, 0x0001)
+HCI_RESET_COMMAND = hci_command_op_code(0x03, 0x0003)
+HCI_SET_EVENT_FILTER_COMMAND = hci_command_op_code(0x03, 0x0005)
+HCI_FLUSH_COMMAND = hci_command_op_code(0x03, 0x0008)
+HCI_READ_PIN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0009)
+HCI_WRITE_PIN_TYPE_COMMAND = hci_command_op_code(0x03, 0x000A)
+HCI_READ_STORED_LINK_KEY_COMMAND = hci_command_op_code(0x03, 0x000D)
+HCI_WRITE_STORED_LINK_KEY_COMMAND = hci_command_op_code(0x03, 0x0011)
+HCI_DELETE_STORED_LINK_KEY_COMMAND = hci_command_op_code(0x03, 0x0012)
+HCI_WRITE_LOCAL_NAME_COMMAND = hci_command_op_code(0x03, 0x0013)
+HCI_READ_LOCAL_NAME_COMMAND = hci_command_op_code(0x03, 0x0014)
+HCI_READ_CONNECTION_ACCEPT_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0015)
+HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0016)
+HCI_READ_PAGE_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0017)
+HCI_WRITE_PAGE_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0018)
+HCI_READ_SCAN_ENABLE_COMMAND = hci_command_op_code(0x03, 0x0019)
+HCI_WRITE_SCAN_ENABLE_COMMAND = hci_command_op_code(0x03, 0x001A)
+HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001B)
+HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001C)
+HCI_READ_INQUIRY_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001D)
+HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x001E)
+HCI_READ_AUTHENTICATION_ENABLE_COMMAND = hci_command_op_code(0x03, 0x001F)
+HCI_WRITE_AUTHENTICATION_ENABLE_COMMAND = hci_command_op_code(0x03, 0x0020)
+HCI_READ_CLASS_OF_DEVICE_COMMAND = hci_command_op_code(0x03, 0x0023)
+HCI_WRITE_CLASS_OF_DEVICE_COMMAND = hci_command_op_code(0x03, 0x0024)
+HCI_READ_VOICE_SETTING_COMMAND = hci_command_op_code(0x03, 0x0025)
+HCI_WRITE_VOICE_SETTING_COMMAND = hci_command_op_code(0x03, 0x0026)
+HCI_READ_AUTOMATIC_FLUSH_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0027)
+HCI_WRITE_AUTOMATIC_FLUSH_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0028)
+HCI_READ_NUM_BROADCAST_RETRANSMISSIONS_COMMAND = hci_command_op_code(0x03, 0x0029)
+HCI_WRITE_NUM_BROADCAST_RETRANSMISSIONS_COMMAND = hci_command_op_code(0x03, 0x002A)
+HCI_READ_HOLD_MODE_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x002B)
+HCI_WRITE_HOLD_MODE_ACTIVITY_COMMAND = hci_command_op_code(0x03, 0x002C)
+HCI_READ_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x03, 0x002D)
+HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND = hci_command_op_code(0x03, 0x002E)
+HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND = hci_command_op_code(0x03, 0x002F)
+HCI_SET_CONTROLLER_TO_HOST_FLOW_CONTROL_COMMAND = hci_command_op_code(0x03, 0x0031)
+HCI_HOST_BUFFER_SIZE_COMMAND = hci_command_op_code(0x03, 0x0033)
+HCI_HOST_NUMBER_OF_COMPLETED_PACKETS_COMMAND = hci_command_op_code(0x03, 0x0035)
+HCI_READ_LINK_SUPERVISION_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0036)
+HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x0037)
+HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND = hci_command_op_code(0x03, 0x0038)
+HCI_READ_CURRENT_IAC_LAP_COMMAND = hci_command_op_code(0x03, 0x0039)
+HCI_WRITE_CURRENT_IAC_LAP_COMMAND = hci_command_op_code(0x03, 0x003A)
+HCI_SET_AFH_HOST_CHANNEL_CLASSIFICATION_COMMAND = hci_command_op_code(0x03, 0x003F)
+HCI_READ_INQUIRY_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0042)
+HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0043)
+HCI_READ_INQUIRY_MODE_COMMAND = hci_command_op_code(0x03, 0x0044)
+HCI_WRITE_INQUIRY_MODE_COMMAND = hci_command_op_code(0x03, 0x0045)
+HCI_READ_PAGE_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0046)
+HCI_WRITE_PAGE_SCAN_TYPE_COMMAND = hci_command_op_code(0x03, 0x0047)
+HCI_READ_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND = hci_command_op_code(0x03, 0x0048)
+HCI_WRITE_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND = hci_command_op_code(0x03, 0x0049)
+HCI_READ_EXTENDED_INQUIRY_RESPONSE_COMMAND = hci_command_op_code(0x03, 0x0051)
+HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND = hci_command_op_code(0x03, 0x0052)
+HCI_REFRESH_ENCRYPTION_KEY_COMMAND = hci_command_op_code(0x03, 0x0053)
+HCI_READ_SIMPLE_PAIRING_MODE_COMMAND = hci_command_op_code(0x03, 0x0055)
+HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND = hci_command_op_code(0x03, 0x0056)
+HCI_READ_LOCAL_OOB_DATA_COMMAND = hci_command_op_code(0x03, 0x0057)
+HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x03, 0x0058)
+HCI_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x03, 0x0059)
+HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND = hci_command_op_code(0x03, 0x005A)
+HCI_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND = hci_command_op_code(0x03, 0x005B)
+HCI_ENHANCED_FLUSH_COMMAND = hci_command_op_code(0x03, 0x005F)
+HCI_SEND_KEYPRESS_NOTIFICATION_COMMAND = hci_command_op_code(0x03, 0x0060)
+HCI_SET_EVENT_MASK_PAGE_2_COMMAND = hci_command_op_code(0x03, 0x0063)
+HCI_READ_FLOW_CONTROL_MODE_COMMAND = hci_command_op_code(0x03, 0x0066)
+HCI_WRITE_FLOW_CONTROL_MODE_COMMAND = hci_command_op_code(0x03, 0x0067)
+HCI_READ_ENHANCED_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x03, 0x0068)
+HCI_READ_LE_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x006C)
+HCI_WRITE_LE_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x006D)
+HCI_SET_MWS_CHANNEL_PARAMETERS_COMMAND = hci_command_op_code(0x03, 0x006E)
+HCI_SET_EXTERNAL_FRAME_CONFIGURATION_COMMAND = hci_command_op_code(0x03, 0x006F)
+HCI_SET_MWS_SIGNALING_COMMAND = hci_command_op_code(0x03, 0x0070)
+HCI_SET_MWS_TRANSPORT_LAYER_COMMAND = hci_command_op_code(0x03, 0x0071)
+HCI_SET_MWS_SCAN_FREQUENCY_TABLE_COMMAND = hci_command_op_code(0x03, 0x0072)
+HCI_SET_MWS_PATTERN_CONFIGURATION_COMMAND = hci_command_op_code(0x03, 0x0073)
+HCI_SET_RESERVED_LT_ADDR_COMMAND = hci_command_op_code(0x03, 0x0074)
+HCI_DELETE_RESERVED_LT_ADDR_COMMAND = hci_command_op_code(0x03, 0x0075)
+HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA_COMMAND = hci_command_op_code(0x03, 0x0076)
+HCI_READ_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND = hci_command_op_code(0x03, 0x0077)
+HCI_WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND = hci_command_op_code(0x03, 0x0078)
+HCI_READ_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x0079)
+HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND = hci_command_op_code(0x03, 0x007A)
+HCI_READ_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x007B)
+HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x007C)
+HCI_READ_LOCAL_OOB_EXTENDED_DATA_COMMAND = hci_command_op_code(0x03, 0x007D)
+HCI_READ_EXTENDED_PAGE_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x007E)
+HCI_WRITE_EXTENDED_PAGE_TIMEOUT_COMMAND = hci_command_op_code(0x03, 0x007F)
+HCI_READ_EXTENDED_INQUIRY_LENGTH_COMMAND = hci_command_op_code(0x03, 0x0080)
+HCI_WRITE_EXTENDED_INQUIRY_LENGTH_COMMAND = hci_command_op_code(0x03, 0x0081)
+HCI_SET_ECOSYSTEM_BASE_INTERVAL_COMMAND = hci_command_op_code(0x03, 0x0082)
+HCI_CONFIGURE_DATA_PATH_COMMAND = hci_command_op_code(0x03, 0x0083)
+HCI_SET_MIN_ENCRYPTION_KEY_SIZE_COMMAND = hci_command_op_code(0x03, 0x0084)
+HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND = hci_command_op_code(0x04, 0x0001)
+HCI_READ_LOCAL_SUPPORTED_COMMANDS_COMMAND = hci_command_op_code(0x04, 0x0002)
+HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x04, 0x0003)
+HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND = hci_command_op_code(0x04, 0x0004)
+HCI_READ_BUFFER_SIZE_COMMAND = hci_command_op_code(0x04, 0x0005)
+HCI_READ_BD_ADDR_COMMAND = hci_command_op_code(0x04, 0x0009)
+HCI_READ_DATA_BLOCK_SIZE_COMMAND = hci_command_op_code(0x04, 0x000A)
+HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND = hci_command_op_code(0x04, 0x000B)
+HCI_READ_LOCAL_SIMPLE_PAIRING_OPTIONS_COMMAND = hci_command_op_code(0x04, 0x000C)
+HCI_READ_LOCAL_SUPPORTED_CODECS_V2_COMMAND = hci_command_op_code(0x04, 0x000D)
+HCI_READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES_COMMAND = hci_command_op_code(0x04, 0x000E)
+HCI_READ_LOCAL_SUPPORTED_CONTROLLER_DELAY_COMMAND = hci_command_op_code(0x04, 0x000F)
+HCI_READ_FAILED_CONTACT_COUNTER_COMMAND = hci_command_op_code(0x05, 0x0001)
+HCI_RESET_FAILED_CONTACT_COUNTER_COMMAND = hci_command_op_code(0x05, 0x0002)
+HCI_READ_LINK_QUALITY_COMMAND = hci_command_op_code(0x05, 0x0003)
+HCI_READ_RSSI_COMMAND = hci_command_op_code(0x05, 0x0005)
+HCI_READ_AFH_CHANNEL_MAP_COMMAND = hci_command_op_code(0x05, 0x0006)
+HCI_READ_CLOCK_COMMAND = hci_command_op_code(0x05, 0x0007)
+HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND = hci_command_op_code(0x05, 0x0008)
+HCI_GET_MWS_TRANSPORT_LAYER_CONFIGURATION_COMMAND = hci_command_op_code(0x05, 0x000C)
+HCI_SET_TRIGGERED_CLOCK_CAPTURE_COMMAND = hci_command_op_code(0x05, 0x000D)
+HCI_READ_LOOPBACK_MODE_COMMAND = hci_command_op_code(0x06, 0x0001)
+HCI_WRITE_LOOPBACK_MODE_COMMAND = hci_command_op_code(0x06, 0x0002)
+HCI_ENABLE_DEVICE_UNDER_TEST_MODE_COMMAND = hci_command_op_code(0x06, 0x0003)
+HCI_WRITE_SIMPLE_PAIRING_DEBUG_MODE_COMMAND = hci_command_op_code(0x06, 0x0004)
+HCI_WRITE_SECURE_CONNECTIONS_TEST_MODE_COMMAND = hci_command_op_code(0x06, 0x000A)
+HCI_LE_SET_EVENT_MASK_COMMAND = hci_command_op_code(0x08, 0x0001)
+HCI_LE_READ_BUFFER_SIZE_COMMAND = hci_command_op_code(0x08, 0x0002)
+HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND = hci_command_op_code(0x08, 0x0003)
+HCI_LE_SET_RANDOM_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x0005)
+HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0006)
+HCI_LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER_COMMAND = hci_command_op_code(0x08, 0x0007)
+HCI_LE_SET_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x0008)
+HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND = hci_command_op_code(0x08, 0x0009)
+HCI_LE_SET_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x000A)
+HCI_LE_SET_SCAN_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x000B)
+HCI_LE_SET_SCAN_ENABLE_COMMAND = hci_command_op_code(0x08, 0x000C)
+HCI_LE_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x08, 0x000D)
+HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND = hci_command_op_code(0x08, 0x000E)
+HCI_LE_READ_FILTER_ACCEPT_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x000F)
+HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND = hci_command_op_code(0x08, 0x0010)
+HCI_LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST_COMMAND = hci_command_op_code(0x08, 0x0011)
+HCI_LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST_COMMAND = hci_command_op_code(0x08, 0x0012)
+HCI_LE_CONNECTION_UPDATE_COMMAND = hci_command_op_code(0x08, 0x0013)
+HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND = hci_command_op_code(0x08, 0x0014)
+HCI_LE_READ_CHANNEL_MAP_COMMAND = hci_command_op_code(0x08, 0x0015)
+HCI_LE_READ_REMOTE_FEATURES_COMMAND = hci_command_op_code(0x08, 0x0016)
+HCI_LE_ENCRYPT_COMMAND = hci_command_op_code(0x08, 0x0017)
+HCI_LE_RAND_COMMAND = hci_command_op_code(0x08, 0x0018)
+HCI_LE_ENABLE_ENCRYPTION_COMMAND = hci_command_op_code(0x08, 0x0019)
+HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND = hci_command_op_code(0x08, 0x001A)
+HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x08, 0x001B)
+HCI_LE_READ_SUPPORTED_STATES_COMMAND = hci_command_op_code(0x08, 0x001C)
+HCI_LE_RECEIVER_TEST_COMMAND = hci_command_op_code(0x08, 0x001D)
+HCI_LE_TRANSMITTER_TEST_COMMAND = hci_command_op_code(0x08, 0x001E)
+HCI_LE_TEST_END_COMMAND = hci_command_op_code(0x08, 0x001F)
+HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND = hci_command_op_code(0x08, 0x0020)
+HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND = hci_command_op_code(0x08, 0x0021)
+HCI_LE_SET_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0022)
+HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0023)
+HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x0024)
+HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND = hci_command_op_code(0x08, 0x0025)
+HCI_LE_GENERATE_DHKEY_COMMAND = hci_command_op_code(0x08, 0x0026)
+HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0027)
+HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0028)
+HCI_LE_CLEAR_RESOLVING_LIST_COMMAND = hci_command_op_code(0x08, 0x0029)
+HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x002A)
+HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x002B)
+HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x002C)
+HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND = hci_command_op_code(0x08, 0x002D)
+HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND = hci_command_op_code(0x08, 0x002E)
+HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x002F)
+HCI_LE_READ_PHY_COMMAND = hci_command_op_code(0x08, 0x0030)
+HCI_LE_SET_DEFAULT_PHY_COMMAND = hci_command_op_code(0x08, 0x0031)
+HCI_LE_SET_PHY_COMMAND = hci_command_op_code(0x08, 0x0032)
+HCI_LE_RECEIVER_TEST_V2_COMMAND = hci_command_op_code(0x08, 0x0033)
+HCI_LE_TRANSMITTER_TEST_V2_COMMAND = hci_command_op_code(0x08, 0x0034)
+HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND = hci_command_op_code(0x08, 0x0035)
+HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0036)
+HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x0037)
+HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND = hci_command_op_code(0x08, 0x0038)
+HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0039)
+HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND = hci_command_op_code(0x08, 0x003A)
+HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND = hci_command_op_code(0x08, 0x003B)
+HCI_LE_REMOVE_ADVERTISING_SET_COMMAND = hci_command_op_code(0x08, 0x003C)
+HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND = hci_command_op_code(0x08, 0x003D)
+HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x003E)
+HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND = hci_command_op_code(0x08, 0x003F)
+HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0040)
+HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0041)
+HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0042)
+HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND = hci_command_op_code(0x08, 0x0043)
+HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x0044)
+HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND = hci_command_op_code(0x08, 0x0045)
+HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x0046)
+HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0047)
+HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0048)
+HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND = hci_command_op_code(0x08, 0x0049)
+HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND = hci_command_op_code(0x08, 0x004A)
+HCI_LE_READ_TRANSMIT_POWER_COMMAND = hci_command_op_code(0x08, 0x004B)
+HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND = hci_command_op_code(0x08, 0x004C)
+HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND = hci_command_op_code(0x08, 0x004D)
+HCI_LE_SET_PRIVACY_MODE_COMMAND = hci_command_op_code(0x08, 0x004E)
+HCI_LE_RECEIVER_TEST_V3_COMMAND = hci_command_op_code(0x08, 0x004F)
+HCI_LE_TRANSMITTER_TEST_V3_COMMAND = hci_command_op_code(0x08, 0x0050)
+HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0051)
+HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0052)
+HCI_LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0053)
+HCI_LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0054)
+HCI_LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0055)
+HCI_LE_CONNECTION_CTE_REQUEST_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0056)
+HCI_LE_CONNECTION_CTE_RESPONSE_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0057)
+HCI_LE_READ_ANTENNA_INFORMATION_COMMAND = hci_command_op_code(0x08, 0x0058)
+HCI_LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0059)
+HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_COMMAND = hci_command_op_code(0x08, 0x005A)
+HCI_LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER_COMMAND = hci_command_op_code(0x08, 0x005B)
+HCI_LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x005C)
+HCI_LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x005D)
+HCI_LE_GENERATE_DHKEY_V2_COMMAND = hci_command_op_code(0x08, 0x005E)
+HCI_LE_MODIFY_SLEEP_CLOCK_ACCURACY_COMMAND = hci_command_op_code(0x08, 0x005F)
+HCI_LE_READ_BUFFER_SIZE_V2_COMMAND = hci_command_op_code(0x08, 0x0060)
+HCI_LE_READ_ISO_TX_SYNC_COMMAND = hci_command_op_code(0x08, 0x0061)
+HCI_LE_SET_CIG_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0062)
+HCI_LE_SET_CIG_PARAMETERS_TEST_COMMAND = hci_command_op_code(0x08, 0x0063)
+HCI_LE_CREATE_CIS_COMMAND = hci_command_op_code(0x08, 0x0064)
+HCI_LE_REMOVE_CIG_COMMAND = hci_command_op_code(0x08, 0x0065)
+HCI_LE_ACCEPT_CIS_REQUEST_COMMAND = hci_command_op_code(0x08, 0x0066)
+HCI_LE_REJECT_CIS_REQUEST_COMMAND = hci_command_op_code(0x08, 0x0067)
+HCI_LE_CREATE_BIG_COMMAND = hci_command_op_code(0x08, 0x0068)
+HCI_LE_CREATE_BIG_TEST_COMMAND = hci_command_op_code(0x08, 0x0069)
+HCI_LE_TERMINATE_BIG_COMMAND = hci_command_op_code(0x08, 0x006A)
+HCI_LE_BIG_CREATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x006B)
+HCI_LE_BIG_TERMINATE_SYNC_COMMAND = hci_command_op_code(0x08, 0x006C)
+HCI_LE_REQUEST_PEER_SCA_COMMAND = hci_command_op_code(0x08, 0x006D)
+HCI_LE_SETUP_ISO_DATA_PATH_COMMAND = hci_command_op_code(0x08, 0x006E)
+HCI_LE_REMOVE_ISO_DATA_PATH_COMMAND = hci_command_op_code(0x08, 0x006F)
+HCI_LE_ISO_TRANSMIT_TEST_COMMAND = hci_command_op_code(0x08, 0x0070)
+HCI_LE_ISO_RECEIVE_TEST_COMMAND = hci_command_op_code(0x08, 0x0071)
+HCI_LE_ISO_READ_TEST_COUNTERS_COMMAND = hci_command_op_code(0x08, 0x0072)
+HCI_LE_ISO_TEST_END_COMMAND = hci_command_op_code(0x08, 0x0073)
+HCI_LE_SET_HOST_FEATURE_COMMAND = hci_command_op_code(0x08, 0x0074)
+HCI_LE_READ_ISO_LINK_QUALITY_COMMAND = hci_command_op_code(0x08, 0x0075)
+HCI_LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x08, 0x0076)
+HCI_LE_READ_REMOTE_TRANSMIT_POWER_LEVEL_COMMAND = hci_command_op_code(0x08, 0x0077)
+HCI_LE_SET_PATH_LOSS_REPORTING_PARAMETERS_COMMAND = hci_command_op_code(0x08, 0x0078)
+HCI_LE_SET_PATH_LOSS_REPORTING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0079)
+HCI_LE_SET_TRANSMIT_POWER_REPORTING_ENABLE_COMMAND = hci_command_op_code(0x08, 0x007A)
+HCI_LE_TRANSMITTER_TEST_V4_COMMAND = hci_command_op_code(0x08, 0x007B)
+HCI_LE_SET_DATA_RELATED_ADDRESS_CHANGES_COMMAND = hci_command_op_code(0x08, 0x007C)
+HCI_LE_SET_DEFAULT_SUBRATE_COMMAND = hci_command_op_code(0x08, 0x007D)
+HCI_LE_SUBRATE_REQUEST_COMMAND = hci_command_op_code(0x08, 0x007E)
HCI_COMMAND_NAMES = {
- HCI_INQUIRY_COMMAND: 'HCI_INQUIRY_COMMAND',
- HCI_INQUIRY_CANCEL_COMMAND: 'HCI_INQUIRY_CANCEL_COMMAND',
- HCI_CREATE_CONNECTION_COMMAND: 'HCI_CREATE_CONNECTION_COMMAND',
- HCI_DISCONNECT_COMMAND: 'HCI_DISCONNECT_COMMAND',
- HCI_ACCEPT_CONNECTION_REQUEST_COMMAND: 'HCI_ACCEPT_CONNECTION_REQUEST_COMMAND',
- HCI_LINK_KEY_REQUEST_REPLY_COMMAND: 'HCI_LINK_KEY_REQUEST_REPLY_COMMAND',
- HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND: 'HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND',
- HCI_AUTHENTICATION_REQUESTED_COMMAND: 'HCI_AUTHENTICATION_REQUESTED_COMMAND',
- HCI_SET_CONNECTION_ENCRYPTION_COMMAND: 'HCI_SET_CONNECTION_ENCRYPTION_COMMAND',
- HCI_REMOTE_NAME_REQUEST_COMMAND: 'HCI_REMOTE_NAME_REQUEST_COMMAND',
- HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND: 'HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND',
- HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND: 'HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND',
- HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND: 'HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND',
- HCI_READ_CLOCK_OFFSET_COMMAND: 'HCI_READ_CLOCK_OFFSET_COMMAND',
- HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND: 'HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND',
- HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND: 'HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND',
- HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND: 'HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND',
- HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND: 'HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND',
- HCI_SNIFF_MODE_COMMAND: 'HCI_SNIFF_MODE_COMMAND',
- HCI_EXIT_SNIFF_MODE_COMMAND: 'HCI_EXIT_SNIFF_MODE_COMMAND',
- HCI_SWITCH_ROLE_COMMAND: 'HCI_SWITCH_ROLE_COMMAND',
- HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND: 'HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND',
- HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND: 'HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND',
- HCI_SNIFF_SUBRATING_COMMAND: 'HCI_SNIFF_SUBRATING_COMMAND',
- HCI_SET_EVENT_MASK_COMMAND: 'HCI_SET_EVENT_MASK_COMMAND',
- HCI_RESET_COMMAND: 'HCI_RESET_COMMAND',
- HCI_SET_EVENT_FILTER_COMMAND: 'HCI_SET_EVENT_FILTER_COMMAND',
- HCI_READ_STORED_LINK_KEY_COMMAND: 'HCI_READ_STORED_LINK_KEY_COMMAND',
- HCI_DELETE_STORED_LINK_KEY_COMMAND: 'HCI_DELETE_STORED_LINK_KEY_COMMAND',
- HCI_WRITE_LOCAL_NAME_COMMAND: 'HCI_WRITE_LOCAL_NAME_COMMAND',
- HCI_READ_LOCAL_NAME_COMMAND: 'HCI_READ_LOCAL_NAME_COMMAND',
- HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND: 'HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND',
- HCI_WRITE_PAGE_TIMEOUT_COMMAND: 'HCI_WRITE_PAGE_TIMEOUT_COMMAND',
- HCI_WRITE_SCAN_ENABLE_COMMAND: 'HCI_WRITE_SCAN_ENABLE_COMMAND',
- HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND: 'HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND',
- HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND: 'HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND',
- HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND: 'HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND',
- HCI_READ_CLASS_OF_DEVICE_COMMAND: 'HCI_READ_CLASS_OF_DEVICE_COMMAND',
- HCI_WRITE_CLASS_OF_DEVICE_COMMAND: 'HCI_WRITE_CLASS_OF_DEVICE_COMMAND',
- HCI_READ_VOICE_SETTING_COMMAND: 'HCI_READ_VOICE_SETTING_COMMAND',
- HCI_WRITE_VOICE_SETTING_COMMAND: 'HCI_WRITE_VOICE_SETTING_COMMAND',
- HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND: 'HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND',
- HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND: 'HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND',
- HCI_HOST_BUFFER_SIZE_COMMAND: 'HCI_HOST_BUFFER_SIZE_COMMAND',
- HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND: 'HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND',
- HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND: 'HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND',
- HCI_READ_CURRENT_IAC_LAP_COMMAND: 'HCI_READ_CURRENT_IAC_LAP_COMMAND',
- HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND: 'HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND',
- HCI_WRITE_INQUIRY_MODE_COMMAND: 'HCI_WRITE_INQUIRY_MODE_COMMAND',
- HCI_READ_PAGE_SCAN_TYPE_COMMAND: 'HCI_READ_PAGE_SCAN_TYPE_COMMAND',
- HCI_WRITE_PAGE_SCAN_TYPE_COMMAND: 'HCI_WRITE_PAGE_SCAN_TYPE_COMMAND',
- HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND: 'HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND',
- HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND: 'HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND',
- HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND: 'HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND',
- HCI_SET_EVENT_MASK_PAGE_2_COMMAND: 'HCI_SET_EVENT_MASK_PAGE_2_COMMAND',
- HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND: 'HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND',
- HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND: 'HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND',
- HCI_READ_LOCAL_SUPPORTED_COMMANDS_COMMAND: 'HCI_READ_LOCAL_SUPPORTED_COMMANDS_COMMAND',
- HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND: 'HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND',
- HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND: 'HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND',
- HCI_READ_BUFFER_SIZE_COMMAND: 'HCI_READ_BUFFER_SIZE_COMMAND',
- HCI_READ_LE_HOST_SUPPORT_COMMAND: 'HCI_READ_LE_HOST_SUPPORT_COMMAND',
- HCI_WRITE_LE_HOST_SUPPORT_COMMAND: 'HCI_WRITE_LE_HOST_SUPPORT_COMMAND',
- HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND: 'HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND',
- HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND: 'HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND',
- HCI_READ_BD_ADDR_COMMAND: 'HCI_READ_BD_ADDR_COMMAND',
- HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND: 'HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND',
- HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND: 'HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND',
- HCI_LE_SET_EVENT_MASK_COMMAND: 'HCI_LE_SET_EVENT_MASK_COMMAND',
- HCI_LE_READ_BUFFER_SIZE_COMMAND: 'HCI_LE_READ_BUFFER_SIZE_COMMAND',
- HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND: 'HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND',
- HCI_LE_SET_RANDOM_ADDRESS_COMMAND: 'HCI_LE_SET_RANDOM_ADDRESS_COMMAND',
- HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND: 'HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND',
- HCI_LE_READ_ADVERTISING_CHANNEL_TX_POWER_COMMAND: 'HCI_LE_READ_ADVERTISING_CHANNEL_TX_POWER_COMMAND',
- HCI_LE_SET_ADVERTISING_DATA_COMMAND: 'HCI_LE_SET_ADVERTISING_DATA_COMMAND',
- HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND: 'HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND',
- HCI_LE_SET_ADVERTISING_ENABLE_COMMAND: 'HCI_LE_SET_ADVERTISING_ENABLE_COMMAND',
- HCI_LE_SET_SCAN_PARAMETERS_COMMAND: 'HCI_LE_SET_SCAN_PARAMETERS_COMMAND',
- HCI_LE_SET_SCAN_ENABLE_COMMAND: 'HCI_LE_SET_SCAN_ENABLE_COMMAND',
- HCI_LE_CREATE_CONNECTION_COMMAND: 'HCI_LE_CREATE_CONNECTION_COMMAND',
- HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND: 'HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND',
- HCI_LE_READ_WHITE_LIST_SIZE_COMMAND: 'HCI_LE_READ_WHITE_LIST_SIZE_COMMAND',
- HCI_LE_CLEAR_WHITE_LIST_COMMAND: 'HCI_LE_CLEAR_WHITE_LIST_COMMAND',
- HCI_LE_ADD_DEVICE_TO_WHITE_LIST_COMMAND: 'HCI_LE_ADD_DEVICE_TO_WHITE_LIST_COMMAND',
- HCI_LE_REMOVE_DEVICE_FROM_WHITE_LIST_COMMAND: 'HCI_LE_REMOVE_DEVICE_FROM_WHITE_LIST_COMMAND',
- HCI_LE_CONNECTION_UPDATE_COMMAND: 'HCI_LE_CONNECTION_UPDATE_COMMAND',
- HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND: 'HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND',
- HCI_LE_READ_CHANNEL_MAP_COMMAND: 'HCI_LE_READ_CHANNEL_MAP_COMMAND',
- HCI_LE_READ_REMOTE_FEATURES_COMMAND: 'HCI_LE_READ_REMOTE_FEATURES_COMMAND',
- HCI_LE_ENCRYPT_COMMAND: 'HCI_LE_ENCRYPT_COMMAND',
- HCI_LE_RAND_COMMAND: 'HCI_LE_RAND_COMMAND',
- HCI_LE_START_ENCRYPTION_COMMAND: 'HCI_LE_START_ENCRYPTION_COMMAND',
- HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND: 'HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND',
- HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_LE_READ_SUPPORTED_STATES_COMMAND: 'HCI_LE_READ_SUPPORTED_STATES_COMMAND',
- HCI_LE_RECEIVER_TEST_COMMAND: 'HCI_LE_RECEIVER_TEST_COMMAND',
- HCI_LE_TRANSMITTER_TEST_COMMAND: 'HCI_LE_TRANSMITTER_TEST_COMMAND',
- HCI_LE_TEST_END_COMMAND: 'HCI_LE_TEST_END_COMMAND',
- HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND: 'HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND',
- HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND: 'HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND',
- HCI_LE_SET_DATA_LENGTH_COMMAND: 'HCI_LE_SET_DATA_LENGTH_COMMAND',
- HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND: 'HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND',
- HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND: 'HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND',
- HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND: 'HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND',
- HCI_LE_GENERATE_DHKEY_COMMAND: 'HCI_LE_GENERATE_DHKEY_COMMAND',
- HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND: 'HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND',
- HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND: 'HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND',
- HCI_LE_CLEAR_RESOLVING_LIST_COMMAND: 'HCI_LE_CLEAR_RESOLVING_LIST_COMMAND',
- HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND: 'HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND',
- HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND: 'HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND',
- HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND: 'HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND',
- HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND: 'HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND',
- HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND: 'HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND',
- HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND: 'HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND',
- HCI_LE_READ_PHY_COMMAND: 'HCI_LE_READ_PHY_COMMAND',
- HCI_LE_SET_DEFAULT_PHY_COMMAND: 'HCI_LE_SET_DEFAULT_PHY_COMMAND',
- HCI_LE_SET_PHY_COMMAND: 'HCI_LE_SET_PHY_COMMAND',
- HCI_LE_ENHANCED_RECEIVER_TEST_COMMAND: 'HCI_LE_ENHANCED_RECEIVER_TEST_COMMAND',
- HCI_LE_ENHANCED_TRANSMITTER_TEST_COMMAND: 'HCI_LE_ENHANCED_TRANSMITTER_TEST_COMMAND',
- HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND: 'HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND',
- HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND: 'HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND',
- HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND: 'HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND',
- HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND: 'HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND',
- HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND: 'HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND',
- HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND: 'HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND',
- HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERETISING_SETS_COMMAND: 'HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERETISING_SETS_COMMAND',
- HCI_LE_REMOVE_ADVERTISING_SET_COMMAND: 'HCI_LE_REMOVE_ADVERTISING_SET_COMMAND',
- HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND: 'HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND',
- HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND: 'HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND',
- HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND: 'HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND',
- HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND: 'HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND',
- HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND: 'HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND',
- HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND: 'HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND',
- HCI_LE_SET_EXTENDED_CREATE_CONNECTION_COMMAND: 'HCI_LE_SET_EXTENDED_CREATE_CONNECTION_COMMAND',
- HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND: 'HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND',
- HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND: 'HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND',
- HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND: 'HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND',
- HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND: 'HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND',
- HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND: 'HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND',
- HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND: 'HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND',
- HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND: 'HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND',
- HCI_LE_READ_TRANSMIT_POWER_COMMAND: 'HCI_LE_READ_TRANSMIT_POWER_COMMAND',
- HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND: 'HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND',
- HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND: 'HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND',
- HCI_LE_SET_PRIVACY_MODE_COMMAND: 'HCI_LE_SET_PRIVACY_MODE_COMMAND'
+ command_code: command_name for (command_name, command_code) in globals().items()
+ if command_name.startswith('HCI_') and command_name.endswith('_COMMAND')
}
-
# HCI Error Codes
# See Bluetooth spec Vol 2, Part D - 1.3 LIST OF ERROR CODES
-HCI_SUCCESS = 0x00
-HCI_UNKNOWN_HCI_COMMAND_ERROR = 0x01
-HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR = 0x02
-HCI_HARDWARE_FAILURE_ERROR = 0x03
-HCI_PAGE_TIMEOUT_ERROR = 0x04
-HCI_AUTHENTICATION_FAILURE_ERROR = 0x05
-HCI_PIN_OR_KEY_MISSING_ERROR = 0x06
-HCI_MEMORY_CAPACITY_EXCEEDED_ERROR = 0x07
-HCI_CONNECTION_TIMEOUT_ERROR = 0x08
-HCI_CONNECTION_LIMIT_EXCEEDED_ERROR = 0x09
-HCI_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED_ERROR = 0x0A
-HCI_CONNECTION_ALREADY_EXISTS_ERROR = 0x0B
-HCI_COMMAND_DISALLOWED_ERROR = 0x0C
-HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR = 0x0D
-HCI_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS_ERROR = 0x0E
-HCI_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR_ERROR = 0x0F
-HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR = 0x10
-HCI_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE_ERROR = 0x11
-HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR = 0x12
-HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR = 0x13
-HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES_ERROR = 0x14
-HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF_ERROR = 0x15
-HCI_CONNECTION_TERMINATED_BY_LOCAL_HOST_ERROR = 0x16
-HCI_UNACCEPTABLE_CONNECTION_PARAMETERS_ERROR = 0x3B
-HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR = 0x3E
-# TODO: more error codes
+HCI_SUCCESS = 0x00
+HCI_UNKNOWN_HCI_COMMAND_ERROR = 0x01
+HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR = 0x02
+HCI_HARDWARE_FAILURE_ERROR = 0x03
+HCI_PAGE_TIMEOUT_ERROR = 0x04
+HCI_AUTHENTICATION_FAILURE_ERROR = 0x05
+HCI_PIN_OR_KEY_MISSING_ERROR = 0x06
+HCI_MEMORY_CAPACITY_EXCEEDED_ERROR = 0x07
+HCI_CONNECTION_TIMEOUT_ERROR = 0x08
+HCI_CONNECTION_LIMIT_EXCEEDED_ERROR = 0x09
+HCI_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED_ERROR = 0x0A
+HCI_CONNECTION_ALREADY_EXISTS_ERROR = 0x0B
+HCI_COMMAND_DISALLOWED_ERROR = 0x0C
+HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR = 0x0D
+HCI_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS_ERROR = 0x0E
+HCI_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR_ERROR = 0x0F
+HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR = 0x10
+HCI_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE_ERROR = 0x11
+HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR = 0x12
+HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR = 0x13
+HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES_ERROR = 0x14
+HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF_ERROR = 0x15
+HCI_CONNECTION_TERMINATED_BY_LOCAL_HOST_ERROR = 0x16
+HCI_REPEATED_ATTEMPTS_ERROR = 0X17
+HCI_PAIRING_NOT_ALLOWED_ERROR = 0X18
+HCI_UNKNOWN_LMP_PDU_ERROR = 0X19
+HCI_UNSUPPORTED_REMOTE_FEATURE_ERROR = 0X1A
+HCI_SCO_OFFSET_REJECTED_ERROR = 0X1B
+HCI_SCO_INTERVAL_REJECTED_ERROR = 0X1C
+HCI_SCO_AIR_MODE_REJECTED_ERROR = 0X1D
+HCI_INVALID_LMP_OR_LL_PARAMETERS_ERROR = 0X1E
+HCI_UNSPECIFIED_ERROR_ERROR = 0X1F
+HCI_UNSUPPORTED_LMP_OR_LL_PARAMETER_VALUE_ERROR = 0X20
+HCI_ROLE_CHANGE_NOT_ALLOWED_ERROR = 0X21
+HCI_LMP_OR_LL_RESPONSE_TIMEOUT_ERROR = 0X22
+HCI_LMP_ERROR_TRANSACTION_COLLISION_OR_LL_PROCEDURE_COLLISION_ERROR = 0X23
+HCI_LMP_PDU_NOT_ALLOWED_ERROR = 0X24
+HCI_ENCRYPTION_MODE_NOT_ACCEPTABLE_ERROR = 0X25
+HCI_LINK_KEY_CANNOT_BE_CHANGED_ERROR = 0X26
+HCI_REQUESTED_QOS_NOT_SUPPORTED_ERROR = 0X27
+HCI_INSTANT_PASSED_ERROR = 0X28
+HCI_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED_ERROR = 0X29
+HCI_DIFFERENT_TRANSACTION_COLLISION_ERROR = 0X2A
+HCI_RESERVED_FOR_FUTURE_USE = 0X2B
+HCI_QOS_UNACCEPTABLE_PARAMETER_ERROR = 0X2C
+HCI_QOS_REJECTED_ERROR = 0X2D
+HCI_CHANNEL_CLASSIFICATION_NOT_SUPPORTED_ERROR = 0X2E
+HCI_INSUFFICIENT_SECURITY_ERROR = 0X2F
+HCI_PARAMETER_OUT_OF_MANDATORY_RANGE_ERROR = 0X30
+HCI_ROLE_SWITCH_PENDING_ERROR = 0X32
+HCI_RESERVED_SLOT_VIOLATION_ERROR = 0X34
+HCI_ROLE_SWITCH_FAILED_ERROR = 0X35
+HCI_EXTENDED_INQUIRY_RESPONSE_TOO_LARGE_ERROR = 0X36
+HCI_SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST_ERROR = 0X37
+HCI_HOST_BUSY_PAIRING_ERROR = 0X38
+HCI_CONNECTION_REJECTED_DUE_TO_NO_SUITABLE_CHANNEL_FOUND_ERROR = 0X39
+HCI_CONTROLLER_BUSY_ERROR = 0X3A
+HCI_UNACCEPTABLE_CONNECTION_PARAMETERS_ERROR = 0X3B
+HCI_ADVERTISING_TIMEOUT_ERROR = 0X3C
+HCI_CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE_ERROR = 0X3D
+HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR = 0X3E
+HCI_COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING_ERROR = 0X40
+HCI_TYPE0_SUBMAP_NOT_DEFINED_ERROR = 0X41
+HCI_UNKNOWN_ADVERTISING_IDENTIFIER_ERROR = 0X42
+HCI_LIMIT_REACHED_ERROR = 0X43
+HCI_OPERATION_CANCELLED_BY_HOST_ERROR = 0X44
+HCI_PACKET_TOO_LONG_ERROR = 0X45
HCI_ERROR_NAMES = {
- HCI_SUCCESS: 'HCI_SUCCESS',
- HCI_UNKNOWN_HCI_COMMAND_ERROR: 'HCI_UNKNOWN_HCI_COMMAND_ERROR',
- HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR: 'HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR',
- HCI_HARDWARE_FAILURE_ERROR: 'HCI_HARDWARE_FAILURE_ERROR',
- HCI_PAGE_TIMEOUT_ERROR: 'HCI_PAGE_TIMEOUT_ERROR',
- HCI_AUTHENTICATION_FAILURE_ERROR: 'HCI_AUTHENTICATION_FAILURE_ERROR',
- HCI_PIN_OR_KEY_MISSING_ERROR: 'HCI_PIN_OR_KEY_MISSING_ERROR',
- HCI_MEMORY_CAPACITY_EXCEEDED_ERROR: 'HCI_MEMORY_CAPACITY_EXCEEDED_ERROR',
- HCI_CONNECTION_TIMEOUT_ERROR: 'HCI_CONNECTION_TIMEOUT_ERROR',
- HCI_CONNECTION_LIMIT_EXCEEDED_ERROR: 'HCI_CONNECTION_LIMIT_EXCEEDED_ERROR',
- HCI_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED_ERROR: 'HCI_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED_ERROR',
- HCI_CONNECTION_ALREADY_EXISTS_ERROR: 'HCI_CONNECTION_ALREADY_EXISTS_ERROR',
- HCI_COMMAND_DISALLOWED_ERROR: 'HCI_COMMAND_DISALLOWED_ERROR',
- HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR: 'HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR',
- HCI_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS_ERROR: 'HCI_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS_ERROR',
- HCI_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR_ERROR: 'HCI_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR_ERROR',
- HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR: 'HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR',
- HCI_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE_ERROR: 'HCI_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE_ERROR',
- HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR: 'HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR',
- HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR: 'HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR',
- HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES_ERROR: 'HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES_ERROR',
- HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF_ERROR: 'HCI_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF_ERROR',
- HCI_CONNECTION_TERMINATED_BY_LOCAL_HOST_ERROR: 'HCI_CONNECTION_TERMINATED_BY_LOCAL_HOST_ERROR',
- HCI_UNACCEPTABLE_CONNECTION_PARAMETERS_ERROR: 'HCI_UNACCEPTABLE_CONNECTION_PARAMETERS_ERROR',
- HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR: 'HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR'
+ error_code: error_name for (error_name, error_code) in globals().items()
+ if error_name.startswith('HCI_') and error_name.endswith('_ERROR')
}
+HCI_ERROR_NAMES[HCI_SUCCESS] = 'HCI_SUCCESS'
# Command Status codes
HCI_COMMAND_STATUS_PENDING = 0
# LE Event Masks
-LE_CONNECTION_COMPLETE_EVENT_MASK = (1 << 0)
-LE_ADVERTISING_REPORT_EVENT_MASK = (1 << 1)
-LE_CONNECTION_UPDATE_COMPLETE_EVENT_MASK = (1 << 2)
-LE_READ_REMOTE_FEATURES_COMPLETE_EVENT_MASK = (1 << 3)
-LE_LONG_TERM_KEY_REQUEST_EVENT_MASK = (1 << 4)
-LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT_MASK = (1 << 5)
-LE_DATA_LENGTH_CHANGE_EVENT_MASK = (1 << 6)
-LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT_MASK = (1 << 7)
-LE_GENERATE_DHKEY_COMPLETE_EVENT_MASK = (1 << 8)
-LE_ENHANCED_CONNECTION_COMPLETE_EVENT_MASK = (1 << 9)
-LE_DIRECTED_ADVERTISING_REPORT_EVENT_MASK = (1 << 10)
-LE_PHY_UPDATE_COMPLETE_EVENT_MASK = (1 << 11)
-LE_EXTENDED_ADVERTISING_REPORT_EVENT_MASK = (1 << 12)
-LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT_MASK = (1 << 13)
-LE_PERIODIC_ADVERTISING_REPORT_EVENT_MASK = (1 << 14)
-LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT_MASK = (1 << 15)
-LE_EXTENDED_SCAN_TIMEOUT_EVENT_MASK = (1 << 16)
-LE_EXTENDED_ADVERTISING_SET_TERMINATED_EVENT_MASK = (1 << 17)
-LE_SCAN_REQUEST_RECEIVED_EVENT_MASK = (1 << 18)
-LE_CHANNEL_SELECTION_ALGORITHM_EVENT_MASK = (1 << 19)
+HCI_LE_CONNECTION_COMPLETE_EVENT_MASK = (1 << 0)
+HCI_LE_ADVERTISING_REPORT_EVENT_MASK = (1 << 1)
+HCI_LE_CONNECTION_UPDATE_COMPLETE_EVENT_MASK = (1 << 2)
+HCI_LE_READ_REMOTE_FEATURES_COMPLETE_EVENT_MASK = (1 << 3)
+HCI_LE_LONG_TERM_KEY_REQUEST_EVENT_MASK = (1 << 4)
+HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_EVENT_MASK = (1 << 5)
+HCI_LE_DATA_LENGTH_CHANGE_EVENT_MASK = (1 << 6)
+HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT_MASK = (1 << 7)
+HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT_MASK = (1 << 8)
+HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT_MASK = (1 << 9)
+HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT_MASK = (1 << 10)
+HCI_LE_PHY_UPDATE_COMPLETE_EVENT_MASK = (1 << 11)
+HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT_MASK = (1 << 12)
+HCI_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED_EVENT_MASK = (1 << 13)
+HCI_LE_PERIODIC_ADVERTISING_REPORT_EVENT_MASK = (1 << 14)
+HCI_LE_PERIODIC_ADVERTISING_SYNC_LOST_EVENT_MASK = (1 << 15)
+HCI_LE_EXTENDED_SCAN_TIMEOUT_EVENT_MASK = (1 << 16)
+HCI_LE_EXTENDED_ADVERTISING_SET_TERMINATED_EVENT_MASK = (1 << 17)
+HCI_LE_SCAN_REQUEST_RECEIVED_EVENT_MASK = (1 << 18)
+HCI_LE_CHANNEL_SELECTION_ALGORITHM_EVENT_MASK = (1 << 19)
+HCI_LE_CONNECTIONLESS_IQ_REPORT_EVENT_MASK = (1 << 20)
+HCI_LE_CONNECTION_IQ_REPORT_EVENT_MASK = (1 << 21)
+HCI_LE_CTE_REQUEST_FAILED_EVENT_MASK = (1 << 22)
+HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_RECEIVED_EVENT_MASK = (1 << 23)
+HCI_LE_CIS_ESTABLISHED_EVENT_MASK = (1 << 24)
+HCI_LE_CIS_REQUEST_EVENT_MASK = (1 << 25)
+HCI_LE_CREATE_BIG_COMPLETE_EVENT_MASK = (1 << 26)
+HCI_LE_TERMINATE_BIG_COMPLETE_EVENT_MASK = (1 << 27)
+HCI_LE_BIG_SYNC_ESTABLISHED_EVENT_MASK = (1 << 28)
+HCI_LE_BIG_SYNC_LOST_EVENT_MASK = (1 << 29)
+HCI_LE_REQUEST_PEER_SCA_COMPLETE_EVENT_MASK = (1 << 30)
+HCI_LE_PATH_LOSS_THRESHOLD_EVENT_MASK = (1 << 31)
+HCI_LE_TRANSMIT_POWER_REPORTING_EVENT_MASK = (1 << 32)
+HCI_LE_BIGINFO_ADVERTISING_REPORT_EVENT_MASK = (1 << 33)
+HCI_LE_SUBRATE_CHANGE_EVENT_MASK = (1 << 34)
+
+HCI_LE_EVENT_MASK_NAMES = {
+ mask: mask_name for (mask_name, mask) in globals().items()
+ if mask_name.startswith('HCI_LE_') and mask_name.endswith('_EVENT_MASK')
+}
# ACL
HCI_ACL_PB_FIRST_NON_FLUSHABLE = 0
@@ -735,6 +756,575 @@
HCI_PUBLIC_IDENTITY_ADDRESS_TYPE = 0x02
HCI_RANDOM_IDENTITY_ADDRESS_TYPE = 0x03
+# Supported Commands Flags
+# See Bluetooth spec @ 6.27 SUPPORTED COMMANDS
+HCI_SUPPORTED_COMMANDS_FLAGS = (
+ # Octet 0
+ (
+ HCI_INQUIRY_COMMAND,
+ HCI_INQUIRY_CANCEL_COMMAND,
+ HCI_PERIODIC_INQUIRY_MODE_COMMAND,
+ HCI_EXIT_PERIODIC_INQUIRY_MODE_COMMAND,
+ HCI_CREATE_CONNECTION_COMMAND,
+ HCI_DISCONNECT_COMMAND,
+ None,
+ HCI_CREATE_CONNECTION_CANCEL_COMMAND
+ ),
+ # Octet 1
+ (
+ HCI_ACCEPT_CONNECTION_REQUEST_COMMAND,
+ HCI_REJECT_CONNECTION_REQUEST_COMMAND,
+ HCI_LINK_KEY_REQUEST_REPLY_COMMAND,
+ HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_PIN_CODE_REQUEST_REPLY_COMMAND,
+ HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND,
+ HCI_AUTHENTICATION_REQUESTED_COMMAND
+ ),
+ # Octet 2
+ (
+ HCI_SET_CONNECTION_ENCRYPTION_COMMAND,
+ HCI_CHANGE_CONNECTION_LINK_KEY_COMMAND,
+ HCI_LINK_KEY_SELECTION_COMMAND,
+ HCI_REMOTE_NAME_REQUEST_COMMAND,
+ HCI_REMOTE_NAME_REQUEST_CANCEL_COMMAND,
+ HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND,
+ HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND,
+ HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND
+ ),
+ # Octet 3
+ (
+ HCI_READ_CLOCK_OFFSET_COMMAND,
+ HCI_READ_LMP_HANDLE_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None
+ ),
+ # Octet 4
+ (
+ None,
+ HCI_HOLD_MODE_COMMAND,
+ HCI_SNIFF_MODE_COMMAND,
+ HCI_EXIT_SNIFF_MODE_COMMAND,
+ None,
+ None,
+ HCI_QOS_SETUP_COMMAND,
+ HCI_ROLE_DISCOVERY_COMMAND
+ ),
+ # Octet 5
+ (
+ HCI_SWITCH_ROLE_COMMAND,
+ HCI_READ_LINK_POLICY_SETTINGS_COMMAND,
+ HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND,
+ HCI_READ_DEFAULT_LINK_POLICY_SETTINGS_COMMAND,
+ HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND,
+ HCI_FLOW_SPECIFICATION_COMMAND,
+ HCI_SET_EVENT_MASK_COMMAND,
+ HCI_RESET_COMMAND
+ ),
+ # Octet 6
+ (
+ HCI_SET_EVENT_FILTER_COMMAND,
+ HCI_FLUSH_COMMAND,
+ HCI_READ_PIN_TYPE_COMMAND,
+ HCI_WRITE_PIN_TYPE_COMMAND,
+ None,
+ HCI_READ_STORED_LINK_KEY_COMMAND,
+ HCI_WRITE_STORED_LINK_KEY_COMMAND,
+ HCI_DELETE_STORED_LINK_KEY_COMMAND
+ ),
+ # Octet 7
+ (
+ HCI_WRITE_LOCAL_NAME_COMMAND,
+ HCI_READ_LOCAL_NAME_COMMAND,
+ HCI_READ_CONNECTION_ACCEPT_TIMEOUT_COMMAND,
+ HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND,
+ HCI_READ_PAGE_TIMEOUT_COMMAND,
+ HCI_WRITE_PAGE_TIMEOUT_COMMAND,
+ HCI_READ_SCAN_ENABLE_COMMAND,
+ HCI_WRITE_SCAN_ENABLE_COMMAND
+ ),
+ # Octet 8
+ (
+ HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND,
+ HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND,
+ HCI_READ_INQUIRY_SCAN_ACTIVITY_COMMAND,
+ HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND,
+ HCI_READ_AUTHENTICATION_ENABLE_COMMAND,
+ HCI_WRITE_AUTHENTICATION_ENABLE_COMMAND,
+ None,
+ None
+ ),
+ # Octet 9
+ (
+ HCI_READ_CLASS_OF_DEVICE_COMMAND,
+ HCI_WRITE_CLASS_OF_DEVICE_COMMAND,
+ HCI_READ_VOICE_SETTING_COMMAND,
+ HCI_WRITE_VOICE_SETTING_COMMAND,
+ HCI_READ_AUTOMATIC_FLUSH_TIMEOUT_COMMAND,
+ HCI_WRITE_AUTOMATIC_FLUSH_TIMEOUT_COMMAND,
+ HCI_READ_NUM_BROADCAST_RETRANSMISSIONS_COMMAND,
+ HCI_WRITE_NUM_BROADCAST_RETRANSMISSIONS_COMMAND
+ ),
+ # Octet 10
+ (
+ HCI_READ_HOLD_MODE_ACTIVITY_COMMAND,
+ HCI_WRITE_HOLD_MODE_ACTIVITY_COMMAND,
+ HCI_READ_TRANSMIT_POWER_LEVEL_COMMAND,
+ HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND,
+ HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND,
+ HCI_SET_CONTROLLER_TO_HOST_FLOW_CONTROL_COMMAND,
+ HCI_HOST_BUFFER_SIZE_COMMAND,
+ HCI_HOST_NUMBER_OF_COMPLETED_PACKETS_COMMAND
+ ),
+ # Octet 11
+ (
+ HCI_READ_LINK_SUPERVISION_TIMEOUT_COMMAND,
+ HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND,
+ HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND,
+ HCI_READ_CURRENT_IAC_LAP_COMMAND,
+ HCI_WRITE_CURRENT_IAC_LAP_COMMAND,
+ None,
+ None,
+ None
+ ),
+ # Octet 12
+ (
+ None,
+ HCI_SET_AFH_HOST_CHANNEL_CLASSIFICATION_COMMAND,
+ None,
+ None,
+ HCI_READ_INQUIRY_SCAN_TYPE_COMMAND,
+ HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND,
+ HCI_READ_INQUIRY_MODE_COMMAND,
+ HCI_WRITE_INQUIRY_MODE_COMMAND
+ ),
+ # Octet 13
+ (
+ HCI_READ_PAGE_SCAN_TYPE_COMMAND,
+ HCI_WRITE_PAGE_SCAN_TYPE_COMMAND,
+ HCI_READ_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND,
+ HCI_WRITE_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ ),
+ # Octet 14
+ (
+ None,
+ None,
+ None,
+ HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND,
+ None,
+ HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
+ HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND,
+ HCI_READ_BUFFER_SIZE_COMMAND
+ ),
+ # Octet 15
+ (
+ None,
+ HCI_READ_BD_ADDR_COMMAND,
+ HCI_READ_FAILED_CONTACT_COUNTER_COMMAND,
+ HCI_RESET_FAILED_CONTACT_COUNTER_COMMAND,
+ HCI_READ_LINK_QUALITY_COMMAND,
+ HCI_READ_RSSI_COMMAND,
+ HCI_READ_AFH_CHANNEL_MAP_COMMAND,
+ HCI_READ_CLOCK_COMMAND
+ ),
+ # Octet 16
+ (
+ HCI_READ_LOOPBACK_MODE_COMMAND,
+ HCI_WRITE_LOOPBACK_MODE_COMMAND,
+ HCI_ENABLE_DEVICE_UNDER_TEST_MODE_COMMAND,
+ HCI_SETUP_SYNCHRONOUS_CONNECTION_COMMAND,
+ HCI_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
+ HCI_REJECT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
+ None,
+ None,
+ ),
+ # Octet 17
+ (
+ HCI_READ_EXTENDED_INQUIRY_RESPONSE_COMMAND,
+ HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND,
+ HCI_REFRESH_ENCRYPTION_KEY_COMMAND,
+ None,
+ HCI_SNIFF_SUBRATING_COMMAND,
+ HCI_READ_SIMPLE_PAIRING_MODE_COMMAND,
+ HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND,
+ HCI_READ_LOCAL_OOB_DATA_COMMAND
+ ),
+ # Octet 18
+ (
+ HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND,
+ HCI_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_COMMAND,
+ HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND,
+ HCI_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND,
+ None,
+ None,
+ None,
+ HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND
+ ),
+ # Octet 19
+ (
+ HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND,
+ HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND,
+ HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_REMOTE_OOB_DATA_REQUEST_REPLY_COMMAND,
+ HCI_WRITE_SIMPLE_PAIRING_DEBUG_MODE_COMMAND,
+ HCI_ENHANCED_FLUSH_COMMAND,
+ HCI_REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY_COMMAND
+ ),
+ # Octet 20
+ (
+ None,
+ None,
+ HCI_SEND_KEYPRESS_NOTIFICATION_COMMAND,
+ HCI_IO_CAPABILITY_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND,
+ None,
+ None,
+ None,
+ ),
+ # Octet 21
+ (
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ),
+ # Octet 22
+ (
+ None,
+ None,
+ HCI_SET_EVENT_MASK_PAGE_2_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ),
+ # Octet 23
+ (
+ HCI_READ_FLOW_CONTROL_MODE_COMMAND,
+ HCI_WRITE_FLOW_CONTROL_MODE_COMMAND,
+ HCI_READ_DATA_BLOCK_SIZE_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ),
+ # Octet 24
+ (
+ HCI_READ_ENHANCED_TRANSMIT_POWER_LEVEL_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ HCI_READ_LE_HOST_SUPPORT_COMMAND,
+ HCI_WRITE_LE_HOST_SUPPORT_COMMAND,
+ None,
+ ),
+ # Octet 25
+ (
+ HCI_LE_SET_EVENT_MASK_COMMAND,
+ HCI_LE_READ_BUFFER_SIZE_COMMAND,
+ HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
+ None,
+ HCI_LE_SET_RANDOM_ADDRESS_COMMAND,
+ HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND,
+ HCI_LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER_COMMAND,
+ HCI_LE_SET_ADVERTISING_DATA_COMMAND,
+ ),
+ # Octet 26
+ (
+ HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND,
+ HCI_LE_SET_ADVERTISING_ENABLE_COMMAND,
+ HCI_LE_SET_SCAN_PARAMETERS_COMMAND,
+ HCI_LE_SET_SCAN_ENABLE_COMMAND,
+ HCI_LE_CREATE_CONNECTION_COMMAND,
+ HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND,
+ HCI_LE_READ_FILTER_ACCEPT_LIST_SIZE_COMMAND,
+ HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND
+ ),
+ # Octet 27
+ (
+ HCI_LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST_COMMAND,
+ HCI_LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST_COMMAND,
+ HCI_LE_CONNECTION_UPDATE_COMMAND,
+ HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND,
+ HCI_LE_READ_CHANNEL_MAP_COMMAND,
+ HCI_LE_READ_REMOTE_FEATURES_COMMAND,
+ HCI_LE_ENCRYPT_COMMAND,
+ HCI_LE_RAND_COMMAND
+ ),
+ # Octet 28
+ (
+ HCI_LE_ENABLE_ENCRYPTION_COMMAND,
+ HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND,
+ HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_LE_READ_SUPPORTED_STATES_COMMAND,
+ HCI_LE_RECEIVER_TEST_COMMAND,
+ HCI_LE_TRANSMITTER_TEST_COMMAND,
+ HCI_LE_TEST_END_COMMAND,
+ None,
+ ),
+ # Octet 29
+ (
+ None,
+ None,
+ None,
+ HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND,
+ HCI_ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
+ HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND,
+ HCI_SET_MWS_CHANNEL_PARAMETERS_COMMAND,
+ HCI_SET_EXTERNAL_FRAME_CONFIGURATION_COMMAND
+ ),
+ # Octet 30
+ (
+ HCI_SET_MWS_SIGNALING_COMMAND,
+ HCI_SET_MWS_TRANSPORT_LAYER_COMMAND,
+ HCI_SET_MWS_SCAN_FREQUENCY_TABLE_COMMAND,
+ HCI_GET_MWS_TRANSPORT_LAYER_CONFIGURATION_COMMAND,
+ HCI_SET_MWS_PATTERN_CONFIGURATION_COMMAND,
+ HCI_SET_TRIGGERED_CLOCK_CAPTURE_COMMAND,
+ HCI_TRUNCATED_PAGE_COMMAND,
+ HCI_TRUNCATED_PAGE_CANCEL_COMMAND
+ ),
+ # Octet 31
+ (
+ HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_COMMAND,
+ HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE_COMMAND,
+ HCI_START_SYNCHRONIZATION_TRAIN_COMMAND,
+ HCI_RECEIVE_SYNCHRONIZATION_TRAIN_COMMAND,
+ HCI_SET_RESERVED_LT_ADDR_COMMAND,
+ HCI_DELETE_RESERVED_LT_ADDR_COMMAND,
+ HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA_COMMAND,
+ HCI_READ_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND
+ ),
+ # Octet 32
+ (
+ HCI_WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND,
+ HCI_REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY_COMMAND,
+ HCI_READ_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND,
+ HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND,
+ HCI_READ_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
+ HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
+ HCI_READ_LOCAL_OOB_EXTENDED_DATA_COMMAND,
+ HCI_WRITE_SECURE_CONNECTIONS_TEST_MODE_COMMAND
+ ),
+ # Octet 33
+ (
+ HCI_READ_EXTENDED_PAGE_TIMEOUT_COMMAND,
+ HCI_WRITE_EXTENDED_PAGE_TIMEOUT_COMMAND,
+ HCI_READ_EXTENDED_INQUIRY_LENGTH_COMMAND,
+ HCI_WRITE_EXTENDED_INQUIRY_LENGTH_COMMAND,
+ HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND,
+ HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND,
+ HCI_LE_SET_DATA_LENGTH_COMMAND,
+ HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND
+ ),
+ # Octet 34
+ (
+ HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
+ HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND,
+ HCI_LE_GENERATE_DHKEY_COMMAND,
+ HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND,
+ HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND,
+ HCI_LE_CLEAR_RESOLVING_LIST_COMMAND,
+ HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND,
+ HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND
+ ),
+ # Octet 35
+ (
+ HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND,
+ HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND,
+ HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND,
+ HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
+ HCI_LE_READ_PHY_COMMAND,
+ HCI_LE_SET_DEFAULT_PHY_COMMAND,
+ HCI_LE_SET_PHY_COMMAND,
+ HCI_LE_RECEIVER_TEST_V2_COMMAND
+ ),
+ # Octet 36
+ (
+ HCI_LE_TRANSMITTER_TEST_V2_COMMAND,
+ HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND,
+ HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND,
+ HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND,
+ HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND,
+ HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND,
+ HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
+ HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND,
+ ),
+ # Octet 37
+ (
+ HCI_LE_REMOVE_ADVERTISING_SET_COMMAND,
+ HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND,
+ HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND,
+ HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND,
+ HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND,
+ HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND,
+ HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND,
+ HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND
+ ),
+ # Octet 38
+ (
+ HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND,
+ HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND,
+ HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND,
+ HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND,
+ HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND,
+ HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND,
+ HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND,
+ HCI_LE_READ_TRANSMIT_POWER_COMMAND
+ ),
+ # Octet 39
+ (
+ HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND,
+ HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND,
+ HCI_LE_SET_PRIVACY_MODE_COMMAND,
+ HCI_LE_RECEIVER_TEST_V3_COMMAND,
+ HCI_LE_TRANSMITTER_TEST_V3_COMMAND,
+ HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS_COMMAND,
+ HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE_COMMAND,
+ HCI_LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE_COMMAND,
+ ),
+ # Octet 40
+ (
+ HCI_LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS_COMMAND,
+ HCI_LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS_COMMAND,
+ HCI_LE_CONNECTION_CTE_REQUEST_ENABLE_COMMAND,
+ HCI_LE_CONNECTION_CTE_RESPONSE_ENABLE_COMMAND,
+ HCI_LE_READ_ANTENNA_INFORMATION_COMMAND,
+ HCI_LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE_COMMAND,
+ HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_COMMAND,
+ HCI_LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER_COMMAND
+ ),
+ # Octet 41
+ (
+ HCI_LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND,
+ HCI_LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND,
+ HCI_LE_GENERATE_DHKEY_V2_COMMAND,
+ HCI_READ_LOCAL_SIMPLE_PAIRING_OPTIONS_COMMAND,
+ HCI_LE_MODIFY_SLEEP_CLOCK_ACCURACY_COMMAND,
+ HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
+ HCI_LE_READ_ISO_TX_SYNC_COMMAND,
+ HCI_LE_SET_CIG_PARAMETERS_COMMAND
+ ),
+ # Octet 42
+ (
+ HCI_LE_SET_CIG_PARAMETERS_TEST_COMMAND,
+ HCI_LE_CREATE_CIS_COMMAND,
+ HCI_LE_REMOVE_CIG_COMMAND,
+ HCI_LE_ACCEPT_CIS_REQUEST_COMMAND,
+ HCI_LE_REJECT_CIS_REQUEST_COMMAND,
+ HCI_LE_CREATE_BIG_COMMAND,
+ HCI_LE_CREATE_BIG_TEST_COMMAND,
+ HCI_LE_TERMINATE_BIG_COMMAND,
+ ),
+ # Octet 43
+ (
+ HCI_LE_BIG_CREATE_SYNC_COMMAND,
+ HCI_LE_BIG_TERMINATE_SYNC_COMMAND,
+ HCI_LE_REQUEST_PEER_SCA_COMMAND,
+ HCI_LE_SETUP_ISO_DATA_PATH_COMMAND,
+ HCI_LE_REMOVE_ISO_DATA_PATH_COMMAND,
+ HCI_LE_ISO_TRANSMIT_TEST_COMMAND,
+ HCI_LE_ISO_RECEIVE_TEST_COMMAND,
+ HCI_LE_ISO_READ_TEST_COUNTERS_COMMAND
+ ),
+ # Octet 44
+ (
+ HCI_LE_ISO_TEST_END_COMMAND,
+ HCI_LE_SET_HOST_FEATURE_COMMAND,
+ HCI_LE_READ_ISO_LINK_QUALITY_COMMAND,
+ HCI_LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL_COMMAND,
+ HCI_LE_READ_REMOTE_TRANSMIT_POWER_LEVEL_COMMAND,
+ HCI_LE_SET_PATH_LOSS_REPORTING_PARAMETERS_COMMAND,
+ HCI_LE_SET_PATH_LOSS_REPORTING_ENABLE_COMMAND,
+ HCI_LE_SET_TRANSMIT_POWER_REPORTING_ENABLE_COMMAND
+ ),
+ # Octet 45
+ (
+ HCI_LE_TRANSMITTER_TEST_V4_COMMAND,
+ HCI_SET_ECOSYSTEM_BASE_INTERVAL_COMMAND,
+ HCI_READ_LOCAL_SUPPORTED_CODECS_V2_COMMAND,
+ HCI_READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES_COMMAND,
+ HCI_READ_LOCAL_SUPPORTED_CONTROLLER_DELAY_COMMAND,
+ HCI_CONFIGURE_DATA_PATH_COMMAND,
+ HCI_LE_SET_DATA_RELATED_ADDRESS_CHANGES_COMMAND,
+ HCI_SET_MIN_ENCRYPTION_KEY_SIZE_COMMAND
+ ),
+ # Octet 46
+ (
+ HCI_LE_SET_DEFAULT_SUBRATE_COMMAND,
+ HCI_LE_SUBRATE_REQUEST_COMMAND,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None
+ )
+)
+
+# LE Supported Features
+HCI_LE_ENCRYPTION_LE_SUPPORTED_FEATURE = 0
+HCI_CONNECTION_PARAMETERS_REQUEST_PROCEDURE_LE_SUPPORTED_FEATURE = 1
+HCI_EXTENDED_REJECT_INDICATION_LE_SUPPORTED_FEATURE = 2
+HCI_PERIPHERAL_INITIATED_FEATURE_EXCHANGE_LE_SUPPORTED_FEATURE = 3
+HCI_LE_PING_LE_SUPPORTED_FEATURE = 4
+HCI_LE_DATA_PACKET_LENGTH_EXTENSION_LE_SUPPORTED_FEATURE = 5
+HCI_LL_PRIVACY_LE_SUPPORTED_FEATURE = 6
+HCI_EXTENDED_SCANNER_FILTER_POLICIES_LE_SUPPORTED_FEATURE = 7
+HCI_LE_2M_PHY_LE_SUPPORTED_FEATURE = 8
+HCI_STABLE_MODULATION_INDEX_TRANSMITTER_LE_SUPPORTED_FEATURE = 9
+HCI_STABLE_MODULATION_INDEX_RECEIVER_LE_SUPPORTED_FEATURE = 10
+HCI_LE_CODED_PHY_LE_SUPPORTED_FEATURE = 11
+HCI_LE_EXTENDED_ADVERTISING_LE_SUPPORTED_FEATURE = 12
+HCI_LE_PERIODIC_ADVERTISING_LE_SUPPORTED_FEATURE = 13
+HCI_CHANNEL_SELECTION_ALGORITHM_2_LE_SUPPORTED_FEATURE = 14
+HCI_LE_POWER_CLASS_1_LE_SUPPORTED_FEATURE = 15
+HCI_MINIMUM_NUMBER_OF_USED_CHANNELS_PROCEDURE_LE_SUPPORTED_FEATURE = 16
+HCI_CONNECTION_CTE_REQUEST_LE_SUPPORTED_FEATURE = 17
+HCI_CONNECTION_CTE_RESPONSE_LE_SUPPORTED_FEATURE = 18
+HCI_CONNECTIONLESS_CTE_TRANSMITTER_LE_SUPPORTED_FEATURE = 19
+HCI_CONNECTIONLESS_CTR_RECEIVER_LE_SUPPORTED_FEATURE = 20
+HCI_ANTENNA_SWITCHING_DURING_CTE_TRANSMISSION_LE_SUPPORTED_FEATURE = 21
+HCI_ANTENNA_SWITCHING_DURING_CTE_RECEPTION_LE_SUPPORTED_FEATURE = 22
+HCI_RECEIVING_CONSTANT_TONE_EXTENSIONS_LE_SUPPORTED_FEATURE = 23
+HCI_PERIODIC_ADVERTISING_SYNC_TRANSFER_SENDER_LE_SUPPORTED_FEATURE = 24
+HCI_PERIODIC_ADVERTISING_SYNC_TRANSFER_RECIPIENT_LE_SUPPORTED_FEATURE = 25
+HCI_SLEEP_CLOCK_ACCURACY_UPDATES_LE_SUPPORTED_FEATURE = 26
+HCI_REMOTE_PUBLIC_KEY_VALIDATION_LE_SUPPORTED_FEATURE = 27
+HCI_CONNECTED_ISOCHRONOUS_STREAM_CENTRAL_LE_SUPPORTED_FEATURE = 28
+HCI_CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL_LE_SUPPORTED_FEATURE = 29
+HCI_ISOCHRONOUS_BROADCASTER_LE_SUPPORTED_FEATURE = 30
+HCI_SYNCHRONIZED_RECEIVER_LE_SUPPORTED_FEATURE = 31
+HCI_CONNECTED_ISOCHRONOUS_STREAM_LE_SUPPORTED_FEATURE = 32
+HCI_LE_POWER_CONTROL_REQUEST_LE_SUPPORTED_FEATURE = 33
+HCI_LE_POWER_CONTROL_REQUEST_DUP_LE_SUPPORTED_FEATURE = 34
+HCI_LE_PATH_LOSS_MONITORING_LE_SUPPORTED_FEATURE = 35
+HCI_PERIODIC_ADVERTISING_ADI_SUPPORT_LE_SUPPORTED_FEATURE = 36
+HCI_CONNECTION_SUBRATING_LE_SUPPORTED_FEATURE = 37
+HCI_CONNECTION_SUBRATING_HOST_SUPPORT_LE_SUPPORTED_FEATURE = 38
+HCI_CHANNEL_CLASSIFICATION_LE_SUPPORTED_FEATURE = 39
+
+HCI_LE_SUPPORTED_FEATURES_NAMES = {
+ flag: feature_name for (feature_name, flag) in globals().items()
+ if feature_name.startswith('HCI_') and feature_name.endswith('_LE_SUPPORTED_FEATURE')
+}
+
# -----------------------------------------------------------------------------
STATUS_SPEC = {'size': 1, 'mapper': lambda x: HCI_Constant.status_name(x)}
@@ -783,6 +1373,13 @@
super().__init__(error_code, 'hci', HCI_Constant.error_name(error_code))
+class HCI_StatusError(ProtocolError):
+ def __init__(self, response):
+ super().__init__(response.status,
+ error_namespace=HCI_Command.command_name(response.command_opcode),
+ error_name=HCI_Constant.status_name(response.status))
+
+
# -----------------------------------------------------------------------------
# Generic HCI object
# -----------------------------------------------------------------------------
@@ -1162,14 +1759,15 @@
cls.name = cls.__name__.upper()
cls.op_code = key_with_value(HCI_COMMAND_NAMES, cls.name)
if cls.op_code is None:
- raise KeyError('command not found in HCI_COMMAND_NAMES')
+ raise KeyError(f'command {cls.name} not found in HCI_COMMAND_NAMES')
cls.fields = fields
cls.return_parameters_fields = return_parameters_fields
# Patch the __init__ method to fix the op_code
- def init(self, parameters=None, **kwargs):
- return HCI_Command.__init__(self, cls.op_code, parameters, **kwargs)
- cls.__init__ = init
+ if fields is not None:
+ def init(self, parameters=None, **kwargs):
+ return HCI_Command.__init__(self, cls.op_code, parameters, **kwargs)
+ cls.__init__ = init
# Register a factory for this class
HCI_Command.command_classes[cls.op_code] = cls
@@ -1192,11 +1790,13 @@
return HCI_Command(op_code, parameters)
# Create a new instance
- self = cls.__new__(cls)
- HCI_Command.__init__(self, op_code, parameters)
- if fields := getattr(self, 'fields', None):
+ if (fields := getattr(cls, 'fields', None)) is not None:
+ self = cls.__new__(cls)
+ HCI_Command.__init__(self, op_code, parameters)
HCI_Object.init_from_bytes(self, parameters, 0, fields)
- return self
+ return self
+ else:
+ return cls.from_parameters(parameters)
@staticmethod
def command_name(op_code):
@@ -2007,12 +2607,12 @@
# -----------------------------------------------------------------------------
@HCI_Command.command(return_parameters_fields=[
- ('status', STATUS_SPEC),
- ('hci_version', 1),
- ('hci_revsion', 2),
- ('lmp_pal_version', 1),
- ('manufacturer_name', 2),
- ('lmp_pal_subversion', 2)
+ ('status', STATUS_SPEC),
+ ('hci_version', 1),
+ ('hci_subversion', 2),
+ ('lmp_version', 1),
+ ('company_identifier', 2),
+ ('lmp_subversion', 2)
])
class HCI_Read_Local_Version_Information_Command(HCI_Command):
'''
@@ -2130,6 +2730,17 @@
# -----------------------------------------------------------------------------
+@HCI_Command.command(return_parameters_fields=[
+ ('status', STATUS_SPEC),
+ ('le_features', 8)
+])
+class HCI_LE_Read_Local_Supported_Features_Command(HCI_Command):
+ '''
+ See Bluetooth spec @ 7.8.3 LE Read Local Supported Features Command
+ '''
+
+
+# -----------------------------------------------------------------------------
@HCI_Command.command([
('random_address', lambda data, offset: Address.parse_address_with_type(data, offset, Address.RANDOM_DEVICE_ADDRESS))
])
@@ -2176,9 +2787,9 @@
# -----------------------------------------------------------------------------
@HCI_Command.command()
-class HCI_LE_Read_Advertising_Channel_Tx_Power_Command(HCI_Command):
+class HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.6 LE Read Advertising Channel Tx Power Command
+ See Bluetooth spec @ 7.8.6 LE Read Advertising Physical Channel Tx Power Command
'''
@@ -2281,17 +2892,17 @@
# -----------------------------------------------------------------------------
@HCI_Command.command()
-class HCI_LE_Read_White_List_Size_Command(HCI_Command):
+class HCI_LE_Read_Filter_Accept_List_Size_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.14 LE Read White List Size Command
+ See Bluetooth spec @ 7.8.14 LE Read Filter Accept List Size Command
'''
# -----------------------------------------------------------------------------
@HCI_Command.command()
-class HCI_LE_Clear_White_List_Command(HCI_Command):
+class HCI_LE_Clear_Filter_Accept_List_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.15 LE Clear White List Command
+ See Bluetooth spec @ 7.8.15 LE Clear Filter Accept List Command
'''
@@ -2300,9 +2911,9 @@
('address_type', Address.ADDRESS_TYPE_SPEC),
('address', Address.parse_address_preceded_by_type)
])
-class HCI_LE_Add_Device_To_White_List_Command(HCI_Command):
+class HCI_LE_Add_Device_To_Filter_Accept_List_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.16 LE Add Device To White List Command
+ See Bluetooth spec @ 7.8.16 LE Add Device To Filter Accept List Command
'''
@@ -2311,9 +2922,9 @@
('address_type', Address.ADDRESS_TYPE_SPEC),
('address', Address.parse_address_preceded_by_type)
])
-class HCI_LE_Remove_Device_From_White_List_Command(HCI_Command):
+class HCI_LE_Remove_Device_From_Filter_Accept_List_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.17 LE Remove Device From White List Command
+ See Bluetooth spec @ 7.8.17 LE Remove Device From Filter Accept List Command
'''
@@ -2350,10 +2961,10 @@
('encrypted_diversifier', 2),
('long_term_key', 16)
])
-class HCI_LE_Start_Encryption_Command(HCI_Command):
+class HCI_LE_Enable_Encryption_Command(HCI_Command):
'''
- See Bluetooth spec @ 7.8.24 LE Start Encryption Command
- (renamed to "LE Enable Encryption Command" in version 5.2 of the specification)
+ See Bluetooth spec @ 7.8.24 LE Enable Encryption Command
+ (renamed from "LE Start Encryption Command" in version prior to 5.2 of the specification)
'''
@@ -2478,6 +3089,96 @@
# -----------------------------------------------------------------------------
+@HCI_Command.command(fields=None)
+class HCI_LE_Set_Extended_Scan_Parameters_Command(HCI_Command):
+ '''
+ See Bluetooth spec @ 7.8.64 LE Set Extended Scan Parameters Command
+ '''
+ PASSIVE_SCANNING = 0
+ ACTIVE_SCANNING = 1
+
+ BASIC_UNFILTERED_POLICY = 0x00
+ BASIC_FILTERED_POLICY = 0x01
+ EXTENDED_UNFILTERED_POLICY = 0x02
+ EXTENDED_FILTERED_POLICY = 0x03
+
+ LE_1M_PHY = 0x00
+ LE_CODED_PHY = 0x02
+
+ @staticmethod
+ def from_parameters(parameters):
+ own_address_type = parameters[0]
+ scanning_filter_policy = parameters[1]
+ scanning_phys = parameters[2]
+
+ phy_bits_set = bin(scanning_phys).count('1')
+ scan_types = []
+ scan_intervals = []
+ scan_windows = []
+ for i in range(phy_bits_set):
+ scan_types.append(parameters[3 + (3 * i)])
+ scan_intervals.append(parameters[3 + (3 * i) + 1])
+ scan_windows.append(parameters[3 + (3 * i) + 2])
+
+ return HCI_LE_Set_Extended_Scan_Parameters_Command(
+ own_address_type = own_address_type,
+ scanning_filter_policy = scanning_filter_policy,
+ scanning_phys = scanning_phys,
+ scan_types = scan_types,
+ scan_intervals = scan_intervals,
+ scan_windows = scan_windows
+ )
+
+ def __init__(
+ self,
+ own_address_type,
+ scanning_filter_policy,
+ scanning_phys,
+ scan_types,
+ scan_intervals,
+ scan_windows
+ ):
+ super().__init__(HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND)
+ self.own_address_type = own_address_type
+ self.scanning_filter_policy = scanning_filter_policy
+ self.scanning_phys = scanning_phys
+ self.scan_types = scan_types
+ self.scan_intervals = scan_intervals
+ self.scan_windows = scan_windows
+
+ self.parameters = bytes([own_address_type, scanning_filter_policy, scanning_phys])
+ phy_bits_set = bin(scanning_phys).count('1')
+ for i in range(phy_bits_set):
+ self.parameters += bytes([scan_types[i], scan_intervals[i], scan_windows[i]])
+
+ def __str__(self):
+ scanning_phys_strs = []
+
+ for bit in range(8):
+ if self.scanning_phys & (1 << bit) != 0:
+ if bit == 0:
+ scanning_phys_strs.append('LE_1M_PHY')
+ elif bit == 2:
+ scanning_phys_strs.append('LE_CODED_PHY')
+ else:
+ scanning_phys_strs.append(f'0x{(1 << bit):02X}')
+
+ fields = [
+ ('own_address_type: ', Address.address_type_name(self.own_address_type)),
+ ('scanning_filter_policy:', self.scanning_filter_policy),
+ ('scanning_phys: ', ','.join(scanning_phys_strs)),
+ ]
+ for (i, scanning_phy_str) in enumerate(scanning_phys_strs):
+ fields.append((f'{scanning_phy_str}.scan_type: ', 'PASSIVE' if self.scan_types[i] == self.PASSIVE_SCANNING else 'ACTIVE'))
+ fields.append((f'{scanning_phy_str}.scan_interval:', self.scan_intervals[i])),
+ fields.append((f'{scanning_phy_str}.scan_window: ', self.scan_windows[i]))
+
+ return color(self.name, 'green') + ':\n' + '\n'.join(
+ [color(field[0], 'cyan') + ' ' + str(field[1]) for field in fields]
+ )
+
+
+# -----------------------------------------------------------------------------
# HCI Events
# -----------------------------------------------------------------------------
class HCI_Event(HCI_Packet):
@@ -2575,6 +3276,9 @@
parameters = b'' if self.parameters is None else self.parameters
return bytes([HCI_EVENT_PACKET, self.event_code, len(parameters)]) + parameters
+ def __bytes__(self):
+ return self.to_bytes()
+
def __str__(self):
result = color(self.name, 'magenta')
if fields := getattr(self, 'fields', None):
diff --git a/bumble/helpers.py b/bumble/helpers.py
index 09b86ef..e9ebf97 100644
--- a/bumble/helpers.py
+++ b/bumble/helpers.py
@@ -18,6 +18,8 @@
import logging
from colors import color
+from bumble.smp import SMP_CID, SMP_Command
+
from .core import name_or_number
from .gatt import ATT_PDU, ATT_CID
from .l2cap import (
@@ -73,6 +75,9 @@
if l2cap_pdu.cid == ATT_CID:
att_pdu = ATT_PDU.from_bytes(l2cap_pdu.payload)
self.analyzer.emit(att_pdu)
+ elif l2cap_pdu.cid == SMP_CID:
+ smp_command = SMP_Command.from_bytes(l2cap_pdu.payload)
+ self.analyzer.emit(smp_command)
elif l2cap_pdu.cid == L2CAP_SIGNALING_CID or l2cap_pdu.cid == L2CAP_LE_SIGNALING_CID:
control_frame = L2CAP_Control_Frame.from_bytes(l2cap_pdu.payload)
self.analyzer.emit(control_frame)
diff --git a/bumble/host.py b/bumble/host.py
index 4057402..cc692d0 100644
--- a/bumble/host.py
+++ b/bumble/host.py
@@ -44,25 +44,20 @@
# -----------------------------------------------------------------------------
class Connection:
- def __init__(self, host, handle, role, peer_address):
+ def __init__(self, host, handle, role, peer_address, transport):
self.host = host
self.handle = handle
self.role = role
self.peer_address = peer_address
self.assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
+ self.transport = transport
def on_hci_acl_data_packet(self, packet):
self.assembler.feed_packet(packet)
def on_acl_pdu(self, pdu):
l2cap_pdu = L2CAP_PDU.from_bytes(pdu)
-
- if l2cap_pdu.cid == ATT_CID:
- self.host.on_gatt_pdu(self, l2cap_pdu.payload)
- elif l2cap_pdu.cid == SMP_CID:
- self.host.on_smp_pdu(self, l2cap_pdu.payload)
- else:
- self.host.on_l2cap_pdu(self, l2cap_pdu.cid, l2cap_pdu.payload)
+ self.host.on_l2cap_pdu(self, l2cap_pdu.cid, l2cap_pdu.payload)
# -----------------------------------------------------------------------------
@@ -81,7 +76,9 @@
self.hc_total_num_acl_data_packets = HOST_HC_TOTAL_NUM_ACL_DATA_PACKETS
self.acl_packet_queue = collections.deque()
self.acl_packets_in_flight = 0
+ self.local_version = HCI_VERSION_BLUETOOTH_CORE_4_0
self.local_supported_commands = bytes(64)
+ self.local_le_features = 0
self.command_semaphore = asyncio.Semaphore(1)
self.long_term_key_provider = None
self.link_key_provider = None
@@ -98,34 +95,64 @@
self.ready = True
response = await self.send_command(HCI_Read_Local_Supported_Commands_Command())
- if response.return_parameters.status != HCI_SUCCESS:
- raise ProtocolError(response.return_parameters.status, 'hci')
- self.local_supported_commands = response.return_parameters.supported_commands
-
- await self.send_command(HCI_Set_Event_Mask_Command(event_mask = bytes.fromhex('FFFFFFFFFFFFFFFF')))
- await self.send_command(HCI_LE_Set_Event_Mask_Command(le_event_mask = bytes.fromhex('FFFFF00000000000')))
- await self.send_command(HCI_Read_Local_Version_Information_Command())
- await self.send_command(HCI_Write_LE_Host_Support_Command(le_supported_host = 1, simultaneous_le_host = 0))
-
- response = await self.send_command(HCI_LE_Read_Buffer_Size_Command())
if response.return_parameters.status == HCI_SUCCESS:
- self.hc_le_acl_data_packet_length = response.return_parameters.hc_le_acl_data_packet_length
- self.hc_total_num_le_acl_data_packets = response.return_parameters.hc_total_num_le_acl_data_packets
- logger.debug(f'HCI LE ACL flow control: hc_le_acl_data_packet_length={response.return_parameters.hc_le_acl_data_packet_length}, hc_total_num_le_acl_data_packets={response.return_parameters.hc_total_num_le_acl_data_packets}')
+ self.local_supported_commands = response.return_parameters.supported_commands
else:
- logger.warn(f'HCI_LE_Read_Buffer_Size_Command failed: {response.return_parameters.status}')
- if response.return_parameters.hc_le_acl_data_packet_length == 0 or response.return_parameters.hc_total_num_le_acl_data_packets == 0:
- # Read the non-LE-specific values
+ logger.warn(f'HCI_Read_Local_Supported_Commands_Command failed: {response.return_parameters.status}')
+
+ if self.supports_command(HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND):
+ response = await self.send_command(HCI_LE_Read_Local_Supported_Features_Command())
+ if response.return_parameters.status == HCI_SUCCESS:
+ self.local_le_features = struct.unpack('<Q', response.return_parameters.le_features)[0]
+ else:
+ logger.warn(f'HCI_LE_Read_Supported_Features_Command failed: {response.return_parameters.status}')
+
+ if self.supports_command(HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND):
+ response = await self.send_command(HCI_Read_Local_Version_Information_Command())
+ if response.return_parameters.status == HCI_SUCCESS:
+ self.local_version = response.return_parameters
+ else:
+ logger.warn(f'HCI_Read_Local_Version_Information_Command failed: {response.return_parameters.status}')
+
+ await self.send_command(HCI_Set_Event_Mask_Command(event_mask = bytes.fromhex('FFFFFFFFFFFFFF3F')))
+
+ if self.local_version.hci_version <= HCI_VERSION_BLUETOOTH_CORE_4_0:
+ # Some older controllers don't like event masks with bits they don't understand
+ le_event_mask = bytes.fromhex('1F00000000000000')
+ else:
+ le_event_mask = bytes.fromhex('FFFFF00000000000')
+ await self.send_command(HCI_LE_Set_Event_Mask_Command(le_event_mask = le_event_mask))
+
+ if self.supports_command(HCI_READ_BUFFER_SIZE_COMMAND):
response = await self.send_command(HCI_Read_Buffer_Size_Command())
if response.return_parameters.status == HCI_SUCCESS:
- self.hc_acl_data_packet_length = response.return_parameters.hc_le_acl_data_packet_length
- self.hc_le_acl_data_packet_length = self.hc_le_acl_data_packet_length or self.hc_acl_data_packet_length
- self.hc_total_num_acl_data_packets = response.return_parameters.hc_total_num_le_acl_data_packets
- self.hc_total_num_le_acl_data_packets = self.hc_total_num_le_acl_data_packets or self.hc_total_num_acl_data_packets
- logger.debug(f'HCI LE ACL flow control: hc_le_acl_data_packet_length={self.hc_le_acl_data_packet_length}, hc_total_num_le_acl_data_packets={self.hc_total_num_le_acl_data_packets}')
+ self.hc_acl_data_packet_length = response.return_parameters.hc_acl_data_packet_length
+ self.hc_total_num_acl_data_packets = response.return_parameters.hc_total_num_acl_data_packets
else:
logger.warn(f'HCI_Read_Buffer_Size_Command failed: {response.return_parameters.status}')
+ if self.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND):
+ response = await self.send_command(HCI_LE_Read_Buffer_Size_Command())
+ if response.return_parameters.status == HCI_SUCCESS:
+ self.hc_le_acl_data_packet_length = response.return_parameters.hc_le_acl_data_packet_length
+ self.hc_total_num_le_acl_data_packets = response.return_parameters.hc_total_num_le_acl_data_packets
+ else:
+ logger.warn(f'HCI_LE_Read_Buffer_Size_Command failed: {response.return_parameters.status}')
+
+ if response.return_parameters.hc_le_acl_data_packet_length == 0 or response.return_parameters.hc_total_num_le_acl_data_packets == 0:
+ # LE and Classic share the same values
+ self.hc_le_acl_data_packet_length = self.hc_acl_data_packet_length
+ self.hc_total_num_le_acl_data_packets = self.hc_total_num_acl_data_packets
+
+ logger.debug(
+ f'HCI ACL flow control: hc_acl_data_packet_length={self.hc_acl_data_packet_length},'
+ f'hc_total_num_acl_data_packets={self.hc_total_num_acl_data_packets}'
+ )
+ logger.debug(
+ f'HCI LE ACL flow control: hc_le_acl_data_packet_length={self.hc_le_acl_data_packet_length},'
+ f'hc_total_num_le_acl_data_packets={self.hc_total_num_le_acl_data_packets}'
+ )
+
self.reset_done = True
@property
@@ -149,8 +176,8 @@
# Wait until we can send (only one pending command at a time)
async with self.command_semaphore:
- assert(self.pending_command is None)
- assert(self.pending_response is None)
+ assert self.pending_command is None
+ assert self.pending_response is None
# Create a future value to hold the eventual response
self.pending_response = asyncio.get_running_loop().create_future()
@@ -183,6 +210,7 @@
offset = 0
pb_flag = 0
while bytes_remaining:
+ # TODO: support different LE/Classic lengths
data_total_length = min(bytes_remaining, self.hc_le_acl_data_packet_length)
acl_packet = HCI_AclDataPacket(
connection_handle = connection_handle,
@@ -205,12 +233,43 @@
logger.debug(f'{self.acl_packets_in_flight} ACL packets in flight, {len(self.acl_packet_queue)} in queue')
def check_acl_packet_queue(self):
- # Send all we can
+ # Send all we can (TODO: support different LE/Classic limits)
while len(self.acl_packet_queue) > 0 and self.acl_packets_in_flight < self.hc_total_num_le_acl_data_packets:
packet = self.acl_packet_queue.pop()
self.send_hci_packet(packet)
self.acl_packets_in_flight += 1
+ def supports_command(self, command):
+ # Find the support flag position for this command
+ for (octet, flags) in enumerate(HCI_SUPPORTED_COMMANDS_FLAGS):
+ for (flag_position, value) in enumerate(flags):
+ if value == command:
+ # Check if the flag is set
+ if octet < len(self.local_supported_commands) and flag_position < 8:
+ return (self.local_supported_commands[octet] & (1 << flag_position)) != 0
+
+ return False
+
+ @property
+ def supported_commands(self):
+ commands = []
+ for (octet, flags) in enumerate(self.local_supported_commands):
+ if octet < len(HCI_SUPPORTED_COMMANDS_FLAGS):
+ for flag in range(8):
+ if flags & (1 << flag) != 0:
+ command = HCI_SUPPORTED_COMMANDS_FLAGS[octet][flag]
+ if command is not None:
+ commands.append(command)
+
+ return commands
+
+ def supports_le_feature(self, feature):
+ return (self.local_le_features & (1 << feature)) != 0
+
+ @property
+ def supported_le_features(self):
+ return [feature for feature in range(64) if self.local_le_features & (1 << feature)]
+
# Packet Sink protocol (packets coming from the controller via HCI)
def on_packet(self, packet):
hci_packet = HCI_Packet.from_bytes(packet)
@@ -249,12 +308,6 @@
if connection := self.connections.get(packet.connection_handle):
connection.on_hci_acl_data_packet(packet)
- def on_gatt_pdu(self, connection, pdu):
- self.emit('gatt_pdu', connection.handle, pdu)
-
- def on_smp_pdu(self, connection, pdu):
- self.emit('smp_pdu', connection.handle, pdu)
-
def on_l2cap_pdu(self, connection, cid, pdu):
self.emit('l2cap_pdu', connection.handle, cid, pdu)
@@ -312,7 +365,7 @@
connection = self.connections.get(event.connection_handle)
if connection is None:
- connection = Connection(self, event.connection_handle, event.role, event.peer_address)
+ connection = Connection(self, event.connection_handle, event.role, event.peer_address, BT_LE_TRANSPORT)
self.connections[event.connection_handle] = connection
# Notify the client
@@ -347,7 +400,7 @@
connection = self.connections.get(event.connection_handle)
if connection is None:
- connection = Connection(self, event.connection_handle, BT_CENTRAL_ROLE, event.bd_addr)
+ connection = Connection(self, event.connection_handle, BT_CENTRAL_ROLE, event.bd_addr, BT_BR_EDR_TRANSPORT)
self.connections[event.connection_handle] = connection
# Notify the client
diff --git a/bumble/l2cap.py b/bumble/l2cap.py
index 7a2ca2b..e39dff1 100644
--- a/bumble/l2cap.py
+++ b/bumble/l2cap.py
@@ -20,11 +20,11 @@
import struct
from colors import color
+from pyee import EventEmitter
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
from .hci import (HCI_LE_Connection_Update_Command, HCI_Object, key_with_value,
name_or_number)
-from .utils import EventEmitter
# -----------------------------------------------------------------------------
# Logging
@@ -414,6 +414,18 @@
EXTENDED_FEATURES_SUPPORTED = 0x0002
FIXED_CHANNELS_SUPPORTED = 0x0003
+ EXTENDED_FEATURE_FLOW_MODE_CONTROL = 0x0001
+ EXTENDED_FEATURE_RETRANSMISSION_MODE = 0x0002
+ EXTENDED_FEATURE_BIDIRECTIONAL_QOS = 0x0004
+ EXTENDED_FEATURE_ENHANCED_RETRANSMISSION_MODE = 0x0008
+ EXTENDED_FEATURE_STREAMING_MODE = 0x0010
+ EXTENDED_FEATURE_FCS_OPTION = 0x0020
+ EXTENDED_FEATURE_EXTENDED_FLOW_SPEC = 0x0040
+ EXTENDED_FEATURE_FIXED_CHANNELS = 0x0080
+ EXTENDED_FEATURE_EXTENDED_WINDOW_SIZE = 0x0100
+ EXTENDED_FEATURE_UNICAST_CONNECTIONLESS_DATA = 0x0200
+ EXTENDED_FEATURE_ENHANCED_CREDIT_BASE_FLOW_CONTROL = 0x0400
+
INFO_TYPE_NAMES = {
CONNECTIONLESS_MTU: 'CONNECTIONLESS_MTU',
EXTENDED_FEATURES_SUPPORTED: 'EXTENDED_FEATURES_SUPPORTED',
@@ -817,11 +829,16 @@
# -----------------------------------------------------------------------------
class ChannelManager:
- def __init__(self):
- self.host = None
- self.channels = {} # Channels, mapped by connection and cid
- self.identifiers = {} # Incrementing identifier values by connection
- self.servers = {} # Servers accepting connections, by PSM
+ def __init__(self, extended_features=None, connectionless_mtu=1024):
+ self.host = None
+ self.channels = {} # Channels, mapped by connection and cid
+ # Fixed channel handlers, mapped by cid
+ self.fixed_channels = {
+ L2CAP_SIGNALING_CID: None, L2CAP_LE_SIGNALING_CID: None}
+ self.identifiers = {} # Incrementing identifier values by connection
+ self.servers = {} # Servers accepting connections, by PSM
+ self.extended_features = [] if extended_features is None else extended_features
+ self.connectionless_mtu = connectionless_mtu
def find_channel(self, connection_handle, cid):
if connection_channels := self.channels.get(connection_handle):
@@ -840,6 +857,13 @@
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
self.identifiers[connection.handle] = identifier
return identifier
+
+ def register_fixed_channel(self, cid, handler):
+ self.fixed_channels[cid] = handler
+
+ def deregister_fixed_channel(self, cid):
+ if cid in self.fixed_channels:
+ del self.fixed_channels[cid]
def register_server(self, psm, server):
self.servers[psm] = server
@@ -855,6 +879,8 @@
control_frame = L2CAP_Control_Frame.from_bytes(pdu)
self.on_control_frame(connection, cid, control_frame)
+ elif cid in self.fixed_channels:
+ self.fixed_channels[cid](connection.handle, pdu)
else:
if (channel := self.find_channel(connection.handle, cid)) is None:
logger.warn(color(f'channel not found for 0x{connection.handle:04X}:{cid}', 'red'))
@@ -999,13 +1025,13 @@
def on_l2cap_information_request(self, connection, cid, request):
if request.info_type == L2CAP_Information_Request.CONNECTIONLESS_MTU:
result = L2CAP_Information_Response.SUCCESS
- data = struct.pack('<H', 1024) # TODO: don't use a fixed value
+ data = self.connectionless_mtu.to_bytes(2, 'little')
elif request.info_type == L2CAP_Information_Request.EXTENDED_FEATURES_SUPPORTED:
result = L2CAP_Information_Response.SUCCESS
- data = bytes.fromhex('00000000') # TODO: don't use a fixed value
+ data = sum(self.extended_features).to_bytes(4, 'little')
elif request.info_type == L2CAP_Information_Request.FIXED_CHANNELS_SUPPORTED:
result = L2CAP_Information_Response.SUCCESS
- data = bytes.fromhex('FFFFFFFFFFFFFFFF') # TODO: don't use a fixed value
+ data = sum(1 << cid for cid in self.fixed_channels).to_bytes(8, 'little')
else:
result = L2CAP_Information_Request.NO_SUPPORTED
diff --git a/bumble/profiles/heart_rate_service.py b/bumble/profiles/heart_rate_service.py
new file mode 100644
index 0000000..b3cd875
--- /dev/null
+++ b/bumble/profiles/heart_rate_service.py
@@ -0,0 +1,221 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+from enum import IntEnum
+import struct
+
+from ..gatt_client import ProfileServiceProxy
+from ..att import ATT_Error
+from ..gatt import (
+ GATT_HEART_RATE_SERVICE,
+ GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
+ GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
+ GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
+ TemplateService,
+ Characteristic,
+ CharacteristicValue,
+ DelegatedCharacteristicAdapter,
+ PackedCharacteristicAdapter
+)
+
+
+# -----------------------------------------------------------------------------
+class HeartRateService(TemplateService):
+ UUID = GATT_HEART_RATE_SERVICE
+ HEART_RATE_CONTROL_POINT_FORMAT = 'B'
+ CONTROL_POINT_NOT_SUPPORTED = 0x80
+ RESET_ENERGY_EXPENDED = 0x01
+
+ class BodySensorLocation(IntEnum):
+ OTHER = 0,
+ CHEST = 1,
+ WRIST = 2,
+ FINGER = 3,
+ HAND = 4,
+ EAR_LOBE = 5,
+ FOOT = 6
+
+ class HeartRateMeasurement:
+ def __init__(
+ self,
+ heart_rate,
+ sensor_contact_detected=None,
+ energy_expended=None,
+ rr_intervals=None
+ ):
+ if heart_rate < 0 or heart_rate > 0xFFFF:
+ raise ValueError('heart_rate out of range')
+
+ if energy_expended is not None and (energy_expended < 0 or energy_expended > 0xFFFF):
+ raise ValueError('energy_expended out of range')
+
+ if rr_intervals:
+ for rr_interval in rr_intervals:
+ if rr_interval < 0 or rr_interval * 1024 > 0xFFFF:
+ raise ValueError('rr_intervals out of range')
+
+ self.heart_rate = heart_rate
+ self.sensor_contact_detected = sensor_contact_detected
+ self.energy_expended = energy_expended
+ self.rr_intervals = rr_intervals
+
+ @classmethod
+ def from_bytes(cls, data):
+ flags = data[0]
+ offset = 1
+
+ if flags & 1:
+ hr = struct.unpack_from('<H', data, offset)[0]
+ offset += 2
+ else:
+ hr = struct.unpack_from('B', data, offset)[0]
+ offset += 1
+
+ if flags & (1 << 2):
+ sensor_contact_detected = (flags & (1 << 1) != 0)
+ else:
+ sensor_contact_detected = None
+
+ if flags & (1 << 3):
+ energy_expended = struct.unpack_from('<H', data, offset)[0]
+ offset += 2
+ else:
+ energy_expended = None
+
+ if flags & (1 << 4):
+ rr_intervals = tuple(
+ struct.unpack_from('<H', data, offset + i * 2)[0] / 1024
+ for i in range((len(data) - offset) // 2)
+ )
+ else:
+ rr_intervals = ()
+
+ return cls(hr, sensor_contact_detected, energy_expended, rr_intervals)
+
+ def __bytes__(self):
+ if self.heart_rate < 256:
+ flags = 0
+ data = struct.pack('B', self.heart_rate)
+ else:
+ flags = 1
+ data = struct.pack('<H', self.heart_rate)
+
+ if self.sensor_contact_detected is not None:
+ flags |= ((1 if self.sensor_contact_detected else 0) << 1) | (1 << 2)
+
+ if self.energy_expended is not None:
+ flags |= (1 << 3)
+ data += struct.pack('<H', self.energy_expended)
+
+ if self.rr_intervals:
+ flags |= (1 << 4)
+ data += b''.join([
+ struct.pack('<H', int(rr_interval * 1024))
+ for rr_interval in self.rr_intervals
+ ])
+
+ return bytes([flags]) + data
+
+ def __str__(self):
+ return f'HeartRateMeasurement(heart_rate={self.heart_rate},'\
+ f' sensor_contact_detected={self.sensor_contact_detected},'\
+ f' energy_expended={self.energy_expended},'\
+ f' rr_intervals={self.rr_intervals})'
+
+ def __init__(
+ self,
+ read_heart_rate_measurement,
+ body_sensor_location=None,
+ reset_energy_expended=None
+ ):
+ self.heart_rate_measurement_characteristic = DelegatedCharacteristicAdapter(
+ Characteristic(
+ GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
+ Characteristic.NOTIFY,
+ 0,
+ CharacteristicValue(read=read_heart_rate_measurement)
+ ),
+ encode=lambda value: bytes(value)
+ )
+ characteristics = [self.heart_rate_measurement_characteristic]
+
+ if body_sensor_location is not None:
+ self.body_sensor_location_characteristic = Characteristic(
+ GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
+ Characteristic.READ,
+ Characteristic.READABLE,
+ bytes([int(body_sensor_location)])
+ )
+ characteristics.append(self.body_sensor_location_characteristic)
+
+ if reset_energy_expended:
+ def write_heart_rate_control_point_value(connection, value):
+ if value == self.RESET_ENERGY_EXPENDED:
+ if reset_energy_expended is not None:
+ reset_energy_expended(connection)
+ else:
+ raise ATT_Error(self.CONTROL_POINT_NOT_SUPPORTED)
+
+ self.heart_rate_control_point_characteristic = PackedCharacteristicAdapter(
+ Characteristic(
+ GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
+ Characteristic.WRITE,
+ Characteristic.WRITEABLE,
+ CharacteristicValue(write=write_heart_rate_control_point_value)
+ ),
+ format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT
+ )
+ characteristics.append(self.heart_rate_control_point_characteristic)
+
+ super().__init__(characteristics)
+
+
+# -----------------------------------------------------------------------------
+class HeartRateServiceProxy(ProfileServiceProxy):
+ SERVICE_CLASS = HeartRateService
+
+ def __init__(self, service_proxy):
+ self.service_proxy = service_proxy
+
+ if characteristics := service_proxy.get_characteristics_by_uuid(GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC):
+ self.heart_rate_measurement = DelegatedCharacteristicAdapter(
+ characteristics[0],
+ decode=HeartRateService.HeartRateMeasurement.from_bytes
+ )
+ else:
+ self.heart_rate_measurement = None
+
+ if characteristics := service_proxy.get_characteristics_by_uuid(GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC):
+ self.body_sensor_location = DelegatedCharacteristicAdapter(
+ characteristics[0],
+ decode=lambda value: HeartRateService.BodySensorLocation(value[0])
+ )
+ else:
+ self.body_sensor_location = None
+
+ if characteristics := service_proxy.get_characteristics_by_uuid(GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC):
+ self.heart_rate_control_point = PackedCharacteristicAdapter(
+ characteristics[0],
+ format=HeartRateService.HEART_RATE_CONTROL_POINT_FORMAT
+ )
+ else:
+ self.heart_rate_control_point = None
+
+ async def reset_energy_expended(self):
+ if self.heart_rate_control_point is not None:
+ return await self.heart_rate_control_point.write_value(HeartRateService.RESET_ENERGY_EXPENDED)
diff --git a/bumble/rfcomm.py b/bumble/rfcomm.py
index 527eaf1..be4d406 100644
--- a/bumble/rfcomm.py
+++ b/bumble/rfcomm.py
@@ -17,9 +17,10 @@
# -----------------------------------------------------------------------------
import logging
import asyncio
-from colors import color
-from .utils import EventEmitter
+from colors import color
+from pyee import EventEmitter
+
from .core import InvalidStateError, ProtocolError, ConnectionError
# -----------------------------------------------------------------------------
diff --git a/bumble/smp.py b/bumble/smp.py
index dbb2b7a..c544a82 100644
--- a/bumble/smp.py
+++ b/bumble/smp.py
@@ -44,6 +44,7 @@
# Constants
# -----------------------------------------------------------------------------
SMP_CID = 0x06
+SMP_BR_CID = 0x07
SMP_PAIRING_REQUEST_COMMAND = 0x01
SMP_PAIRING_RESPONSE_COMMAND = 0x02
@@ -152,6 +153,8 @@
# Crypto salt
SMP_CTKD_H7_LEBR_SALT = bytes.fromhex('00000000000000000000000000000000746D7031')
+SMP_CTKD_H7_BRLE_SALT = bytes.fromhex('00000000000000000000000000000000746D7032')
+
# -----------------------------------------------------------------------------
# Utils
@@ -598,6 +601,7 @@
self.pairing_config = pairing_config
self.wait_before_continuing = None
self.completed = False
+ self.ctkd_task = None
# Decide if we're the initiator or the responder
self.is_initiator = (connection.role == BT_CENTRAL_ROLE)
@@ -868,7 +872,7 @@
# distribute the long term and/or other keys over an encrypted connection
asyncio.create_task(
self.manager.device.host.send_command(
- HCI_LE_Start_Encryption_Command(
+ HCI_LE_Enable_Encryption_Command(
connection_handle = self.connection.handle,
random_number = bytes(8),
encrypted_diversifier = 0,
@@ -877,10 +881,21 @@
)
)
+ async def derive_ltk(self):
+ link_key = await self.manager.device.get_link_key(self.connection.peer_address)
+ assert link_key is not None
+ ilk = crypto.h7(
+ salt=SMP_CTKD_H7_BRLE_SALT,
+ w=link_key) if self.ct2 else crypto.h6(link_key, b'tmp2')
+ self.ltk = crypto.h6(ilk, b'brle')
+
def distribute_keys(self):
# Distribute the keys as required
if self.is_initiator:
- if not self.sc:
+ # CTKD: Derive LTK from LinkKey
+ if self.connection.transport == BT_BR_EDR_TRANSPORT and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
+ self.ctkd_task = asyncio.create_task(self.derive_ltk())
+ elif not self.sc:
# Distribute the LTK, EDIV and RAND
if self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
self.send_command(SMP_Encryption_Information_Command(long_term_key=self.ltk))
@@ -900,7 +915,7 @@
csrk = bytes(16) # FIXME: testing
if self.initiator_key_distribution & SMP_SIGN_KEY_DISTRIBUTION_FLAG:
self.send_command(SMP_Signing_Information_Command(signature_key=csrk))
-
+
# CTKD, calculate BR/EDR link key
if self.initiator_key_distribution & SMP_LINK_KEY_DISTRIBUTION_FLAG:
ilk = crypto.h7(
@@ -909,8 +924,11 @@
self.link_key = crypto.h6(ilk, b'lebr')
else:
+ # CTKD: Derive LTK from LinkKey
+ if self.connection.transport == BT_BR_EDR_TRANSPORT and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
+ self.ctkd_task = asyncio.create_task(self.derive_ltk())
# Distribute the LTK, EDIV and RAND
- if not self.sc:
+ elif not self.sc:
if self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG:
self.send_command(SMP_Encryption_Information_Command(long_term_key=self.ltk))
self.send_command(SMP_Master_Identification_Command(ediv=self.ltk_ediv, rand=self.ltk_rand))
@@ -929,7 +947,7 @@
csrk = bytes(16) # FIXME: testing
if self.responder_key_distribution & SMP_SIGN_KEY_DISTRIBUTION_FLAG:
self.send_command(SMP_Signing_Information_Command(signature_key=csrk))
-
+
# CTKD, calculate BR/EDR link key
if self.responder_key_distribution & SMP_LINK_KEY_DISTRIBUTION_FLAG:
ilk = crypto.h7(
@@ -940,7 +958,7 @@
def compute_peer_expected_distributions(self, key_distribution_flags):
# Set our expectations for what to wait for in the key distribution phase
self.peer_expected_distributions = []
- if not self.sc:
+ if not self.sc and self.connection.transport == BT_LE_TRANSPORT:
if (key_distribution_flags & SMP_ENC_KEY_DISTRIBUTION_FLAG != 0):
self.peer_expected_distributions.append(SMP_Encryption_Information_Command)
self.peer_expected_distributions.append(SMP_Master_Identification_Command)
@@ -963,12 +981,7 @@
self.peer_expected_distributions.remove(command_class)
logger.debug(f'remaining distributions: {[c.__name__ for c in self.peer_expected_distributions]}')
if not self.peer_expected_distributions:
- # The initiator can now send its keys
- if self.is_initiator:
- self.distribute_keys()
-
- # Nothing left to expect, we're done
- self.on_pairing()
+ self.on_peer_key_distribution_complete()
else:
logger.warn(color(f'!!! unexpected key distribution command: {command_class.__name__}', 'red'))
self.send_pairing_failed(SMP_UNSPECIFIED_REASON_ERROR)
@@ -989,17 +1002,28 @@
self.connection.remove_listener('connection_encryption_key_refresh', self.on_connection_encryption_key_refresh)
self.manager.on_session_end(self)
+ def on_peer_key_distribution_complete(self):
+ # The initiator can now send its keys
+ if self.is_initiator:
+ self.distribute_keys()
+
+ asyncio.create_task(self.on_pairing())
+
def on_connection_encryption_change(self):
if self.connection.is_encrypted:
if self.is_responder:
# The responder distributes its keys first, the initiator later
self.distribute_keys()
+ # If we're not expecting key distributions from the peer, we're done
+ if not self.peer_expected_distributions:
+ self.on_peer_key_distribution_complete()
+
def on_connection_encryption_key_refresh(self):
# Do as if the connection had just been encrypted
self.on_connection_encryption_change()
- def on_pairing(self):
+ async def on_pairing(self):
logger.debug('pairing complete')
if self.completed:
@@ -1016,11 +1040,16 @@
else:
peer_address = self.connection.peer_address
+ # Wait for link key fetch and key derivation
+ if self.ctkd_task is not None:
+ await self.ctkd_task
+ self.ctkd_task = None
+
# Create an object to hold the keys
keys = PairingKeys()
keys.address_type = peer_address.address_type
authenticated = self.pairing_method != self.JUST_WORKS
- if self.sc:
+ if self.sc or self.connection.transport == BT_BR_EDR_TRANSPORT:
keys.ltk = PairingKeys.Key(
value = self.ltk,
authenticated = authenticated
@@ -1059,7 +1088,6 @@
value = self.link_key,
authenticated = authenticated
)
-
self.manager.on_pairing(self, peer_address, keys)
def on_pairing_failure(self, reason):
@@ -1137,6 +1165,12 @@
# Respond
self.send_pairing_response_command()
+ # Vol 3, Part C, 5.2.2.1.3
+ # CTKD over BR/EDR should happen after the connection has been encrypted,
+ # so when receiving pairing requests, responder should start distributing keys
+ if self.connection.transport == BT_BR_EDR_TRANSPORT and self.connection.is_encrypted and self.is_responder and accepted:
+ self.distribute_keys()
+
def on_smp_pairing_response_command(self, command):
if self.is_responder:
logger.warn(color('received pairing response as a responder', 'red'))
@@ -1462,7 +1496,8 @@
def send_command(self, connection, command):
logger.debug(f'>>> Sending SMP Command on connection [0x{connection.handle:04X}] {connection.peer_address}: {command}')
- connection.send_l2cap_pdu(SMP_CID, command.to_bytes())
+ cid = SMP_BR_CID if connection.transport == BT_BR_EDR_TRANSPORT else SMP_CID
+ connection.send_l2cap_pdu(cid, command.to_bytes())
def on_smp_pdu(self, connection, pdu):
# Look for a session with this connection, and create one if none exists
diff --git a/bumble/transport/usb.py b/bumble/transport/usb.py
index dfc169f..80a8871 100644
--- a/bumble/transport/usb.py
+++ b/bumble/transport/usb.py
@@ -37,33 +37,38 @@
'''
Open a USB transport.
The parameter string has this syntax:
- either <index> or <vendor>:<product>[/<serial-number>]
+ either <index> or
+ <vendor>:<product> or
+ <vendor>:<product>/<serial-number>] or
+ <vendor>:<product>#<index>
With <index> as the 0-based index to select amongst all the devices that appear
to be supporting Bluetooth HCI (0 being the first one), or
Where <vendor> and <product> are the vendor ID and product ID in hexadecimal. The
- /<serial-number> suffix max be specified when more than one device with the same
- vendor and product identifiers are present.
+ /<serial-number> suffix or #<index> suffix max be specified when more than one device with
+ the same vendor and product identifiers are present.
Examples:
0 --> the first BT USB dongle
04b4:f901 --> the BT USB dongle with vendor=04b4 and product=f901
+ 04b4:f901#2 --> the third USB device with vendor=04b4 and product=f901
04b4:f901/00E04C239987 --> the BT USB dongle with vendor=04b4 and product=f901 and serial number 00E04C239987
'''
USB_RECIPIENT_DEVICE = 0x00
USB_REQUEST_TYPE_CLASS = 0x01 << 5
- USB_ENDPOINT_EVENTS_IN = 0x81
- USB_ENDPOINT_ACL_IN = 0x82
- USB_ENDPOINT_ACL_OUT = 0x02
USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
+ USB_ENDPOINT_TRANSFER_TYPE_BULK = 0x02
+ USB_ENDPOINT_TRANSFER_TYPE_INTERRUPT = 0x03
+ USB_ENDPOINT_IN = 0x80
READ_SIZE = 1024
class UsbPacketSink:
- def __init__(self, device):
+ def __init__(self, device, acl_out):
self.device = device
+ self.acl_out = acl_out
self.transfer = device.getTransfer()
self.packets = collections.deque() # Queue of packets waiting to be sent
self.loop = asyncio.get_running_loop()
@@ -112,7 +117,7 @@
packet_type = packet[0]
if packet_type == hci.HCI_ACL_DATA_PACKET:
self.transfer.setBulk(
- USB_ENDPOINT_ACL_OUT,
+ self.acl_out,
packet[1:],
callback=self.on_packet_sent
)
@@ -148,10 +153,12 @@
logger.debug('OUT transfer likely already completed')
class UsbPacketSource(asyncio.Protocol, ParserSource):
- def __init__(self, context, device):
+ def __init__(self, context, device, acl_in, events_in):
super().__init__()
self.context = context
self.device = device
+ self.acl_in = acl_in
+ self.events_in = events_in
self.loop = asyncio.get_running_loop()
self.queue = asyncio.Queue()
self.closed = False
@@ -168,7 +175,7 @@
# Set up transfer objects for input
self.events_in_transfer = device.getTransfer()
self.events_in_transfer.setInterrupt(
- USB_ENDPOINT_EVENTS_IN,
+ self.events_in,
READ_SIZE,
callback=self.on_packet_received,
user_data=hci.HCI_EVENT_PACKET
@@ -177,7 +184,7 @@
self.acl_in_transfer = device.getTransfer()
self.acl_in_transfer.setBulk(
- USB_ENDPOINT_ACL_IN,
+ self.acl_in,
READ_SIZE,
callback=self.on_packet_received,
user_data=hci.HCI_ACL_DATA_PACKET
@@ -190,7 +197,7 @@
def on_packet_received(self, transfer):
packet_type = transfer.getUserData()
status = transfer.getStatus()
- # logger.debug(f'<<< USB IN transfer callback: status={status} packet_type={packet_type}')
+ # logger.debug(f'<<< USB IN transfer callback: status={status} packet_type={packet_type} length={transfer.getActualLength()}')
if status == usb1.TRANSFER_COMPLETED:
packet = bytes([packet_type]) + transfer.getBuffer()[:transfer.getActualLength()]
@@ -244,7 +251,7 @@
await self.event_loop_done
class UsbTransport(Transport):
- def __init__(self, context, device, interface, source, sink):
+ def __init__(self, context, device, interface, setting, source, sink):
super().__init__(source, sink)
self.context = context
self.device = device
@@ -253,6 +260,10 @@
# Get exclusive access
device.claimInterface(interface)
+ # Set the alternate setting if not the default
+ if setting != 0:
+ device.setInterfaceAltSetting(interface, setting)
+
# The source and sink can now start
source.start()
sink.start()
@@ -271,19 +282,25 @@
found = None
if ':' in spec:
vendor_id, product_id = spec.split(':')
+ serial_number = None
+ device_index = 0
if '/' in product_id:
product_id, serial_number = product_id.split('/')
- for device in context.getDeviceIterator(skip_on_error=True):
- if (
- device.getVendorID() == int(vendor_id, 16) and
- device.getProductID() == int(product_id, 16) and
- device.getSerialNumber() == serial_number
- ):
+ elif '#' in product_id:
+ product_id, device_index_str = product_id.split('#')
+ device_index = int(device_index_str)
+
+ for device in context.getDeviceIterator(skip_on_error=True):
+ if (
+ device.getVendorID() == int(vendor_id, 16) and
+ device.getProductID() == int(product_id, 16) and
+ (serial_number is None or device.getSerialNumber() == serial_number)
+ ):
+ if device_index == 0:
found = device
break
- device.close()
- else:
- found = context.getByVendorIDAndProductID(int(vendor_id, 16), int(product_id, 16), skip_on_error=True)
+ device_index -= 1
+ device.close()
else:
device_index = int(spec)
for device in context.getDeviceIterator(skip_on_error=True):
@@ -303,22 +320,64 @@
raise ValueError('device not found')
logger.debug(f'USB Device: {found}')
+
+ # Look for the first interface with the right class and endpoints
+ def find_endpoints(device):
+ for (configuration_index, configuration) in enumerate(device):
+ interface = None
+ for interface in configuration:
+ setting = None
+ for setting in interface:
+ if (
+ setting.getClass() != USB_DEVICE_CLASS_WIRELESS_CONTROLLER or
+ setting.getSubClass() != USB_DEVICE_SUBCLASS_RF_CONTROLLER or
+ setting.getProtocol() != USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
+ ):
+ continue
+
+ events_in = None
+ acl_in = None
+ acl_out = None
+ for endpoint in setting:
+ attributes = endpoint.getAttributes()
+ address = endpoint.getAddress()
+ if attributes & 0x03 == USB_ENDPOINT_TRANSFER_TYPE_BULK:
+ if address & USB_ENDPOINT_IN and acl_in is None:
+ acl_in = address
+ elif acl_out is None:
+ acl_out = address
+ elif attributes & 0x03 == USB_ENDPOINT_TRANSFER_TYPE_INTERRUPT:
+ if address & USB_ENDPOINT_IN and events_in is None:
+ events_in = address
+
+ # Return if we found all 3 endpoints
+ if acl_in is not None and acl_out is not None and events_in is not None:
+ return (
+ configuration_index + 1,
+ setting.getNumber(),
+ setting.getAlternateSetting(),
+ acl_in,
+ acl_out,
+ events_in
+ )
+ else:
+ logger.debug(f'skipping configuration {configuration_index + 1} / interface {setting.getNumber()}')
+
+ endpoints = find_endpoints(found)
+ if endpoints is None:
+ raise ValueError('no compatible interface found for device')
+ (configuration, interface, setting, acl_in, acl_out, events_in) = endpoints
+ logger.debug(
+ f'selected endpoints: configuration={configuration}, '
+ f'interface={interface}, '
+ f'setting={setting}, '
+ f'acl_in=0x{acl_in:02X}, '
+ f'acl_out=0x{acl_out:02X}, '
+ f'events_in=0x{events_in:02X}, '
+ )
+
device = found.open()
- # Set the configuration if needed
- try:
- configuration = device.getConfiguration()
- logger.debug(f'current configuration = {configuration}')
- except usb1.USBError:
- try:
- logger.debug('setting configuration 1')
- device.setConfiguration(1)
- except usb1.USBError:
- logger.debug('failed to set configuration 1')
-
- # Use the first interface
- interface = 0
-
# Detach the kernel driver if supported and needed
if usb1.hasCapability(usb1.CAP_SUPPORTS_DETACH_KERNEL_DRIVER):
try:
@@ -328,9 +387,23 @@
except usb1.USBError:
pass
- source = UsbPacketSource(context, device)
- sink = UsbPacketSink(device)
- return UsbTransport(context, device, interface, source, sink)
+ # Set the configuration if needed
+ try:
+ current_configuration = device.getConfiguration()
+ logger.debug(f'current configuration = {current_configuration}')
+ except usb1.USBError:
+ current_configuration = 0
+
+ if current_configuration != configuration:
+ try:
+ logger.debug(f'setting configuration {configuration}')
+ device.setConfiguration(configuration)
+ except usb1.USBError:
+ logger.warning('failed to set configuration')
+
+ source = UsbPacketSource(context, device, acl_in, events_in)
+ sink = UsbPacketSink(device, acl_out)
+ return UsbTransport(context, device, interface, setting, source, sink)
except usb1.USBError as error:
logger.warning(color(f'!!! failed to open USB device: {error}', 'red'))
context.close()
diff --git a/docs/mkdocs/src/apps_and_tools/console.md b/docs/mkdocs/src/apps_and_tools/console.md
index 9abdf2b..c8e1769 100644
--- a/docs/mkdocs/src/apps_and_tools/console.md
+++ b/docs/mkdocs/src/apps_and_tools/console.md
@@ -7,10 +7,12 @@
* scanning
* advertising
- * connecting to devices
+ * connecting to and disconnecting from devices
* changing connection parameters
+ * enabling encryption
* discovering GATT services and characteristics
- * read & write GATT characteristics
+ * reading and writing GATT characteristics
+ * subscribing to and unsubscribing from GATT characteristics
The console user interface has 3 main panes:
diff --git a/docs/mkdocs/src/apps_and_tools/usb_probe.md b/docs/mkdocs/src/apps_and_tools/usb_probe.md
new file mode 100644
index 0000000..80e2f41
--- /dev/null
+++ b/docs/mkdocs/src/apps_and_tools/usb_probe.md
@@ -0,0 +1,38 @@
+USB PROBE TOOL
+==============
+
+This tool lists all the USB devices, with details about each device.
+For each device, the different possible Bumble transport strings that can
+refer to it are listed.
+If the device is known to be a Bluetooth HCI device, its identifier is printed
+in reverse colors, and the transport names in cyan color.
+For other devices, regardless of their type, the transport names are printed
+in red. Whether that device is actually a Bluetooth device or not depends on
+whether it is a Bluetooth device that uses a non-standard Class, or some other
+type of device (there's no way to tell).
+
+## Usage
+
+This command line tool takes no arguments.
+When installed from PyPI, run as
+```
+$ bumble-usb-probe
+```
+
+When running from the source distribution:
+```
+$ python3 apps/usb-probe.py
+```
+
+!!! example
+ ```
+ $ python3 apps/usb_probe.py
+
+ ID 0A12:0001
+ Bumble Transport Names: usb:0 or usb:0A12:0001
+ Bus/Device: 020/034
+ Class: Wireless Controller
+ Subclass/Protocol: 1/1 [Bluetooth]
+ Manufacturer: None
+ Product: USB2.0-BT
+ ```
\ No newline at end of file
diff --git a/docs/mkdocs/src/transports/usb.md b/docs/mkdocs/src/transports/usb.md
index f35259b..6fe5e2a 100644
--- a/docs/mkdocs/src/transports/usb.md
+++ b/docs/mkdocs/src/transports/usb.md
@@ -4,16 +4,56 @@
The USB transport interfaces with a local Bluetooth USB dongle.
## Moniker
-The moniker for a USB transport is either `usb:<index>` or `usb:<vendor>:<product>`
-with `<index>` as the 0-based index to select amongst all the devices that appear to be supporting Bluetooth HCI (0 being the first one), or where `<vendor>` and `<product>` are a vendor ID and product ID in hexadecimal.
+The moniker for a USB transport is either:
+ * `usb:<index>`
+ * `usb:<vendor>:<product>`
+ * `usb:<vendor>:<product>/<serial-number>`
+ * `usb:<vendor>:<product>#<index>`
-!!! example
+with `<index>` as a 0-based index (0 being the first one) to select amongst all the matching devices when there are more than one.
+In the `usb:<index>` form, matching devices are the ones supporting Bluetooth HCI, as declared by their Class, Subclass and Protocol.
+In the `usb:<vendor>:<product>#<index>` form, matching devices are the ones with the specified `<vendor>` and `<product>` identification.
+
+`<vendor>` and `<product>` are a vendor ID and product ID in hexadecimal.
+
+!!! examples
`usb:04b4:f901`
- Use the USB dongle with `vendor` equal to `04b4` and `product` equal to `f901`
+ The USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
`usb:0`
- Use the first Bluetooth dongle
+ The first Bluetooth HCI dongle that's declared as such by Class/Subclass/Protocol
+
+ `usb:04b4:f901/0016A45B05D8`
+ The USB dongle with `<vendor>` equal to `04b4`, `<product>` equal to `f901` and `<serial>` equal to `0016A45B05D8`
+
+ `usb:04b4:f901/#1`
+ The second USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
## Alternative
The library includes two different implementations of the USB transport, implemented using different python bindings for `libusb`.
Using the transport prefix `pyusb:` instead of `usb:` selects the implementation based on [PyUSB](https://pypi.org/project/pyusb/), using the synchronous API of `libusb`, whereas the default implementation is based on [libusb1](https://pypi.org/project/libusb1/), using the asynchronous API of `libusb`. In order to use the alternative PyUSB-based implementation, you need to ensure that you have installed that python module, as it isn't installed by default as a dependency of Bumble.
+
+## Listing Available USB Devices
+
+### With `usb_probe`
+You can use the [`usb_probe`](../apps_and_tools/usb_probe.md) tool to list all the USB devices attached to your host computer.
+The tool will also show the `usb:XXX` transport name(s) you can use to reference each device.
+
+
+### With `lsusb`
+On Linux and macOS, the `lsusb` tool serves a similar purpose to Bumble's own `usb_probe` tool (without the Bumble specifics)
+
+#### Installing lsusb
+
+On Mac: `brew install lsusb`
+On Linux: `sudo apt-get install usbutils`
+
+#### Using lsusb
+
+```
+$ lsusb
+Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
+Bus 003 Device 014: ID 0b05:17cb ASUSTek Computer, Inc. Broadcom BCM20702A0 Bluetooth
+```
+
+The device id for the Bluetooth interface in this case is `0b05:17cb`.
\ No newline at end of file
diff --git a/examples/battery_client.py b/examples/battery_client.py
index 888b23e..f545f12 100644
--- a/examples/battery_client.py
+++ b/examples/battery_client.py
@@ -43,28 +43,24 @@
# Connect to the peer
target_address = sys.argv[2]
print(f'=== Connecting to {target_address}...')
- connection = await device.connect(target_address)
- print(f'=== Connected to {connection}')
+ async with device.connect_as_gatt(target_address) as peer:
+ print(f'=== Connected to {peer}')
+ battery_service = peer.create_service_proxy(BatteryServiceProxy)
- # Discover the Battery Service
- peer = Peer(connection)
- print('=== Discovering Battery Service')
- battery_service = await peer.discover_and_create_service_proxy(BatteryServiceProxy)
+ # Check that the service was found
+ if not battery_service:
+ print('!!! Service not found')
+ return
- # Check that the service was found
- if not battery_service:
- print('!!! Service not found')
- return
+ # Subscribe to and read the battery level
+ if battery_service.battery_level:
+ await battery_service.battery_level.subscribe(
+ lambda value: print(f'{color("Battery Level Update:", "green")} {value}')
+ )
+ value = await battery_service.battery_level.read_value()
+ print(f'{color("Initial Battery Level:", "green")} {value}')
- # Subscribe to and read the battery level
- if battery_service.battery_level:
- await battery_service.battery_level.subscribe(
- lambda value: print(f'{color("Battery Level Update:", "green")} {value}')
- )
- value = await battery_service.battery_level.read_value()
- print(f'{color("Initial Battery Level:", "green")} {value}')
-
- await hci_source.wait_for_termination()
+ await peer.sustain()
# -----------------------------------------------------------------------------
diff --git a/examples/battery_server.py b/examples/battery_server.py
index 3fabf0d..6868527 100644
--- a/examples/battery_server.py
+++ b/examples/battery_server.py
@@ -38,7 +38,7 @@
async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink):
device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink)
- # Add a Device Information Service and Battery Service to the GATT sever
+ # Add a Battery Service to the GATT sever
battery_service = BatteryService(lambda _: random.randint(0, 100))
device.add_service(battery_service)
diff --git a/examples/heart_rate_client.py b/examples/heart_rate_client.py
new file mode 100644
index 0000000..130aac0
--- /dev/null
+++ b/examples/heart_rate_client.py
@@ -0,0 +1,72 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import asyncio
+import sys
+import os
+import logging
+from colors import color
+from bumble.device import Device, Peer
+from bumble.transport import open_transport
+from bumble.profiles.heart_rate_service import HeartRateServiceProxy
+
+
+# -----------------------------------------------------------------------------
+async def main():
+ if len(sys.argv) != 3:
+ print('Usage: heart_rate_client.py <transport-spec> <bluetooth-address>')
+ print('example: heart_rate_client.py usb:0 E1:CA:72:48:C4:E8')
+ return
+
+ print('<<< connecting to HCI...')
+ async with await open_transport(sys.argv[1]) as (hci_source, hci_sink):
+ print('<<< connected')
+
+ # Create and start a device
+ device = Device.with_hci('Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink)
+ await device.power_on()
+
+ # Connect to the peer
+ target_address = sys.argv[2]
+ print(f'=== Connecting to {target_address}...')
+ async with device.connect_as_gatt(target_address) as peer:
+ print(f'=== Connected to {peer}')
+
+ heart_rate_service = peer.create_service_proxy(HeartRateServiceProxy)
+
+ # Check that the service was found
+ if not heart_rate_service:
+ print('!!! Service not found')
+ return
+
+ # Read the body sensor location
+ if heart_rate_service.body_sensor_location:
+ location = await heart_rate_service.body_sensor_location.read_value()
+ print(color('Sensor Location:', 'green'), location)
+
+ # Subscribe to the heart rate measurement
+ if heart_rate_service.heart_rate_measurement:
+ await heart_rate_service.heart_rate_measurement.subscribe(
+ lambda value: print(f'{color("Heart Rate Measurement:", "green")} {value}')
+ )
+
+ await peer.sustain()
+
+
+# -----------------------------------------------------------------------------
+logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
+asyncio.run(main())
diff --git a/examples/heart_rate_server.py b/examples/heart_rate_server.py
new file mode 100644
index 0000000..e6c008c
--- /dev/null
+++ b/examples/heart_rate_server.py
@@ -0,0 +1,95 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import sys
+import time
+import math
+import random
+import struct
+import logging
+import asyncio
+import os
+
+from bumble.core import AdvertisingData
+from bumble.device import Device
+from bumble.transport import open_transport_or_link
+from bumble.profiles.device_information_service import DeviceInformationService
+from bumble.profiles.heart_rate_service import HeartRateService
+
+
+# -----------------------------------------------------------------------------
+async def main():
+ if len(sys.argv) != 3:
+ print('Usage: python heart_rate_server.py <device-config> <transport-spec>')
+ print('example: python heart_rate_server.py device1.json usb:0')
+ return
+
+ async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink):
+ device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink)
+
+ # Keep track of accumulated expended energy
+ energy_start_time = time.time()
+
+ def reset_energy_expended():
+ nonlocal energy_start_time
+ energy_start_time = time.time()
+
+ # Add a Device Information Service and Heart Rate Service to the GATT sever
+ device_information_service = DeviceInformationService(
+ manufacturer_name = 'ACME',
+ model_number = 'HR-102',
+ serial_number = '7654321',
+ hardware_revision = '1.1.3',
+ software_revision = '2.5.6',
+ system_id = (0x123456, 0x8877665544)
+ )
+
+ heart_rate_service = HeartRateService(
+ read_heart_rate_measurement = lambda _: HeartRateService.HeartRateMeasurement(
+ heart_rate = 100 + int(50 * math.sin(time.time() * math.pi / 60)),
+ sensor_contact_detected = random.choice((True, False, None)),
+ energy_expended = random.choice((int((time.time() - energy_start_time) * 100), None)),
+ rr_intervals = random.choice(((random.randint(900, 1100) / 1000, random.randint(900, 1100) / 1000), None))
+ ),
+ body_sensor_location=HeartRateService.BodySensorLocation.WRIST,
+ reset_energy_expended=lambda _: reset_energy_expended()
+ )
+
+ device.add_services([device_information_service, heart_rate_service])
+
+ # Set the advertising data
+ device.advertising_data = bytes(
+ AdvertisingData([
+ (AdvertisingData.COMPLETE_LOCAL_NAME, bytes('Bumble Heart', 'utf-8')),
+ (AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, bytes(heart_rate_service.uuid)),
+ (AdvertisingData.APPEARANCE, struct.pack('<H', 0x0340))
+ ])
+ )
+
+ # Go!
+ await device.power_on()
+ await device.start_advertising(auto_restart=True)
+
+ # Notify every 3 seconds
+ while True:
+ await asyncio.sleep(3.0)
+ await device.notify_subscribers(heart_rate_service.heart_rate_measurement_characteristic)
+
+
+# -----------------------------------------------------------------------------
+logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
+asyncio.run(main())
diff --git a/setup.cfg b/setup.cfg
index 1711d7c..05d07c9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,13 +16,15 @@
name = bumble
use_scm_version = True
description = Bluetooth Stack for Apps, Emulation, Test and Experimentation
+long_description = file: README.md
+long_description_content_type = text/markdown
author = Google
author_email = [email protected]
url = https://github.com/google/bumble
[options]
python_requires = >=3.8
-packages = bumble, bumble.transport, bumble.apps, bumble.apps.link_relay
+packages = bumble, bumble.transport, bumble.profiles, bumble.apps, bumble.apps.link_relay
package_dir =
bumble = bumble
bumble.apps = apps
@@ -52,15 +54,17 @@
bumble-scan = bumble.apps.scan:main
bumble-show = bumble.apps.show:main
bumble-unbond = bumble.apps.unbond:main
+ bumble-usb-probe = bumble.apps.usb_probe:main
bumble-link-relay = bumble.apps.link_relay.link_relay:main
[options.extras_require]
+build =
+ build >= 0.7
test =
pytest >= 6.2
pytest-asyncio >= 0.17
development =
invoke >= 1.4
- build >= 0.7
nox >= 2022
documentation =
mkdocs >= 1.2.3
diff --git a/tasks.py b/tasks.py
index c368291..61f3dd6 100644
--- a/tasks.py
+++ b/tasks.py
@@ -23,35 +23,52 @@
ns = Collection()
+# Building
build_tasks = Collection()
-ns.add_collection(build_tasks, name='build')
-
+ns.add_collection(build_tasks, name="build")
@task
-def build(ctx):
- ctx.run('python -m build')
+def build(ctx, install=False):
+ if install:
+ ctx.run('python -m pip install .[build]')
-build_tasks.add_task(build, default=True, name='build')
+ ctx.run("python -m build")
+build_tasks.add_task(build, default=True)
+
+@task
+def release_build(ctx):
+ build(ctx, install=True)
+
+build_tasks.add_task(release_build, name="release")
+
+@task
+def mkdocs(ctx):
+ ctx.run("mkdocs build -f docs/mkdocs/mkdocs.yml")
+
+build_tasks.add_task(mkdocs, name="mkdocs")
+
+# Testing
test_tasks = Collection()
-ns.add_collection(test_tasks, name='test')
+ns.add_collection(test_tasks, name="test")
@task
-def test(ctx, filter=None, junit=False):
+def test(ctx, filter=None, junit=False, install=False):
+ # Install the package before running the tests
+ if install:
+ ctx.run("python -m pip install .[test]")
+
args = ""
if junit:
args += "--junit-xml test-results.xml"
if filter is not None:
args += " -k '{}'".format(filter)
- ctx.run('python -m pytest {} {}'
- .format(os.path.join(ROOT_DIR, "tests"), args))
+ ctx.run("python -m pytest {} {}".format(os.path.join(ROOT_DIR, "tests"), args))
-test_tasks.add_task(test, name='test', default=True)
-
+test_tasks.add_task(test, default=True)
@task
-def mkdocs(ctx):
- ctx.run('mkdocs build -f docs/mkdocs/mkdocs.yml')
+def release_test(ctx):
+ test(ctx, install=True)
-
-ns.add_task(mkdocs)
+test_tasks.add_task(release_test, name="release")
diff --git a/tests/gatt_test.py b/tests/gatt_test.py
index 5df6e08..4bfcce8 100644
--- a/tests/gatt_test.py
+++ b/tests/gatt_test.py
@@ -419,10 +419,12 @@
assert(len(c) == 1)
c3 = c[0]
+ c1._called = False
c1._last_update = None
- def on_c1_update(connection, value):
- c1._last_update = (connection, value)
+ def on_c1_update(value):
+ c1._called = True
+ c1._last_update = value
c1.on('update', on_c1_update)
await peer.subscribe(c1)
@@ -434,44 +436,73 @@
assert(not characteristic1._last_subscription[2])
await server.indicate_subscribers(characteristic1)
await async_barrier()
- assert(c1._last_update is None)
+ assert(not c1._called)
await server.notify_subscribers(characteristic1)
await async_barrier()
- assert(c1._last_update is not None)
- assert(c1._last_update[1] == characteristic1.value)
+ assert(c1._called)
+ assert(c1._last_update == characteristic1.value)
+ c1._called = False
+ await peer.unsubscribe(c1)
+ await server.notify_subscribers(characteristic1)
+ assert(not c1._called)
+
+ c2._called = False
c2._last_update = None
def on_c2_update(value):
- c2._last_update = (connection, value)
+ c2._called = True
+ c2._last_update = value
await peer.subscribe(c2, on_c2_update)
await async_barrier()
await server.notify_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier()
- assert(c2._last_update is None)
+ assert(not c2._called)
await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier()
- assert(c2._last_update is not None)
- assert(c2._last_update[1] == characteristic2.value)
+ assert(c2._called)
+ assert(c2._last_update == characteristic2.value)
- c3._last_update = None
+ c2._called = False
+ await peer.unsubscribe(c2, on_c2_update)
+ await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
+ await async_barrier()
+ assert(not c2._called)
- def on_c3_update(connection, value):
- c3._last_update = (connection, value)
+ def on_c3_update(value):
+ c3._called = True
+ c3._last_update = value
+
+ def on_c3_update_2(value):
+ c3._called_2 = True
+ c3._last_update_2 = value
c3.on('update', on_c3_update)
- await peer.subscribe(c3)
+ await peer.subscribe(c3, on_c3_update_2)
await async_barrier()
await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier()
- assert(c3._last_update is not None)
- assert(c3._last_update[1] == characteristic3.value)
+ assert(c3._called)
+ assert(c3._last_update == characteristic3.value)
+ assert(c3._called_2)
+ assert(c3._last_update_2 == characteristic3.value)
characteristic3.value = bytes([1, 2, 3])
await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier()
- assert(c3._last_update is not None)
- assert(c3._last_update[1] == characteristic3.value)
+ assert(c3._called)
+ assert(c3._last_update == characteristic3.value)
+ assert(c3._called_2)
+ assert(c3._last_update_2 == characteristic3.value)
+
+ c3._called = False
+ c3._called_2 = False
+ await peer.unsubscribe(c3)
+ await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
+ await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
+ await async_barrier()
+ assert(not c3._called)
+ assert(not c3._called_2)
# -----------------------------------------------------------------------------
diff --git a/tests/hci_test.py b/tests/hci_test.py
index c4da67e..6331847 100644
--- a/tests/hci_test.py
+++ b/tests/hci_test.py
@@ -294,8 +294,8 @@
# -----------------------------------------------------------------------------
-def test_HCI_LE_Add_Device_To_White_List_Command():
- command = HCI_LE_Add_Device_To_White_List_Command(
+def test_HCI_LE_Add_Device_To_Filter_Accept_List_Command():
+ command = HCI_LE_Add_Device_To_Filter_Accept_List_Command(
address_type = 1,
address = Address('00:11:22:33:44:55')
)
@@ -303,8 +303,8 @@
# -----------------------------------------------------------------------------
-def test_HCI_LE_Remove_Device_From_White_List_Command():
- command = HCI_LE_Remove_Device_From_White_List_Command(
+def test_HCI_LE_Remove_Device_From_Filter_Accept_List_Command():
+ command = HCI_LE_Remove_Device_From_Filter_Accept_List_Command(
address_type = 1,
address = Address('00:11:22:33:44:55')
)
@@ -344,6 +344,23 @@
# -----------------------------------------------------------------------------
+def test_HCI_LE_Set_Extended_Scan_Parameters_Command():
+ command = HCI_LE_Set_Extended_Scan_Parameters_Command(
+ own_address_type=Address.RANDOM_DEVICE_ADDRESS,
+ scanning_filter_policy=HCI_LE_Set_Extended_Scan_Parameters_Command.BASIC_FILTERED_POLICY,
+ scanning_phys=(1 << HCI_LE_Set_Extended_Scan_Parameters_Command.LE_1M_PHY | 1 << HCI_LE_Set_Extended_Scan_Parameters_Command.LE_CODED_PHY | 1 << 4),
+ scan_types=[
+ HCI_LE_Set_Extended_Scan_Parameters_Command.ACTIVE_SCANNING,
+ HCI_LE_Set_Extended_Scan_Parameters_Command.ACTIVE_SCANNING,
+ HCI_LE_Set_Extended_Scan_Parameters_Command.PASSIVE_SCANNING
+ ],
+ scan_intervals=[1, 2, 3],
+ scan_windows=[4, 5, 6]
+ )
+ basic_check(command)
+
+
+# -----------------------------------------------------------------------------
def test_address():
a = Address('C4:F2:17:1A:1D:BB')
assert(not a.is_public)
@@ -391,11 +408,12 @@
test_HCI_LE_Set_Scan_Parameters_Command()
test_HCI_LE_Set_Scan_Enable_Command()
test_HCI_LE_Create_Connection_Command()
- test_HCI_LE_Add_Device_To_White_List_Command()
- test_HCI_LE_Remove_Device_From_White_List_Command()
+ test_HCI_LE_Add_Device_To_Filter_Accept_List_Command()
+ test_HCI_LE_Remove_Device_From_Filter_Accept_List_Command()
test_HCI_LE_Connection_Update_Command()
test_HCI_LE_Read_Remote_Features_Command()
test_HCI_LE_Set_Default_PHY_Command()
+ test_HCI_LE_Set_Extended_Scan_Parameters_Command()
# -----------------------------------------------------------------------------
diff --git a/tests/self_test.py b/tests/self_test.py
index a1d2ddd..7316b2f 100644
--- a/tests/self_test.py
+++ b/tests/self_test.py
@@ -164,6 +164,44 @@
# -----------------------------------------------------------------------------
[email protected]
+async def test_self_gatt_long_read():
+ # Create two devices, each with a controller, attached to the same link
+ two_devices = TwoDevices()
+
+ # Add some GATT characteristics to device 1
+ characteristics = [
+ Characteristic(
+ f'3A143AD7-D4A7-436B-97D6-5B62C315{i:04X}',
+ Characteristic.READ,
+ Characteristic.READABLE,
+ bytes([x & 255 for x in range(i)])
+ )
+ for i in range(0, 513)
+ ]
+
+ service = Service('8140E247-04F0-42C1-BC34-534C344DAFCA', characteristics)
+ two_devices.devices[1].add_service(service)
+
+ # Start
+ await two_devices.devices[0].power_on()
+ await two_devices.devices[1].power_on()
+
+ # Connect the two devices
+ connection = await two_devices.devices[0].connect(two_devices.devices[1].random_address)
+ peer = Peer(connection)
+
+ result = await peer.discover_service(service.uuid)
+ assert(len(result) == 1)
+ found_service = result[0]
+ found_characteristics = await found_service.discover_characteristics()
+ assert(len(found_characteristics) == 513)
+ for (i, characteristic) in enumerate(found_characteristics):
+ value = await characteristic.read_value()
+ assert(value == characteristics[i].value)
+
+
+# -----------------------------------------------------------------------------
async def _test_self_smp_with_configs(pairing_config1, pairing_config2):
# Create two devices, each with a controller, attached to the same link
two_devices = TwoDevices()
@@ -208,8 +246,7 @@
SC = [False, True]
MITM = [False, True]
# Key distribution is a 4-bit bitmask
-# IdKey is necessary for current SMP structure
-KEY_DIST = [i for i in range(16) if (i & SMP_ID_KEY_DISTRIBUTION_FLAG)]
+KEY_DIST = range(16)
@pytest.mark.asyncio
@pytest.mark.parametrize('io_cap, sc, mitm, key_dist',
@@ -274,7 +311,7 @@
pairing_config2.delegate.peer_delegate = pairing_config1.delegate
await _test_self_smp_with_configs(pairing_config1, pairing_config2)
-
+
# -----------------------------------------------------------------------------
@@ -323,6 +360,7 @@
async def run_test_self():
await test_self_connection()
await test_self_gatt()
+ await test_self_gatt_long_read()
await test_self_smp()
await test_self_smp_reject()
await test_self_smp_wrong_pin()