|
| 1 | +# Copyright 2024 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +# ----------------------------------------------------------------------------- |
| 16 | +# Imports |
| 17 | +# ----------------------------------------------------------------------------- |
| 18 | +from __future__ import annotations |
| 19 | + |
| 20 | +import asyncio |
| 21 | +import logging |
| 22 | +import sys |
| 23 | +import os |
| 24 | +import functools |
| 25 | + |
| 26 | +from bumble import core |
| 27 | +from bumble import hci |
| 28 | +from bumble.device import Connection, Device, ChannelSoundingCapabilities |
| 29 | +from bumble.transport import open_transport_or_link |
| 30 | + |
| 31 | +# From https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/system/gd/hci/distance_measurement_manager.cc. |
| 32 | +CS_TONE_ANTENNA_CONFIG_MAPPING_TABLE = [ |
| 33 | + [0, 4, 5, 6], |
| 34 | + [1, 7, 7, 7], |
| 35 | + [2, 7, 7, 7], |
| 36 | + [3, 7, 7, 7], |
| 37 | +] |
| 38 | +CS_PREFERRED_PEER_ANTENNA_MAPPING_TABLE = [1, 1, 1, 1, 3, 7, 15, 3] |
| 39 | +CS_ANTENNA_PERMUTATION_ARRAY = [ |
| 40 | + [1, 2, 3, 4], |
| 41 | + [2, 1, 3, 4], |
| 42 | + [1, 3, 2, 4], |
| 43 | + [3, 1, 2, 4], |
| 44 | + [3, 2, 1, 4], |
| 45 | + [2, 3, 1, 4], |
| 46 | + [1, 2, 4, 3], |
| 47 | + [2, 1, 4, 3], |
| 48 | + [1, 4, 2, 3], |
| 49 | + [4, 1, 2, 3], |
| 50 | + [4, 2, 1, 3], |
| 51 | + [2, 4, 1, 3], |
| 52 | + [1, 4, 3, 2], |
| 53 | + [4, 1, 3, 2], |
| 54 | + [1, 3, 4, 2], |
| 55 | + [3, 1, 4, 2], |
| 56 | + [3, 4, 1, 2], |
| 57 | + [4, 3, 1, 2], |
| 58 | + [4, 2, 3, 1], |
| 59 | + [2, 4, 3, 1], |
| 60 | + [4, 3, 2, 1], |
| 61 | + [3, 4, 2, 1], |
| 62 | + [3, 2, 4, 1], |
| 63 | + [2, 3, 4, 1], |
| 64 | +] |
| 65 | + |
| 66 | + |
| 67 | +# ----------------------------------------------------------------------------- |
| 68 | +async def main() -> None: |
| 69 | + if len(sys.argv) < 3: |
| 70 | + print( |
| 71 | + 'Usage: run_channel_sounding.py <config-file> <transport-spec-for-device>' |
| 72 | + '[target_address](If missing, run as reflector)' |
| 73 | + ) |
| 74 | + print('example: run_channel_sounding.py cs_reflector.json usb:0') |
| 75 | + print( |
| 76 | + 'example: run_channel_sounding.py cs_initiator.json usb:0 F0:F1:F2:F3:F4:F5' |
| 77 | + ) |
| 78 | + return |
| 79 | + |
| 80 | + print('<<< connecting to HCI...') |
| 81 | + async with await open_transport_or_link(sys.argv[2]) as hci_transport: |
| 82 | + print('<<< connected') |
| 83 | + |
| 84 | + device = Device.from_config_file_with_hci( |
| 85 | + sys.argv[1], hci_transport.source, hci_transport.sink |
| 86 | + ) |
| 87 | + await device.power_on() |
| 88 | + assert (local_cs_capabilities := device.cs_capabilities) |
| 89 | + |
| 90 | + if len(sys.argv) == 3: |
| 91 | + print('<<< Start Advertising') |
| 92 | + await device.start_advertising( |
| 93 | + own_address_type=hci.OwnAddressType.RANDOM, auto_restart=True |
| 94 | + ) |
| 95 | + |
| 96 | + def on_cs_capabilities( |
| 97 | + connection: Connection, capabilities: ChannelSoundingCapabilities |
| 98 | + ): |
| 99 | + del capabilities |
| 100 | + print('<<< Set CS Settings') |
| 101 | + asyncio.create_task(device.set_default_cs_settings(connection)) |
| 102 | + |
| 103 | + device.on( |
| 104 | + 'connection', |
| 105 | + lambda connection: connection.on( |
| 106 | + 'channel_sounding_capabilities', |
| 107 | + functools.partial(on_cs_capabilities, connection), |
| 108 | + ), |
| 109 | + ) |
| 110 | + else: |
| 111 | + target_address = hci.Address(sys.argv[3]) |
| 112 | + |
| 113 | + print(f'<<< Connecting to {target_address}') |
| 114 | + connection = await device.connect( |
| 115 | + target_address, transport=core.BT_LE_TRANSPORT |
| 116 | + ) |
| 117 | + print('<<< ACL Connected') |
| 118 | + if not (await device.get_long_term_key(connection.handle, b'', 0)): |
| 119 | + print('<<< No bond, start pairing') |
| 120 | + await connection.pair() |
| 121 | + print('<<< Pairing complete') |
| 122 | + |
| 123 | + print('<<< Encrypting Connection') |
| 124 | + await connection.encrypt() |
| 125 | + |
| 126 | + print('<<< Getting remote CS Capabilities...') |
| 127 | + remote_capabilities = await device.get_remote_cs_capabilities(connection) |
| 128 | + print('<<< Set CS Settings...') |
| 129 | + await device.set_default_cs_settings(connection) |
| 130 | + print('<<< Set CS Config...') |
| 131 | + config = await device.create_cs_config(connection) |
| 132 | + print('<<< Enable CS Security...') |
| 133 | + await device.enable_cs_security(connection) |
| 134 | + tone_antenna_config_selection = CS_TONE_ANTENNA_CONFIG_MAPPING_TABLE[ |
| 135 | + local_cs_capabilities.num_antennas_supported - 1 |
| 136 | + ][remote_capabilities.num_antennas_supported - 1] |
| 137 | + print('<<< Set CS Procedure Parameters...') |
| 138 | + await device.set_cs_procedure_parameters( |
| 139 | + connection=connection, |
| 140 | + config=config, |
| 141 | + tone_antenna_config_selection=tone_antenna_config_selection, |
| 142 | + preferred_peer_antenna=CS_PREFERRED_PEER_ANTENNA_MAPPING_TABLE[ |
| 143 | + tone_antenna_config_selection |
| 144 | + ], |
| 145 | + ) |
| 146 | + print('<<< Enable CS Procedure...') |
| 147 | + await device.enable_cs_procedure(connection=connection, config=config) |
| 148 | + |
| 149 | + await hci_transport.source.terminated |
| 150 | + |
| 151 | + |
| 152 | +# ----------------------------------------------------------------------------- |
| 153 | +logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper()) |
| 154 | +asyncio.run(main()) |
0 commit comments