Last active
April 29, 2025 07:30
-
-
Save davidgyoung/0127ad914ea1b6b0f21a53ac3bb97ad3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// To compile this program, you need to install: | |
// sudo apt-get install libbluetooth-dev | |
// Then you can compile it with: | |
// cc scanner.c -lbluetooth -o scanner | |
// You can then run it with: | |
// ./scanner | |
// Copyright (c) 2021 David G. Young | |
// Copyright (c) 2015 Damian Kołakowski. All rights reserved. | |
// License: BSD 3. See: https://github.com/davidgyoung/ble-scanner | |
#include <stdlib.h> | |
#include <errno.h> | |
#include <unistd.h> | |
#include <sys/ioctl.h> | |
#include <sys/socket.h> | |
#include <bluetooth/bluetooth.h> | |
#include <bluetooth/hci.h> | |
#include <bluetooth/hci_lib.h> | |
#include <time.h> | |
struct hci_request ble_hci_request(uint16_t ocf, int clen, void * status, void * cparam) | |
{ | |
struct hci_request rq; | |
memset(&rq, 0, sizeof(rq)); | |
rq.ogf = OGF_LE_CTL; | |
rq.ocf = ocf; | |
rq.cparam = cparam; | |
rq.clen = clen; | |
rq.rparam = status; | |
rq.rlen = 1; | |
return rq; | |
} | |
int main() | |
{ | |
int ret, status; | |
// Get HCI device. | |
int device = hci_open_dev(1); | |
if ( device < 0 ) { | |
device = hci_open_dev(0); | |
if (device >= 0) { | |
printf("Using hci0\n"); | |
} | |
} | |
else { | |
printf("Using hci1\n"); | |
} | |
if ( device < 0 ) { | |
perror("Failed to open HCI device."); | |
return 0; | |
} | |
// Set BLE scan parameters. | |
le_set_scan_parameters_cp scan_params_cp; | |
memset(&scan_params_cp, 0, sizeof(scan_params_cp)); | |
scan_params_cp.type = 0x00; | |
scan_params_cp.interval = htobs(0x0010); | |
scan_params_cp.window = htobs(0x0010); | |
scan_params_cp.own_bdaddr_type = 0x00; // Public Device Address (default). | |
scan_params_cp.filter = 0x00; // Accept all. | |
struct hci_request scan_params_rq = ble_hci_request(OCF_LE_SET_SCAN_PARAMETERS, LE_SET_SCAN_PARAMETERS_CP_SIZE, &status, &scan_params_cp); | |
ret = hci_send_req(device, &scan_params_rq, 1000); | |
if ( ret < 0 ) { | |
hci_close_dev(device); | |
perror("Failed to set scan parameters data."); | |
return 0; | |
} | |
// Set BLE events report mask. | |
le_set_event_mask_cp event_mask_cp; | |
memset(&event_mask_cp, 0, sizeof(le_set_event_mask_cp)); | |
int i = 0; | |
for ( i = 0 ; i < 8 ; i++ ) event_mask_cp.mask[i] = 0xFF; | |
struct hci_request set_mask_rq = ble_hci_request(OCF_LE_SET_EVENT_MASK, LE_SET_EVENT_MASK_CP_SIZE, &status, &event_mask_cp); | |
ret = hci_send_req(device, &set_mask_rq, 1000); | |
if ( ret < 0 ) { | |
hci_close_dev(device); | |
perror("Failed to set event mask."); | |
return 0; | |
} | |
// Enable scanning. | |
le_set_scan_enable_cp scan_cp; | |
memset(&scan_cp, 0, sizeof(scan_cp)); | |
scan_cp.enable = 0x01; // Enable flag. | |
scan_cp.filter_dup = 0x00; // Filtering disabled. | |
struct hci_request enable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp); | |
ret = hci_send_req(device, &enable_adv_rq, 1000); | |
if ( ret < 0 ) { | |
hci_close_dev(device); | |
perror("Failed to enable scan."); | |
return 0; | |
} | |
// Get Results. | |
struct hci_filter nf; | |
hci_filter_clear(&nf); | |
hci_filter_set_ptype(HCI_EVENT_PKT, &nf); | |
hci_filter_set_event(EVT_LE_META_EVENT, &nf); | |
if ( setsockopt(device, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0 ) { | |
hci_close_dev(device); | |
perror("Could not set socket options\n"); | |
return 0; | |
} | |
uint8_t buf[HCI_MAX_EVENT_SIZE]; | |
evt_le_meta_event * meta_event; | |
le_advertising_info * info; | |
int len; | |
int count = 0; | |
unsigned now = (unsigned)time(NULL); | |
unsigned last_detection_time = now; | |
// Keep scanning until we see nothing for 10 secs or we have seen lots of advertisements. Then exit. | |
// We exit in this case because the scan may have failed or stopped. Higher level code can restart | |
while ( last_detection_time - now < 10 && count < 1000 ) { | |
len = read(device, buf, sizeof(buf)); | |
if ( len >= HCI_EVENT_HDR_SIZE ) { | |
count++; | |
last_detection_time = (unsigned)time(NULL); | |
meta_event = (evt_le_meta_event*)(buf+HCI_EVENT_HDR_SIZE+1); | |
if ( meta_event->subevent == EVT_LE_ADVERTISING_REPORT ) { | |
uint8_t reports_count = meta_event->data[0]; | |
void * offset = meta_event->data + 1; | |
while ( reports_count-- ) { | |
info = (le_advertising_info *)offset; | |
char addr[18]; | |
ba2str(&(info->bdaddr), addr); | |
printf("%s %d", addr, (int8_t)info->data[info->length]); | |
for (int i = 0; i < info->length; i++) { | |
printf(" %02X", (unsigned char)info->data[i]); | |
} | |
printf("\n"); | |
offset = info->data + info->length + 2; | |
} | |
} | |
} | |
now = (unsigned)time(NULL); | |
} | |
// Disable scanning. | |
memset(&scan_cp, 0, sizeof(scan_cp)); | |
scan_cp.enable = 0x00; // Disable flag. | |
struct hci_request disable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp); | |
ret = hci_send_req(device, &disable_adv_rq, 1000); | |
if ( ret < 0 ) { | |
hci_close_dev(device); | |
perror("Failed to disable scan."); | |
return 0; | |
} | |
hci_close_dev(device); | |
return 0; | |
} |
I decided to convert this GIST into a hosted project and created an answer to your question here: davidgyoung/ble-scanner#1. Please feel free to comment there.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I gave it a try and it works well, but the output is a bit "bursty" for 1-2 seconds i get nothing then 3-4 things at once then nothing for 1-2 seconds and so on. Is there a way i can make it return results each 0.5 seconds constantly?