├── LICENSE ├── README.md └── scanner.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, David G. Young 2 | Copyright (c) 2015, Damian Kołakowski 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of intel-edison-playground nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLE Scanner 2 | 3 | A command line utility for Bluetooth LE scanning on Linux / BlueZ 4 | 5 | ## Compiling 6 | 7 | 1. Install prerequisites 8 | 9 | `sudo apt-get install libbluetooth-dev` 10 | 11 | 2. Compile the program 12 | 13 | `cc scanner.c -lbluetooth -o scanner` 14 | 15 | ## Running 16 | 17 | Run the program like this: 18 | 19 | `./scanner` 20 | 21 | Each time it detects a Bluetooth LE advertisement it prints out the MAC address of the advertisting device, the RSSI and the bytes of the advertisement like this: 22 | 23 | ``` 24 | B8:27:EB:1F:93:4D -68 02 01 06 11 06 82 75 25 D9 37 9D D7 8F 5F 4A F4 20 00 00 75 30 25 | 71:5C:23:9D:BC:7F -68 02 01 1A 02 0A 0C 0B FF 4C 00 10 06 03 1A 3B D4 B2 EB 26 | B8:27:EB:1F:93:4D -68 02 01 06 11 06 82 75 25 D9 37 9D D7 8F 5F 4A F4 20 00 00 75 30 27 | ``` 28 | 29 | In the first line above, `B8:27:EB:1F:93:4D` is the hardware MAC address, -68 is the RSSI and the remaining values are the hex bytes of the advertisement. 30 | 31 | The program will auto exit after one of two things happens: (a) 10 seconds pass with no detections. (b) 1000 advertisements are detected. It does this for the sake of stability. Rarely, the underlying bluetooth stack will crash. By exiting after 10 seconds it allows you to restart the program and get new detections. Running forever is actually less stable. 32 | 33 | ## Need Help? Want to change this? 34 | 35 | Feel free to open an Issue or submit a Pull Request on Github here: https://github.com/davidgyoung/ble-scanner 36 | 37 | ## License 38 | 39 | [BSD 3](./LICENSE) 40 | 41 | ## Credits 42 | 43 | This program is based off of code by Damian Kołakowski here https://github.com/damian-kolakowski/intel-edison-playground/blob/master/scan.c 44 | 45 | -------------------------------------------------------------------------------- /scanner.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 David G. Young 2 | // Copyright (c) 2015 Damian Kołakowski. All rights reserved. 3 | 4 | // cc scanner.c -lbluetooth -o scanner 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | int device; 18 | 19 | struct hci_request ble_hci_request(uint16_t ocf, int clen, void * status, void * cparam) 20 | { 21 | struct hci_request rq; 22 | memset(&rq, 0, sizeof(rq)); 23 | rq.ogf = OGF_LE_CTL; 24 | rq.ocf = ocf; 25 | rq.cparam = cparam; 26 | rq.clen = clen; 27 | rq.rparam = status; 28 | rq.rlen = 1; 29 | return rq; 30 | } 31 | 32 | // cleanup and exit the program with exit code 0 33 | void exit_clean() 34 | { 35 | int ret, status; 36 | 37 | // Disable scanning. 38 | 39 | le_set_scan_enable_cp scan_cp; 40 | memset(&scan_cp, 0, sizeof(scan_cp)); 41 | scan_cp.enable = 0x00; // Disable flag. 42 | 43 | struct hci_request disable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp); 44 | ret = hci_send_req(device, &disable_adv_rq, 1000); 45 | if ( ret < 0 ) 46 | perror("Failed to disable scan."); 47 | 48 | hci_close_dev(device); 49 | exit( 0 ); 50 | } 51 | 52 | // handles timeout 53 | void signal_handler( int s ) 54 | { 55 | //printf( "received SIGALRM\n" ); 56 | exit_clean(); 57 | } 58 | 59 | int main() 60 | { 61 | int ret, status; 62 | 63 | // Get HCI device. 64 | 65 | device = hci_open_dev(1); 66 | if ( device < 0 ) { 67 | device = hci_open_dev(0); 68 | if (device >= 0) { 69 | printf("Using hci0\n"); 70 | } 71 | } 72 | else { 73 | printf("Using hci1\n"); 74 | } 75 | 76 | if ( device < 0 ) { 77 | perror("Failed to open HCI device."); 78 | return 0; 79 | } 80 | 81 | // Set BLE scan parameters. 82 | 83 | le_set_scan_parameters_cp scan_params_cp; 84 | memset(&scan_params_cp, 0, sizeof(scan_params_cp)); 85 | scan_params_cp.type = 0x00; 86 | scan_params_cp.interval = htobs(0x0010); 87 | scan_params_cp.window = htobs(0x0010); 88 | scan_params_cp.own_bdaddr_type = 0x00; // Public Device Address (default). 89 | scan_params_cp.filter = 0x00; // Accept all. 90 | 91 | struct hci_request scan_params_rq = ble_hci_request(OCF_LE_SET_SCAN_PARAMETERS, LE_SET_SCAN_PARAMETERS_CP_SIZE, &status, &scan_params_cp); 92 | 93 | ret = hci_send_req(device, &scan_params_rq, 1000); 94 | if ( ret < 0 ) { 95 | hci_close_dev(device); 96 | perror("Failed to set scan parameters data."); 97 | return 0; 98 | } 99 | 100 | // Set BLE events report mask. 101 | 102 | le_set_event_mask_cp event_mask_cp; 103 | memset(&event_mask_cp, 0, sizeof(le_set_event_mask_cp)); 104 | int i = 0; 105 | for ( i = 0 ; i < 8 ; i++ ) event_mask_cp.mask[i] = 0xFF; 106 | 107 | struct hci_request set_mask_rq = ble_hci_request(OCF_LE_SET_EVENT_MASK, LE_SET_EVENT_MASK_CP_SIZE, &status, &event_mask_cp); 108 | ret = hci_send_req(device, &set_mask_rq, 1000); 109 | if ( ret < 0 ) { 110 | hci_close_dev(device); 111 | perror("Failed to set event mask."); 112 | return 0; 113 | } 114 | 115 | // Enable scanning. 116 | 117 | le_set_scan_enable_cp scan_cp; 118 | memset(&scan_cp, 0, sizeof(scan_cp)); 119 | scan_cp.enable = 0x01; // Enable flag. 120 | scan_cp.filter_dup = 0x00; // Filtering disabled. 121 | 122 | struct hci_request enable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp); 123 | 124 | ret = hci_send_req(device, &enable_adv_rq, 1000); 125 | if ( ret < 0 ) { 126 | hci_close_dev(device); 127 | perror("Failed to enable scan."); 128 | return 0; 129 | } 130 | 131 | // Get Results. 132 | 133 | struct hci_filter nf; 134 | hci_filter_clear(&nf); 135 | hci_filter_set_ptype(HCI_EVENT_PKT, &nf); 136 | hci_filter_set_event(EVT_LE_META_EVENT, &nf); 137 | if ( setsockopt(device, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0 ) { 138 | hci_close_dev(device); 139 | perror("Could not set socket options\n"); 140 | return 0; 141 | } 142 | 143 | 144 | uint8_t buf[HCI_MAX_EVENT_SIZE]; 145 | evt_le_meta_event * meta_event; 146 | le_advertising_info * info; 147 | int len; 148 | int count = 0; 149 | 150 | const int timeout = 10; 151 | const int reset_timeout = 1; // wether to reset the timer on a received scan event (continuous scanning) 152 | const int max_count = 1000; 153 | 154 | // Install a signal handler so that we can set the exit code and clean up 155 | if ( signal( SIGALRM, signal_handler ) == SIG_ERR ) 156 | { 157 | hci_close_dev(device); 158 | perror( "Could not install signal handler\n" ); 159 | return 0; 160 | } 161 | 162 | if ( timeout > 0 ) 163 | alarm( timeout ); // set the alarm timer, when time is up the program will be terminated 164 | 165 | sigset_t sigalrm_set; // apparently the signal must be unblocked in some cases 166 | sigemptyset ( &sigalrm_set ); 167 | sigaddset ( &sigalrm_set, SIGALRM ); 168 | if ( sigprocmask( SIG_UNBLOCK, &sigalrm_set, NULL ) != 0 ) 169 | { 170 | hci_close_dev(device); 171 | perror( "Could not unblock alarm signal" ); 172 | return 0; 173 | } 174 | 175 | // Keep scanning until the timeout is triggered or we have seen lots of advertisements. Then exit. 176 | // We exit in this case because the scan may have failed or stopped. Higher level code can restart 177 | while ( count < max_count || max_count <= 0 ) 178 | { 179 | len = read(device, buf, sizeof(buf)); 180 | if ( len >= HCI_EVENT_HDR_SIZE ) 181 | { 182 | meta_event = (evt_le_meta_event*)(buf+HCI_EVENT_HDR_SIZE+1); 183 | if ( meta_event->subevent == EVT_LE_ADVERTISING_REPORT ) 184 | { 185 | count++; 186 | if ( reset_timeout != 0 && timeout > 0 ) // reset/restart the alarm timer 187 | alarm( timeout ); 188 | 189 | // print results 190 | uint8_t reports_count = meta_event->data[0]; 191 | void * offset = meta_event->data + 1; 192 | while ( reports_count-- ) 193 | { 194 | info = (le_advertising_info *)offset; 195 | char addr[18]; 196 | ba2str( &(info->bdaddr), addr); 197 | printf("%s %d", addr, (int8_t)info->data[info->length]); 198 | for (int i = 0; i < info->length; i++) 199 | printf(" %02X", (unsigned char)info->data[i]); 200 | printf("\n"); 201 | offset = info->data + info->length + 2; 202 | } 203 | } 204 | } 205 | } 206 | 207 | // Prevent SIGALARM from firing during the clean up procedure 208 | if ( sigprocmask( SIG_BLOCK, &sigalrm_set, NULL ) != 0 ) 209 | { 210 | hci_close_dev(device); 211 | perror( "Could not block alarm signal" ); 212 | return 0; 213 | } 214 | 215 | exit_clean(); 216 | return 0; 217 | } 218 | --------------------------------------------------------------------------------