gattlib: Introduce helper function to select Eddystone devices

pull/120/head
Olivier Martin 2019-07-15 11:39:04 +02:00 committed by Olivier Martin
parent 50129fe753
commit 193c86590e
12 changed files with 331 additions and 50 deletions

View File

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 Olivier Martin <olivier@labapart.org>
# Copyright (C) 2016-2019 Olivier Martin <olivier@labapart.org>
#
#
# This program is free software; you can redistribute it and/or modify
@ -76,7 +76,8 @@ set(gattlib_SRCS gattlib_adapter.c
gattlib_connect.c
gattlib_discover.c
gattlib_read_write.c
${CMAKE_SOURCE_DIR}/common/gattlib_common.c)
${CMAKE_SOURCE_DIR}/common/gattlib_common.c
${CMAKE_SOURCE_DIR}/common/gattlib_eddystone.c)
# Added Glib support
pkg_search_module(GLIB REQUIRED glib-2.0)

View File

@ -289,9 +289,49 @@ int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int en
*descriptors = descriptor_data.descriptors;
*descriptor_count = descriptor_data.descriptors_count;
return 0;
return GATTLIB_SUCCESS;
}
int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) {
return gattlib_discover_desc_range(connection, 0x0001, 0xffff, descriptors, descriptor_count);
}
/**
* @brief Function to retrieve Advertisement Data from a MAC Address
*
* @param adapter is the adapter the new device has been seen
* @param mac_address is the MAC address of the device to get the RSSI
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data(gatt_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
{
return GATTLIB_NOT_SUPPORTED;
}
/**
* @brief Function to retrieve Advertisement Data from a MAC Address
*
* @param adapter is the adapter the new device has been seen
* @param mac_address is the MAC address of the device to get the RSSI
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
{
return GATTLIB_NOT_SUPPORTED;
}

View File

@ -0,0 +1,61 @@
#include <string.h>
#include "gattlib_internal.h"
#define EDDYSTONE_SERVICE_UUID "0000FEAA-0000-1000-8000-00805F9B34FB"
struct on_eddystone_discovered_device_arg {
gattlib_discovered_device_with_data_t discovered_device_cb;
void *user_data;
};
static void on_eddystone_discovered_device(void *adapter, const char* addr, const char* name, void *user_data)
{
struct on_eddystone_discovered_device_arg *callback_data = user_data;
gattlib_advertisement_data_t *advertisement_data;
size_t advertisement_data_count;
uint16_t manufacturer_id;
uint8_t *manufacturer_data;
size_t manufacturer_data_size;
int ret;
ret = gattlib_get_advertisement_data_from_mac(adapter, addr,
&advertisement_data, &advertisement_data_count,
&manufacturer_id, &manufacturer_data, &manufacturer_data_size);
if (ret != 0) {
return;
}
callback_data->discovered_device_cb(adapter, addr, name,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size,
callback_data->user_data);
}
int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddsytone_types,
gattlib_discovered_device_with_data_t discovered_device_cb, int timeout, void *user_data)
{
uuid_t eddystone_uuid;
uint32_t enabled_filters = GATTLIB_DISCOVER_FILTER_USE_UUID;
int ret;
ret = gattlib_string_to_uuid(EDDYSTONE_SERVICE_UUID, strlen(EDDYSTONE_SERVICE_UUID) + 1, &eddystone_uuid);
if (ret != 0) {
fprintf(stderr, "Fail to convert characteristic TX to UUID.\n");
return GATTLIB_ERROR_INTERNAL;
}
uuid_t *uuid_filter_list[] = { &eddystone_uuid, NULL };
if (eddsytone_types & GATTLIB_EDDYSTONE_LIMIT_RSSI) {
enabled_filters |= GATTLIB_DISCOVER_FILTER_USE_RSSI;
}
struct on_eddystone_discovered_device_arg callback_data = {
.discovered_device_cb = discovered_device_cb,
.user_data = user_data
};
return gattlib_adapter_scan_enable_with_filter(adapter, uuid_filter_list, rssi_threshold, enabled_filters,
on_eddystone_discovered_device, timeout, &callback_data);
}

View File

@ -87,6 +87,7 @@ set(gattlib_SRCS gattlib.c
gattlib_stream.c
bluez5/lib/uuid.c
${CMAKE_SOURCE_DIR}/common/gattlib_common.c
${CMAKE_SOURCE_DIR}/common/gattlib_eddystone.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-adaptater1.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-device1.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-gattcharacteristic1.c

View File

@ -52,6 +52,8 @@ int get_advertisement_data_from_device(OrgBluezDevice1 *bluez_device1,
return GATTLIB_INVALID_PARAMETER;
}
*manufacturer_id = 0;
*manufacturer_data_size = 0;
manufacturer_data_variant = org_bluez_device1_get_manufacturer_data(bluez_device1);
if (manufacturer_data_variant != NULL) {
fprintf(stderr, "Warning: Manufacturer Data not supported: %s\n",
@ -92,6 +94,8 @@ int get_advertisement_data_from_device(OrgBluezDevice1 *bluez_device1,
index++;
}
} else {
*advertisement_data_count = 0;
}
return GATTLIB_SUCCESS;

View File

@ -8,40 +8,30 @@ from gattlib import adapter
parser = argparse.ArgumentParser(description='Gattlib Find Eddystone device example')
args = parser.parse_args()
EDDYSTONE_COMMON_DATA_UUID = 'FEAA'
EDDYSTONE_URL_SCHEME_PREFIX = {
0x00: "http://www.",
0x01: "https://www.",
0x02: "http://",
0x03: "https://",
}
# Use default adapter
default_adapter = adapter.Adapter()
def on_discovered_ble_device(device, user_data):
def on_eddystone_device_found(device, advertisement_data, manufacturer_id, manufacturer_data, user_data):
rssi = default_adapter.get_rssi_from_mac(device.id)
print("Find Eddystone device %s (RSSI:%d)" % (device.id, rssi))
# Retrieve Advertisement Data
advertisement_data, manufacturer_id, manufacturer_data = default_adapter.gattlib_get_advertisement_data_from_mac(device.id)
# Service Data
service_data = advertisement_data[0xFEAA]
if service_data[0] == 0x00:
print("Eddystone UID: TX Power:0x%x NID:%s BID:%s" % (service_data[1], service_data[2:12], service_data[12:18]))
elif service_data[0] == 0x10:
print("Eddystone URL: TX Power:0x%x URL:%s%s" % (service_data[1], EDDYSTONE_URL_SCHEME_PREFIX[service_data[2]], service_data[3:].decode("utf-8")))
elif service_data[0] == 0x20:
eddystone_data = advertisement_data[adapter.EDDYSTONE_COMMON_DATA_UUID]
if eddystone_data[0] == adapter.EDDYSTONE_TYPE_UID:
print("Eddystone UID: TX Power:0x%x NID:%s BID:%s" % (eddystone_data[1], eddystone_data[2:12], eddystone_data[12:18]))
elif eddystone_data[0] == adapter.EDDYSTONE_TYPE_URL:
print("Eddystone URL: TX Power:0x%x URL:%s%s" % (eddystone_data[1], adapter.EDDYSTONE_URL_SCHEME_PREFIX[eddystone_data[2]], eddystone_data[3:].decode("utf-8")))
elif eddystone_data[0] == adapter.EDDYSTONE_TYPE_TLM:
print("Eddystone TLM")
elif service_data[0] == 0x30:
elif eddystone_data[0] == adapter.EDDYSTONE_TYPE_EID:
print("Eddystone EID")
else:
print("Eddystone frame not supported: 0x%x" % service_data[0])
print("Eddystone frame not supported: 0x%x" % eddystone_data[0])
# Scan for 30 seconds
default_adapter.open()
default_adapter.scan_enable(on_discovered_ble_device, 30, uuids=[EDDYSTONE_COMMON_DATA_UUID])
default_adapter.scan_eddystone_enable(on_eddystone_device_found,
adapter.GATTLIB_EDDYSTONE_TYPE_UID | adapter.GATTLIB_EDDYSTONE_TYPE_URL | adapter.GATTLIB_EDDYSTONE_TYPE_TLM | adapter.GATTLIB_EDDYSTONE_TYPE_EID,
30) # Look for 30 seconds

View File

@ -2,14 +2,32 @@
import argparse
from gattlib import device
from gattlib import adapter, device
from environment_service import environment_service
from sound_service import sound_service
# Use default adapter
default_adapter = adapter.Adapter()
def on_thingy_device_found(device, user_data):
print("Found Nordic Thingy!")
# We pick-up the first device. Disable scanning
default_adapter.scan_disable()
# Connect to the found device
device.connect()
device.discover()
args = user_data
args.func(args, device)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Gattlib example for Nordic Thingy')
parser.add_argument('mac', type=str, help='Mac Address of the GATT device to connect')
parser.add_argument('--mac', type=str, help='Mac Address of the GATT device to connect')
subparsers = parser.add_subparsers(help='sub-command help')
environment_parser = subparsers.add_parser('environment', help='Environment Command')
@ -24,9 +42,18 @@ if __name__ == '__main__':
if not hasattr(args, 'func'):
raise RuntimeError("Please specify the command to launch: 'environment', 'sound'")
gatt_device = device.Device(adapter=None, addr=args.mac)
gatt_device.connect()
gatt_device.discover()
if args.mac:
gatt_device = device.Device(adapter=None, addr=mac)
gatt_device.connect()
gatt_device.discover()
# Launch the sub-command specific function
args.func(args, gatt_device)
# Launch the sub-command specific function
args.func(args, gatt_device)
else:
default_adapter.open()
NORDIC_THINGY_CONFIGURATION_SERVICE = "EF680100-9B35-4933-9B10-52FFA9740042"
default_adapter.scan_enable(on_thingy_device_found, timeout=10,
uuids=[NORDIC_THINGY_CONFIGURATION_SERVICE],
user_data=args)

View File

@ -40,6 +40,10 @@ def play_sample(config_characteristic, speaker_characteristic):
config_characteristic.write(sound_config)
# Test speaker
speaker_characteristic.write(b'\x03')
# Wait a bit before finishing
time.sleep(1)
m_mainloop.quit()
@ -85,6 +89,10 @@ def play_wav_file(config_characteristic, speaker_characteristic, wav_filepath):
stream.close()
print("All WAV file has been sent")
# Wait a bit before finishing
time.sleep(1)
m_mainloop.quit()

View File

@ -66,12 +66,25 @@ gattlib_adapter_open = gattlib.gattlib_adapter_open
gattlib_adapter_open.argtypes = [c_char_p, POINTER(c_void_p)]
# typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, const char* name, void *user_data)
gattlib_discovered_device_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, c_void_p)
gattlib_discovered_device_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, py_object)
# typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char* addr, const char* name,
# gattlib_advertisement_data_t *advertisement_data, size_t advertisement_data_count,
# uint16_t manufacturer_id, uint8_t *manufacturer_data, size_t manufacturer_data_size,
# void *user_data);
gattlib_discovered_device_with_data_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p,
POINTER(GattlibAdvertisementData), c_size_t, c_uint16, c_void_p, c_size_t,
py_object)
# int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
# gattlib_discovered_device_t discovered_device_cb, int timeout, void *user_data)
gattlib_adapter_scan_enable_with_filter = gattlib.gattlib_adapter_scan_enable_with_filter
gattlib_adapter_scan_enable_with_filter.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)), c_int16, c_uint32, gattlib_discovered_device_type, c_int, c_void_p]
gattlib_adapter_scan_enable_with_filter.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)), c_int16, c_uint32, gattlib_discovered_device_type, c_int, py_object]
# int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddsytone_types,
# gattlib_discovered_device_with_data_t discovered_device_cb, int timeout, void *user_data)
gattlib_adapter_scan_eddystone = gattlib.gattlib_adapter_scan_eddystone
gattlib_adapter_scan_eddystone.argtypes = [c_void_p, c_int16, c_uint32, gattlib_discovered_device_with_data_type, c_int, py_object]
# int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
gattlib_discover_primary = gattlib.gattlib_discover_primary

View File

@ -1,17 +1,38 @@
from gattlib import *
from .device import Device
from .exception import handle_return
from .exception import handle_return, AdapterNotOpened
from .uuid import gattlib_uuid_to_int
GATTLIB_DISCOVER_FILTER_USE_UUID = (1 << 0)
GATTLIB_DISCOVER_FILTER_USE_RSSI = (1 << 1)
GATTLIB_EDDYSTONE_TYPE_UID = (1 << 0)
GATTLIB_EDDYSTONE_TYPE_URL = (1 << 1)
GATTLIB_EDDYSTONE_TYPE_TLM = (1 << 2)
GATTLIB_EDDYSTONE_TYPE_EID = (1 << 3)
GATTLIB_EDDYSTONE_LIMIT_RSSI = (1 << 4)
EDDYSTONE_TYPE_UID = 0x00
EDDYSTONE_TYPE_URL = 0x10
EDDYSTONE_TYPE_TLM = 0x20
EDDYSTONE_TYPE_EID = 0x30
EDDYSTONE_COMMON_DATA_UUID = 0xFEAA
EDDYSTONE_URL_SCHEME_PREFIX = {
0x00: "http://www.",
0x01: "https://www.",
0x02: "http://",
0x03: "https://",
}
class Adapter:
def __init__(self, name=c_char_p(None)):
self._name = name
self._adapter = c_void_p(None)
self._is_opened = False # Note: 'self._adapter != c_void_p(None)' does not seem to return the expected result
@property
def name(self):
@ -23,10 +44,15 @@ class Adapter:
return []
def open(self):
return gattlib_adapter_open(self._name, byref(self._adapter))
ret = gattlib_adapter_open(self._name, byref(self._adapter))
if ret == 0:
self._is_opened = True
return ret
def close(self):
return gattlib.gattlib_adapter_close(self._adapter)
ret = gattlib.gattlib_adapter_close(self._adapter)
self._is_opened = False
return ret
def on_discovered_device(self, adapter, addr, name, user_data):
device = Device(self, addr, name)
@ -36,6 +62,9 @@ class Adapter:
assert on_discovered_device_callback != None
self.on_discovered_device_callback = on_discovered_device_callback
if not self._is_opened:
raise AdapterNotOpened()
enabled_filters = 0
uuid_list = None
rssi = 0
@ -66,6 +95,58 @@ class Adapter:
timeout, user_data)
handle_return(ret)
@staticmethod
def on_discovered_ble_device_with_details(adapter, mac_addr, name, advertisement_data_buffer, advertisement_data_count,
manufacturer_id, manufacturer_data_buffer, manufacturer_data_size,
user_data):
advertisement_data = {}
manufacturer_data = None
for i in range(0, advertisement_data_count):
service_data = advertisement_data_buffer[i]
uuid = gattlib_uuid_to_int(service_data.uuid)
pointer_type = POINTER(c_byte * service_data.data_length)
c_bytearray = cast(service_data.data, pointer_type)
data = bytearray(service_data.data_length)
for i in range(service_data.data_length):
data[i] = c_bytearray.contents[i] & 0xFF
advertisement_data[uuid] = data
if manufacturer_data_size > 0:
pointer_type = POINTER(c_byte * manufacturer_data_size)
c_bytearray = cast(manufacturer_data_buffer, pointer_type)
manufacturer_data = bytearray(manufacturer_data_size)
for i in range(manufacturer_data_size):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF
device = Device(user_data["adapter"], mac_addr, name)
user_data["callback"](device, advertisement_data, manufacturer_id, manufacturer_data, user_data["user_data"])
def scan_eddystone_enable(self, on_discovered_device_callback, eddystone_filters, timeout, rssi_threshold=None, user_data=None):
if not self._is_opened:
raise AdapterNotOpened()
rssi = 0
if rssi_threshold is not None:
eddystone_filters |= GATTLIB_EDDYSTONE_LIMIT_RSSI
rssi = int(rssi_threshold)
args = {
"adapter": self,
"callback": on_discovered_device_callback,
"user_data": user_data
}
ret = gattlib_adapter_scan_eddystone(self._adapter, rssi, eddystone_filters,
gattlib_discovered_device_with_data_type(Adapter.on_discovered_ble_device_with_details),
timeout, args)
handle_return(ret)
def scan_disable(self):
ret = gattlib.gattlib_adapter_scan_disable(self._adapter)
handle_return(ret)

View File

@ -1,32 +1,44 @@
GATTLIB_SUCCESS = 0
GATTLIB_INVALID_PARAMETER = 1
GATTLIB_NOT_FOUND = 2
GATTLIB_OUT_OF_MEMORY = 3
GATTLIB_NOT_SUPPORTED = 4
GATTLIB_DEVICE_ERROR = 5
GATTLIB_ERROR_DBUS = 6
GATTLIB_SUCCESS = 0
GATTLIB_INVALID_PARAMETER = 1
GATTLIB_NOT_FOUND = 2
GATTLIB_OUT_OF_MEMORY = 3
GATTLIB_NOT_SUPPORTED = 4
GATTLIB_DEVICE_ERROR = 5
GATTLIB_ERROR_DBUS = 6
class GattlibException(Exception):
pass
class AdapterNotOpened(GattlibException):
pass
class InvalidParameter(GattlibException):
pass
class NotFound(GattlibException):
pass
class OutOfMemory(GattlibException):
pass
class NotSupported(GattlibException):
pass
class DeviceError(GattlibException):
pass
class DBusError(GattlibException):
pass
def handle_return(ret):
if ret == GATTLIB_INVALID_PARAMETER:
raise InvalidParameter()

View File

@ -56,6 +56,8 @@ extern "C" {
#define GATTLIB_NOT_SUPPORTED 4
#define GATTLIB_DEVICE_ERROR 5
#define GATTLIB_ERROR_DBUS 6
#define GATTLIB_ERROR_BLUEZ 7
#define GATTLIB_ERROR_INTERNAL 8
/* GATT Characteristic Properties Bitfield values */
#define GATTLIB_CHARACTERISTIC_BROADCAST 0x01
@ -93,9 +95,21 @@ extern "C" {
#define GATTLIB_DISCOVER_FILTER_USE_UUID (1 << 0)
#define GATTLIB_DISCOVER_FILTER_USE_RSSI (1 << 1)
#define GATTLIB_EDDYSTONE_TYPE_UID (1 << 0)
#define GATTLIB_EDDYSTONE_TYPE_URL (1 << 1)
#define GATTLIB_EDDYSTONE_TYPE_TLM (1 << 2)
#define GATTLIB_EDDYSTONE_TYPE_EID (1 << 3)
#define GATTLIB_EDDYSTONE_LIMIT_RSSI (1 << 4)
typedef struct _gatt_connection_t gatt_connection_t;
typedef struct _gatt_stream_t gatt_stream_t;
typedef struct {
uuid_t uuid;
uint8_t* data;
size_t data_length;
} gattlib_advertisement_data_t;
typedef void (*gattlib_event_handler_t)(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data);
/**
@ -116,6 +130,24 @@ typedef void (*gattlib_disconnection_handler_t)(void* user_data);
*/
typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, const char* name, void *user_data);
/**
* @brief Handler called on new discovered BLE device
*
* @param adapter is the adapter that has found the BLE device
* @param addr is the MAC address of the BLE device
* @param name is the name of BLE device if advertised
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char* addr, const char* name,
gattlib_advertisement_data_t *advertisement_data, size_t advertisement_data_count,
uint16_t manufacturer_id, uint8_t *manufacturer_data, size_t manufacturer_data_size,
void *user_data);
/**
* @brief Handler called on asynchronous connection when connection is ready
*
@ -173,6 +205,23 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
gattlib_discovered_device_t discovered_device_cb, int timeout, void *user_data);
/**
* @brief Enable Eddystone Bluetooth Device scanning on a given adapter
*
* @param adapter is the context of the newly opened adapter
* @param rssi_threshold is the imposed RSSI threshold for the returned devices.
* @param eddystone_types defines the type(s) of Eddystone advertisement data type to select.
* The types are defined by the macros `GATTLIB_EDDYSTONE_TYPE_*`. The macro `GATTLIB_EDDYSTONE_LIMIT_RSSI`
* can also be used to limit RSSI with rssi_threshold.
* @param discovered_device_cb is the function callback called for each new Bluetooth device discovered
* @param timeout defines the duration of the Bluetooth scanning
* @param user_data is the data passed to the callback `discovered_device_cb()`
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddsytone_types,
gattlib_discovered_device_with_data_t discovered_device_cb, int timeout, void *user_data);
/**
* @brief Disable Bluetooth scanning on a given adapter
*
@ -252,12 +301,6 @@ typedef struct {
uuid_t uuid;
} gattlib_descriptor_t;
typedef struct {
uuid_t uuid;
uint8_t* data;
size_t data_length;
} gattlib_advertisement_data_t;
/**
* @brief Function to discover GATT Services
*