http://tvaira.free.fr/flower-power/ble-scan.c
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <sys/types.h>
#include <sys/socket.h>
// $ gcc ble-scan.c -lbluetooth -o ble-scan
#define EIR_FLAGS 0x01 /* flags */
#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A /* transmit power level */
#define EIR_DEVICE_ID 0x10 /* device ID */
#define EIR_MANUFACTURE_SPECIFIC 0xFF
void process_data(uint8_t *data, size_t data_len, le_advertising_info *info);
int connecter(int sock, char dest[18]);
void deconnecter(int sock, uint16_t handle);
int main (int argc, char **argv)
{
int sock, retval;
int i, len;
unsigned char buf[HCI_MAX_FRAME_SIZE];
char btAddress[18];
uint16_t handle;
struct sockaddr_hci addr;
struct hci_filter filter;
int encore = 1;
sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (-1 == sock)
{
perror("socket"); return 1;
}
hci_filter_clear(&filter);
hci_filter_all_ptypes(&filter);
hci_filter_all_events(&filter);
retval = setsockopt(sock, SOL_HCI, HCI_FILTER, &filter, sizeof(filter));
if (-1 == retval)
{
perror("setsockopt"); return 1;
}
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = 0;
retval = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
if (-1 == retval)
{
perror("bind"); return 1;
}
uint8_t scan_type = 0x00; /* Passive */
uint16_t interval = htobs(0x0010);
uint16_t window = htobs(0x0010);
uint8_t own_type = 0x00;
uint8_t filter_policy = 0x00; /* 1 -> Whitelist */
retval = hci_le_set_scan_parameters(sock, scan_type, interval, window, own_type, filter_policy, 1000);
//retval = hci_le_set_scan_parameters(sock, 0, 0x10, 0x10, 0, 0, 1000);
if (retval < 0)
{
perror("hci_le_set_scan_parameters"); //return 1;
}
retval = hci_le_set_scan_enable(sock, 1 /* 1 - turn on, 0 - turn off */, 0 /* 0-filtering disabled, 1-filter out duplicates */, 1000 /* timeout */);
if (retval < 0)
{
perror("hci_le_set_scan_enable"); //return 1;
}
do
{
memset (buf, 0, sizeof(buf));
retval = recv (sock, buf, sizeof(buf), 0);
if (-1 == retval)
{
perror("recv"); return 1;
}
printf ("# ");
for(i=0;i<retval;i++)
printf ("0x%02X ", buf[i]);
//printf ("%c ", buf[i]);
printf ("(%d)\n\n", retval);
/*printf ("# 0x%02X 0x%02X 0x%02X 0x%02X (%d)\n",
buf[0], buf[1],
buf[2], buf[3], retval);*/
switch (buf[1])
{
case EVT_CMD_STATUS: // 0x0F
if (buf[3])
{
printf ("Erreur !\n");
encore = 0;
}
else
{
printf ("Commande en cours\n");
}
break;
case EVT_INQUIRY_RESULT: // 0x02
printf ("Périphérique trouvé:\n");
printf (" * Adresse : %02x:%02x:%02x:%02x:%02x:%02x\n",
buf[9], buf[8],
buf[7], buf[6],
buf[5], buf[4]);
printf (" * Classe : 0x%02x%02x%02x\n\n",
buf[15], buf[14], buf[13]);
break;
case EVT_EXTENDED_INQUIRY_RESULT: // 0x2F
printf ("Périphérique trouvé:\n");
printf (" * Adresse : %02x:%02x:%02x:%02x:%02x:%02x\n",
buf[9], buf[8],
buf[7], buf[6],
buf[5], buf[4]);
printf (" * Classe : 0x%02x%02x%02x\n",
buf[14], buf[13], buf[12]);
printf (" * RSSI : %d\n\n", // Received Signal Strength Indication
buf[17]);
break;
case EVT_INQUIRY_COMPLETE: // 0x01
encore = 0;
break;
case EVT_LE_META_EVENT: // 0x3E
len = retval;
evt_le_meta_event *meta = (void *)(buf + (1 + HCI_EVENT_HDR_SIZE));
len -= (1 + HCI_EVENT_HDR_SIZE);
if (meta->subevent == EVT_LE_ADVERTISING_REPORT)
{
printf("EVT_LE_ADVERTISING_REPORT (0x%02X)\n", meta->subevent);
le_advertising_info *info = (le_advertising_info *) (meta->data + 1);
int8_t rssi;
ba2str(&info->bdaddr, btAddress);
printf("* %s (%s) [ ", btAddress, (info->bdaddr_type == LE_PUBLIC_ADDRESS) ? "public" : "random");
for (i = 0; i < info->length; i++)
{
printf("0x%02X ", info->data[i]);
}
rssi = *(info->data + info->length);
printf("] rssi = %d dBm\n", rssi);
if(info->length != 0)
{
int current_index = 0;
int data_error = 0;
while(!data_error && current_index < info->length)
{
size_t data_len = info->data[current_index];
if(data_len + 1 > info->length)
{
printf("EIR data length is longer than EIR packet length. %d + 1 > %d", (int)data_len, info->length);
data_error = 1;
}
else
{
process_data(info->data + current_index + 1, data_len, info);
current_index += data_len + 1;
}
}
}
else
printf("info->length == 0 !\n");
}
else
printf("EVT_LE = 0x%02X\n", meta->subevent);
//handle = connecter(sock, btAddress);
//deconnecter(sock, handle);
encore = 0;
break;
default:
break;
}
}
while (encore);
retval = hci_le_set_scan_enable(sock, 0 /* 1 - turn on, 0 - turn off */, 0 /* 0-filtering disabled, 1-filter out duplicates */, 1000 /* timeout */);
if (retval < 0)
{
perror("hci_le_set_scan_enable"); //return 1;
}
close (sock);
return 0;
}
void process_data(uint8_t *data, size_t data_len, le_advertising_info *info)
{
printf("process_data: %d octets\n", (int)data_len);
if(data[0] == EIR_NAME_SHORT || data[0] == EIR_NAME_COMPLETE)
{
size_t name_len = data_len - 1;
char *name = malloc(name_len + 1);
memset(name, 0, name_len + 1);
memcpy(name, &data[2], name_len);
char addr[18];
ba2str(&info->bdaddr, addr);
printf("addr=%s name=%s\n", addr, name);
free(name);
}
else if(data[0] == EIR_FLAGS)
{
printf("-> Flag type: len=%d\n", (int)data_len);
int i;
for(i=1; i<data_len; i++)
{
printf("\tFlag data: 0x%02X\n", data[i]); // 0x06 -> 0000 0110
}
/*
bit 0 LE Limited Discoverable Mode
bit 1 LE General Discoverable Mode
bit 2 BR/EDR Supported
bit 3 Simultaneous LE and BR/EDR to Same Device Capable (controller)
bit 4 Simultaneous LE and BR/EDR to Same Device Capable (Host)
*/
}
else if(data[0] == EIR_MANUFACTURE_SPECIFIC)
{
printf("-> Manufacture specific type: len=%d\n", (int)data_len);
// https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
// TODO int company_id = data[current_index + 2]
int i;
for(i=1; i<data_len; i++)
{
printf("\tData: 0x%02X\n", data[i]);
}
}
else if(data[0] == EIR_UUID128_SOME)
{
printf("-> UUID 128 type: len=%d\n", (int)data_len);
printf("\t");
int i;
for(i=data_len-1; i>0; i--)
{
printf("%02x", data[i]);
}
printf("\n");
}
else
{
printf("-> Unknown type: type=0x%02X\n", data[0]);
}
}
int connecter(int sock, char dest[18])
{
struct hci_dev_info di;
uint16_t handle;
char addr[18];
bdaddr_t bdaddr;
uint16_t interval, latency, max_ce_length, max_interval, min_ce_length;
uint16_t min_interval, supervision_timeout, window;
uint8_t initiator_filter, own_bdaddr_type, peer_bdaddr_type;
int retval;
char name[248];
if (hci_devinfo(0, &di) < 0)
{
perror("hci_devinfo");
}
ba2str(&di.bdaddr, addr);
printf("Device : %s [%s]\n", di.name, addr);
str2ba(dest, &bdaddr);
interval = htobs(0x0004);
window = htobs(0x0004);
//initiator_filter = 0x01; /* Use white list */
//peer_bdaddr_type = LE_RANDOM_ADDRESS;
own_bdaddr_type = 0x00;
min_interval = htobs(0x000F);
max_interval = htobs(0x000F);
latency = htobs(0x0000);
supervision_timeout = htobs(0x0C80);
min_ce_length = htobs(0x0001);
max_ce_length = htobs(0x0001);
retval = hci_le_create_conn(sock, interval, window, initiator_filter,
peer_bdaddr_type, bdaddr, own_bdaddr_type, min_interval,
max_interval, latency, supervision_timeout,
min_ce_length, max_ce_length, &handle, 25000);
if (retval < 0)
{
perror("hci_le_create_conn");
// TODO close(sock);
return -1;
}
printf("Handle : %d\n", handle);
/*if (hci_read_remote_name(sock, &bdaddr, sizeof(name), name, 25000) == 0)
printf("Name : %s\n", name);*/
//sleep(1);
return handle;
}
void deconnecter(int sock, uint16_t handle)
{
hci_disconnect(sock, handle, HCI_OE_USER_ENDED_CONNECTION, 10000);
}
只能搜索到一个:
# chmod +x /usr/bin/test4 && /usr/bin/test4
# 0x04 0x3E 0x22 0x02 0x01 0x00 0x01 0x4B 0x9D 0xE5 0x78 0x50 0xED 0x16 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x45 0x44 0x35 0x30 0x37 0x38 0x45 0x35 0x39 0x44 0x34 0x42 0xA5 (37)
EVT_LE_ADVERTISING_REPORT (0x02)
* ED:50:78:E5:9D:4B (random) [ 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x45 0x44 0x35 0x30 0x37 0x38 0x45 0x35 0x39 0x44 0x34 0x42 ] rssi = -91 dBm
process_data: 2 octets
-> Flag type: len=2
Flag data: 0x06
process_data: 18 octets
addr=ED:50:78:E5:9D:4B name=YCOMED5078E59D4B▒
#
#
#
# chmod +x /usr/bin/test4 && /usr/bin/test4
# 0x04 0x3E 0x22 0x02 0x01 0x00 0x01 0xF1 0x49 0xDF 0xB0 0x2C 0xC7 0x16 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x43 0x37 0x32 0x43 0x42 0x30 0x44 0x46 0x34 0x39 0x46 0x31 0xAA (37)
EVT_LE_ADVERTISING_REPORT (0x02)
* C7:2C:B0:DF:49:F1 (random) [ 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x43 0x37 0x32 0x43 0x42 0x30 0x44 0x46 0x34 0x39 0x46 0x31 ] rssi = -86 dBm
process_data: 2 octets
-> Flag type: len=2
Flag data: 0x06
process_data: 18 octets
addr=C7:2C:B0:DF:49:F1 name=YCOMC72CB0DF49F1▒
#
#
#
#
#
# chmod +x /usr/bin/test4 && /usr/bin/test4
# 0x04 0x3E 0x22 0x02 0x01 0x00 0x01 0x8E 0xD0 0xD0 0x82 0xE5 0xCC 0x16 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x43 0x43 0x45 0x35 0x38 0x32 0x44 0x30 0x44 0x30 0x38 0x45 0xB8 (37)
EVT_LE_ADVERTISING_REPORT (0x02)
* CC:E5:82:D0:D0:8E (random) [ 0x02 0x01 0x06 0x12 0x09 0x44 0x59 0x43 0x4F 0x4D 0x43 0x43 0x45 0x35 0x38 0x32 0x44 0x30 0x44 0x30 0x38 0x45 ] rssi = -72 dBm
process_data: 2 octets
-> Flag type: len=2
Flag data: 0x06
process_data: 18 octets
addr=CC:E5:82:D0:D0:8E name=YCOMCCE582D0D08E▒
离线
离线
弄了个极度经典版的 hcitool.c :
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2000-2001 Qualcomm Incorporated
* Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
* Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <signal.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/* Unofficial value, might still change */
#define LE_LINK 0x80
#define FLAGS_AD_TYPE 0x01
#define FLAGS_LIMITED_MODE_BIT 0x01
#define FLAGS_GENERAL_MODE_BIT 0x02
#define EIR_FLAGS 0x01 /* flags */
#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A /* transmit power level */
#define EIR_DEVICE_ID 0x10 /* device ID */
#define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, NULL)) != -1)
static volatile int signal_received = 0;
static void usage(void);
static void helper_arg(int min_num_arg, int max_num_arg, int *argc,
char ***argv, const char *usage)
{
*argc -= optind;
/* too many arguments, but when "max_num_arg < min_num_arg" then no
limiting (prefer "max_num_arg=-1" to gen infinity)
*/
if ( (*argc > max_num_arg) && (max_num_arg >= min_num_arg ) ) {
fprintf(stderr, "%s: too many arguments (maximal: %i)\n",
*argv[0], max_num_arg);
printf("%s", usage);
exit(1);
}
/* print usage */
if (*argc < min_num_arg) {
fprintf(stderr, "%s: too few arguments (minimal: %i)\n",
*argv[0], min_num_arg);
printf("%s", usage);
exit(0);
}
*argv += optind;
}
static char *type2str(uint8_t type)
{
switch (type) {
case SCO_LINK:
return "SCO";
case ACL_LINK:
return "ACL";
case ESCO_LINK:
return "eSCO";
case LE_LINK:
return "LE";
default:
return "Unknown";
}
}
static void hex_dump(char *pref, int width, unsigned char *buf, int len)
{
register int i,n;
for (i = 0, n = 1; i < len; i++, n++) {
if (n == 1)
printf("%s", pref);
printf("%2.2X ", buf[i]);
if (n == width) {
printf("\n");
n = 0;
}
}
if (i && n!=1)
printf("\n");
}
static int read_flags(uint8_t *flags, const uint8_t *data, size_t size)
{
size_t offset;
if (!flags || !data)
return -EINVAL;
offset = 0;
while (offset < size) {
uint8_t len = data[offset];
uint8_t type;
/* Check if it is the end of the significant part */
if (len == 0)
break;
if (len + offset > size)
break;
type = data[offset + 1];
if (type == FLAGS_AD_TYPE) {
*flags = data[offset + 2];
return 0;
}
offset += 1 + len;
}
return -ENOENT;
}
static int check_report_filter(uint8_t procedure, le_advertising_info *info)
{
uint8_t flags;
/* If no discovery procedure is set, all reports are treat as valid */
if (procedure == 0)
return 1;
/* Read flags AD type value from the advertising report if it exists */
if (read_flags(&flags, info->data, info->length))
return 0;
switch (procedure) {
case 'l': /* Limited Discovery Procedure */
if (flags & FLAGS_LIMITED_MODE_BIT)
return 1;
break;
case 'g': /* General Discovery Procedure */
if (flags & (FLAGS_LIMITED_MODE_BIT | FLAGS_GENERAL_MODE_BIT))
return 1;
break;
default:
fprintf(stderr, "Unknown discovery procedure\n");
}
return 0;
}
static void sigint_handler(int sig)
{
signal_received = sig;
}
static void eir_parse_name(uint8_t *eir, size_t eir_len,
char *buf, size_t buf_len)
{
size_t offset;
offset = 0;
while (offset < eir_len) {
uint8_t field_len = eir[0];
size_t name_len;
/* Check for the end of EIR */
if (field_len == 0)
break;
if (offset + field_len > eir_len)
goto failed;
switch (eir[1]) {
case EIR_NAME_SHORT:
case EIR_NAME_COMPLETE:
name_len = field_len - 1;
if (name_len > buf_len)
goto failed;
memcpy(buf, &eir[2], name_len);
return;
}
offset += field_len + 1;
eir += field_len + 1;
}
failed:
snprintf(buf, buf_len, "(unknown)");
}
static int print_advertising_devices(int dd, uint8_t filter_type)
{
unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
struct hci_filter nf, of;
struct sigaction sa;
socklen_t olen;
int len;
olen = sizeof(of);
if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) {
printf("Could not get socket options\n");
return -1;
}
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
printf("Could not set socket options\n");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, NULL);
while (1) {
evt_le_meta_event *meta;
le_advertising_info *info;
char addr[18];
while ((len = read(dd, buf, sizeof(buf))) < 0) {
if (errno == EINTR && signal_received == SIGINT) {
len = 0;
goto done;
}
if (errno == EAGAIN || errno == EINTR)
continue;
goto done;
}
ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
len -= (1 + HCI_EVENT_HDR_SIZE);
meta = (void *) ptr;
if (meta->subevent != 0x02)
goto done;
/* Ignoring multiple reports */
info = (le_advertising_info *) (meta->data + 1);
if (check_report_filter(filter_type, info)) {
char name[30];
memset(name, 0, sizeof(name));
ba2str(&info->bdaddr, addr);
eir_parse_name(info->data, info->length,
name, sizeof(name) - 1);
printf("%s %s\n", addr, name);
}
}
done:
setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
if (len < 0)
return -1;
return 0;
}
static struct option lescan_options[] = {
{ "help", 0, 0, 'h' },
{ "static", 0, 0, 's' },
{ "privacy", 0, 0, 'p' },
{ "passive", 0, 0, 'P' },
{ "whitelist", 0, 0, 'w' }, /* Deprecated. Kept for compatibility. */
{ "acceptlist", 0, 0, 'a' },
{ "discovery", 1, 0, 'd' },
{ "duplicates", 0, 0, 'D' },
{ 0, 0, 0, 0 }
};
static const char *lescan_help =
"Usage:\n"
"\tlescan [--privacy] enable privacy\n"
"\tlescan [--passive] set scan type passive (default active)\n"
"\tlescan [--acceptlist] scan for address in the accept list only\n"
"\tlescan [--discovery=g|l] enable general or limited discovery"
"procedure\n"
"\tlescan [--duplicates] don't filter duplicates\n";
static void cmd_lescan(int dev_id, int argc, char **argv)
{
int err, opt, dd;
uint8_t own_type = LE_PUBLIC_ADDRESS;
uint8_t scan_type = 0x01;
uint8_t filter_type = 0;
uint8_t filter_policy = 0x00;
uint16_t interval = htobs(0x0010);
uint16_t window = htobs(0x0010);
uint8_t filter_dup = 0x01;
for_each_opt(opt, lescan_options, NULL) {
switch (opt) {
case 's':
own_type = LE_RANDOM_ADDRESS;
break;
case 'p':
own_type = LE_RANDOM_ADDRESS;
break;
case 'P':
scan_type = 0x00; /* Passive */
break;
case 'w': /* Deprecated. Kept for compatibility. */
case 'a':
filter_policy = 0x01; /* Accept list */
break;
case 'd':
filter_type = optarg[0];
if (filter_type != 'g' && filter_type != 'l') {
fprintf(stderr, "Unknown discovery procedure\n");
exit(1);
}
interval = htobs(0x0012);
window = htobs(0x0012);
break;
case 'D':
filter_dup = 0x00;
break;
default:
printf("%s", lescan_help);
return;
}
}
helper_arg(0, 1, &argc, &argv, lescan_help);
if (dev_id < 0)
dev_id = hci_get_route(NULL);
dd = hci_open_dev(dev_id);
if (dd < 0) {
perror("Could not open device");
exit(1);
}
err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
own_type, filter_policy, 10000);
if (err < 0) {
perror("Set scan parameters failed");
exit(1);
}
err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
if (err < 0) {
perror("Enable scan failed");
exit(1);
}
printf("LE Scan ...\n");
err = print_advertising_devices(dd, filter_type);
if (err < 0) {
perror("Could not receive advertising events");
exit(1);
}
printf("-----------------------------------------------------\n");
err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);
if (err < 0) {
perror("Disable scan failed");
exit(1);
}
hci_close_dev(dd);
}
static struct {
char *cmd;
void (*func)(int dev_id, int argc, char **argv);
char *doc;
} command[] = {
{ "lescan", cmd_lescan, "Start LE scan" },
{ NULL, NULL, 0 }
};
static void usage(void)
{
int i;
printf("hcitool - HCI Tool ver %s\n", VERSION);
printf("Usage:\n"
"\thcitool [options] <command> [command parameters]\n");
printf("Options:\n"
"\t--help\tDisplay help\n"
"\t-i dev\tHCI device\n");
printf("Commands:\n");
for (i = 0; command[i].cmd; i++)
printf("\t%-4s\t%s\n", command[i].cmd,
command[i].doc);
printf("\n"
"For more information on the usage of each command use:\n"
"\thcitool <command> --help\n" );
}
static struct option main_options[] = {
{ "help", 0, 0, 'h' },
{ "device", 1, 0, 'i' },
{ 0, 0, 0, 0 }
};
int main(int argc, char *argv[])
{
int opt, i, dev_id = -1;
bdaddr_t ba;
while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
switch (opt) {
case 'i':
dev_id = hci_devid(optarg);
if (dev_id < 0) {
perror("Invalid device");
exit(1);
}
break;
case 'h':
default:
usage();
exit(0);
}
}
argc -= optind;
argv += optind;
optind = 0;
if (argc < 1) {
usage();
exit(0);
}
if (dev_id != -1 && hci_devba(dev_id, &ba) < 0) {
perror("Device is not available");
exit(1);
}
for (i = 0; command[i].cmd; i++) {
if (strncmp(command[i].cmd,
argv[0], strlen(command[i].cmd)))
continue;
command[i].func(dev_id, argc, argv);
break;
}
if (command[i].cmd == 0) {
fprintf(stderr, "Unknown command - \"%s\"\n", *argv);
exit(1);
}
return 0;
}
离线
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2000-2001 Qualcomm Incorporated
* Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
* Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <signal.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/* Unofficial value, might still change */
#define LE_LINK 0x80
#define FLAGS_AD_TYPE 0x01
#define FLAGS_LIMITED_MODE_BIT 0x01
#define FLAGS_GENERAL_MODE_BIT 0x02
#define EIR_FLAGS 0x01 /* flags */
#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A /* transmit power level */
#define EIR_DEVICE_ID 0x10 /* device ID */
#define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, NULL)) != -1)
static volatile int signal_received = 0;
static void usage(void);
static void helper_arg(int min_num_arg, int max_num_arg, int *argc,
char ***argv, const char *usage)
{
*argc -= optind;
/* too many arguments, but when "max_num_arg < min_num_arg" then no
limiting (prefer "max_num_arg=-1" to gen infinity)
*/
if ( (*argc > max_num_arg) && (max_num_arg >= min_num_arg ) ) {
fprintf(stderr, "%s: too many arguments (maximal: %i)\n",
*argv[0], max_num_arg);
printf("%s", usage);
exit(1);
}
/* print usage */
if (*argc < min_num_arg) {
fprintf(stderr, "%s: too few arguments (minimal: %i)\n",
*argv[0], min_num_arg);
printf("%s", usage);
exit(0);
}
*argv += optind;
}
static char *type2str(uint8_t type)
{
switch (type) {
case SCO_LINK:
return "SCO";
case ACL_LINK:
return "ACL";
case ESCO_LINK:
return "eSCO";
case LE_LINK:
return "LE";
default:
return "Unknown";
}
}
static void hex_dump(char *pref, int width, unsigned char *buf, int len)
{
register int i,n;
for (i = 0, n = 1; i < len; i++, n++) {
if (n == 1)
printf("%s", pref);
printf("%2.2X ", buf[i]);
if (n == width) {
printf("\n");
n = 0;
}
}
if (i && n!=1)
printf("\n");
}
static int read_flags(uint8_t *flags, const uint8_t *data, size_t size)
{
size_t offset;
if (!flags || !data)
return -EINVAL;
offset = 0;
while (offset < size) {
uint8_t len = data[offset];
uint8_t type;
/* Check if it is the end of the significant part */
if (len == 0)
break;
if (len + offset > size)
break;
type = data[offset + 1];
if (type == FLAGS_AD_TYPE) {
*flags = data[offset + 2];
return 0;
}
offset += 1 + len;
}
return -ENOENT;
}
static int check_report_filter(uint8_t procedure, le_advertising_info *info)
{
uint8_t flags;
/* If no discovery procedure is set, all reports are treat as valid */
if (procedure == 0)
return 1;
/* Read flags AD type value from the advertising report if it exists */
if (read_flags(&flags, info->data, info->length))
return 0;
switch (procedure) {
case 'l': /* Limited Discovery Procedure */
if (flags & FLAGS_LIMITED_MODE_BIT)
return 1;
break;
case 'g': /* General Discovery Procedure */
if (flags & (FLAGS_LIMITED_MODE_BIT | FLAGS_GENERAL_MODE_BIT))
return 1;
break;
default:
fprintf(stderr, "Unknown discovery procedure\n");
}
return 0;
}
static void sigint_handler(int sig)
{
signal_received = sig;
}
static void eir_parse_name(uint8_t *eir, size_t eir_len,
char *buf, size_t buf_len)
{
size_t offset;
offset = 0;
while (offset < eir_len) {
uint8_t field_len = eir[0];
size_t name_len;
/* Check for the end of EIR */
if (field_len == 0)
break;
if (offset + field_len > eir_len)
goto failed;
switch (eir[1]) {
case EIR_NAME_SHORT:
case EIR_NAME_COMPLETE:
name_len = field_len - 1;
if (name_len > buf_len)
goto failed;
memcpy(buf, &eir[2], name_len);
return;
}
offset += field_len + 1;
eir += field_len + 1;
}
failed:
snprintf(buf, buf_len, "(unknown)");
}
static int print_advertising_devices(int dd, uint8_t filter_type)
{
unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
struct hci_filter nf, of;
struct sigaction sa;
socklen_t olen;
int len;
olen = sizeof(of);
if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) {
printf("Could not get socket options\n");
return -1;
}
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
printf("Could not set socket options\n");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, NULL);
while (1) {
evt_le_meta_event *meta;
le_advertising_info *info;
char addr[18];
while ((len = read(dd, buf, sizeof(buf))) < 0) {
if (errno == EINTR && signal_received == SIGINT) {
len = 0;
goto done;
}
if (errno == EAGAIN || errno == EINTR)
continue;
goto done;
}
ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
len -= (1 + HCI_EVENT_HDR_SIZE);
meta = (void *) ptr;
if (meta->subevent != 0x02)
goto done;
/* Ignoring multiple reports */
info = (le_advertising_info *) (meta->data + 1);
if (check_report_filter(filter_type, info)) {
char name[30];
memset(name, 0, sizeof(name));
ba2str(&info->bdaddr, addr);
eir_parse_name(info->data, info->length,
name, sizeof(name) - 1);
printf("%s %s\n", addr, name);
}
}
done:
setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
if (len < 0)
return -1;
return 0;
}
static void cmd_lescan(int dev_id, int argc, char **argv)
{
int err, opt, dd;
uint8_t own_type = LE_PUBLIC_ADDRESS;
uint8_t scan_type = 0x01;
uint8_t filter_type = 0;
uint8_t filter_policy = 0x00;
uint16_t interval = htobs(0x0010);
uint16_t window = htobs(0x0010);
uint8_t filter_dup = 0x01;
if (dev_id < 0)
dev_id = hci_get_route(NULL);
dd = hci_open_dev(dev_id);
if (dd < 0) {
perror("Could not open device");
exit(1);
}
err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
own_type, filter_policy, 10000);
if (err < 0) {
perror("Set scan parameters failed");
exit(1);
}
err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
if (err < 0) {
perror("Enable scan failed");
exit(1);
}
printf("LE Scan ...\n");
err = print_advertising_devices(dd, filter_type);
if (err < 0) {
perror("Could not receive advertising events");
exit(1);
}
printf("-----------------------------------------------------\n");
err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);
if (err < 0) {
perror("Disable scan failed");
exit(1);
}
hci_close_dev(dd);
}
static struct {
char *cmd;
void (*func)(int dev_id, int argc, char **argv);
char *doc;
} command[] = {
{ "lescan", cmd_lescan, "Start LE scan" },
{ NULL, NULL, 0 }
};
static void usage(void)
{
int i;
printf("hcitool - HCI Tool ver %s\n", VERSION);
printf("Usage:\n"
"\thcitool [options] <command> [command parameters]\n");
printf("Options:\n"
"\t--help\tDisplay help\n"
"\t-i dev\tHCI device\n");
printf("Commands:\n");
for (i = 0; command[i].cmd; i++)
printf("\t%-4s\t%s\n", command[i].cmd,
command[i].doc);
printf("\n"
"For more information on the usage of each command use:\n"
"\thcitool <command> --help\n" );
}
static struct option main_options[] = {
{ "help", 0, 0, 'h' },
{ "device", 1, 0, 'i' },
{ 0, 0, 0, 0 }
};
int main(int argc, char *argv[])
{
int opt, i, dev_id = -1;
bdaddr_t ba;
while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
switch (opt) {
case 'i':
dev_id = hci_devid(optarg);
if (dev_id < 0) {
perror("Invalid device");
exit(1);
}
break;
case 'h':
default:
usage();
exit(0);
}
}
argc -= optind;
argv += optind;
optind = 0;
if (argc < 1) {
usage();
exit(0);
}
if (dev_id != -1 && hci_devba(dev_id, &ba) < 0) {
perror("Device is not available");
exit(1);
}
for (i = 0; command[i].cmd; i++) {
if (strncmp(command[i].cmd,
argv[0], strlen(command[i].cmd)))
continue;
command[i].func(dev_id, argc, argv);
break;
}
if (command[i].cmd == 0) {
fprintf(stderr, "Unknown command - \"%s\"\n", *argv);
exit(1);
}
return 0;
}
继续精简
离线
/opt/buildroot/buildroot-2023/output/host/bin/aarch64-linux-gnu-gcc -DVERSION=\"AAAA\" -o hcitoolhcitool.c -I./output/build/bluez5_utils-5.68/ -lbluetooth
离线
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <sys/types.h>
#include <sys/socket.h>
// $ gcc ble-scan.c -lbluetooth -o ble-scan
#define EIR_FLAGS 0x01 /* flags */
#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A /* transmit power level */
#define EIR_DEVICE_ID 0x10 /* device ID */
#define EIR_MANUFACTURE_SPECIFIC 0xFF
void process_data(uint8_t *data, size_t data_len, le_advertising_info *info);
int connecter(int sock, char dest[18]);
void deconnecter(int sock, uint16_t handle);
int main (int argc, char **argv)
{
int sock, retval;
int i, len;
unsigned char buf[HCI_MAX_FRAME_SIZE];
char btAddress[18];
uint16_t handle;
struct sockaddr_hci addr;
struct hci_filter filter;
int encore = 1;
sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (-1 == sock)
{
perror("socket"); return 1;
}
hci_filter_clear(&filter);
hci_filter_all_ptypes(&filter);
hci_filter_all_events(&filter);
retval = setsockopt(sock, SOL_HCI, HCI_FILTER, &filter, sizeof(filter));
if (-1 == retval)
{
perror("setsockopt"); return 1;
}
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = 0;
retval = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
if (-1 == retval)
{
perror("bind"); return 1;
}
uint8_t scan_type = 0x00; /* Passive */
uint16_t interval = htobs(0x0010);
uint16_t window = htobs(0x0010);
uint8_t own_type = 0x00;
uint8_t filter_policy = 0x00; /* 1 -> Whitelist */
retval = hci_le_set_scan_parameters(sock, scan_type, interval, window, own_type, filter_policy, 1000);
//retval = hci_le_set_scan_parameters(sock, 0, 0x10, 0x10, 0, 0, 1000);
if (retval < 0)
{
perror("hci_le_set_scan_parameters"); //return 1;
}
retval = hci_le_set_scan_enable(sock, 1 /* 1 - turn on, 0 - turn off */, 0 /* 0-filtering disabled, 1-filter out duplicates */, 1000 /* timeout */);
if (retval < 0)
{
perror("hci_le_set_scan_enable"); //return 1;
}
do
{
memset (buf, 0, sizeof(buf));
retval = recv (sock, buf, sizeof(buf), 0);
if (-1 == retval)
{
perror("recv"); return 1;
}
#if 0
printf ("# ");
for(i=0;i<retval;i++)
printf ("0x%02X ", buf[i]);
//printf ("%c ", buf[i]);
printf ("(%d)\n\n", retval);
/*printf ("# 0x%02X 0x%02X 0x%02X 0x%02X (%d)\n",
buf[0], buf[1],
buf[2], buf[3], retval);*/
#endif
switch (buf[1])
{
#if 0
case EVT_CMD_STATUS: // 0x0F
if (buf[3])
{
printf ("Erreur !\n");
//encore = 0;
}
else
{
printf ("Commande en cours\n");
}
break;
case EVT_INQUIRY_RESULT: // 0x02
printf ("Périphérique trouvé:\n");
printf (" * Adresse : %02x:%02x:%02x:%02x:%02x:%02x\n",
buf[9], buf[8],
buf[7], buf[6],
buf[5], buf[4]);
printf (" * Classe : 0x%02x%02x%02x\n\n",
buf[15], buf[14], buf[13]);
break;
case EVT_EXTENDED_INQUIRY_RESULT: // 0x2F
printf ("Périphérique trouvé:\n");
printf (" * Adresse : %02x:%02x:%02x:%02x:%02x:%02x\n",
buf[9], buf[8],
buf[7], buf[6],
buf[5], buf[4]);
printf (" * Classe : 0x%02x%02x%02x\n",
buf[14], buf[13], buf[12]);
printf (" * RSSI : %d\n\n", // Received Signal Strength Indication
buf[17]);
break;
case EVT_INQUIRY_COMPLETE: // 0x01
//encore = 0;
break;
#endif
case EVT_LE_META_EVENT: // 0x3E
len = retval;
evt_le_meta_event *meta = (void *)(buf + (1 + HCI_EVENT_HDR_SIZE));
len -= (1 + HCI_EVENT_HDR_SIZE);
if (meta->subevent == EVT_LE_ADVERTISING_REPORT)
{
// printf("EVT_LE_ADVERTISING_REPORT (0x%02X)\n", meta->subevent);
le_advertising_info *info = (le_advertising_info *) (meta->data + 1);
int8_t rssi;
ba2str(&info->bdaddr, btAddress);
#if 0
printf("* %s (%s) [ ", btAddress, (info->bdaddr_type == LE_PUBLIC_ADDRESS) ? "public" : "random");
for (i = 0; i < info->length; i++)
{
printf("0x%02X ", info->data[i]);
}
rssi = *(info->data + info->length);
printf("] rssi = %d dBm\n", rssi);
#endif
if(info->length != 0)
{
int current_index = 0;
int data_error = 0;
while(!data_error && current_index < info->length)
{
size_t data_len = info->data[current_index];
if(data_len + 1 > info->length)
{
printf("EIR data length is longer than EIR packet length. %d + 1 > %d", (int)data_len, info->length);
data_error = 1;
}
else
{
process_data(info->data + current_index + 1, data_len, info);
current_index += data_len + 1;
}
}
}
else
printf("info->length == 0 !\n");
}
else
printf("EVT_LE = 0x%02X\n", meta->subevent);
//handle = connecter(sock, btAddress);
//deconnecter(sock, handle);
//encore = 0;
break;
default:
break;
}
}
while (encore);
retval = hci_le_set_scan_enable(sock, 0 /* 1 - turn on, 0 - turn off */, 0 /* 0-filtering disabled, 1-filter out duplicates */, 1000 /* timeout */);
if (retval < 0)
{
perror("hci_le_set_scan_enable"); //return 1;
}
close (sock);
return 0;
}
void process_data(uint8_t *data, size_t data_len, le_advertising_info *info)
{
// printf("process_data: %d octets\n", (int)data_len);
if(data[0] == EIR_NAME_SHORT || data[0] == EIR_NAME_COMPLETE)
{
size_t name_len = data_len - 1;
char *name = malloc(name_len + 1);
memset(name, 0, name_len + 1);
memcpy(name, &data[2], name_len);
char addr[18];
ba2str(&info->bdaddr, addr);
printf("addr=%s name=%s\n", addr, name);
free(name);
}
else if(data[0] == EIR_FLAGS)
{
#if 0
printf("-> Flag type: len=%d\n", (int)data_len);
int i;
for(i=1; i<data_len; i++)
{
printf("\tFlag data: 0x%02X\n", data[i]); // 0x06 -> 0000 0110
}
/*
bit 0 LE Limited Discoverable Mode
bit 1 LE General Discoverable Mode
bit 2 BR/EDR Supported
bit 3 Simultaneous LE and BR/EDR to Same Device Capable (controller)
bit 4 Simultaneous LE and BR/EDR to Same Device Capable (Host)
*/
#endif
}
else if(data[0] == EIR_MANUFACTURE_SPECIFIC)
{
#if 0
printf("-> Manufacture specific type: len=%d\n", (int)data_len);
// https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
// TODO int company_id = data[current_index + 2]
int i;
for(i=1; i<data_len; i++)
{
printf("\tData: 0x%02X\n", data[i]);
}
#endif
}
else if(data[0] == EIR_UUID128_SOME)
{
printf("-> UUID 128 type: len=%d\n", (int)data_len);
printf("\t");
int i;
for(i=data_len-1; i>0; i--)
{
printf("%02x", data[i]);
}
printf("\n");
}
else
{
printf("-> Unknown type: type=0x%02X\n", data[0]);
}
}
int connecter(int sock, char dest[18])
{
struct hci_dev_info di;
uint16_t handle;
char addr[18];
bdaddr_t bdaddr;
uint16_t interval, latency, max_ce_length, max_interval, min_ce_length;
uint16_t min_interval, supervision_timeout, window;
uint8_t initiator_filter, own_bdaddr_type, peer_bdaddr_type;
int retval;
char name[248];
if (hci_devinfo(0, &di) < 0)
{
perror("hci_devinfo");
}
ba2str(&di.bdaddr, addr);
printf("Device : %s [%s]\n", di.name, addr);
str2ba(dest, &bdaddr);
interval = htobs(0x0004);
window = htobs(0x0004);
//initiator_filter = 0x01; /* Use white list */
//peer_bdaddr_type = LE_RANDOM_ADDRESS;
own_bdaddr_type = 0x00;
min_interval = htobs(0x000F);
max_interval = htobs(0x000F);
latency = htobs(0x0000);
supervision_timeout = htobs(0x0C80);
min_ce_length = htobs(0x0001);
max_ce_length = htobs(0x0001);
retval = hci_le_create_conn(sock, interval, window, initiator_filter,
peer_bdaddr_type, bdaddr, own_bdaddr_type, min_interval,
max_interval, latency, supervision_timeout,
min_ce_length, max_ce_length, &handle, 25000);
if (retval < 0)
{
perror("hci_le_create_conn");
// TODO close(sock);
return -1;
}
printf("Handle : %d\n", handle);
/*if (hci_read_remote_name(sock, &bdaddr, sizeof(name), name, 25000) == 0)
printf("Name : %s\n", name);*/
//sleep(1);
return handle;
}
void deconnecter(int sock, uint16_t handle)
{
hci_disconnect(sock, handle, HCI_OE_USER_ENDED_CONNECTION, 10000);
}
精简了一下,可以一直搜索附近的BLE设备
离线
蓝牙的uuid16和uuid128的意义:
蓝牙的UUID (Universally Unique Identifier) 是用于唯一标识蓝牙设备、服务和特性的值。UUID 可以使用不同的长度进行表示,其中包括 UUID16 和 UUID128。
① UUID16:UUID16 是一个 16 位的短格式 UUID。它由 4 个十六进制数字表示,例如 0x180D。UUID16 范围从 0x0000 到 0xFFFF,共有 65536 个可能的值。UUID16 常用于标识通用的蓝牙服务,如心率监测(Heart Rate Monitor)、电池服务(Battery Service)等。由于 UUID16 长度较短,因此存在较大的可能性发生冲突。
② UUID128:UUID128 是一个 128 位的长格式 UUID。它由 32 个十六进制数字表示,例如 00002a37-0000-1000-8000-00805f9b34fb。UUID128 的长度更长,因此具有更高的唯一性。UUID128 常用于自定义的蓝牙服务和特性,以确保全球范围内的唯一性。
需要注意的是,UUID16 和 UUID128 之间是可以相互转换的。UUID16 可以通过添加预定义的基础 UUID(Base UUID)或 Bluetooth SIG 定义的预定义服务 UUID 来扩展为 UUID128。UUID128 可以通过截断前面的字节来缩短为 UUID16。
在蓝牙设备之间进行通信时,UUID 用于唯一标识服务和特性。通过使用不同长度的 UUID,可以满足不同场景下的需求,并确保唯一性和互操作性。
离线
蓝牙开发常用UUID表
Sample Services
0000180d-0000-1000-8000-00805f9b34fb Heart Rate Service
0000180a-0000-1000-8000-00805f9b34fb Device Information Service
Sample Characteristics.
00002a37-0000-1000-8000-00805f9b34fb Heart Rate Measurement
00002a29-0000-1000-8000-00805f9b34fb Manufacturer Name String
GATT Services
00001800-0000-1000-8000-00805f9b34fb GenericAccess
00001801-0000-1000-8000-00805f9b34fb GenericAttribute
GATT Declarations
00002800-0000-1000-8000-00805f9b34fb Primary Service
00002801-0000-1000-8000-00805f9b34fb Secondary Service
00002802-0000-1000-8000-00805f9b34fb Include
00002803-0000-1000-8000-00805f9b34fb Characteristic
GATT Descriptors
00002900-0000-1000-8000-00805f9b34fb Characteristic Extended Properties
00002901-0000-1000-8000-00805f9b34fb Characteristic User Description
00002902-0000-1000-8000-00805f9b34fb Client Characteristic Configuration
00002903-0000-1000-8000-00805f9b34fb Server Characteristic Configuration
00002904-0000-1000-8000-00805f9b34fb Characteristic Presentation Format
00002905-0000-1000-8000-00805f9b34fb Characteristic Aggregate Format
00002906-0000-1000-8000-00805f9b34fb Valid Range
00002907-0000-1000-8000-00805f9b34fb External Report Reference Descriptor
00002908-0000-1000-8000-00805f9b34fb Report Reference Descriptor
GATT Characteristics
00002a00-0000-1000-8000-00805f9b34fb Device Name
00002a01-0000-1000-8000-00805f9b34fb Appearance
00002a02-0000-1000-8000-00805f9b34fb Peripheral Privacy Flag
00002a03-0000-1000-8000-00805f9b34fb Reconnection Address
00002a04-0000-1000-8000-00805f9b34fb PPCP
00002a05-0000-1000-8000-00805f9b34fb Service Changed
GATT Service UUIDs
00001802-0000-1000-8000-00805f9b34fb Immediate Alert
00001803-0000-1000-8000-00805f9b34fb Link Loss
00001804-0000-1000-8000-00805f9b34fb Tx Power
00001805-0000-1000-8000-00805f9b34fb Current Time Service
00001806-0000-1000-8000-00805f9b34fb Reference Time Update Service
00001807-0000-1000-8000-00805f9b34fb Next DST Change Service
00001808-0000-1000-8000-00805f9b34fb Glucose
00001809-0000-1000-8000-00805f9b34fb Health Thermometer
0000180a-0000-1000-8000-00805f9b34fb Device Information
0000180b-0000-1000-8000-00805f9b34fb Network Availability
0000180d-0000-1000-8000-00805f9b34fb Heart Rate
0000180e-0000-1000-8000-00805f9b34fb Phone Alert Status Service
0000180f-0000-1000-8000-00805f9b34fb Battery Service
00001810-0000-1000-8000-00805f9b34fb Blood Pressure
00001811-0000-1000-8000-00805f9b34fb Alert Notification Service
00001812-0000-1000-8000-00805f9b34fb Human Interface Device
00001813-0000-1000-8000-00805f9b34fb Scan Parameters
00001814-0000-1000-8000-00805f9b34fb Running Speed and Cadence
00001816-0000-1000-8000-00805f9b34fb Cycling Speed and Cadence
00001818-0000-1000-8000-00805f9b34fb Cycling Power
00001819-0000-1000-8000-00805f9b34fb Location and Navigation
GATT Characteristic UUIDs
00002a06-0000-1000-8000-00805f9b34fb Alert Level
00002a07-0000-1000-8000-00805f9b34fb Tx Power Level
00002a08-0000-1000-8000-00805f9b34fb Date Time
00002a09-0000-1000-8000-00805f9b34fb Day of Week
00002a0a-0000-1000-8000-00805f9b34fb Day Date Time
00002a0c-0000-1000-8000-00805f9b34fb Exact Time 256
00002a0d-0000-1000-8000-00805f9b34fb DST Offset
00002a0e-0000-1000-8000-00805f9b34fb Time Zone
00002a0f-0000-1000-8000-00805f9b34fb Local Time Information
00002a11-0000-1000-8000-00805f9b34fb Time with DST
00002a12-0000-1000-8000-00805f9b34fb Time Accuracy
00002a13-0000-1000-8000-00805f9b34fb Time Source
00002a14-0000-1000-8000-00805f9b34fb Reference Time Information
00002a16-0000-1000-8000-00805f9b34fb Time Update Control Point
00002a17-0000-1000-8000-00805f9b34fb Time Update State
00002a18-0000-1000-8000-00805f9b34fb Glucose Measurement
00002a19-0000-1000-8000-00805f9b34fb Battery Level
00002a1c-0000-1000-8000-00805f9b34fb Temperature Measurement
00002a1d-0000-1000-8000-00805f9b34fb Temperature Type
00002a1e-0000-1000-8000-00805f9b34fb Intermediate Temperature
00002a21-0000-1000-8000-00805f9b34fb Measurement Interval
00002a22-0000-1000-8000-00805f9b34fb Boot Keyboard Input Report
00002a23-0000-1000-8000-00805f9b34fb System ID
00002a24-0000-1000-8000-00805f9b34fb Model Number String
00002a25-0000-1000-8000-00805f9b34fb Serial Number String
00002a26-0000-1000-8000-00805f9b34fb Firmware Revision String
00002a27-0000-1000-8000-00805f9b34fb Hardware Revision String
00002a28-0000-1000-8000-00805f9b34fb Software Revision String
00002a29-0000-1000-8000-00805f9b34fb Manufacturer Name String
00002a2a-0000-1000-8000-00805f9b34fb IEEE 11073-20601 Regulatory Certification Data List
00002a2b-0000-1000-8000-00805f9b34fb Current Time
00002a31-0000-1000-8000-00805f9b34fb Scan Refresh
00002a32-0000-1000-8000-00805f9b34fb Boot Keyboard Output Report
00002a33-0000-1000-8000-00805f9b34fb Boot Mouse Input Report
00002a34-0000-1000-8000-00805f9b34fb Glucose Measurement Context
00002a35-0000-1000-8000-00805f9b34fb Blood Pressure Measurement
00002a36-0000-1000-8000-00805f9b34fb Intermediate Cuff Pressure
00002a37-0000-1000-8000-00805f9b34fb Heart Rate Measurement
00002a38-0000-1000-8000-00805f9b34fb Body Sensor Location
00002a39-0000-1000-8000-00805f9b34fb Heart Rate Control Point
00002a3e-0000-1000-8000-00805f9b34fb Network Availability
00002a3f-0000-1000-8000-00805f9b34fb Alert Status
00002a40-0000-1000-8000-00805f9b34fb Ringer Control Point
00002a41-0000-1000-8000-00805f9b34fb Ringer Setting
00002a42-0000-1000-8000-00805f9b34fb Alert Category ID Bit Mask
00002a43-0000-1000-8000-00805f9b34fb Alert Category ID
00002a44-0000-1000-8000-00805f9b34fb Alert Notification Control Point
00002a45-0000-1000-8000-00805f9b34fb Unread Alert Status
00002a46-0000-1000-8000-00805f9b34fb New Alert
00002a47-0000-1000-8000-00805f9b34fb Supported New Alert Category
00002a48-0000-1000-8000-00805f9b34fb Supported Unread Alert Category
00002a49-0000-1000-8000-00805f9b34fb Blood Pressure Feature
00002a4a-0000-1000-8000-00805f9b34fb HID Information
00002a4b-0000-1000-8000-00805f9b34fb Report Map
00002a4c-0000-1000-8000-00805f9b34fb HID Control Point
00002a4d-0000-1000-8000-00805f9b34fb Report
00002a4e-0000-1000-8000-00805f9b34fb Protocol Mode
00002a4f-0000-1000-8000-00805f9b34fb Scan Interval Window
00002a50-0000-1000-8000-00805f9b34fb PnP ID
00002a51-0000-1000-8000-00805f9b34fb Glucose Feature
00002a52-0000-1000-8000-00805f9b34fb Record Access Control Point
00002a53-0000-1000-8000-00805f9b34fb RSC Measurement
00002a54-0000-1000-8000-00805f9b34fb RSC Feature
00002a55-0000-1000-8000-00805f9b34fb SC Control Point
00002a5b-0000-1000-8000-00805f9b34fb CSC Measurement
00002a5c-0000-1000-8000-00805f9b34fb CSC Feature
00002a5d-0000-1000-8000-00805f9b34fb Sensor Location
00002a63-0000-1000-8000-00805f9b34fb Cycling Power Measurement
00002a64-0000-1000-8000-00805f9b34fb Cycling Power Vector
00002a65-0000-1000-8000-00805f9b34fb Cycling Power Feature
00002a66-0000-1000-8000-00805f9b34fb Cycling Power Control Point
00002a67-0000-1000-8000-00805f9b34fb Location and Speed
00002a68-0000-1000-8000-00805f9b34fb Navigation
00002a69-0000-1000-8000-00805f9b34fb Position Quality
00002a6a-0000-1000-8000-00805f9b34fb LN Feature
00002a6b-0000-1000-8000-00805f9b34fb LN Control Point"
比如电池电量特征的uuid16 就是 2a19,uuid128就是 00002a19-0000-1000-8000-00805f9b34fb
离线
先输入P执行连接,
运行 btstack 的测试程序:ble_central_test -u /dev/ttyS1
离线
SM: IO_CAPABILITY_NO_INPUT_NO_OUTPUT, MITM protection 0
SM: key range [7..16], OOB data: None
Privacy 0
Device name: BTstack
c/C - connectable off | j/J - Read (Long) Characteristic Value by handle
d/D - bondable off/on | k/K - Read Characteristic Value by UUID16/UUID128
--- | l/L - Read (Long) Characteristic Descriptor by handle
1 - enable privacy using random non-resolvable private address | N - Read Multiple Characteristic Values
2 - clear Peripheral Privacy Flag on PTS | O - Write without Response
3 - set Peripheral Privacy Flag on PTS | q/Q - Write (Long) Characteristic Value
9 - create HCI Classic connection to addr 23:11:23:00:76:A1 | r - Characteristic Reliable Write
s/S - passive/active scanning | R - Signed Write
a - enable Advertisements | u/U - Write (Long) Characteristic Descriptor
b - start bonding | T - Read Generic Profile Attributes by Type
n - query GAP Device Name | E - Prepare Write
o - set GAP Reconnection Address | v - Execute Write
t - terminate connection, stop connecting | V - Cancel Write
p - auto connect to PTS | ---
P - direct connect to PTS | 4 - IO_CAPABILITY_DISPLAY_ONLY
w - signed write on characteristic with UUID b00d | 5 - IO_CAPABILITY_DISPLAY_YES_NO
W - signed write on attribute with handle 0x00b1 and value 0x12 | 6 - IO_CAPABILITY_NO_INPUT_NO_OUTPUT
z - Update L2CAP Connection Parameters | 7 - IO_CAPABILITY_KEYBOARD_ONLY
--- | 8 - IO_CAPABILITY_KEYBOARD_DISPLAY
e - Discover all Primary Services | m/M - MITM protection off
f/F - Discover Primary Service by UUID16/UUID128 | x/X - encryption key range [7..16]/[16..16]
g - Discover all characteristics by UUID16 | y/Y - OOB data off/on/toggle A/B
G - Discover all characteristics in range | ---
h - Discover Characteristic Descriptors | Ctrl-c - exit
i - Find all included services
Direct Connection Establishment to type 0, addr 23:11:23:00:76:A1
Connection complete, handle 0x0012
Primary Service with UUID 180f, start group handle 0x0001, end group handle 0x0004
Primary Service with UUID 1804, start group handle 0x0005, end group handle 0x0007
Primary Service with UUID 1803, start group handle 0x0008, end group handle 0x000b
Primary Service with UUID 1802, start group handle 0x000c, end group handle 0x000f
Primary Service with UUID ffe0, start group handle 0x0010, end group handle 0x0026
Primary Service with UUID 5833FF01-9B8B-5191-6142-22A4536EF123, start group handle 0x0027, end group handle 0x002c
Primary Service Discovery complete
Please enter service UUID16: 180f
Discover Primary Services with UUID16 180f
Primary Service with UUID 180f, start group handle 0x0001, end group handle 0x0004
Characteristic found at 0x0002 with value handle 0x0003, uuid 2a19
Please enter service UUID16: 1804
Discover Primary Services with UUID16 1804
Primary Service with UUID 1804, start group handle 0x0005, end group handle 0x0007
Characteristic found at 0x0006 with value handle 0x0007, uuid 2a07
Please enter service UUID16: 1803
Discover Primary Services with UUID16 1803
Primary Service with UUID 1803, start group handle 0x0008, end group handle 0x000b
Characteristic found at 0x0009 with value handle 0x000a, uuid 2a06
Please enter service UUID16: 1802
Discover Primary Services with UUID16 1802
Primary Service with UUID 1802, start group handle 0x000c, end group handle 0x000f
Characteristic found at 0x000d with value handle 0x000e, uuid 2a06
离线
查询 2a19 对应的 handle:
先输入P连接BLE设备,输入k,2a19,5获取电量数据:
离线
离线
发送端(/usr/bin/ble_central_test -u /dev/ttyS1):
接收端(/usr/bin/nordic_spp_le_counter -u /dev/ttyS1):
离线
btstack终于可以同时连两个BLE设备了
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "btstack_config.h"
#include "ad_parser.h"
#include "ble/att_db.h"
#include "ble/att_server.h"
#include "ble/le_device_db.h"
#include "ble/sm.h"
#include "btstack_debug.h"
#include "btstack_event.h"
#include "btstack_memory.h"
#include "btstack_run_loop.h"
#include "gap.h"
#include "hci.h"
#include "hci_dump.h"
#include "l2cap.h"
#include "btstack_stdin.h"
// test profile
#include "ble_central_test.h"
#include "ble/gatt_client.h"
// Non standard IXIT
#define PTS_USES_RECONNECTION_ADDRESS_FOR_ITSELF
#define PTS_UUID128_REPRESENTATION
extern void le_device_db_local_csrk_get(int index, sm_key_t csrk);
typedef enum {
CENTRAL_IDLE,
CENTRAL_W4_NAME_QUERY_COMPLETE,
CENTRAL_W4_NAME_VALUE,
CENTRAL_W4_RECONNECTION_ADDRESS_QUERY_COMPLETE,
CENTRAL_W4_PERIPHERAL_PRIVACY_FLAG_QUERY_COMPLETE,
CENTRAL_W4_SIGNED_WRITE_QUERY_COMPLETE,
CENTRAL_W4_PRIMARY_SERVICES,
CENTRAL_ENTER_SERVICE_UUID_4_DISCOVER_CHARACTERISTICS,
CENTRAL_ENTER_START_HANDLE_4_DISCOVER_CHARACTERISTICS,
CENTRAL_ENTER_END_HANDLE_4_DISCOVER_CHARACTERISTICS,
CENTRAL_W4_CHARACTERISTICS,
CENTRAL_W4_DISCOVER_CHARACTERISTIC_DESCRIPTORS,
CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_HANDLE,
CENTRAL_ENTER_HANDLE_4_READ_CHARACTERISTIC_VALUE_BY_UUID,
CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_UUID,
CENTRAL_ENTER_OFFSET_4_READ_LONG_CHARACTERISTIC_VALUE_BY_HANDLE,
CENTRAL_W4_READ_LONG_CHARACTERISTIC_VALUE_BY_HANDLE,
CENTRAL_W4_READ_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE,
CENTRAL_ENTER_OFFSET_4_READ_LONG_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE,
CENTRAL_W4_READ_LONG_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE,
CENTRAL_W4_READ_MULTIPLE_CHARACTERISTIC_VALUES,
CENTRAL_W4_WRITE_WITHOUT_RESPONSE,
CENTRAL_W4_WRITE_CHARACTERICISTIC_VALUE,
CENTRAL_ENTER_HANDLE_4_WRITE_LONG_CHARACTERISTIC_VALUE,
CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_VALUE,
CENTRAL_W4_RELIABLE_WRITE,
CENTRAL_W4_WRITE_CHARACTERISTIC_DESCRIPTOR,
CENTRAL_ENTER_HANDLE_4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR,
CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR,
CENTRAL_W4_SIGNED_WRITE,
CENTRAL_W4_ENTER_HANDLE_4_PREPARE_WRITE,
CENTRAL_W4_ENTER_OFFSET_4_PREPARE_WRITE,
CENTRAL_GPA_ENTER_UUID,
CENTRAL_GPA_ENTER_START_HANDLE,
CENTRAL_GPA_ENTER_END_HANDLE,
CENTRAL_GPA_W4_RESPONSE,
CENTRAL_GPA_W4_RESPONSE2,
CENTRAL_GPA_W4_RESPONSE3,
CENTRAL_GPA_W4_RESPONSE4,
} central_state_t;
typedef struct advertising_report {
uint8_t type;
uint8_t event_type;
uint8_t address_type;
bd_addr_t address;
uint8_t rssi;
uint8_t length;
uint8_t * data;
} advertising_report_t;
static const uint8_t gpa_format_type_len[] = {
/* 0x00 */
1,1,1,1,1,
/* 0x05 */
2,2,
/* 0x07 */
3,4,6,8,16,
/* 0x0c */
1,2,2,3,4,6,8,16,
/* 0x14 */
4,8,2,4,4
};
static int gap_privacy = 0;
static int gap_bondable = 1;
static char gap_device_name[20];
static int gap_connectable = 0;
static char * sm_io_capabilities = NULL;
static int sm_mitm_protection = 0;
static int sm_have_oob_data = 0;
static uint8_t * sm_oob_data_A = (uint8_t *) "0123456789012345"; // = { 0x30...0x39, 0x30..0x35}
static uint8_t * sm_oob_data_B = (uint8_t *) "3333333333333333"; // = { 0x30...0x39, 0x30..0x35}
static int sm_min_key_size = 7;
static uint8_t pts_privacy_flag;
static int ui_passkey = 0;
static int ui_digits_for_passkey = 0;
static int ui_uint16 = 0;
static int ui_uint16_request = 0;
static int ui_uint16_pos = 0;
static int ui_uuid16 = 0;
static int ui_uuid128_request = 0;
static int ui_uuid128_pos = 0;
static uint8_t ui_uuid128[16];
static int ui_handles_count;
static int ui_handles_index;
static uint16_t ui_handles[10];
static uint16_t ui_attribute_handle;
static int ui_attribute_offset;
static int ui_value_request = 0;
static uint8_t ui_value_data[50];
static int ui_value_pos = 0;
static uint16_t ui_start_handle;
static uint16_t ui_end_handle;
static uint8_t ui_presentation_format[7];
static uint16_t ui_aggregate_handle;
static uint16_t handle = 0;
//static bd_addr_t public_pts_address = {0x00, 0x1B, 0xDC, 0x07, 0x32, 0xef};
static bd_addr_t public_pts_address = {0x23, 0x11, 0x23, 0x00, 0x76, 0xa1}; //iTag
static bd_addr_t public_pts_address2 = {0x0B, 0x3B, 0x22, 0xAC, 0x88, 0x20}; //A133模拟的le_counter0B:3B:22:AC:88:20
//static bd_addr_t public_pts_address = {0xe2, 0x9a, 0xd0, 0x68, 0x6d, 0x66};
//static bd_addr_t public_pts_address = {0xF7, 0xAA, 0x0D, 0xE2, 0x71, 0x16};
static int public_pts_address_type = 0;
static int public_pts_address_type2 = 0;
static bd_addr_t current_pts_address;
static int current_pts_address_type;
static int reconnection_address_set = 0;
static bd_addr_t our_private_address;
static uint16_t pts_signed_write_characteristic_uuid = 0xb00d;
static uint16_t pts_signed_write_characteristic_handle = 0x00b1;
static uint8_t signed_write_value[] = { 0x12 };
static central_state_t central_state = CENTRAL_IDLE;
static gatt_client_characteristic_t gap_name_characteristic;
static gatt_client_characteristic_t gap_reconnection_address_characteristic;
static gatt_client_characteristic_t gap_peripheral_privacy_flag_characteristic;
static gatt_client_characteristic_t signed_write_characteristic;
static gatt_client_service_t service;
static btstack_packet_callback_registration_t hci_event_callback_registration;
static void show_usage(void);
///
static void printUUID(uint8_t * uuid128, uint16_t uuid16){
if (uuid16){
printf("%04x",uuid16);
} else {
printf("%s", uuid128_to_str(uuid128));
}
}
static const char * att_errors[] = {
"OK",
"Invalid Handle",
"Read Not Permitted",
"Write Not Permitted",
"Invalid PDU",
"Insufficient Authentication",
"Request No Supported",
"Invalid Offset",
"Insufficient Authorization",
"Prepare Queue Full",
"Attribute Not Found",
"Attribute Not Long",
"Insufficient Encryption Size",
"Invalid Attribute Value Length",
"Unlikely Error",
"Insufficient Encryption",
"Unsupported Group Type",
"Insufficient Resource"
};
static const char * att_error_reserved = "Reserved";
static const char * att_error_application = "Application Error";
static const char * att_error_common_error = "Common Profile and Service Error Codes";
static const char * att_error_timeout = "Timeout";
static const char * att_error_string_for_code(uint8_t code){
if (code >= 0xe0) return att_error_common_error;
if (code >= 0xa0) return att_error_reserved;
if (code >= 0x80) return att_error_application;
if (code == 0x7f) return att_error_timeout;
if (code >= 0x12) return att_error_reserved;
return att_errors[ code];
}
const char * ad_event_types[] = {
"Connectable undirected advertising",
"Connectable directed advertising",
"Scannable undirected advertising",
"Non connectable undirected advertising",
"Scan Response"
};
static void handle_advertising_event(uint8_t * packet, int size){
// filter PTS
bd_addr_t addr;
gap_event_advertising_report_get_address(packet, addr);
uint8_t addr_type = gap_event_advertising_report_get_address_type(packet);
// always request address resolution
sm_address_resolution_lookup(addr_type, addr);
// ignore advertisement from devices other than pts
// if (memcmp(addr, current_pts_address, 6)) return;
uint8_t adv_event_type = gap_event_advertising_report_get_advertising_event_type(packet);
printf("Advertisement: %s - %s, ", bd_addr_to_str(addr), ad_event_types[adv_event_type]);
int adv_size = gap_event_advertising_report_get_data_length(packet);
const uint8_t * adv_data = gap_event_advertising_report_get_data(packet);
// check flags
ad_context_t context;
for (ad_iterator_init(&context, adv_size, (uint8_t *)adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){
uint8_t data_type = ad_iterator_get_data_type(&context);
// uint8_t size = ad_iterator_get_data_len(&context);
const uint8_t * data = ad_iterator_get_data(&context);
switch (data_type){
case 1: // AD_FLAGS
if (*data & 1) printf("LE Limited Discoverable Mode, ");
if (*data & 2) printf("LE General Discoverable Mode, ");
break;
default:
break;
}
}
// dump data
printf("Data: ");
printf_hexdump(adv_data, adv_size);
}
static uint8_t gap_adv_type(void){
// if (gap_scannable) return 0x02;
// if (gap_directed_connectable) return 0x01;
if (!gap_connectable) return 0x03;
return 0x00;
}
static void update_advertisment_params(void){
uint8_t adv_type = gap_adv_type();
printf("GAP: Connectable = %u -> advertising_type %u (%s)\n", gap_connectable, adv_type, ad_event_types[adv_type]);
bd_addr_t null_addr;
memset(null_addr, 0, 6);
uint16_t adv_int_min = 0x800;
uint16_t adv_int_max = 0x800;
switch (adv_type){
case 0:
case 2:
case 3:
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
break;
case 1:
case 4:
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, public_pts_address_type, public_pts_address, 0x07, 0x00);
break;
}
}
static void gap_run(void){
if (!hci_can_send_command_packet_now()) return;
}
static void app_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
uint16_t aHandle;
switch (packet_type) {
case HCI_EVENT_PACKET:
switch (packet[0]) {
case BTSTACK_EVENT_STATE:
// bt stack activated, get started
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){
printf("Central test ready\n");
show_usage();
gap_run();
}
break;
case HCI_EVENT_LE_META:
switch (hci_event_le_meta_get_subevent_code(packet)) {
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
handle = little_endian_read_16(packet, 4);
printf("Connection complete, handle 0x%04x\n", handle);
break;
default:
break;
}
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
aHandle = little_endian_read_16(packet, 3);
printf("Disconnected from handle 0x%04x\n", aHandle);
break;
case GAP_EVENT_ADVERTISING_REPORT:
handle_advertising_event(packet, size);
break;
default:
break;
}
}
gap_run();
}
static void use_public_pts_address(void){
memcpy(current_pts_address, public_pts_address, 6);
current_pts_address_type = public_pts_address_type;
}
static void extract_service(gatt_client_service_t * aService, uint8_t * data){
aService->start_group_handle = little_endian_read_16(data, 0);
aService->end_group_handle = little_endian_read_16(data, 2);
aService->uuid16 = 0;
reverse_128(&data[4], aService->uuid128);
if (uuid_has_bluetooth_prefix(aService->uuid128)){
aService->uuid16 = big_endian_read_32(aService->uuid128, 0);
}
}
static void extract_characteristic(gatt_client_characteristic_t * characteristic, uint8_t * packet){
characteristic->start_handle = little_endian_read_16(packet, 4);
characteristic->value_handle = little_endian_read_16(packet, 6);
characteristic->end_handle = little_endian_read_16(packet, 8);
characteristic->properties = little_endian_read_16(packet, 10);
characteristic->uuid16 = 0;
reverse_128(&packet[12], characteristic->uuid128);
if (uuid_has_bluetooth_prefix(characteristic->uuid128)){
characteristic->uuid16 = big_endian_read_32(characteristic->uuid128, 0);
}
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
if (packet_type != HCI_EVENT_PACKET) return;
uint8_t address_type;
bd_addr_t flipped_address;
gatt_client_characteristic_t characteristic;
uint8_t * value;
uint16_t value_handle;
uint16_t value_length;
uint16_t value_offset;
uint8_t status;
switch(packet[0]){
case GATT_EVENT_SERVICE_QUERY_RESULT:
switch (central_state){
case CENTRAL_W4_PRIMARY_SERVICES:
case CENTRAL_ENTER_SERVICE_UUID_4_DISCOVER_CHARACTERISTICS:
extract_service(&service, &packet[4]);
printf("Primary Service with UUID ");
printUUID(service.uuid128, service.uuid16);
printf(", start group handle 0x%04x, end group handle 0x%04x\n", service.start_group_handle, service.end_group_handle);
break;
extract_service(&service, &packet[4]);
printf("Primary Service with UUID ");
printUUID(service.uuid128, service.uuid16);
printf(", start group handle 0x%04x, end group handle 0x%04x\n", service.start_group_handle, service.end_group_handle);
break;
default:
break;
}
break;
case GATT_EVENT_INCLUDED_SERVICE_QUERY_RESULT:
value_handle = little_endian_read_16(packet, 4);
extract_service(&service, &packet[6]);
printf("Included Service at 0x%04x: ", value_handle);
printf("start group handle 0x%04x, end group handle 0x%04x with UUID ", service.start_group_handle, service.end_group_handle);
printUUID(service.uuid128, service.uuid16);
printf("\n");
break;
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
extract_characteristic(&characteristic, packet);
switch (central_state) {
case CENTRAL_W4_NAME_QUERY_COMPLETE:
gap_name_characteristic = characteristic;
printf("GAP Name Characteristic found, value handle: 0x04%x\n", gap_name_characteristic.value_handle);
break;
case CENTRAL_W4_RECONNECTION_ADDRESS_QUERY_COMPLETE:
gap_reconnection_address_characteristic = characteristic;
printf("GAP Reconnection Address Characteristic found, value handle: 0x04%x\n", gap_reconnection_address_characteristic.value_handle);
break;
case CENTRAL_W4_PERIPHERAL_PRIVACY_FLAG_QUERY_COMPLETE:
gap_peripheral_privacy_flag_characteristic = characteristic;
printf("GAP Peripheral Privacy Flag Characteristic found, value handle: 0x04%x\n", gap_peripheral_privacy_flag_characteristic.value_handle);
break;
case CENTRAL_W4_SIGNED_WRITE_QUERY_COMPLETE:
signed_write_characteristic = characteristic;
printf("Characteristic for Signed Write found, value handle: 0x%04x\n", signed_write_characteristic.value_handle);
break;
case CENTRAL_W4_CHARACTERISTICS:
printf("Characteristic found at 0x%04x with value handle 0x%04x, uuid ", characteristic.start_handle, characteristic.value_handle);
if (characteristic.uuid16){
printf("%04x\n", characteristic.uuid16);
} else {
printf_hexdump(characteristic.uuid128, 16);
}
break;
case CENTRAL_GPA_W4_RESPONSE2:
switch (ui_uuid16){
case GATT_CHARACTERISTIC_PRESENTATION_FORMAT:
case GATT_CHARACTERISTIC_AGGREGATE_FORMAT:
ui_attribute_handle = characteristic.value_handle;
break;
default:
break;
}
break;
default:
break;
}
break;
case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT: {
uint16_t descriptor_handle = little_endian_read_16(packet, 4);
uint8_t uuid128[16];
reverse_128(&packet[6], uuid128);
if (uuid_has_bluetooth_prefix(uuid128)){
printf("Characteristic descriptor at 0x%04x with UUID %04x\n", descriptor_handle, big_endian_read_32(uuid128, 0));
} else {
printf("Characteristic descriptor at 0x%04x with UUID %s\n", descriptor_handle, uuid128_to_str(uuid128));
}
break;
}
case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
value_handle = little_endian_read_16(packet, 4);
value_length = little_endian_read_16(packet, 6);
value = &packet[8];
switch (central_state){
case CENTRAL_W4_NAME_VALUE:
value[value_length] = 0;
printf("GAP Service: Device Name: %s\n", value);
break;
case CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_HANDLE:
case CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_UUID:
case CENTRAL_W4_READ_MULTIPLE_CHARACTERISTIC_VALUES:
printf("Value: ");
printf_hexdump(value, value_length);
break;
case CENTRAL_GPA_W4_RESPONSE:
switch (ui_uuid16){
case GATT_PRIMARY_SERVICE_UUID:
printf ("Attribute handle 0x%04x, primary service 0x%04x\n", value_handle, little_endian_read_16(value,0));
break;
case GATT_SECONDARY_SERVICE_UUID:
printf ("Attribute handle 0x%04x, secondary service 0x%04x\n", value_handle, little_endian_read_16(value,0));
break;
case GATT_INCLUDE_SERVICE_UUID:
printf ("Attribute handle 0x%04x, included service attribute handle 0x%04x, end group handle 0x%04x, uuid %04x\n",
value_handle, little_endian_read_16(value,0), little_endian_read_16(value,2), little_endian_read_16(value,4));
break;
case GATT_CHARACTERISTICS_UUID:
printf ("Attribute handle 0x%04x, properties 0x%02x, value handle 0x%04x, uuid ",
value_handle, value[0], little_endian_read_16(value,1));
if (value_length < 19){
printf("%04x\n", little_endian_read_16(value, 3));
} else {
uint8_t uuid128[16];
reverse_128(&value[3], uuid128);
printf("%s\n", uuid128_to_str(uuid128));
}
break;
case GATT_CHARACTERISTIC_EXTENDED_PROPERTIES:
printf ("Attribute handle 0x%04x, gatt characteristic properties 0x%04x\n", value_handle, little_endian_read_16(value,0));
break;
case GATT_CHARACTERISTIC_USER_DESCRIPTION:
// go the value, but PTS 6.3 requires another request
printf("Read by type request received, store attribute handle for read request\n");
ui_attribute_handle = value_handle;
break;
case GATT_CLIENT_CHARACTERISTICS_CONFIGURATION:
printf ("Attribute handle 0x%04x, gatt client characteristic configuration 0x%04x\n", value_handle, little_endian_read_16(value,0));
break;
case GATT_CHARACTERISTIC_AGGREGATE_FORMAT:
ui_handles_count = value_length >> 1;
printf ("Attribute handle 0x%04x, gatt characteristic aggregate format. Handles: ", value_handle);
for (ui_handles_index = 0; ui_handles_index < ui_handles_count ; ui_handles_index++){
ui_handles[ui_handles_index] = little_endian_read_16(value, (ui_handles_index << 1));
printf("0x%04x, ", ui_handles[ui_handles_index]);
}
printf("\n");
ui_handles_index = 0;
ui_aggregate_handle = value_handle;
break;
case GATT_CHARACTERISTIC_PRESENTATION_FORMAT:
printf("Presentation format: ");
printf_hexdump(value, value_length);
memcpy(ui_presentation_format, value, 7);
break;
default:
printf("Value: ");
printf_hexdump(value, value_length);
break;
}
break;
case CENTRAL_GPA_W4_RESPONSE3:
switch (ui_uuid16){
case GATT_CHARACTERISTIC_PRESENTATION_FORMAT:
printf("Value: ");
printf_hexdump(value, value_length);
printf("Format 0x%02x, Exponent 0x%02x, Unit 0x%04x\n",
ui_presentation_format[0], ui_presentation_format[1], little_endian_read_16(ui_presentation_format, 2));
break;
case GATT_CHARACTERISTIC_AGGREGATE_FORMAT:
printf("Aggregated value: ");
printf_hexdump(value, value_length);
memcpy(ui_value_data, value, value_length);
ui_value_pos = 0;
central_state = CENTRAL_GPA_W4_RESPONSE4;
default:
break;
}
break;
default:
break;
}
break;
case GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT:
value = &packet[10];
value_offset = little_endian_read_16(packet, 6);
value_length = little_endian_read_16(packet, 8);
central_state = CENTRAL_IDLE;
printf("Value (offset %02u): ", value_offset);
printf_hexdump(value, value_length);
break;
case GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
value_handle = little_endian_read_16(packet, 4);
value_length = little_endian_read_16(packet, 6);
value = &packet[8];
switch (central_state){
case CENTRAL_GPA_W4_RESPONSE2:
switch (ui_uuid16){
case GATT_CHARACTERISTIC_USER_DESCRIPTION:
value[value_length] = 0;
printf ("Attribute handle 0x%04x, characteristic user descriptor: %s\n", value_handle, value);
break;
default:
break;
}
break;
case CENTRAL_GPA_W4_RESPONSE4:
// only characteristic aggregate format
printf("Value: ");
printf_hexdump(&ui_value_data[ui_value_pos], gpa_format_type_len[value[0]]);
ui_value_pos += gpa_format_type_len[value[0]];
printf("Format 0x%02x, Exponent 0x%02x, Unit 0x%04x\n",
value[0], value[1], little_endian_read_16(value, 2));
break;
default:
printf("Value: ");
printf_hexdump(value, value_length);
break;
}
break;
case GATT_EVENT_LONG_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
value = &packet[10];
value_offset = little_endian_read_16(packet, 6);
value_length = little_endian_read_16(packet, 8);
printf("Value (offset %02u): ", value_offset);
printf_hexdump(value, value_length);
break;
case GATT_EVENT_QUERY_COMPLETE:
status = packet[4];
if (status){
central_state = CENTRAL_IDLE;
printf("GATT_EVENT_QUERY_COMPLETE: %s 0x%02x\n",
att_error_string_for_code(status), status);
break;
}
switch (central_state){
case CENTRAL_W4_NAME_QUERY_COMPLETE:
central_state = CENTRAL_W4_NAME_VALUE;
gatt_client_read_value_of_characteristic(handle_gatt_client_event, handle, &gap_name_characteristic);
break;
case CENTRAL_W4_RECONNECTION_ADDRESS_QUERY_COMPLETE:
central_state = CENTRAL_IDLE;
gap_le_get_own_address(&address_type, our_private_address);
printf("Our private address: %s\n", bd_addr_to_str(our_private_address));
reverse_bd_addr(our_private_address, flipped_address);
gatt_client_write_value_of_characteristic(handle_gatt_client_event, handle, gap_reconnection_address_characteristic.value_handle, 6, flipped_address);
reconnection_address_set = 1;
#ifdef PTS_USES_RECONNECTION_ADDRESS_FOR_ITSELF
memcpy(current_pts_address, our_private_address, 6);
current_pts_address_type = 1;
#endif
break;
case CENTRAL_W4_PERIPHERAL_PRIVACY_FLAG_QUERY_COMPLETE:
central_state = CENTRAL_IDLE;
switch (pts_privacy_flag){
case 0:
use_public_pts_address();
printf("Peripheral Privacy Flag set to FALSE, connecting to public PTS address again\n");
gatt_client_write_value_of_characteristic(handle_gatt_client_event, handle, gap_peripheral_privacy_flag_characteristic.value_handle, 1, &pts_privacy_flag);
break;
case 1:
printf("Peripheral Privacy Flag set to TRUE\n");
gatt_client_write_value_of_characteristic(handle_gatt_client_event, handle, gap_peripheral_privacy_flag_characteristic.value_handle, 1, &pts_privacy_flag);
break;
default:
break;
}
break;
case CENTRAL_W4_SIGNED_WRITE_QUERY_COMPLETE:
printf("Signed write on Characteristic with UUID 0x%04x\n", pts_signed_write_characteristic_uuid);
gatt_client_signed_write_without_response(handle_gatt_client_event, handle, signed_write_characteristic.value_handle, sizeof(signed_write_value), signed_write_value);
break;
case CENTRAL_W4_PRIMARY_SERVICES:
printf("Primary Service Discovery complete\n");
central_state = CENTRAL_IDLE;
break;
case CENTRAL_ENTER_SERVICE_UUID_4_DISCOVER_CHARACTERISTICS:
gatt_client_discover_characteristics_for_service(handle_gatt_client_event, handle, &service);
central_state = CENTRAL_W4_CHARACTERISTICS;
break;
case CENTRAL_GPA_W4_RESPONSE:
switch (ui_uuid16){
case GATT_CHARACTERISTIC_USER_DESCRIPTION:
central_state = CENTRAL_GPA_W4_RESPONSE2;
printf("Sending Read Characteristic Descriptor at 0x%04x\n", ui_attribute_handle);
gatt_client_read_characteristic_descriptor_using_descriptor_handle(handle_gatt_client_event, handle, ui_attribute_handle);
break;
case GATT_CHARACTERISTIC_PRESENTATION_FORMAT:
case GATT_CHARACTERISTIC_AGGREGATE_FORMAT:
{
printf("Searching Characteristic Declaration\n");
central_state = CENTRAL_GPA_W4_RESPONSE2;
gatt_client_service_t aService;
aService.start_group_handle = ui_start_handle;
aService.end_group_handle = ui_end_handle;
gatt_client_discover_characteristics_for_service(handle_gatt_client_event, handle, &aService);
break;
}
break;
default:
break;
}
break;
case CENTRAL_GPA_W4_RESPONSE2:
switch(ui_uuid16){
case GATT_CHARACTERISTIC_PRESENTATION_FORMAT:
case GATT_CHARACTERISTIC_AGGREGATE_FORMAT:
printf("Reading characteristic value at 0x%04x\n", ui_attribute_handle);
central_state = CENTRAL_GPA_W4_RESPONSE3;
gatt_client_read_value_of_characteristic_using_value_handle(handle_gatt_client_event, handle, ui_attribute_handle);
break;
default:
break;
}
break;
case CENTRAL_GPA_W4_RESPONSE4:
// so far, only GATT_CHARACTERISTIC_AGGREGATE_FORMAT
if (ui_handles_index < ui_handles_count) {
printf("Reading Characteristic Presentation Format at 0x%04x\n", ui_handles[ui_handles_index]);
gatt_client_read_characteristic_descriptor_using_descriptor_handle(handle_gatt_client_event, handle, ui_handles[ui_handles_index]);
ui_handles_index++;
break;
}
if (ui_handles_index == ui_handles_count ) {
// PTS rqequires to read the characteristic aggregate descriptor again (no idea why)
gatt_client_read_value_of_characteristic_using_value_handle(handle_gatt_client_event, handle, ui_aggregate_handle);
ui_handles_index++;
}
break;
default:
central_state = CENTRAL_IDLE;
break;
}
break;
case GATT_EVENT_NOTIFICATION:
value_handle = little_endian_read_16(packet, 4);
value_length = little_endian_read_16(packet, 6);
value = &packet[8];
printf("Notification handle 0x%04x, value: ", value_handle);
printf_hexdump(value, value_length);
break;
case GATT_EVENT_INDICATION:
value_handle = little_endian_read_16(packet, 4);
value_length = little_endian_read_16(packet, 6);
value = &packet[8];
printf("Indication handle 0x%04x, value: ", value_handle);
printf_hexdump(value, value_length);
break;
default:
break;
}
}
uint16_t value_handle = 1;
uint16_t attribute_size = 1;
int scanning_active = 0;
int num_rows = 0;
int num_lines = 0;
const char * rows[100];
const char * lines[100];
const char * empty_string = "";
const int width = 70;
static void reset_screen(void){
// free memory
int i = 0;
for (i=0;i<num_rows;i++) {
free((void*)rows[i]);
rows[i] = NULL;
}
num_rows = 0;
for (i=0;i<num_lines;i++) {
free((void*)lines[i]);
lines[i] = NULL;
}
num_lines = 0;
}
static void print_line(const char * format, ...){
va_list argptr;
va_start(argptr, format);
char * line = malloc(80);
vsnprintf(line, 80, format, argptr);
va_end(argptr);
lines[num_lines] = line;
num_lines++;
}
static void printf_row(const char * format, ...){
va_list argptr;
va_start(argptr, format);
char * row = malloc(80);
vsnprintf(row, 80, format, argptr);
va_end(argptr);
rows[num_rows] = row;
num_rows++;
}
static void print_screen(void){
// clear screen
printf("\e[1;1H\e[2J");
// full lines on top
int i;
for (i=0;i<num_lines;i++){
printf("%s\n", lines[i]);
}
printf("\n");
// two columns
int second_half = (num_rows + 1) / 2;
for (i=0;i<second_half;i++){
int pos = strlen(rows[i]);
printf("%s", rows[i]);
while (pos < width){
printf(" ");
pos++;
}
if (i + second_half < num_rows){
printf("| %s", rows[i+second_half]);
}
printf("\n");
}
printf("\n");
}
static void show_usage(void){
uint8_t iut_address_type;
bd_addr_t iut_address;
gap_le_get_own_address(&iut_address_type, iut_address);
reset_screen();
print_line("--- CLI for LE Central ---");
print_line("PTS: addr type %u, addr %s", current_pts_address_type, bd_addr_to_str(current_pts_address));
print_line("IUT: addr type %u, addr %s", iut_address_type, bd_addr_to_str(iut_address));
print_line("--------------------------");
print_line("GAP: connectable %u, bondable %u", gap_connectable, gap_bondable);
print_line("SM: %s, MITM protection %u", sm_io_capabilities, sm_mitm_protection);
print_line("SM: key range [%u..16], OOB data: %s", sm_min_key_size,
sm_have_oob_data ? (sm_have_oob_data == 1 ? (const char*) sm_oob_data_A : (const char*) sm_oob_data_B) : "None");
print_line("Privacy %u", gap_privacy);
print_line("Device name: %s", gap_device_name);
printf_row("c/C - connectable off");
printf_row("d/D - bondable off/on");
printf_row("---");
printf_row("1 - enable privacy using random non-resolvable private address");
printf_row("2 - clear Peripheral Privacy Flag on PTS");
printf_row("3 - set Peripheral Privacy Flag on PTS");
printf_row("9 - create HCI Classic connection to addr %s", bd_addr_to_str(public_pts_address));
printf_row("s/S - passive/active scanning");
printf_row("a - enable Advertisements");
printf_row("b - start bonding");
printf_row("n - query GAP Device Name");
printf_row("o - set GAP Reconnection Address");
printf_row("t - terminate connection, stop connecting");
printf_row("p - auto connect to PTS");
printf_row("P - direct connect to PTS");
printf_row("w - signed write on characteristic with UUID %04x", pts_signed_write_characteristic_uuid);
printf_row("W - signed write on attribute with handle 0x%04x and value 0x12", pts_signed_write_characteristic_handle);
printf_row("z - Update L2CAP Connection Parameters");
printf_row("---");
printf_row("e - Discover all Primary Services");
printf_row("f/F - Discover Primary Service by UUID16/UUID128");
printf_row("g - Discover all characteristics by UUID16");
printf_row("G - Discover all characteristics in range");
printf_row("h - Discover Characteristic Descriptors");
printf_row("i - Find all included services");
printf_row("j/J - Read (Long) Characteristic Value by handle");
printf_row("k/K - Read Characteristic Value by UUID16/UUID128");
printf_row("l/L - Read (Long) Characteristic Descriptor by handle");
printf_row("N - Read Multiple Characteristic Values");
printf_row("O - Write without Response");
printf_row("q/Q - Write (Long) Characteristic Value");
printf_row("r - Characteristic Reliable Write");
printf_row("R - Signed Write");
printf_row("u/U - Write (Long) Characteristic Descriptor");
printf_row("T - Read Generic Profile Attributes by Type");
printf_row("E - Prepare Write");
printf_row("v - Execute Write");
printf_row("V - Cancel Write");
printf_row("---");
printf_row("4 - IO_CAPABILITY_DISPLAY_ONLY");
printf_row("5 - IO_CAPABILITY_DISPLAY_YES_NO");
printf_row("6 - IO_CAPABILITY_NO_INPUT_NO_OUTPUT");
printf_row("7 - IO_CAPABILITY_KEYBOARD_ONLY");
printf_row("8 - IO_CAPABILITY_KEYBOARD_DISPLAY");
printf_row("m/M - MITM protection off");
printf_row("x/X - encryption key range [7..16]/[16..16]");
printf_row("y/Y - OOB data off/on/toggle A/B");
printf_row("---");
printf_row("Ctrl-c - exit");
print_screen();
}
static int hexForChar(char c){
if (c >= '0' && c <= '9'){
return c - '0';
}
if (c >= 'a' && c <= 'f'){
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F'){
return c - 'A' + 10;
}
return -1;
}
static void ui_request_uint16(const char * message){
printf("%s", message);
fflush(stdout);
ui_uint16_request = 1;
ui_uint16 = 0;
ui_uint16_pos = 0;
}
static void ui_request_data(const char * message){
printf("%s", message);
fflush(stdout);
ui_value_request = 1;
ui_value_pos = 0;
memset(ui_value_data, 0, sizeof(ui_value_data));
}
static int ui_process_digits_for_passkey(char buffer){
if (buffer < '0' || buffer > '9') {
return 0;
}
printf("%c", buffer);
fflush(stdout);
ui_passkey = ui_passkey * 10 + buffer - '0';
ui_digits_for_passkey--;
if (ui_digits_for_passkey == 0){
printf("\nSending Passkey %u (0x%x)\n", ui_passkey, ui_passkey);
sm_passkey_input(handle, ui_passkey);
}
return 0;
}
static int ui_process_uint16_request(char buffer){
if (buffer == 0x7f || buffer == 0x08) {
if (ui_uint16_pos){
printf("\b \b");
fflush(stdout);
ui_uint16 >>= 4;
ui_uint16_pos--;
}
return 0;
}
if (buffer == '\n' || buffer == '\r'){
ui_uint16_request = 0;
printf("\n");
switch (central_state){
case CENTRAL_W4_PRIMARY_SERVICES:
printf("Discover Primary Services with UUID16 %04x\n", ui_uint16);
gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, handle, ui_uint16);
return 0;
case CENTRAL_ENTER_SERVICE_UUID_4_DISCOVER_CHARACTERISTICS:
printf("Discover Primary Services with UUID16 %04x\n", ui_uint16);
gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, handle, ui_uint16);
return 0;
case CENTRAL_ENTER_START_HANDLE_4_DISCOVER_CHARACTERISTICS:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter end handle: ");
central_state = CENTRAL_ENTER_END_HANDLE_4_DISCOVER_CHARACTERISTICS;
return 0;
case CENTRAL_ENTER_END_HANDLE_4_DISCOVER_CHARACTERISTICS: {
printf("Discover Characteristics from 0x%04x to 0x%04x\n", ui_attribute_handle, ui_uint16);
central_state = CENTRAL_W4_CHARACTERISTICS;
gatt_client_service_t aService;
aService.start_group_handle = ui_attribute_handle;
aService.end_group_handle = ui_uint16;
gatt_client_discover_characteristics_for_service(handle_gatt_client_event, handle, &aService);
return 0;
}
case CENTRAL_W4_CHARACTERISTICS:
printf("Discover Characteristics with UUID16 %04x\n", ui_uint16);
gatt_client_discover_characteristics_for_handle_range_by_uuid16(handle_gatt_client_event, handle, 0x0001, 0xffff, ui_uint16);
return 0;
case CENTRAL_W4_DISCOVER_CHARACTERISTIC_DESCRIPTORS: {
gatt_client_characteristic_t characteristic;
characteristic.value_handle = ui_uint16 - 1;
characteristic.end_handle = ui_uint16;
gatt_client_discover_characteristic_descriptors(handle_gatt_client_event, handle, &characteristic);
break;
}
case CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_HANDLE:
printf("Read Characteristic Value with handle 0x%04x\n", ui_uint16);
gatt_client_read_value_of_characteristic_using_value_handle(handle_gatt_client_event, handle, ui_uint16);
return 0;
case CENTRAL_ENTER_OFFSET_4_READ_LONG_CHARACTERISTIC_VALUE_BY_HANDLE:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter long value offset: ");
central_state = CENTRAL_W4_READ_LONG_CHARACTERISTIC_VALUE_BY_HANDLE;
return 0;
case CENTRAL_W4_READ_LONG_CHARACTERISTIC_VALUE_BY_HANDLE:
printf("Read Long Characteristic Value with handle 0x%04x, offset 0x%04x\n", ui_attribute_handle, ui_uint16);
gatt_client_read_long_value_of_characteristic_using_value_handle_with_offset(handle_gatt_client_event, handle, ui_attribute_handle, ui_uint16);
return 0;
case CENTRAL_W4_READ_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE:
printf("Read Characteristic Descriptor with handle 0x%04x\n", ui_uint16);
gatt_client_read_characteristic_descriptor_using_descriptor_handle(handle_gatt_client_event, handle, ui_uint16);
return 0;
case CENTRAL_ENTER_OFFSET_4_READ_LONG_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter long characteristic offset: ");
central_state = CENTRAL_W4_READ_LONG_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE;
return 0;
case CENTRAL_W4_READ_LONG_CHARACTERISTIC_DESCRIPTOR_BY_HANDLE:
printf("Read Long Characteristic Descriptor with handle 0x%04x, offset 0x%04x\n", ui_attribute_handle, ui_uint16);
gatt_client_read_long_characteristic_descriptor_using_descriptor_handle_with_offset(handle_gatt_client_event, handle, ui_attribute_handle, ui_uint16);
return 0;
case CENTRAL_ENTER_HANDLE_4_READ_CHARACTERISTIC_VALUE_BY_UUID:
ui_uuid16 = ui_uint16;
ui_request_uint16("Please enter start handle: ");
central_state = CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_UUID;
return 0;
case CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_UUID:
printf("Read Characteristic Value with UUID16 0x%04x\n", ui_uint16);
gatt_client_read_value_of_characteristics_by_uuid16(handle_gatt_client_event, handle, ui_uint16, 0xffff, ui_uuid16);
return 0;
case CENTRAL_W4_READ_MULTIPLE_CHARACTERISTIC_VALUES:
if (ui_uint16){
ui_handles[ui_handles_count++] = ui_uint16;
ui_request_uint16("Please enter handle: ");
} else {
int i;
printf("Read multiple values, handles: ");
for (i=0;i<ui_handles_count;i++){
printf("0x%04x, ", ui_handles[i]);
}
printf("\n");
gatt_client_read_multiple_characteristic_values(handle_gatt_client_event, handle, ui_handles_count, ui_handles);
}
return 0;
case CENTRAL_ENTER_HANDLE_4_WRITE_LONG_CHARACTERISTIC_VALUE:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter offset: ");
central_state = CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_VALUE;
return 0;
case CENTRAL_ENTER_HANDLE_4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter offset: ");
central_state = CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR;
return 0;
case CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_VALUE:
case CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR:
ui_attribute_offset = ui_uint16;
ui_request_data("Please enter data: ");
return 0;
case CENTRAL_W4_WRITE_WITHOUT_RESPONSE:
case CENTRAL_W4_WRITE_CHARACTERICISTIC_VALUE:
case CENTRAL_W4_RELIABLE_WRITE:
case CENTRAL_W4_WRITE_CHARACTERISTIC_DESCRIPTOR:
case CENTRAL_W4_SIGNED_WRITE:
ui_attribute_handle = ui_uint16;
ui_request_data("Please enter data: ");
return 0;
case CENTRAL_W4_ENTER_OFFSET_4_PREPARE_WRITE:
ui_attribute_offset = ui_uint16;
ui_request_data("Please enter data: ");
return 0;
case CENTRAL_W4_ENTER_HANDLE_4_PREPARE_WRITE:
ui_attribute_handle = ui_uint16;
ui_request_uint16("Please enter offset: ");
central_state = CENTRAL_W4_ENTER_OFFSET_4_PREPARE_WRITE;
return 0;
case CENTRAL_GPA_ENTER_START_HANDLE:
ui_start_handle = ui_uint16;
central_state = CENTRAL_GPA_ENTER_END_HANDLE;
ui_request_uint16("Please enter end handle: ");
return 0;
case CENTRAL_GPA_ENTER_END_HANDLE:
ui_end_handle = ui_uint16;
central_state = CENTRAL_GPA_W4_RESPONSE;
ui_request_uint16("Please enter uuid: ");
return 0;
case CENTRAL_GPA_W4_RESPONSE:
ui_uuid16 = ui_uint16;
printf("Read by type: range 0x%04x-0x%04x, uuid %04x\n", ui_start_handle, ui_end_handle, ui_uuid16);
gatt_client_read_value_of_characteristics_by_uuid16(handle_gatt_client_event, handle, ui_start_handle, ui_end_handle, ui_uuid16);
return 0;
default:
return 0;
}
}
int hex = hexForChar(buffer);
if (hex < 0){
return 0;
}
printf("%c", buffer);
fflush(stdout);
ui_uint16 = ui_uint16 << 4 | hex;
ui_uint16_pos++;
return 0;
}
static int uuid128_pos_starts_with_dash(int pos){
switch(pos){
case 8:
case 12:
case 16:
case 20:
#ifdef PTS_UUID128_REPRESENTATION
case 4:
case 24:
#endif
return 1;
default:
return 0;
}
}
static int ui_process_uuid128_request(char buffer){
if (buffer == '-') return 0; // skip -
if (buffer == 0x7f || buffer == 0x08) {
if (ui_uuid128_pos){
if (uuid128_pos_starts_with_dash(ui_uuid128_pos)){
printf("\b \b");
fflush(stdout);
}
printf("\b \b");
fflush(stdout);
ui_uuid128_pos--;
}
return 0;
}
int hex = hexForChar(buffer);
if (hex < 0){
return 0;
}
printf("%c", buffer);
fflush(stdout);
if (ui_uuid128_pos & 1){
ui_uuid128[ui_uuid128_pos >> 1] = (ui_uuid128[ui_uuid128_pos >> 1] & 0xf0) | hex;
} else {
ui_uuid128[ui_uuid128_pos >> 1] = hex << 4;
}
ui_uuid128_pos++;
if (ui_uuid128_pos == 32){
ui_uuid128_request = 0;
printf("\n");
switch (central_state){
case CENTRAL_W4_PRIMARY_SERVICES:
printf("Discover Primary Services with UUID128 %s\n", uuid128_to_str(ui_uuid128));
gatt_client_discover_primary_services_by_uuid128(handle_gatt_client_event, handle, ui_uuid128);
return 0;
case CENTRAL_W4_READ_CHARACTERISTIC_VALUE_BY_UUID:
printf("Read Characteristic Value with UUID128 %s\n", uuid128_to_str(ui_uuid128));
gatt_client_read_value_of_characteristics_by_uuid128(handle_gatt_client_event, handle, 0x0001, 0xffff, ui_uuid128);
return 0;
default:
return 0;
}
}
if (uuid128_pos_starts_with_dash(ui_uuid128_pos)){
printf("-");
fflush(stdout);
}
return 0;
}
static void ui_announce_write(const char * method){
printf("Request: %s handle 0x%04x data: ", method, ui_uint16);
printf_hexdump(ui_value_data, ui_value_pos >> 1);
printf("\n");
}
static int ui_process_data_request(char buffer){
if (buffer == 0x7f || buffer == 0x08) {
if (ui_value_pos){
if ((ui_value_pos & 1) == 0){
printf("\b");
}
printf("\b \b");
fflush(stdout);
ui_value_pos--;
}
return 0;
}
if (buffer == '\n' || buffer == '\r'){
ui_value_request = 0;
printf("\n");
uint16_t value_len = ui_value_pos >> 1;
switch (central_state){
case CENTRAL_W4_WRITE_WITHOUT_RESPONSE:
ui_announce_write("Write without response");
gatt_client_write_value_of_characteristic_without_response(handle, ui_attribute_handle, value_len, ui_value_data);
break;
case CENTRAL_W4_WRITE_CHARACTERICISTIC_VALUE:
ui_announce_write("Write Characteristic Value");
gatt_client_write_value_of_characteristic(handle_gatt_client_event, handle, ui_attribute_handle, value_len, ui_value_data);
break;
case CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_VALUE:
ui_announce_write("Write Long Characteristic Value");
gatt_client_write_long_value_of_characteristic_with_offset(handle_gatt_client_event, handle, ui_attribute_handle, ui_attribute_offset, value_len, ui_value_data);
break;
case CENTRAL_W4_RELIABLE_WRITE:
ui_announce_write("Reliabe Write");
gatt_client_reliable_write_long_value_of_characteristic(handle_gatt_client_event, handle, ui_attribute_handle, value_len, ui_value_data);
break;
case CENTRAL_W4_WRITE_CHARACTERISTIC_DESCRIPTOR:
ui_announce_write("Write Characteristic Descriptor");
gatt_client_write_characteristic_descriptor_using_descriptor_handle(handle_gatt_client_event, handle, ui_attribute_handle, value_len, ui_value_data);
break;
case CENTRAL_W4_WRITE_LONG_CHARACTERISTIC_DESCRIPTOR:
ui_announce_write("Write Long Characteristic Descriptor");
gatt_client_write_long_characteristic_descriptor_using_descriptor_handle_with_offset(handle_gatt_client_event, handle, ui_attribute_handle, ui_attribute_offset, value_len, ui_value_data);
break;
case CENTRAL_W4_SIGNED_WRITE:
ui_announce_write("Signed Write");
gatt_client_signed_write_without_response(handle_gatt_client_event, handle, ui_attribute_handle, value_len, ui_value_data);
break;
case CENTRAL_W4_ENTER_OFFSET_4_PREPARE_WRITE:
ui_announce_write("Preprare Write");
gatt_client_prepare_write(handle_gatt_client_event, handle, ui_attribute_handle, ui_attribute_offset, value_len, ui_value_data);
break;
default:
break;
}
return 0;
}
// ignore spaces
if (buffer == ' ') return 0;
int hex = hexForChar(buffer);
if (hex < 0){
return 0;
}
printf("%c", buffer);
if (ui_value_pos & 1){
ui_value_data[ui_value_pos >> 1] = (ui_value_data[ui_value_pos >> 1] & 0xf0) | hex;
printf(" ");
} else {
ui_value_data[ui_value_pos >> 1] = hex << 4;
}
ui_value_pos++;
fflush(stdout);
return 0;
}
static void ui_process_command(char buffer){
//int res;
switch (buffer){
case '1':
printf("Enabling non-resolvable private address\n");
gap_random_address_set_mode(GAP_RANDOM_ADDRESS_NON_RESOLVABLE);
gap_privacy = 1;
update_advertisment_params();
show_usage();
break;
case 'a':
handle = 0x12;
printf("handle = 0x%x\n", handle);
break;
case 'b':
handle = 0x13;
printf("handle = 0x%x\n", handle);
break;
case 'P':
memcpy(current_pts_address, public_pts_address, 6);
current_pts_address_type = public_pts_address_type;
gap_connect(current_pts_address, current_pts_address_type);
printf("Direct Connection Establishment to type %u, addr %s\n", current_pts_address_type, bd_addr_to_str(current_pts_address));
break;
case 'A':
memcpy(current_pts_address, public_pts_address2, 6);
current_pts_address_type = public_pts_address_type2;
gap_connect(current_pts_address, current_pts_address_type);
printf("Direct Connection Establishment to type %u, addr %s\n", current_pts_address_type, bd_addr_to_str(current_pts_address));
break;
case 's':
if (scanning_active){
gap_stop_scan();
scanning_active = 0;
break;
}
printf("Start passive scanning\n");
gap_set_scan_parameters(0, 48, 48);
gap_start_scan();
scanning_active = 1;
break;
case 'S':
if (scanning_active){
printf("Stop scanning\n");
gap_stop_scan();
scanning_active = 0;
break;
}
printf("Start active scanning\n");
gap_set_scan_parameters(1, 48, 48);
gap_start_scan();
scanning_active = 1;
break;
case 't':
printf("Terminating connection\n");
hci_send_cmd(&hci_disconnect, handle, 0x13);
gap_auto_connection_stop_all();
gap_connect_cancel();
break;
case 'O':
central_state = CENTRAL_W4_WRITE_WITHOUT_RESPONSE;
ui_request_uint16("Please enter handle: ");
break;
case 'q':
central_state = CENTRAL_W4_WRITE_CHARACTERICISTIC_VALUE;
ui_request_uint16("Please enter handle: ");
break;
case 'Q':
central_state = CENTRAL_ENTER_HANDLE_4_WRITE_LONG_CHARACTERISTIC_VALUE;
ui_request_uint16("Please enter handle: ");
break;
default:
show_usage();
break;
}
}
static void stdin_process(char c){
if (ui_digits_for_passkey){
ui_process_digits_for_passkey(c);
return;
}
if (ui_uint16_request){
ui_process_uint16_request(c);
return;
}
if (ui_uuid128_request){
ui_process_uuid128_request(c);
return;
}
if (ui_value_request){
ui_process_data_request(c);
return;
}
ui_process_command(c);
return;
}
// ATT Client Read Callback for Dynamic Data
// - if buffer == NULL, don't copy data, just return size of value
// - if buffer != NULL, copy data and return number bytes copied
// @param offset defines start of attribute value
static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
printf("READ Callback, handle %04x, offset %u, buffer size %u\n", handle, offset, buffer_size);
uint16_t att_value_len;
uint16_t uuid16 = att_uuid_for_handle(handle);
switch (uuid16){
case 0x2a00:
att_value_len = strlen(gap_device_name);
if (buffer) {
memcpy(buffer, gap_device_name, att_value_len);
}
return att_value_len;
default:
break;
}
return 0;
}
int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]){
printf("BTstack LE Peripheral starting up...\n");
memset(rows, 0, sizeof(char *) * 100);
memset(lines, 0, sizeof(char *) * 100);
strcpy(gap_device_name, "BTstack");
// register for HCI Events
hci_event_callback_registration.callback = &app_packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
// set up l2cap_le
l2cap_init();
// setup GATT Client
gatt_client_init();
// Setup ATT/GATT Server
att_server_init(profile_data, att_read_callback, NULL);
att_server_register_packet_handler(app_packet_handler);
// Setup LE Device DB
le_device_db_init();
uint8_t pts_irk[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
le_device_db_add(public_pts_address_type, public_pts_address, pts_irk);
le_device_db_add(public_pts_address_type2, public_pts_address2, pts_irk);
// set adv params
update_advertisment_params();
memcpy(current_pts_address, public_pts_address, 6);
current_pts_address_type = public_pts_address_type;
// classic discoverable / connectable
gap_connectable_control(0);
gap_discoverable_control(1);
// allow foor terminal input
btstack_stdin_setup(stdin_process);
// turn on!
hci_power_control(HCI_POWER_ON);
return 0;
}
输入 P 连接第一个设备,输入a切换到第一个设备对应的handle
输入O
输入e回车
输入1回车
第一个设备哔哔哔响
输入 A 连接第二个设备,输入b切换到第二个设备对应的handle
输入q
输入6回车
输入16进制数据
第二个设备收到串口数据
离线