Initial Python support

pull/110/head
Olivier Martin 2019-05-19 12:47:21 +02:00 committed by Olivier Martin
parent a1ad48f20c
commit e74543fa56
16 changed files with 844 additions and 28 deletions

View File

@ -44,6 +44,8 @@ script:
#
# Setup build for ARM 32-bit
#
# Ensure folder is empty
- rm -Rf $PWD/debian-stable-arm/*
- cross-sysroot --distribution debian --distribution-version stable --architecture armhf --build-root $PWD/debian-stable-arm requirements.dep
# Set environment variables
@ -58,6 +60,8 @@ script:
#
# Setup build for ARM 64-bit
#
# Ensure folder is empty
- rm -Rf $PWD/debian-stable-arm64/*
- cross-sysroot --distribution debian --distribution-version stable --architecture arm64 --build-root $PWD/debian-stable-arm64 requirements.dep
# Set environment variables

View File

@ -1,13 +1,95 @@
#if defined(WITH_PYTHON)
#include <Python.h>
#endif
#include <stdio.h>
#include "gattlib_internal.h"
void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) {
connection->notification_handler = notification_handler;
connection->notification_user_data = user_data;
connection->notification.type = NATIVE_NOTIFICATION;
connection->notification.notification_handler = notification_handler;
connection->notification.user_data = user_data;
}
void gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) {
connection->indication_handler = indication_handler;
connection->indication_user_data = user_data;
connection->indication.type = NATIVE_NOTIFICATION;
connection->indication.notification_handler = indication_handler;
connection->indication.user_data = user_data;
}
void gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
connection->disconnection.type = NATIVE_DISCONNECTION;
connection->disconnection.disconnection_handler = handler;
connection->disconnection.user_data = user_data;
}
#if defined(WITH_PYTHON)
void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data) {
connection->notification.type = PYTHON;
connection->notification.python_handler = notification_handler;
connection->notification.user_data = user_data;
}
void gattlib_register_indication_python(gatt_connection_t* connection, PyObject *indication_handler, PyObject *user_data) {
connection->indication.type = PYTHON;
connection->indication.python_handler = indication_handler;
connection->indication.user_data = user_data;
}
void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data) {
connection->disconnection.type = PYTHON;
connection->disconnection.python_handler = handler;
connection->disconnection.user_data = user_data;
}
#endif
bool gattlib_has_valid_handler(struct gattlib_handler *handler) {
return ((handler->type != UNKNOWN) && (handler->notification_handler != NULL));
}
void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
if (handler->type == NATIVE_NOTIFICATION) {
handler->notification_handler(uuid, data, data_length, handler->user_data);
}
#if defined(WITH_PYTHON)
else if (handler->type == PYTHON) {
char uuid_str[MAX_LEN_UUID_STR + 1];
PyGILState_STATE d_gstate;
gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
d_gstate = PyGILState_Ensure();
PyObject *arglist = Py_BuildValue("(sIIO)", uuid_str, data, data_length, handler->user_data);
PyEval_CallObject((PyObject *)handler->notification_handler, arglist);
PyGILState_Release(d_gstate);
}
#endif
else {
fprintf(stderr, "Invalid notification handler.\n");
}
}
void gattlib_call_disconnection_handler(struct gattlib_handler *handler) {
if (handler->type == NATIVE_NOTIFICATION) {
handler->disconnection_handler(handler->user_data);
}
#if defined(WITH_PYTHON)
else if (handler->type == PYTHON) {
PyGILState_STATE d_gstate;
d_gstate = PyGILState_Ensure();
PyObject *arglist = Py_BuildValue("(O)", handler->user_data);
PyEval_CallObject((PyObject *)handler->disconnection_handler, arglist);
PyGILState_Release(d_gstate);
}
#endif
else {
fprintf(stderr, "Invalid disconnection handler.\n");
}
}
void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) {

View File

@ -1,19 +1,32 @@
#ifndef __GATTLIB_INTERNAL_DEFS_H__
#define __GATTLIB_INTERNAL_DEFS_H__
#include <stdbool.h>
#include "gattlib.h"
enum handler_type { UNKNOWN = 0, NATIVE_NOTIFICATION, NATIVE_DISCONNECTION, PYTHON };
struct gattlib_handler {
enum handler_type type;
union {
gattlib_event_handler_t notification_handler;
gattlib_disconnection_handler_t disconnection_handler;
void* python_handler;
};
void* user_data;
};
struct _gatt_connection_t {
void* context;
gattlib_event_handler_t notification_handler;
void* notification_user_data;
gattlib_event_handler_t indication_handler;
void* indication_user_data;
gattlib_disconnection_handler_t disconnection_handler;
void* disconnection_user_data;
struct gattlib_handler notification;
struct gattlib_handler indication;
struct gattlib_handler disconnection;
};
bool gattlib_has_valid_handler(struct gattlib_handler *handler);
void gattlib_call_disconnection_handler(struct gattlib_handler *handler);
void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length);
#endif

View File

@ -93,6 +93,21 @@ endif()
set(gattlib_LIBS ${GLIB_LDFLAGS} ${GIO_UNIX_LDFLAGS})
#
# Add Python Support
#
pkg_search_module(PYTHON python3)
if (NOT PYTHON_FOUND)
pkg_search_module(PYTHON python)
endif()
if (PYTHON_FOUND)
include_directories(${PYTHON_INCLUDE_DIRS})
list(APPEND gattlib_LIBS ${PYTHON_LDFLAGS})
add_definitions(-DWITH_PYTHON)
endif()
# Gattlib
add_library(gattlib SHARED ${gattlib_SRCS})
target_link_libraries(gattlib ${gattlib_LIBS})

View File

@ -280,11 +280,9 @@ gboolean on_handle_device_property_change(
while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
if (strcmp(key, "Connected") == 0) {
if (!g_variant_get_boolean(value)) {
printf("on_handle_device_property_change: Connected FALSE\n");
// Disconnection case
if (connection->disconnection_handler) {
printf("[C] Call disconnection handler:%p\n", connection->disconnection_handler);
connection->disconnection_handler(connection->disconnection_user_data);
if (gattlib_has_valid_handler(&connection->disconnection)) {
gattlib_call_disconnection_handler(&connection->disconnection);
}
}
} else if (strcmp(key, "ServicesResolved") == 0) {
@ -447,11 +445,6 @@ int gattlib_disconnect(gatt_connection_t* connection) {
return GATTLIB_SUCCESS;
}
void gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
connection->disconnection_handler = handler;
connection->disconnection_user_data = user_data;
}
// Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38)
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
@ -1417,7 +1410,7 @@ gboolean on_handle_battery_level_property_change(
static guint8 percentage;
gatt_connection_t* connection = user_data;
if (connection->notification_handler) {
if (gattlib_has_valid_handler(&connection->notification)) {
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
GVariantIter *iter;
@ -1431,9 +1424,9 @@ gboolean on_handle_battery_level_property_change(
// GATT connection notifiying to Battery level
percentage = g_variant_get_byte(value);
connection->notification_handler(&m_battery_level_uuid,
(const uint8_t*)&percentage, sizeof(percentage),
connection->notification_user_data);
gattlib_call_notification_handler(&connection->notification,
&m_battery_level_uuid,
(const uint8_t*)&percentage, sizeof(percentage));
break;
}
}
@ -1451,7 +1444,7 @@ static gboolean on_handle_characteristic_property_change(
{
gatt_connection_t* connection = user_data;
if (connection->notification_handler) {
if (gattlib_has_valid_handler(&connection->notification)) {
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
GVariantIter *iter;
@ -1470,7 +1463,8 @@ static gboolean on_handle_characteristic_property_change(
MAX_LEN_UUID_STR + 1,
&uuid);
connection->notification_handler(&uuid, data, data_length, connection->notification_user_data);
gattlib_call_notification_handler(&connection->notification,
&uuid, data, data_length);
break;
}
}

20
examples/ble_scan/ble_scan.py Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# export LD_LIBRARY_PATH=/home/olivier/dev/gattlib/build/dbus/:$LD_LIBRARY_PATH
from gattlib import adapter
adapters = adapter.Adapter.list()
print("BLE Adapters: %s" % adapters)
def on_discovered_device(device):
print("Discovered '%s'" % device)
# device.connect()
# device.discover()
default_adapter = adapter.Adapter()
default_adapter.open()
default_adapter.scan_enable(on_discovered_device, 10)

4
gattlib-py/TODO.md Normal file
View File

@ -0,0 +1,4 @@
- Add advertisement data
- Add C function to list BLE adapters
- Add indication support

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
import argparse
import struct
import sys
import threading
from dbus.mainloop.glib import DBusGMainLoop
try:
from gi.repository import GLib, GObject
except ImportError:
import gobject as GObject
import sys
import numpy
from matplotlib.pylab import *
from mpl_toolkits.axes_grid1 import host_subplot
import matplotlib.animation as animation
from gattlib import device, uuid
last_measures = {
'temperature': { 'value': None, 'min': None, 'max': None },
'pressure': { 'value': None, 'min': None, 'max': None },
'humidity': { 'value': None, 'min': None, 'max': None },
}
def temperature_notification(value, user_data):
last_measures['temperature']['value'] = float("%d.%d" % (value[0], value[1]))
print("Temperature: %f" % last_measures['temperature']['value'])
def pressure_notification(value, user_data):
(pressure_integer, pressure_decimal) = struct.unpack("<IB", value)
last_measures['pressure']['value'] = float("%d.%d" % (pressure_integer, pressure_decimal))
print("Pressure: %f" % last_measures['pressure']['value'])
def humidity_notification(value, user_data):
last_measures['humidity']['value'] = value[0]
print("Humidity: %d%%" % last_measures['humidity']['value'])
# Data Placeholders
temperature = zeros(0)
humidity = zeros(0)
t = zeros(0)
x = 0.0
xmax = 1000.0
temp_line = None
hum_line = None
ax_temp = None
ax_hum = None
simulation = None
def graph_init():
global x
global temperature, humidity, t
global temp_line, hum_line
global ax_temp, ax_hum
global simulation
font = {'size' : 9}
matplotlib.rc('font', **font)
# Setup figure and subplots
f0 = figure(num=0, figsize=(12, 8)) # , dpi = 100)
f0.suptitle("Nordic Thingy", fontsize=12)
ax_temp = host_subplot(111)
ax_hum = ax_temp.twinx()
# Set titles of subplots
ax_temp.set_title('Temperature/Humidity vs Time')
# set y-limits
ax_temp.set_ylim(0, 45)
ax_hum.set_ylim(0, 100)
# sex x-limits
ax_temp.set_xlim(0, xmax)
ax_hum.set_xlim(0, xmax)
# Turn on grids
ax_temp.grid(True)
# set label names
ax_temp.set_xlabel("t")
ax_temp.set_ylabel("temperature")
ax_hum.set_ylabel("humidity")
temp_line, = ax_temp.plot(t, temperature, 'b-', label="temperature")
hum_line, = ax_hum.plot(t, humidity, 'g-', label="humidity")
# set lagends
ax_temp.legend([temp_line, hum_line], [temp_line.get_label(), hum_line.get_label()])
# interval: draw new frame every 'interval' ms
# Note: We expose simulation to prevent Python garbage collector to rmeove it!
simulation = animation.FuncAnimation(f0, graph_update, blit=False, interval=20)
plt.show()
def graph_update(self):
global x, xmax
global temperature, humidity, t
global temp_line, hum_line
if last_measures['temperature']['value']:
temperature = append(temperature, last_measures['temperature']['value'])
if last_measures['temperature']['min']:
last_measures['temperature']['min'] = min(last_measures['temperature']['min'], last_measures['temperature']['value'] - 5)
else:
last_measures['temperature']['min'] = last_measures['temperature']['value'] - 5
if last_measures['temperature']['max']:
last_measures['temperature']['max'] = max(last_measures['temperature']['max'], last_measures['temperature']['value'] + 5)
else:
last_measures['temperature']['max'] = last_measures['temperature']['value'] + 5
ax_temp.set_ylim(last_measures['temperature']['min'], last_measures['temperature']['max'])
if last_measures['humidity']['value']:
humidity = append(humidity, last_measures['humidity']['value'])
if last_measures['humidity']['min']:
last_measures['humidity']['min'] = min(last_measures['humidity']['min'], last_measures['humidity']['value'] - 20)
else:
last_measures['humidity']['min'] = last_measures['humidity']['value'] - 20
if last_measures['humidity']['max']:
last_measures['humidity']['max'] = max(last_measures['humidity']['max'], last_measures['humidity']['value'] + 20)
else:
last_measures['humidity']['max'] = last_measures['humidity']['value'] + 20
ax_hum.set_ylim(last_measures['humidity']['min'], last_measures['humidity']['max'])
t = append(t, x)
x += 0.05
temp_line.set_data(t, temperature)
hum_line.set_data(t, humidity)
if x >= xmax - 1.00:
temp_line.axes.set_xlim(x - xmax + 1.0, x + 1.0)
hum_line.axes.set_xlim(x - xmax + 1.0, x + 1.0)
return temp_line, hum_line
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')
args = parser.parse_args()
NORDIC_THINGY_WEATHER_STATION_SERVICE = uuid.gattlib_uuid_str_to_int("EF680200-9B35-4933-9B10-52FFA9740042")
NORDIC_THINGY_TEMPERATURE_CHAR = uuid.gattlib_uuid_str_to_int("EF680201-9B35-4933-9B10-52FFA9740042")
NORDIC_THINGY_PRESSURE_CHAR = uuid.gattlib_uuid_str_to_int("EF680202-9B35-4933-9B10-52FFA9740042")
NORDIC_THINGY_HUMIDITY_CHAR = uuid.gattlib_uuid_str_to_int("EF680203-9B35-4933-9B10-52FFA9740042")
NORDIC_THINGY_AIR_QUALITY_CHAR = uuid.gattlib_uuid_str_to_int("EF680204-9B35-4933-9B10-52FFA9740042")
gatt_device = device.Device(adapter=None, addr=args.mac)
gatt_device.connect()
temperature_characteristic = gatt_device.characteristics[NORDIC_THINGY_TEMPERATURE_CHAR]
pressure_characteristic = gatt_device.characteristics[NORDIC_THINGY_PRESSURE_CHAR]
humidity_characteristic = gatt_device.characteristics[NORDIC_THINGY_HUMIDITY_CHAR]
air_quality_characteristic = gatt_device.characteristics[NORDIC_THINGY_AIR_QUALITY_CHAR]
# Initialize graph
threading.Thread(target=graph_init).start()
try:
DBusGMainLoop(set_as_default=True)
mainloop = GLib.MainLoop()
temperature_characteristic.register_notification(temperature_notification)
temperature_characteristic.notification_start()
pressure_characteristic.register_notification(pressure_notification)
pressure_characteristic.notification_start()
humidity_characteristic.register_notification(humidity_notification)
humidity_characteristic.notification_start()
mainloop.run()
except KeyboardInterrupt:
mainloop.quit()
finally:
humidity_characteristic.notification_stop()
pressure_characteristic.notification_stop()
temperature_characteristic.notification_stop()
gatt_device.disconnect()

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
import argparse
from gattlib import device, uuid
parser = argparse.ArgumentParser(description='Gattlib read_write example')
parser.add_argument('mac', type=str, help='Mac Address of the GATT device to connect')
parser.add_argument('action', choices=['read', 'write'], help='Tell if we want to read/write the GATT characteristic')
parser.add_argument('uuid', type=str, help='UUID of the GATT Characteristic')
parser.add_argument('value', type=str, nargs='?', help='Value to write to the GATT characteristic')
args = parser.parse_args()
gatt_device = device.Device(adapter=None, addr=args.mac)
gatt_device.connect()
uuid = uuid.gattlib_uuid_str_to_int(args.uuid)
if uuid not in gatt_device.characteristics:
raise RuntimeError("Failed to find GATT characteristic '%s'" % args.uuid)
characteristic = gatt_device.characteristics[uuid]
if args.action == "read":
value = characteristic.read()
print(value)
elif args.action == "write":
characteristic.write(value)
gatt_device.disconnect()

View File

@ -0,0 +1,90 @@
from ctypes import *
gattlib = CDLL("libgattlib.so")
# typedef struct {
# uint8_t data[16];
# } uint128_t;
class GattlibUuid128(Structure):
_fields_ = [("data", c_byte * 16)]
# typedef struct {
# uint8_t type;
# union {
# uint16_t uuid16;
# uint32_t uuid32;
# uint128_t uuid128;
# } value;
# } uuid_t;
class GattlibUuidValue(Union):
_fields_ = [("uuid16", c_ushort), ("uuid32", c_uint), ("uuid128", GattlibUuid128)]
class GattlibUuid(Structure):
_fields_ = [("type", c_byte), ("value", GattlibUuidValue)]
# typedef struct {
# uint16_t attr_handle_start;
# uint16_t attr_handle_end;
# uuid_t uuid;
# } gattlib_primary_service_t;
class GattlibPrimaryService(Structure):
_fields_ = [("attr_handle_start", c_ushort),
("attr_handle_end", c_ushort),
("uuid", GattlibUuid)]
# typedef struct {
# uint16_t handle;
# uint8_t properties;
# uint16_t value_handle;
# uuid_t uuid;
# } gattlib_characteristic_t;
class GattlibCharacteristic(Structure):
_fields_ = [("handle", c_ushort),
("properties", c_byte),
("value_handle", c_ushort),
("uuid", GattlibUuid)]
# int gattlib_adapter_open(const char* adapter_name, void** adapter);
gattlib_adapter_open = gattlib.gattlib_adapter_open
gattlib_adapter_open.argtypes = [c_char_p, POINTER(c_void_p)]
# typedef void (*gattlib_discovered_device_t)(const char* addr, const char* name)
gattlib_discovered_device_type = CFUNCTYPE(None, c_char_p, c_char_p)
# int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
gattlib_discover_primary = gattlib.gattlib_discover_primary
gattlib_discover_primary.argtypes = [c_void_p, POINTER(POINTER(GattlibPrimaryService)), POINTER(c_int)]
# int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristic_count);
gattlib_discover_char = gattlib.gattlib_discover_char
gattlib_discover_char.argtypes = [c_void_p, POINTER(POINTER(GattlibCharacteristic)), POINTER(c_int)]
# int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void** buffer, size_t* buffer_len);
gattlib_read_char_by_uuid = gattlib.gattlib_read_char_by_uuid
gattlib_read_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), POINTER(c_void_p), POINTER(c_size_t)]
# int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
gattlib_write_char_by_uuid = gattlib.gattlib_write_char_by_uuid
gattlib_write_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), c_void_p, c_size_t]
# int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid);
gattlib_notification_start = gattlib.gattlib_notification_start
gattlib_notification_start.argtypes = [c_void_p, POINTER(GattlibUuid)]
# int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid);
gattlib_notification_stop = gattlib.gattlib_notification_stop
gattlib_notification_stop.argtypes = [c_void_p, POINTER(GattlibUuid)]
# void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data)
gattlib_register_notification = gattlib.gattlib_register_notification_python
gattlib_register_notification.argtypes = [c_void_p, py_object, py_object]
# void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data)
gattlib_register_on_disconnect = gattlib.gattlib_register_on_disconnect_python
gattlib_register_on_disconnect.argtypes = [c_void_p, py_object, py_object]

View File

@ -0,0 +1,38 @@
from gattlib import *
from .device import Device
from .exception import handle_return
class Adapter:
def __init__(self, name=c_char_p(None)):
self._name = name
self._adapter = c_void_p(None)
@property
def name(self):
return self._name
@staticmethod
def list():
#TODO: Add support
return []
def open(self):
return gattlib_adapter_open(self._name, byref(self._adapter))
def close(self):
return gattlib.gattlib_adapter_close(self._adapter)
def on_discovered_device(self, addr, name):
device = Device(self, addr, name)
self.on_discovered_device_callback(device)
def scan_enable(self, on_discovered_device_callback, timeout):
assert on_discovered_device_callback != None
self.on_discovered_device_callback = on_discovered_device_callback
ret = gattlib.gattlib_adapter_scan_enable(self._adapter, gattlib_discovered_device_type(self.on_discovered_device), timeout)
handle_return(ret)
def scan_disable(self):
ret = gattlib.gattlib_adapter_scan_disable(self._adapter)
handle_return(ret)

View File

@ -0,0 +1,164 @@
import logging
import uuid
from gattlib import *
from .exception import handle_return, DeviceError
from .gatt import GattService, GattCharacteristic
from .uuid import gattlib_uuid_to_int
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC = (1 << 0)
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM = (1 << 1)
CONNECTION_OPTIONS_LEGACY_BT_SEC_LOW = (1 << 2)
CONNECTION_OPTIONS_LEGACY_BT_SEC_MEDIUM = (1 << 3)
CONNECTION_OPTIONS_LEGACY_BT_SEC_HIGH = (1 << 4)
CONNECTION_OPTIONS_LEGACY_DEFAULT = \
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC | \
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM | \
CONNECTION_OPTIONS_LEGACY_BT_SEC_LOW
class Device:
def __init__(self, adapter, addr, name=None):
self._adapter = adapter
if type(addr) == str:
self._addr = addr.encode("utf-8")
else:
self._addr = addr
self._name = name
self._connection = c_void_p(None)
# Keep track if notification handler has been initialized
self._is_notification_init = False
# Dictionnary for GATT characteristic callback
self._gatt_characteristic_callbacks = {}
@property
def id(self):
return self._addr.decode("utf-8")
@property
def connection(self):
return self._connection
def connect(self, options=CONNECTION_OPTIONS_LEGACY_DEFAULT):
if self._adapter:
adapter_name = self._adapter.name
else:
adapter_name = None
self._connection = gattlib.gattlib_connect(adapter_name, self._addr, options)
if self._connection == 0:
raise DeviceError()
@staticmethod
def disconnection_callback(user_data):
this = user_data
this.disconnection_callback(this.disconnection_user_data)
def register_on_disconnect(self, callback, user_data):
self.disconnection_callback = callback
self.disconnection_user_data = user_data
gattlib.gattlib_register_on_disconnect(self._connection, Device.disconnection_callback, self)
def disconnect(self):
ret = gattlib.gattlib_disconnect(self._connection)
handle_return(ret)
def discover(self):
#
# Discover GATT Services
#
_services = POINTER(GattlibPrimaryService)()
_services_count = c_int(0)
ret = gattlib_discover_primary(self._connection, byref(_services), byref(_services_count))
handle_return(ret)
self._services = {}
for i in range(0, _services_count.value):
service = GattService(self, _services[i])
self._services[service.short_uuid] = service
logging.debug("Service UUID:0x%x" % service.short_uuid)
#
# Discover GATT Characteristics
#
_characteristics = POINTER(GattlibCharacteristic)()
_characteristics_count = c_int(0)
ret = gattlib_discover_char(self._connection, byref(_characteristics), byref(_characteristics_count))
handle_return(ret)
self._characteristics = {}
for i in range(0, _characteristics_count.value):
characteristic = GattCharacteristic(self, _characteristics[i])
self._characteristics[characteristic.short_uuid] = characteristic
logging.debug("Characteristic UUID:0x%x" % characteristic.short_uuid)
@property
def services(self):
if not hasattr(self, '_services'):
logging.warning("Start GATT discovery implicitly")
self.discover()
return self._services
@property
def characteristics(self):
if not hasattr(self, '_characteristics'):
logging.warning("Start GATT discovery implicitly")
self.discover()
return self._characteristics
@staticmethod
def notification_callback(uuid_str, data, data_len, user_data):
this = user_data
notification_uuid = uuid.UUID(uuid_str)
short_uuid = notification_uuid.int
if short_uuid not in this._gatt_characteristic_callbacks:
raise RuntimeError("UUID '%s' is expected to be part of the notification list")
else:
characteristic_callback = this._gatt_characteristic_callbacks[short_uuid]
# value = bytearray(data_len)
# for i in range(data_len):
# value[i] = data[i]
pointer_type = POINTER(c_byte * data_len)
c_bytearray = cast(data, pointer_type)
value = bytearray(data_len)
for i in range(data_len):
value[i] = c_bytearray.contents[i] & 0xFF
# Call GATT characteristic Notification callback
characteristic_callback['callback'](value, characteristic_callback['user_data'])
def _notification_init(self):
if self._is_notification_init:
return
self._is_notification_init = True
gattlib_register_notification(self._connection, Device.notification_callback, self)
def _notification_add_gatt_characteristic_callback(self, gatt_characteristic, callback, user_data):
if not self._is_notification_init:
self._notification_init()
self._gatt_characteristic_callbacks[gatt_characteristic.short_uuid] = { 'callback': callback, 'user_data': user_data }
def __str__(self):
name = self._name
if name:
return str(name)
else:
return str(self._addr)

View File

@ -0,0 +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
class GattlibException(Exception):
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()
elif ret == GATTLIB_NOT_FOUND:
raise NotFound()
elif ret == GATTLIB_OUT_OF_MEMORY:
raise OutOfMemory()
elif ret == GATTLIB_NOT_SUPPORTED:
raise NotSupported()
elif ret == GATTLIB_DEVICE_ERROR:
raise DeviceError()
elif ret == GATTLIB_ERROR_DBUS:
raise DBusError()
elif ret != 0:
raise RuntimeError("Gattlib exception %d" % ret)

View File

@ -0,0 +1,77 @@
from gattlib import *
from .uuid import gattlib_uuid_to_uuid, gattlib_uuid_to_int
from .exception import handle_return
class GattService():
def __init__(self, device, gattlib_primary_service):
self._device = device
self._gattlib_primary_service = gattlib_primary_service
@property
def uuid(self):
return gattlib_uuid_to_uuid(self._gattlib_primary_service.uuid)
@property
def short_uuid(self):
return gattlib_uuid_to_int(self._gattlib_primary_service.uuid)
class GattCharacteristic():
def __init__(self, device, gattlib_characteristic):
self._device = device
self._gattlib_characteristic = gattlib_characteristic
@property
def uuid(self):
return gattlib_uuid_to_uuid(self._gattlib_characteristic.uuid)
@property
def short_uuid(self):
return gattlib_uuid_to_int(self._gattlib_characteristic.uuid)
@property
def connection(self):
return self._device.connection
def read(self, callback=None):
if callback:
raise RuntimeError("Not supported yet")
else:
_buffer = c_void_p(None)
_buffer_len = c_size_t(0)
ret = gattlib_read_char_by_uuid(self.connection, self._gattlib_characteristic.uuid, byref(_buffer), byref(_buffer_len))
pointer_type = POINTER(c_byte * _buffer_len.value)
c_bytearray = cast(_buffer, pointer_type)
value = bytearray(_buffer_len.value)
for i in range(_buffer_len.value):
value[i] = c_bytearray.contents[i]
return value
def write(self, data):
buffer_type = c_char * len(data)
buffer = data
buffer_len = len(data)
ret = gattlib_write_char_by_uuid(self.connection, self._gattlib_characteristic.uuid, buffer_type.from_buffer_copy(buffer), buffer_len)
handle_return(ret)
def register_notification(self, callback, user_data=None):
self._device._notification_add_gatt_characteristic_callback(self, callback, user_data)
def notification_start(self):
ret = gattlib_notification_start(self.connection, self._gattlib_characteristic.uuid)
handle_return(ret)
def notification_stop(self):
ret = gattlib_notification_stop(self.connection, self._gattlib_characteristic.uuid)
handle_return(ret)
def __str__(self):
return str(self.uuid)

View File

@ -0,0 +1,47 @@
import re
from uuid import UUID
from gattlib import *
SDP_UUID16 = 0x19
SDP_UUID32 = 0x1A
SDP_UUID128 = 0x1C
GATT_STANDARD_UUID_FORMAT = re.compile("(\S+)-0000-1000-8000-00805f9b34fb", flags=re.IGNORECASE)
def gattlib_uuid_to_uuid(gattlib_uuid):
if gattlib_uuid.type == SDP_UUID16:
return UUID(fields=(gattlib_uuid.value.uuid16, 0x0000, 0x1000, 0x80, 0x00, 0x00805f9b34fb))
elif gattlib_uuid.type == SDP_UUID32:
return UUID(fields=(gattlib_uuid.value.uuid32, 0x0000, 0x1000, 0x80, 0x00, 0x00805f9b34fb))
elif gattlib_uuid.type == SDP_UUID128:
data = bytes(gattlib_uuid.value.uuid128.data)
return UUID(bytes=data)
else:
return ValueError("Gattlib UUID not recognized (type:0x%x)" % gattlib_uuid.type)
def gattlib_uuid_to_int(gattlib_uuid):
if gattlib_uuid.type == SDP_UUID16:
return gattlib_uuid.value.uuid16
elif gattlib_uuid.type == SDP_UUID32:
return gattlib_uuid.value.uuid32
elif gattlib_uuid.type == SDP_UUID128:
data = bytes(gattlib_uuid.value.uuid128.data)
return int.from_bytes(data, byteorder='big')
else:
return ValueError("Gattlib UUID not recognized (type:0x%x)" % gattlib_uuid.type)
def gattlib_uuid_str_to_int(uuid_str):
# Check if the string could already encode a UUID16 or UUID32
if len(uuid_str) <= 8:
return int(uuid_str, 16)
# Check if it is a standard UUID or not
match = GATT_STANDARD_UUID_FORMAT.search(uuid_str)
if match:
return int(match.group(1), 16)
else:
return UUID(uuid_str).int

View File

@ -1,3 +1,5 @@
libbluetooth3
libbluetooth-dev
libglib2.0-dev
libglib2.0-dev
python3-dev
libpython3-dev