mirror of https://github.com/labapart/gattlib
Initial Python support
parent
a1ad48f20c
commit
e74543fa56
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,4 @@
|
|||
- Add advertisement data
|
||||
|
||||
- Add C function to list BLE adapters
|
||||
- Add indication support
|
|
@ -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()
|
|
@ -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()
|
|
@ -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]
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -1,3 +1,5 @@
|
|||
libbluetooth3
|
||||
libbluetooth-dev
|
||||
libglib2.0-dev
|
||||
libglib2.0-dev
|
||||
python3-dev
|
||||
libpython3-dev
|
Loading…
Reference in New Issue