1# Copyright 2021-2022 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# ----------------------------------------------------------------------------- 18import asyncio 19import os 20import logging 21import click 22from bumble.company_ids import COMPANY_IDENTIFIERS 23 24from bumble.colors import color 25from bumble.core import name_or_number 26from bumble.hci import ( 27 map_null_terminated_utf8_string, 28 HCI_SUCCESS, 29 HCI_LE_SUPPORTED_FEATURES_NAMES, 30 HCI_VERSION_NAMES, 31 LMP_VERSION_NAMES, 32 HCI_Command, 33 HCI_Command_Complete_Event, 34 HCI_Command_Status_Event, 35 HCI_READ_BD_ADDR_COMMAND, 36 HCI_Read_BD_ADDR_Command, 37 HCI_READ_LOCAL_NAME_COMMAND, 38 HCI_Read_Local_Name_Command, 39 HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND, 40 HCI_LE_Read_Maximum_Data_Length_Command, 41 HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND, 42 HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command, 43 HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND, 44 HCI_LE_Read_Maximum_Advertising_Data_Length_Command, 45) 46from bumble.host import Host 47from bumble.transport import open_transport_or_link 48 49 50# ----------------------------------------------------------------------------- 51def command_succeeded(response): 52 if isinstance(response, HCI_Command_Status_Event): 53 return response.status == HCI_SUCCESS 54 if isinstance(response, HCI_Command_Complete_Event): 55 return response.return_parameters.status == HCI_SUCCESS 56 return False 57 58 59# ----------------------------------------------------------------------------- 60async def get_classic_info(host): 61 if host.supports_command(HCI_READ_BD_ADDR_COMMAND): 62 response = await host.send_command(HCI_Read_BD_ADDR_Command()) 63 if command_succeeded(response): 64 print() 65 print( 66 color('Classic Address:', 'yellow'), response.return_parameters.bd_addr 67 ) 68 69 if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND): 70 response = await host.send_command(HCI_Read_Local_Name_Command()) 71 if command_succeeded(response): 72 print() 73 print( 74 color('Local Name:', 'yellow'), 75 map_null_terminated_utf8_string(response.return_parameters.local_name), 76 ) 77 78 79# ----------------------------------------------------------------------------- 80async def get_le_info(host): 81 print() 82 83 if host.supports_command(HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND): 84 response = await host.send_command( 85 HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command() 86 ) 87 if command_succeeded(response): 88 print( 89 color('LE Number Of Supported Advertising Sets:', 'yellow'), 90 response.return_parameters.num_supported_advertising_sets, 91 '\n', 92 ) 93 94 if host.supports_command(HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND): 95 response = await host.send_command( 96 HCI_LE_Read_Maximum_Advertising_Data_Length_Command() 97 ) 98 if command_succeeded(response): 99 print( 100 color('LE Maximum Advertising Data Length:', 'yellow'), 101 response.return_parameters.max_advertising_data_length, 102 '\n', 103 ) 104 105 if host.supports_command(HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND): 106 response = await host.send_command(HCI_LE_Read_Maximum_Data_Length_Command()) 107 if command_succeeded(response): 108 print( 109 color('Maximum Data Length:', 'yellow'), 110 ( 111 f'tx:{response.return_parameters.supported_max_tx_octets}/' 112 f'{response.return_parameters.supported_max_tx_time}, ' 113 f'rx:{response.return_parameters.supported_max_rx_octets}/' 114 f'{response.return_parameters.supported_max_rx_time}' 115 ), 116 '\n', 117 ) 118 119 print(color('LE Features:', 'yellow')) 120 for feature in host.supported_le_features: 121 print(' ', name_or_number(HCI_LE_SUPPORTED_FEATURES_NAMES, feature)) 122 123 124# ----------------------------------------------------------------------------- 125async def async_main(transport): 126 print('<<< connecting to HCI...') 127 async with await open_transport_or_link(transport) as (hci_source, hci_sink): 128 print('<<< connected') 129 130 host = Host(hci_source, hci_sink) 131 await host.reset() 132 133 # Print version 134 print(color('Version:', 'yellow')) 135 print( 136 color(' Manufacturer: ', 'green'), 137 name_or_number(COMPANY_IDENTIFIERS, host.local_version.company_identifier), 138 ) 139 print( 140 color(' HCI Version: ', 'green'), 141 name_or_number(HCI_VERSION_NAMES, host.local_version.hci_version), 142 ) 143 print(color(' HCI Subversion:', 'green'), host.local_version.hci_subversion) 144 print( 145 color(' LMP Version: ', 'green'), 146 name_or_number(LMP_VERSION_NAMES, host.local_version.lmp_version), 147 ) 148 print(color(' LMP Subversion:', 'green'), host.local_version.lmp_subversion) 149 150 # Get the Classic info 151 await get_classic_info(host) 152 153 # Get the LE info 154 await get_le_info(host) 155 156 # Print the list of commands supported by the controller 157 print() 158 print(color('Supported Commands:', 'yellow')) 159 for command in host.supported_commands: 160 print(' ', HCI_Command.command_name(command)) 161 162 163# ----------------------------------------------------------------------------- 164@click.command() 165@click.argument('transport') 166def main(transport): 167 logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper()) 168 asyncio.run(async_main(transport)) 169 170 171# ----------------------------------------------------------------------------- 172if __name__ == '__main__': 173 main() 174